Skip to content

Commit 5001116

Browse files
max-charlambMax CharlambCopilot
authored
Fix flaky AsyncContinuation cDAC dump test on osx-arm64 by using Task.Yield (#128004)
> [!NOTE] > This pull request was prepared with assistance from GitHub Copilot. ## Problem `AsyncContinuationDumpTests.ThreadLocalContinuation_IsContinuation` fails intermittently on **osx-arm64 R2R** with: > Could not find AsyncDispatcherInfo type in CoreLib Failure tracked in #127774. Recently observed in builds [1413917](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1413917) and [1411121](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1411121). Fixes #127774 ## Root cause The test relies on the runtime having executed `AsyncHelpers+RuntimeAsyncTask<T>.DispatchContinuations()`, which writes `AsyncDispatcherInfo.t_current` and forces the JIT to load the `AsyncDispatcherInfo` MethodTable. The current debuggee uses `await Task.Delay(1)` to force a suspension. On fast machines (in particular Apple Silicon CI machines) the 1ms timer can fire before the runtime-async `Await` helper checks `IsCompleted`, so the awaiter is observed as already-completed and the await runs straight through synchronously. With no suspension there is no dispatch, no `t_current` write, and the `AsyncDispatcherInfo` MT is never loaded. I downloaded the failing osx-arm64 dump and walked the crashed thread: ``` [2-4] FailFast [5] InnerAsync [6] OuterAsync (async2 body) [7] OuterAsync (thunk) [8] Main [9] CallEntryPoint ``` There is no `DispatchContinuations` frame on the stack. `OuterAsync` calls `InnerAsync` directly, confirming the await did not suspend. I also walked all 3 modules' `TypeDefToMethodTable` maps and confirmed `AsyncDispatcherInfo` MT is genuinely null everywhere (i.e. cDAC is reporting reality - the type never got loaded). `Object`, `Task`, and `Continuation` MTs are all loaded as expected. ## Fix Replace `await Task.Delay(1)` with `await Task.Yield()` in `InnerAsync`. `Task.Yield()` returns a `YieldAwaitable` whose awaiter's `IsCompleted` always returns `false`, so the continuation is unconditionally posted back via `DispatchContinuations`. This guarantees `AsyncDispatcherInfo.t_current` is written before `InnerAsync` resumes and calls `FailFast`, regardless of machine timing. ## Validation Posting as a draft to run CI across all platforms; the osx-arm64 R2R leg is the one to watch. Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent df032fb commit 5001116

1 file changed

Lines changed: 1 addition & 1 deletion

File tree

  • src/native/managed/cdac/tests/DumpTests/Debuggees/AsyncContinuation

src/native/managed/cdac/tests/DumpTests/Debuggees/AsyncContinuation/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal static class Program
1818
{
1919
internal static async Task<int> InnerAsync(int value)
2020
{
21-
await Task.Delay(1);
21+
await Task.Yield();
2222

2323
// Crash while still inside Resume — t_current is set on this thread
2424
// and NextContinuation points to OuterAsync's continuation.

0 commit comments

Comments
 (0)