Skip to content

Introduce the per-run BuildContext foundation (FT-2)#451

Open
ChrisonSimtian wants to merge 2 commits into
Fallout-build:mainfrom
ChrisonSimtian:engine/ft2-build-context
Open

Introduce the per-run BuildContext foundation (FT-2)#451
ChrisonSimtian wants to merge 2 commits into
Fallout-build:mainfrom
ChrisonSimtian:engine/ft2-build-context

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

Stacked on #450 (FT-1) — until that merges, this PR's diff includes FT-1's commit; once #450 lands I'll rebase onto main so this narrows to just the FT-2 change. Draft until then.

Engine de-statification ([Foundation] epic #315) — FT-2 / #307. The foundation the rest of the de-static work rides.

Adds BuildContext — an internal, per-run, AsyncLocal-ambient scope:

  • activated at the top of BuildManager.Execute (using var context = BuildContext.Activate()), disposed at method exit;
  • owns the per-run global-state lifecycle — the Console.CancelKeyPress / ToolOptions.Created subscriptions, the cancellation-handler list (moved off BuildManager), and the FT-1 teardown (now run on dispose);
  • BuildManager.CancellationHandler becomes a thin facade over BuildContext.Current — the first "static surface over the context" seam.

Subsequent steps move the per-run services onto the context: FT-4 ParameterService, FT-5 tool-path config, FT-6 logging scope. Internal-only — not a public contract until the SDK (milestone #7). Disposing on method exit means a failed new T() also leaks nothing. Full build + test suite green.

🤖 Generated with Claude Code

…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.
Adds BuildContext — an internal, per-run, AsyncLocal-ambient scope activated at the top of
BuildManager.Execute (`using var context = BuildContext.Activate()`) and disposed at method exit.
It owns the per-run global-state lifecycle: the Console.CancelKeyPress / ToolOptions.Created
subscriptions, the cancellation-handler list, and the teardown FT-1 centralised (now run on dispose).
BuildManager.CancellationHandler becomes a thin facade over BuildContext.Current — the first
"static surface over the context" seam, and the rail subsequent steps ride (FT-4 ParameterService,
FT-5 tool-path config, FT-6 logging scope move their state onto the context).

Internal-only; not a public contract until the SDK (milestone #7). Disposing on method exit means a
failed `new T()` also leaks nothing. Full build + test suite green.
@ChrisonSimtian

Copy link
Copy Markdown
Collaborator Author

@dennisdoomen this one here, start of the de-static work

@ChrisonSimtian ChrisonSimtian marked this pull request as ready for review July 4, 2026 22:39
@ChrisonSimtian ChrisonSimtian requested a review from a team as a code owner July 4, 2026 22:39
/// </remarks>
internal sealed class BuildContext : IDisposable
{
private static readonly AsyncLocal<BuildContext> s_current = new();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
private static readonly AsyncLocal<BuildContext> s_current = new();
private static readonly AsyncLocal<BuildContext> currentInstance = new();

probably predating the rules change we made this week, so wrong naming convention...

@ChrisonSimtian ChrisonSimtian self-assigned this Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

target/2026 Targets the 2026 calendar-version line (current). See ADR-0004.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant