背景
2026-08-30 验证 #136 的改动时,SalmonEgg.Presentation.Core.Tests 全量套件出现两类与改动无关的偶发失败。已在干净 HEAD(b90b77aa,无任何未提交改动)的临时 worktree 上复现,证明是既有问题。
问题 1(主):测试进程被 unhandled exception 炸死(exit 134 / SIGABRT)
栈:
Unhandled exception. System.Threading.Tasks.TaskCanceledException: A task was canceled.
at ChatViewModel.ApplyCurrentStoreProjectionAsync(...) // ChatViewModel.cs:2047
at ChatViewModel.SetModeAsync(SessionModeViewModel) // ChatViewModel.CommandWorkflow.cs:1433
at AsyncRelayCommand.AwaitAndThrowIfFailed(Task) // CommunityToolkit.Mvvm
at System.Threading.Tasks.Task.<>c.<ThrowAsync>...
at System.Threading.ThreadPoolWorkQueue.Dispatch()
- fire-and-forget 的
AsyncRelayCommand 任务被取消后,取消异常从 AwaitAndThrowIfFailed rethrow 到线程池,成为 unhandled exception,测试进程直接 SIGABRT。
- 症状极具迷惑性:MTP 总结显示
failed: 0(甚至 succeeded: 全部已跑)但退出码 134;全量"总数"忽多忽少(实测 651 / 873 / 1026 / 1786 / 3273)——进程炸死时排在后面的测试根本没执行。极易被误读为"绿了"或"总数漂移"。
- 复现率:主树与干净基线各至少 1 次(共约 9 次全量运行中出现 4 次)。
问题 2:VoiceInputDiagnosticsProbeViewModelTests 两个用例负载敏感偶发红
StopProbeAsync_FromBackgroundThread_MarshalsBindableStateChangesThroughUiDispatcher(VoiceInputDiagnosticsProbeViewModelTests.cs:113)
StartProbeAsync_WhenActivationArrivesBeforeDeniedFlowSettles_RetriesAfterProbeBecomesIdle(同文件 :311)
主树 1 次 + 干净基线 1 次;重跑即绿。疑似 wall-clock/调度依赖,与 [[StartViewModelTests]] 曾修过的 busy-wait flake 同族。
期望
ChatViewModel 的取消路径不得逃逸为 unhandled exception:fire-and-forget 执行处收口 OperationCanceledException(或取消时改走可观察的完成通道),而不是依赖 AwaitAndThrowIfFailed 不炸。
- 两个 VoiceInput 用例消除负载敏感性(异步谓词等待,而非 wall-clock 假设)。
- 顺带排查测试 host 是否还有其他 fire-and-forget 异常逃逸入口。
背景
2026-08-30 验证 #136 的改动时,
SalmonEgg.Presentation.Core.Tests全量套件出现两类与改动无关的偶发失败。已在干净 HEAD(b90b77aa,无任何未提交改动)的临时 worktree 上复现,证明是既有问题。问题 1(主):测试进程被 unhandled exception 炸死(exit 134 / SIGABRT)
栈:
AsyncRelayCommand任务被取消后,取消异常从AwaitAndThrowIfFailedrethrow 到线程池,成为 unhandled exception,测试进程直接 SIGABRT。failed: 0(甚至succeeded: 全部已跑)但退出码 134;全量"总数"忽多忽少(实测 651 / 873 / 1026 / 1786 / 3273)——进程炸死时排在后面的测试根本没执行。极易被误读为"绿了"或"总数漂移"。问题 2:VoiceInputDiagnosticsProbeViewModelTests 两个用例负载敏感偶发红
StopProbeAsync_FromBackgroundThread_MarshalsBindableStateChangesThroughUiDispatcher(VoiceInputDiagnosticsProbeViewModelTests.cs:113)StartProbeAsync_WhenActivationArrivesBeforeDeniedFlowSettles_RetriesAfterProbeBecomesIdle(同文件:311)主树 1 次 + 干净基线 1 次;重跑即绿。疑似 wall-clock/调度依赖,与 [[StartViewModelTests]] 曾修过的 busy-wait flake 同族。
期望
ChatViewModel的取消路径不得逃逸为 unhandled exception:fire-and-forget 执行处收口OperationCanceledException(或取消时改走可观察的完成通道),而不是依赖AwaitAndThrowIfFailed不炸。