Skip to content

Destroy Runner/MasterMinion resources in salt-run and fix MasterMinion.__del__ - #70177

Open
twangboy wants to merge 5 commits into
saltstack:3008.xfrom
twangboy:fix/70174/3008.x
Open

Destroy Runner/MasterMinion resources in salt-run and fix MasterMinion.__del__#70177
twangboy wants to merge 5 commits into
saltstack:3008.xfrom
twangboy:fix/70174/3008.x

Conversation

@twangboy

Copy link
Copy Markdown
Contributor

What does this PR do?

salt-run left its Runner un-destroy()'d, and RunnerClient / WheelClient never cleaned up the MasterMinion they lazily create via SyncClientMixin.mminion. Both were only reclaimed by __del__'s GC-time safety net, which now logs a loud "unclosed Runner"/ "unclosed MasterMinion" WARNING on every salt-run invocation instead of being silently filtered.

  • salt/cli/run.py: wrap SaltRun.run() in try / finally so runner.destroy() always runs, covering both the --doc early-exit and the normal path.
  • salt/runner.py, salt/wheel/__init__.py: destroy() now also tears down self._mminion.
  • salt/minion.py: MasterMinion.destroy() leaves returners/functions/utils as {}, not None, but __del__'s "already torn down" check tested for None specifically, so even a properly destroy()'d MasterMinion kept tripping the warning. Check falsiness instead.

What issues does this PR fix or reference?

Fixes #70174

Merge requirements satisfied?

[NOTICE] Bug fixes or features added to Salt require tests.

Commits signed with GPG?

Yes

@twangboy twangboy added this to the Argon v3008.3 milestone Aug 28, 2026
@twangboy twangboy self-assigned this Aug 28, 2026
@twangboy
twangboy requested a review from a team as a code owner August 28, 2026 19:08
@twangboy twangboy added the test:full Run the full test suite label Aug 28, 2026
twangboy added a commit to twangboy/salt that referenced this pull request Aug 28, 2026
pylint's blacklisted-module check (saltpylint) flags direct use of
unittest.mock; Salt's own tests must import MagicMock from
tests.support.mock instead. Fixes the "Lint Salt's Test Suite" CI
failure on PR saltstack#70177.
Comment thread salt/cli/run.py Outdated
…n.__del__

salt-run left its Runner un-destroy()'d, and RunnerClient/WheelClient
never cleaned up the MasterMinion they lazily create via
SyncClientMixin.mminion. Both were only reclaimed by __del__'s
GC-time safety net, which now logs a loud "unclosed Runner"/
"unclosed MasterMinion" WARNING on every salt-run invocation instead
of being silently filtered.

- salt/cli/run.py: wrap SaltRun.run() in try/finally so runner.destroy()
  always runs, covering both the --doc early-exit and the normal path.
- salt/runner.py, salt/wheel/__init__.py: destroy() now also tears down
  self._mminion.
- salt/minion.py: MasterMinion.destroy() leaves returners/functions/utils
  as {}, not None, but __del__'s "already torn down" check tested for
  None specifically, so even a properly destroy()'d MasterMinion kept
  tripping the warning. Check falsiness instead.

Fixes saltstack#70174
pylint's blacklisted-module check (saltpylint) flags direct use of
unittest.mock; Salt's own tests must import MagicMock from
tests.support.mock instead. Fixes the "Lint Salt's Test Suite" CI
failure on PR saltstack#70177.
Review follow-up for saltstack#70177/saltstack#70174: MasterMinion.destroy(),
RunnerClient.destroy(), and WheelClient.destroy() ran their
per-component teardown steps back-to-back with no exception
isolation. If any one component's .destroy() call raised, every
later step -- including resetting the remaining attributes to {}
and, in RunnerClient/WheelClient's case, tearing down the lazily
created MasterMinion (self._mminion), the whole point of this fix --
was silently skipped, and the exception propagated out of destroy()
itself.

That's now unconditionally called from salt/cli/run.py's
finally: runner.destroy(), so an exception there would replace
SaltRun.run()'s real result/control flow with an unrelated
destroy()-time traceback.

Wrap each component's teardown call in its own try/except (mirroring
the existing pattern in salt/utils/asynchronous.py's
SyncWrapper.destroy()), and make each attribute reset unconditional,
so one component failing can never block another's teardown, and
destroy() itself can never raise.
Review follow-up for saltstack#70177: RunnerClient already gained __enter__/
__exit__ (calling destroy()) as part of this fix, so salt/cli/run.py
can use `with salt.runner.Runner(self.config) as runner:` instead of
a manual try/finally calling runner.destroy() directly.
@twangboy

Copy link
Copy Markdown
Contributor Author

Done — pushed c7288fc. RunnerClient (which Runner subclasses) already gained __enter__/__exit__ earlier in this PR (__exit__ calls destroy()), so salt/cli/run.py now does with salt.runner.Runner(self.config) as runner: instead of the manual try/finally: runner.destroy(). Also rebased onto current 3008.x.

The context-manager refactor (c7288fc) changed salt/cli/run.py to
`with salt.runner.Runner(self.config) as runner:` instead of a
manual try/finally. The existing tests patched salt.runner.Runner to
return a bare MagicMock(), which broke two ways:

- MagicMock()'s auto-generated __enter__() returns a *different*
  mock than the one the test asserts against, so `runner` inside the
  with block was never the mocked instance -- runner.print_docs()/
  runner.run() calls landed on the wrong mock, hence 'DID NOT RAISE
  SystemExit' / 'print_docs called 0 times' on every platform in CI.
- Wiring __exit__ to call destroy() directly returned destroy()'s
  mock return value (truthy), which made the with statement treat
  the SystemExit raised inside the block as handled and swallow it.

Add a _fake_runner() helper that wires __enter__ to return the mock
itself and __exit__ to call destroy() while still returning None,
mirroring RunnerClient.__enter__/__exit__ for real.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:full Run the full test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants