Skip to content

fix: serialize MTP server-mode concurrent sessions to prevent stochastic 0-result runs#6002

Draft
Copilot wants to merge 3 commits into
mainfrom
copilot/mtp-server-mode-multi-session-findings
Draft

fix: serialize MTP server-mode concurrent sessions to prevent stochastic 0-result runs#6002
Copilot wants to merge 3 commits into
mainfrom
copilot/mtp-server-mode-multi-session-findings

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 22, 2026

Multiple concurrent testing/runTests RPCs to one long-lived TUnit process race on shared static state (GlobalContext.Current, AppDomain.UnhandledException handlers, Trace.Listeners), producing stochastic 0-result sessions. Sequential execution is deterministic and correct; concurrency introduces variance.

Changes

  • Serialize ExecuteRequestAsync — added SemaphoreSlim(1, 1) in TUnitTestFramework so concurrent RPCs queue and execute sequentially. The perf win from MTP server mode is avoiding process startup overhead, not intra-process parallelism.

  • Make TUnitInitializer.Initialize idempotent — static volatile flags prevent repeated AppDomain.UnhandledException handler registration and Trace.Listeners insertion across sessions.

  • Add multi-session RPC tests — validates both sequential (3×) and concurrent (3× parallel) runTests RPCs against the same server produce consistent, complete results.

// TUnitTestFramework.cs
private readonly SemaphoreSlim _executeRequestLock = new(1, 1);

public async Task ExecuteRequestAsync(ExecuteRequestContext context)
{
    await _executeRequestLock.WaitAsync(context.CancellationToken);
    try { /* ... */ }
    finally
    {
        _executeRequestLock.Release();
        context.Complete();
    }
}

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 22, 2026

Not up to standards ⛔

🔴 Issues 1 high

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

Category Results
ErrorProne 1 high

View in Codacy

🟢 Metrics 4 complexity

Metric Results
Complexity 4

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…tic 0-result runs

When multiple concurrent testing/runTests RPCs arrive at the same TUnit
process, they race on shared static state (GlobalContext.Current,
AppDomain exception handlers, Trace.Listeners). This causes stochastic
0-result sessions under concurrency.

Changes:
- Add SemaphoreSlim(1,1) in TUnitTestFramework.ExecuteRequestAsync to
  serialize concurrent requests
- Make TUnitInitializer.Initialize idempotent with static flags to
  prevent repeated global handler/listener registration
- Add multi-session RPC tests validating sequential and concurrent
  runTests RPCs produce consistent results
Copilot AI changed the title [WIP] Document multi-session findings from mutation-testing prototype fix: serialize MTP server-mode concurrent sessions to prevent stochastic 0-result runs May 22, 2026
Copilot AI requested a review from thomhurst May 22, 2026 17:17
@thomhurst
Copy link
Copy Markdown
Owner

@copilot this is a band aid - we should correctly scope static state to either instance level or async local. Concurrent runs is a valid pattern.

…sions

Replace the shared static field with AsyncLocal<GlobalContext?> (matching
TestSessionContext, BeforeTestDiscoveryContext, TestDiscoveryContext).
A process-level DefaultInstance serves source-gen module initializers that
run before any session sets the AsyncLocal value.

This allows concurrent testing/runTests RPCs to each have their own
GlobalContext without overwriting each other, enabling true concurrent
multi-session support in MTP server mode.

Removes the SemaphoreSlim serialization approach (band-aid) in favor of
properly scoped state as requested.
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 22, 2026

@copilot this is a band aid - we should correctly scope static state to either instance level or async local. Concurrent runs is a valid pattern.

Agreed — removed the SemaphoreSlim and converted GlobalContext.Current to AsyncLocal<GlobalContext?> (matching the pattern already used by TestSessionContext, BeforeTestDiscoveryContext, TestDiscoveryContext). A static DefaultInstance serves source-gen module initializers that run before any session sets the value. Concurrent sessions now each get their own context without races.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MTP server mode: multi-session findings from a mutation-testing prototype

2 participants