Skip to content

fix(launch_manager): avoid leaking ControlProvider on failure - #653

Closed
hskang-amelia wants to merge 3 commits into
eclipse-score:mainfrom
hskang-amelia:fix/control-provider-create-leak
Closed

hskang-amelia wants to merge 3 commits into
eclipse-score:mainfrom
hskang-amelia:fix/control-provider-create-leak

Conversation

@hskang-amelia

@hskang-amelia hskang-amelia commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

What

ControlProvider::Create() heap-allocates a ControlProvider via a bare
new and returns MakeUnexpected(...) on any of the four setup-step
failure paths without ever deleting it, leaking the object.

How

Wraps the allocation in a unique_ptr guard that frees it automatically on
any early return, and only release()s the raw pointer once every setup
step has succeeded. No change to control_provider.hpp or the
Result<ControlProvider*> signature.

Testing

  • bazel build --lockfile_mode=error --config=x86_64-linux //score/launch_manager/src/daemon/src/control:control_provider //score/launch_manager/src/daemon:launch_manager
  • bazel test --lockfile_mode=error --config=x86_64-linux //score/launch_manager/... (the subset that isn't blocked by this environment's unrelated Ferrocene/glibc issue)
  • Added control_provider_UT with two tests exercising Create()'s happy
    path and a second Create() for an already-offered instance failing
    cleanly. Neither reaches the unique_ptr guard itself (both fail earlier,
    at LmControlSkeleton::Create()'s flock check) — no config-based way to
    make RegisterHandler/OfferService fail after skeleton creation
    succeeds was found without mocking, noted in the test file.
  • Real end-to-end regression check: built a real launch_manager daemon off
    this branch and ran it against a real client application over actual
    shared-memory IPC in a private downstream integration harness — 2/2
    ActivateRunTarget round trips succeeded.

Related

  • Result<ControlProvider> at 1c243af (pre-final-review) tried to fix
    this same non-movability concern via a Pimpl; superseded by this repo's
    own Result<ControlProvider*> pointer-return design in the final review
    pass. This PR keeps that design and only fixes the leak on top of it.

Create() heap-allocated a ControlProvider via raw `new` and returned
MakeUnexpected(...) on any of the four setup-step failure paths
without ever deleting it, leaking the object.

Wrap the allocation in a unique_ptr guard that frees it on any early
return and only release()s the raw pointer once every setup step has
succeeded, matching the existing Result<ControlProvider*> signature.
There was previously no unit test coverage for ControlProvider::Create
at all. Adds two: the happy path, and a second Create() for an
already-offered instance failing cleanly rather than crashing.

Neither exercises the unique_ptr guard added in the previous commit
(both fail earlier, at LmControlSkeleton::Create's flock check) -- no
config-based way to make RegisterHandler or OfferService fail after
skeleton creation succeeds was found. Noted in the test comments.
Traced RegisterHandler down to the lola binding's concrete
implementation (unconditional success) to prove three of the four
setup-step failure branches are unreachable with this binding, rather
than just asserting no trigger was found.
@github-project-automation github-project-automation Bot moved this from Backlog to Done in LCM - Lifecycle & Health FT Sep 18, 2026
@hskang-amelia
hskang-amelia deleted the fix/control-provider-create-leak branch September 18, 2026 04:35
@hskang-amelia

hskang-amelia commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Superseded by #654 — while working on this, #649 (already investigated and implemented on a separate branch) appears to offer a more complete solution to the same root cause. That solution makes ControlProvider properly movable via a Pimpl, which incidentally also fixes the memory leak from Create() failures, and also resolves a lifetime issue in main.cpp that the Pimpl redesign itself could introduce. To avoid having two PRs touching the same code, closing this leak-only fix and prioritizing #649's solution.

hskang-amelia added a commit to hskang-amelia/lifecycle that referenced this pull request Sep 21, 2026
Fulfills the test-coverage gap flagged on PR eclipse-score#654 (eclipse-score/lifecycle):
the static_assert-only test added previously checks that ControlProvider's
type is move-constructible, but that doesn't prove a move is actually safe.

Adds a real end-to-end regression test: creates a ControlProvider via
Create() over a real mw::com skeleton (using a dedicated test service
config, mirroring the one from eclipse-score#653), moves it into an outer-scoped
variable, destroys the moved-from original, then invokes the callback
registered via registerActiveRunTargetCallback. That callback closure
captures Impl*, not ControlProvider's own `this`, so it must still
dispatch correctly through the moved-to instance's Impl -- if a future
regression stored Impl by value instead of behind a unique_ptr, this
would be a genuine use-after-free/scope, catchable under
--config=asan_ubsan_lsan.

Verified: bazel test --config=x86_64-linux and
bazel test --config=asan_ubsan_lsan both pass, 2/2 tests.

Signed-off-by: amelia@ivis.ai <amelia@ivis.ai>

This branch is waiting to be deployed

1 waiting deployment
workflow-approval — 50666558 Waiting Sep 18, 2026 by hskang-amelia via Build and test unit-tests-arm64-qnx / approval #862
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant