test(galaxy): shutdown stream-cancel signal from the iterator's exit, not a token registration (#856)
Found while load-verifying #856: ShutdownAsync_cancels_the_event_stream_and_releases_the_pump timed out 7/700 under parallel load. The fake's StreamCancelled was set by a CancellationToken registration scoped by `using` inside the async iterator. On Cancel() the newer ReadAllAsync registration runs first and resumes the iterator on a pool thread, whose `using` can dispose our registration while Cancel() is still walking the callback list - so it never runs. Signal from the iterator's finally (when the token is cancelled) instead, which also proves the stream actually ended. 0/700 after. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
+15
-3
@@ -68,10 +68,22 @@ public sealed class GalaxyDriverShutdownTests
|
||||
public async IAsyncEnumerable<MxEvent> StreamEventsAsync([EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
Interlocked.Increment(ref StreamsOpened);
|
||||
using var _ = cancellationToken.Register(() => StreamCancelled.TrySetResult());
|
||||
await foreach (var ev in _events.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
|
||||
// Signal from the iterator's own exit, not from a token registration: a registration made
|
||||
// here is disposed by the `using` when the cancelled ReadAllAsync resumes the body on a
|
||||
// pool thread — which can happen while Cancel() on the pump's thread is still walking its
|
||||
// callback list, unregistering ours before it runs (CTS runs callbacks newest-first, and
|
||||
// ReadAllAsync's is newer). Observed ~1% under parallel load as a 10 s timeout. A finally
|
||||
// also proves the stronger fact: the stream actually ended because it was cancelled.
|
||||
try
|
||||
{
|
||||
yield return ev;
|
||||
await foreach (var ev in _events.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return ev;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) StreamCancelled.TrySetResult();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user