Skip to content

Reset per-run global state at the end of Execute (cross-invocation leaks, FT-1)#450

Merged
ChrisonSimtian merged 3 commits into
Fallout-build:mainfrom
ChrisonSimtian:engine/ft1-cross-invocation-leaks
Jul 7, 2026
Merged

Reset per-run global state at the end of Execute (cross-invocation leaks, FT-1)#450
ChrisonSimtian merged 3 commits into
Fallout-build:mainfrom
ChrisonSimtian:engine/ft1-cross-invocation-leaks

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

First step of the engine de-statification ([Foundation] epic #315) — FT-1 / #306. Non-breaking groundwork before the BuildContext foundation (FT-2).

BuildManager.Execute leaked process-global state across invocations, so a second build in the same process (tests, hosted/reentrant scenarios) inherited the first's residue:

  • Console.CancelKeyPress / ToolOptions.Created handlers were re-subscribed every call and never removed — accumulating;
  • the cancellation-handler list kept each run's Finish (capturing a stale build);
  • Logging.InMemorySink.Instance carried log events over;
  • ValueInjectionUtility's value cache and the NuGet/Npm tool-path resolver config persisted.

Fix: the two global handlers are held in locals and the finally undoes exactly what the run set — unsubscribes both, drops Finish from the cancellation list, and resets the carried-over state (InMemorySink.Clear, ValueInjectionUtility.ClearCache, NuGet/NpmToolPathResolver.Reset). new T() moved ahead of the subscriptions so a failed build creation leaks nothing.

No public API change. The reentrancy guarantee itself is asserted by FT-9 (the isolation harness); this PR removes the leaks it will assert against. Full build + test suite green.

🤖 Generated with Claude Code

@ChrisonSimtian ChrisonSimtian marked this pull request as ready for review July 1, 2026 01:42
@ITaluone ITaluone force-pushed the engine/ft1-cross-invocation-leaks branch from 4591a6f to dd3b12f Compare July 2, 2026 13:17
@ChrisonSimtian ChrisonSimtian force-pushed the engine/ft1-cross-invocation-leaks branch from dd3b12f to 3bc1de0 Compare July 2, 2026 22:48
@ChrisonSimtian ChrisonSimtian force-pushed the engine/ft1-cross-invocation-leaks branch from 3bc1de0 to 92a6fe4 Compare July 4, 2026 22:37
@ChrisonSimtian

Copy link
Copy Markdown
Collaborator Author

@dennisdoomen this one please, should be quick :-)

@dennisdoomen dennisdoomen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Can't we add some test to verify this behavior?

@ChrisonSimtian ChrisonSimtian force-pushed the engine/ft1-cross-invocation-leaks branch from 7accd11 to 974c83a Compare July 7, 2026 10:04
@ChrisonSimtian ChrisonSimtian enabled auto-merge (rebase) July 7, 2026 10:04
ChrisonSimtian and others added 3 commits July 7, 2026 12:09
…ion leaks)

BuildManager.Execute leaked process-global state across invocations, so a second build in the same
process (tests, hosted/reentrant scenarios) inherited the first's residue:
- Console.CancelKeyPress / ToolOptions.Created handlers were re-subscribed every call and never
  removed (accumulating);
- the CancellationHandler list kept each run's Finish (capturing a stale build);
- Logging.InMemorySink.Instance carried log events over;
- ValueInjectionUtility's value cache and the NuGet/Npm tool-path resolver config persisted.

Fix (non-breaking): the two global handlers are now held in locals and the finally undoes exactly
what the run set — unsubscribes both, removes Finish from the cancellation list, and resets the
carried-over state (InMemorySink.Clear, ValueInjectionUtility.ClearCache, NuGet/NpmToolPathResolver.Reset).
Build creation moved ahead of the subscriptions so a failed `new T()` leaks nothing. Groundwork for
the BuildContext foundation (FT-2). Full build + test suite green; reentrancy is verified by FT-9.
Drop the `s_` prefix on the private static field in BuildManager, per
CSharpGuidelines AV1705 (no `_`/`m_`/`g_`/`s_` field prefixes) and AV1702
(camelCase private fields). Localized cleanup while we are in this file for
FT-1; a repo-wide naming rule is handled separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds tests for the cross-invocation reset entry points BuildManager.Execute's finally now calls,
so the leak-removal behaviour is pinned:
- ToolPathResolverResetSpecs: NuGet/NpmToolPathResolver.Reset() null every package-location field.
  Shares the resolvers' static state with ToolTasksToolPathSpecs, so both sit in a non-parallelized
  collection.
- InMemorySinkSpecs: InMemorySink.Clear() (and Dispose(), which delegates to it) drop accumulated events.
- ValueInjectionUtilitySpecs: a value is cached on first read; ClearCache() forces re-injection — made
  observable with a counting injection attribute.

The full reentrant Execute<T> run stays with the FT-9 harness. Tooling.Specs 66, Build.Specs 108, all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ITaluone ITaluone force-pushed the engine/ft1-cross-invocation-leaks branch from 974c83a to e72d72b Compare July 7, 2026 10:09
@ChrisonSimtian ChrisonSimtian merged commit ef6bbd8 into Fallout-build:main Jul 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

target/vCurrent Targets the current version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants