Skip to content

Add --crash-sequence option to MTP CrashDump extension (#7262)#8526

Merged
Evangelink merged 3 commits into
mainfrom
dev/amauryleve/issue-7262-crash-sequence
May 25, 2026
Merged

Add --crash-sequence option to MTP CrashDump extension (#7262)#8526
Evangelink merged 3 commits into
mainfrom
dev/amauryleve/issue-7262-crash-sequence

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Closes #7262.

What

Adds a --crash-sequence on|off option (default on) to Microsoft.Testing.Extensions.CrashDump that generates a sequence file recording the start and end of each test as the run progresses. On a crash, the controller parses the file, prints a friendly "tests still running at the time of the crash" banner, and publishes the file as a FileArtifact.

This addresses the long-standing pain point from the issue: today, on --crashdump/--crash-report, users have to open the .dmp to know which test was running at crash time. --hangdump already produces an equivalent .log over IPC, but that path is unavailable for crashes because the testhost is dead.

How

  • CrashDumpSequenceLogger (new testhost IDataConsumer + ITestSessionLifetimeHandler) appends STARTED\t<iso>\t<uid>\t<displayName> / ENDED\t<iso>\t<uid>\t<state> lines per TestNodeUpdateMessage to a sequence file. Uses StreamWriter.AutoFlush=true (no fsync) so records reach the OS cache and survive Environment.FailFast at sub-microsecond cost per write.
  • CrashDumpEnvironmentVariableProvider computes a path with a per-controller 8-char GUID token (avoids cross-host collisions for parallel testhost launches into the same results directory) and exposes it via TESTINGPLATFORM_CRASHDUMP_SEQUENCE_FILE.
  • CrashDumpProcessLifetimeHandler on crash: parses the file, computes started \ ended set, prints the friendly banner, publishes the file. On graceful exit / hangdump kill: deletes the file (no diagnostic value).
  • --crash-sequence follows the --ansi style (on/off/true/false/enable/disable/1/0), validated by the existing CommandLineOptionArgumentValidator.

Performance

The cost is two small writes per test (~10 µs each, no fsync). For 10 000 tests this is ~200 ms total — well under 1 % on huge suites. The user discussed and confirmed this in the design conversation; the default-on policy is justified by the very low cost.

Limitations (documented in source)

  • Best-effort: a test that starts and immediately FailFasts within a few ms may not be recorded because the MTP message bus is asynchronous by design. This matches the same trade-off described in the issue ("mtp does not send the currently started test to testhost controller because of possible overhead"). The added value is for the much more common "test runs for some time, then crashes" pattern.
  • Sequence file does not survive an OS-level crash or power loss (no fsync).

Tests

  • 9 new unit-test datarow combinations cover argument validation and the rejection of --crash-sequence without --crashdump/--crash-report.
  • 5 new acceptance tests: sequence-on-crash content, off-by-flag, graceful-exit cleanup, invalid argument, missing main option. The CrashDump test asset was extended to publish fake in-progress test nodes before FailFast (with a small sync delay to give the async consumer time to drain, since this is a deterministic test setup).
  • Help/info expectations updated and XLF files regenerated via UpdateXlf.

Out of scope

This PR does not change --hangdump behavior: it already generates an equivalent in-progress test list over IPC.

Adds a testhost-side sequence file that records test state transitions (STARTED/ENDED) so the controller can list the tests that were running at the time of a crash without requiring dump analysis. Mirrors HangDump's UX but uses file-based persistence because the testhost is dead by the time the controller inspects it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 23, 2026 16:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds crash-time test “sequence file” journaling to the Microsoft.Testing.Platform (MTP) CrashDump extension, controlled by a new --crash-sequence on|off option, so users can see which tests were still running when a testhost crashes (and the file is also published as an artifact).

Changes:

  • Introduces testhost-side CrashDumpSequenceLogger to append STARTED/ENDED records to a controller-provided sequence file path.
  • Adds controller-side parsing/publishing/cleanup of the sequence file on crash vs. graceful exit, and wires the path via TESTINGPLATFORM_CRASHDUMP_SEQUENCE_FILE.
  • Updates command-line validation, help/info expectations, resources/XLFs, and adds unit + acceptance coverage for the new option.
Show a summary per file
File Description
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CrashDumpTests.cs Adds unit tests covering --crash-sequence validation and the “missing main option” rule.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HelpInfoAllExtensionsTests.cs Updates --help / --info expectations to include --crash-sequence.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CrashDumpTests.cs Adds acceptance tests for sequence generation, disabling, cleanup on graceful exit, and invalid usage; updates the CrashDump test asset to publish fake in-progress nodes.
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/CrashDumpResources.resx Adds new localized strings for the option, banner text, and artifact metadata.
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.cs.xlf Regenerates localization entries for new CrashDump sequence resources (Czech).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.de.xlf Regenerates localization entries for new CrashDump sequence resources (German).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.es.xlf Regenerates localization entries for new CrashDump sequence resources (Spanish).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.fr.xlf Regenerates localization entries for new CrashDump sequence resources (French).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.it.xlf Regenerates localization entries for new CrashDump sequence resources (Italian).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.ja.xlf Regenerates localization entries for new CrashDump sequence resources (Japanese).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.ko.xlf Regenerates localization entries for new CrashDump sequence resources (Korean).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.pl.xlf Regenerates localization entries for new CrashDump sequence resources (Polish).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.pt-BR.xlf Regenerates localization entries for new CrashDump sequence resources (Portuguese - Brazil).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.ru.xlf Regenerates localization entries for new CrashDump sequence resources (Russian).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.tr.xlf Regenerates localization entries for new CrashDump sequence resources (Turkish).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.zh-Hans.xlf Regenerates localization entries for new CrashDump sequence resources (Chinese Simplified).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Resources/xlf/CrashDumpResources.zh-Hant.xlf Regenerates localization entries for new CrashDump sequence resources (Chinese Traditional).
src/Platform/Microsoft.Testing.Extensions.CrashDump/Microsoft.Testing.Extensions.CrashDump.csproj Links in RoslynString helper from MTP to support shared helper usage in this extension.
src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs New testhost-side consumer/lifetime handler that writes the sequence journal file.
src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs Adds crash-time parsing/banner output and artifact publication; deletes sequence file on graceful exit/hangdump kill.
src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpExtensions.cs Wires the new composite CrashDumpSequenceLogger into the testhost extension pipeline.
src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpEnvironmentVariableProvider.cs Computes and exports the sequence file path env var; persists it into CrashDump configuration for controller-side handling.
src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpConfiguration.cs Adds SequenceFileName to share computed path with the process lifetime handler.
src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpCommandLineProvider.cs Adds --crash-sequence option and validates boolean-like arguments; treats it as requiring --crashdump/--crash-report.
src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpCommandLineOptions.cs Adds the crash-sequence option name constant.

Copilot's findings

  • Files reviewed: 25/25 changed files
  • Comments generated: 3

Comment on lines +333 to +339
catch (IOException ex)
{
// Best-effort diagnostic. If we cannot read the sequence file we still publish it so the
// user can inspect it manually, but skip the friendly summary.
await _outputDisplay.DisplayAsync(this, new ErrorMessageOutputDeviceData(string.Format(CultureInfo.InvariantCulture, CrashDumpResources.CrashDumpSequenceFileReadError, sequenceFilePath, ex.Message)), cancellationToken).ConfigureAwait(false);
await _messageBus.PublishAsync(this, new FileArtifact(new FileInfo(sequenceFilePath), CrashDumpResources.CrashDumpSequenceArtifactDisplayName, CrashDumpResources.CrashDumpSequenceArtifactDescription)).ConfigureAwait(false);
return;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — done in 906fe9a. The exception filter on TryPublishSequenceFileAsync now also covers UnauthorizedAccessException, SecurityException, ArgumentException and NotSupportedException (the documented throwers of File.ReadLines/iteration), and the file is still published as a FileArtifact so the user can inspect it manually.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 906fe9a. TryPublishSequenceFileAsync now catches the broader set IOException or UnauthorizedAccessException or System.Security.SecurityException or ArgumentException or NotSupportedException (filtered catch (Exception ex) when ...) and still publishes the file artifact in that branch so the user can inspect the raw file manually. Failing the crash-handling path because of a diagnostic file read would be strictly worse than missing the summary.

@Evangelink

Copy link
Copy Markdown
Member Author

🔍 Build Failure Analysis

Summary — The build failed due to analyzer violations in the newly added CrashDumpSequenceLogger.cs file. There are 6 distinct violation sites across two root causes: synchronous I/O calls (VSTHRD103) and legacy lock usage (IDE0330).


Root cause 1: Synchronous I/O operations in async context (VSTHRD103)

The new CrashDumpSequenceLogger class uses synchronous WriteLine(), Flush(), and Dispose() calls within async methods (OnTestSessionStartingAsync and ConsumeAsync), violating the VSTHRD103 analyzer rule which enforces async-all-the-way patterns in async code paths.

Design consideration: The PR description explicitly documents that the sequence file uses StreamWriter.AutoFlush=true (no fsync) for sub-microsecond per-write performance and states that synchronous writes are intentional for this diagnostic feature. However, the VS Threading analyzer is treating these as violations because the containing methods are async.

Affected files / errors

All errors occur in CrashDumpSequenceLogger.cs and affect all target frameworks (net8.0, net9.0, netstandard2.0):

Proposed fix

Two options:

  1. Suppress the analyzer with #pragma warning disable VSTHRD103 around the affected lines, documenting that synchronous I/O is intentional for this low-latency diagnostic feature (as described in the PR). This preserves the sub-microsecond performance guarantee.

  2. Switch to async I/O by changing to WriteLineAsync(), FlushAsync(), and DisposeAsync(), accepting the trade-off of slightly higher per-test overhead (though likely negligible for the documented 10k-test / 200ms budget).

Recommendation: Option 1 (suppress) aligns with the PR's stated performance design goals. The synchronous writes are load-bearing for the "survives Environment.FailFast" crash-safety property.


Root cause 2: Legacy lock object (IDE0330)

On .NET 9.0 only, line 42 uses new object() for _writeLock, but .NET 9 introduced System.Threading.Lock as a dedicated lock primitive with better diagnostics and performance characteristics. The IDE0330 analyzer enforces this upgrade.

Affected files / errors

Proposed fix

Use TFM-conditional compilation to choose the appropriate lock type:

#if NET9_0_OR_GREATER
    private readonly System.Threading.Lock _writeLock = new();
#else
    private readonly object _writeLock = new();
#endif

Build overview
  • Project: Microsoft.Testing.Extensions.CrashDump.csproj
  • Target frameworks: net8.0, net9.0, netstandard2.0
  • Exit code: 1 (Build FAILED)
  • Failed target: Compilation (analyzer violations)
  • Total errors: 14 instances across 6 unique violation sites (each appears once per TFM, except IDE0330 which is net9.0-only)
All MSBuild errors (14)
Code TFM File:Line Message
VSTHRD103 net8.0 CrashDumpSequenceLogger.cs:100 WriteLine synchronously blocks. Await WriteLineAsync instead.
VSTHRD103 net8.0 CrashDumpSequenceLogger.cs:101 Flush synchronously blocks. Await FlushAsync instead.
VSTHRD103 net8.0 CrashDumpSequenceLogger.cs:110 Dispose synchronously blocks. Await DisposeAsync instead.
VSTHRD103 net8.0 CrashDumpSequenceLogger.cs:163 WriteLine synchronously blocks. Await WriteLineAsync instead.
VSTHRD103 net8.0 CrashDumpSequenceLogger.cs:188 Flush synchronously blocks. Await FlushAsync instead.
VSTHRD103 net9.0 CrashDumpSequenceLogger.cs:100 WriteLine synchronously blocks. Await WriteLineAsync instead.
VSTHRD103 net9.0 CrashDumpSequenceLogger.cs:101 Flush synchronously blocks. Await FlushAsync instead.
VSTHRD103 net9.0 CrashDumpSequenceLogger.cs:110 Dispose synchronously blocks. Await DisposeAsync instead.
VSTHRD103 net9.0 CrashDumpSequenceLogger.cs:163 WriteLine synchronously blocks. Await WriteLineAsync instead.
VSTHRD103 net9.0 CrashDumpSequenceLogger.cs:188 Flush synchronously blocks. Await FlushAsync instead.
IDE0330 net9.0 CrashDumpSequenceLogger.cs:42 Use 'System.Threading.Lock'
VSTHRD103 netstandard2.0 CrashDumpSequenceLogger.cs:100 WriteLine synchronously blocks. Await WriteLineAsync instead.
VSTHRD103 netstandard2.0 CrashDumpSequenceLogger.cs:101 Flush synchronously blocks. Await FlushAsync instead.
VSTHRD103 netstandard2.0 CrashDumpSequenceLogger.cs:163 WriteLine synchronously blocks. Await WriteLineAsync instead.
VSTHRD103 netstandard2.0 CrashDumpSequenceLogger.cs:188 Flush synchronously blocks. Await FlushAsync instead.

🤖 Generated by the Build Failure Analysis workflow using binlog analysis · commit 74e7850

Generated by Build Failure Analysis for issue #8526 · ● 1.2M ·

@Evangelink Evangelink left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Generated by Build Failure Analysis for issue #8526 · ● 1.2M

Comment thread src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs Outdated
- Convert sync StreamWriter calls to async (WriteLineAsync/FlushAsync/DisposeAsync) to satisfy VSTHRD103.
- Replace plain `lock` with SemaphoreSlim so we can await inside the critical section (also avoids IDE0330 push to System.Threading.Lock which would require multi-TFM gymnastics).
- Suppress CA1416 narrowly on the sync Dispose path's SemaphoreSlim.Wait(), explaining that the path is unreachable on browser because the controller-side env var provider requires NETCOREAPP.
- Fix CrashDump test asset to use the netfx-compatible char[] overload of String.Split.

End-to-end validation: full `build.cmd -pack` now succeeds on origin/main; 19 CrashDumpTests acceptance tests pass (3 Windows-only skips for --crash-report); 9 HelpInfoAllExtensionsTests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs Dismissed
#pragma warning restore CA1416
try
{
if (_disposed)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same pattern as the ConsumeAsync re-check: the outer if (_disposed) return; is the fast path; the inner check under the semaphore is the canonical double-checked dispose pattern that prevents a second caller from racing through the dispose body after the first caller already flipped _disposed = true. CodeQL's single-method dataflow doesn't see that the field is mutated under the lock. Keeping as-is.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Keeping the inner if (_disposed) recheck. The outer fast-path check is before _writeSemaphore.Wait(): two threads can pass it concurrently before either acquires the semaphore. Without the inner recheck the second thread would re-dispose _writer and re-call _writeSemaphore.Dispose() in the finally, the latter throwing SemaphoreFullException (or NRE on _writer?.Dispose() is fine, but the double-Dispose() of the semaphore is not). The recheck is the standard double-checked-locking idiom and is not constant — CodeQL is missing the cross-thread mutation.

…delay

- CrashDumpProcessLifetimeHandler.TryPublishSequenceFileAsync now catches
  UnauthorizedAccessException/SecurityException/ArgumentException/NotSupportedException
  in addition to IOException so the controller crash path never fails on a
  best-effort diagnostic read.
- CrashDumpCommandLineProvider.ValidateOptionArgumentsAsync collapses the
  nested 'if' inside each option branch into a single '&&' condition, and
  the orphan TODO is rewritten as a rationale comment (no .dmp extension
  enforcement on --crashdump-filename).
- CrashDump test asset replaces the fixed 2000ms Task.Delay with a bounded
  polling loop (50ms tick, 30s deadline) on the sequence file via the
  TESTINGPLATFORM_CRASHDUMP_SEQUENCE_FILE env var, so the test is typically
  fast and still deterministic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 25, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 25/25 changed files
  • Comments generated: 1

Comment on lines +378 to +386
catch (IOException)
{
// Best-effort cleanup; a leftover sequence file is harmless beyond cluttering the results
// directory. Avoid surfacing this as a user-visible error because the run itself succeeded.
}
catch (UnauthorizedAccessException)
{
// Same rationale as IOException.
}
@Evangelink
Evangelink merged commit 0a94887 into main May 25, 2026
398 checks passed
@Evangelink
Evangelink deleted the dev/amauryleve/issue-7262-crash-sequence branch May 25, 2026 11:32
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 Dump] Support sequence files similar to VSTest

2 participants