Skip to content

Fix race condition in UnfoldResourceAsyncSource callback invocation#7859

Merged
Aaronontheweb merged 8 commits into
akkadotnet:devfrom
Aaronontheweb:claude-wt-UnfoldResourceAsyncSourceFailure2
Oct 5, 2025
Merged

Fix race condition in UnfoldResourceAsyncSource callback invocation#7859
Aaronontheweb merged 8 commits into
akkadotnet:devfrom
Aaronontheweb:claude-wt-UnfoldResourceAsyncSourceFailure2

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Member

Summary

Fixed a race condition in UnfoldResourceSourceAsync where resource cleanup was skipped when streams were cancelled quickly (e.g., with Sink.Cancelled).

Changes

  • Changed UnfoldResourceSourceAsync to use InvokeWithFeedback instead of direct callback invocation
  • Used GetTypedAsyncCallback to obtain IAsyncCallback<T> for proper feedback handling
  • Implemented async local function pattern to properly catch StreamDetachedException
  • Ensures resource cleanup occurs even when callback cannot be processed due to stream completion

Root Cause

The previous implementation used Action<T> invocation which silently drops callbacks after stage shutdown. This caused the close callback to never execute when the stream stopped before the resource creation callback could be invoked.

Technical Details

The fix matches the Scala Akka implementation's use of invokeWithFeedback, which returns a Future/Task that fails with StreamDetachedException when the stage has stopped. This allows proper cleanup in the exception handler.

Test Results

  • ✅ All 20 tests in UnfoldResourceAsyncSourceSpec pass
  • ✅ Specifically fixes the flaky test: A_UnfoldResourceAsyncSource_must_close_resource_when_stream_is_quickly_cancelled_reproducer_2
  • ✅ Verified stable across 10 consecutive runs

Files Changed

  • src/core/Akka.Streams/Implementation/Sources.cs

Changed UnfoldResourceSourceAsync to use InvokeWithFeedback instead of
direct callback invocation to properly handle StreamDetachedException
when the stream stops before resource creation completes.

The previous implementation used Action<T> invocation which silently
drops callbacks after stage shutdown. This caused resource cleanup to
be skipped when streams were cancelled quickly (e.g., with Sink.Cancelled),
leading to test timeouts waiting for close callbacks.

The fix uses GetTypedAsyncCallback to obtain IAsyncCallback<T> and calls
InvokeWithFeedback with an async local function pattern. This ensures
StreamDetachedException is properly caught and resource cleanup occurs
even when the callback cannot be processed due to stream completion.

Fixes UnfoldResourceAsyncSourceSpec.A_UnfoldResourceAsyncSource_must_close_resource_when_stream_is_quickly_cancelled_reproducer_2

@Aaronontheweb Aaronontheweb 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.

Detailed my changes

return strategy != null ? strategy.Decider : Deciders.StoppingDecider;
});

_createdCallback = new Lazy<IAsyncCallback<Try<TSource>>>(() =>

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.

This is another stage that really needs the InvokeWithFeedback changes we introduced in #7578

}
}

_ = InvokeCallback();

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.

Invokes the callback and ensures that the stage gets closed afterwards in the event of failure, so that way we can't miss / fail resource clean-up.

@Aaronontheweb Aaronontheweb enabled auto-merge (squash) October 4, 2025 15:22
@Aaronontheweb Aaronontheweb disabled auto-merge October 5, 2025 14:16
@Aaronontheweb Aaronontheweb merged commit ccad240 into akkadotnet:dev Oct 5, 2025
11 checks passed
@Aaronontheweb Aaronontheweb deleted the claude-wt-UnfoldResourceAsyncSourceFailure2 branch October 5, 2025 14:16
Aaronontheweb added a commit to Aaronontheweb/akka.net that referenced this pull request Oct 5, 2025
…kkadotnet#7859)

Changed UnfoldResourceSourceAsync to use InvokeWithFeedback instead of
direct callback invocation to properly handle StreamDetachedException
when the stream stops before resource creation completes.

The previous implementation used Action<T> invocation which silently
drops callbacks after stage shutdown. This caused resource cleanup to
be skipped when streams were cancelled quickly (e.g., with Sink.Cancelled),
leading to test timeouts waiting for close callbacks.

The fix uses GetTypedAsyncCallback to obtain IAsyncCallback<T> and calls
InvokeWithFeedback with an async local function pattern. This ensures
StreamDetachedException is properly caught and resource cleanup occurs
even when the callback cannot be processed due to stream completion.

Fixes UnfoldResourceAsyncSourceSpec.A_UnfoldResourceAsyncSource_must_close_resource_when_stream_is_quickly_cancelled_reproducer_2
Arkatufus pushed a commit to Arkatufus/akka.net that referenced this pull request Mar 23, 2026
…kkadotnet#7859)

Changed UnfoldResourceSourceAsync to use InvokeWithFeedback instead of
direct callback invocation to properly handle StreamDetachedException
when the stream stops before resource creation completes.

The previous implementation used Action<T> invocation which silently
drops callbacks after stage shutdown. This caused resource cleanup to
be skipped when streams were cancelled quickly (e.g., with Sink.Cancelled),
leading to test timeouts waiting for close callbacks.

The fix uses GetTypedAsyncCallback to obtain IAsyncCallback<T> and calls
InvokeWithFeedback with an async local function pattern. This ensures
StreamDetachedException is properly caught and resource cleanup occurs
even when the callback cannot be processed due to stream completion.

Fixes UnfoldResourceAsyncSourceSpec.A_UnfoldResourceAsyncSource_must_close_resource_when_stream_is_quickly_cancelled_reproducer_2

(cherry picked from commit ccad240)
Arkatufus added a commit that referenced this pull request Mar 23, 2026
[1.5] Port #7859 and #7875 to fix UnfoldResourceAsyncSourceSpec
This was referenced May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant