Skip to content

Conversation

@bjschoenfeld
Copy link

In the Channel.fulfill and Channel.fail methods, the line await state.removeAllWaiters() allows for actor reentrance. This means that after resuming all the CheckedContinuations (waiters), but before removing them, another waiter could be added to the array. Then once the state.removeAllWaiters resumes execution, any CheckedContinuations would be removed without having been resumed. This leads to leaking CheckedContinuations.

This was observed in my application, where I would get the logged message

SWIFT TASK CONTINUATION MISUSE: value leaked its continuation without resuming it. This may cause tasks waiting on it to remain suspended forever.

which originates from Swift here.

I verified that Channel.value was the culprit by renaming it and observing the changed name in the CONTINUATION MISUSE message.

This change set removes the possibility of actor reentrancy and leaking CheckedContinuations. By inlining the State data members in Channel and deleting the State actor completely, we remove all await calls inside Channel.fulfill and Channel.fail.

@bjschoenfeld bjschoenfeld force-pushed the refactor/simplify-channel branch from ea2a05c to e26c023 Compare September 10, 2025 20:54
@JaapWijnen
Copy link

JaapWijnen commented Sep 10, 2025

Glad you found a fix! One nit I have with this (which was already present in the old implementation) is the two optionals. This could be encapsulated in a single 'Result<Success, Failure>' object

@bjschoenfeld
Copy link
Author

One nit I have with this (which was already present in the old implementation) is the two optionals. This could be encapsulated in a single 'Result<Success, Failure>' object

Totally agree. I almost made something like that. I'm working on a test/reproducer to help demonstrate this fix. I'll swing back around after.

@bjschoenfeld bjschoenfeld force-pushed the refactor/simplify-channel branch from e26c023 to 49e76b8 Compare September 11, 2025 00:08
Brandon Schoenfeld added 4 commits September 11, 2025 00:11
…actor reentrancy

In the Channel.fulfill and Channel.fail methods, the line `await state.removeAllWaiters()` allows for actor reentrance. This means that after resuming all the CheckedContinuations (waiters), but before removing them, another waiter could be added to the array. Then once the `state.removeAllWaiters` resumes execution, any CheckedContinuations would be removed without having been resumed. This leads to leaking CheckedContinuations.

This was observed in my application, where I would get a logger message

SWIFT TASK CONTINUATION MISUSE: value leaked its continuation without resuming it. This may cause tasks waiting on it to remain suspended forever.

which originates from Swift here: https://github.com/swiftlang/swift/blob/b06eed151c8aa2bc2f4e081b0bb7b5e5c65f3bba/stdlib/public/Concurrency/CheckedContinuation.swift#L82

I verified that Channel.value was the culprit by renaming it and observing the changed name in the CONTINUATION MISUSE message.

This change set removes the possibility of actor reentrancy and leaking CheckedContinuations. By inlining the State data members in Channel and deleting the State actor completely, we remove all await calls inside Channel.fulfill and Channel.fail.
@bjschoenfeld bjschoenfeld force-pushed the refactor/simplify-channel branch from 49e76b8 to cf261ba Compare September 11, 2025 05:23
@bjschoenfeld
Copy link
Author

bjschoenfeld commented Sep 11, 2025

I've refactored to use the Result<Success, Failure> syntax and simplified some of the implementation style.

Copy link
Member

@NeedleInAJayStack NeedleInAJayStack left a comment

Choose a reason for hiding this comment

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

This looks great to me. I like the change from 'for' to 'popLast' as well. Thanks Brandon!

@NeedleInAJayStack NeedleInAJayStack merged commit 15ff327 into GraphQLSwift:main Sep 11, 2025
9 checks passed
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.

3 participants