fix: avoid leaking CheckedContinuations by removing possible Channel actor reentrancy#27
Merged
NeedleInAJayStack merged 8 commits intoGraphQLSwift:mainfrom Sep 11, 2025
Conversation
ea2a05c to
e26c023
Compare
|
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 |
Author
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. |
added 4 commits
September 10, 2025 18:28
e26c023 to
49e76b8
Compare
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.
49e76b8 to
cf261ba
Compare
Author
|
I've refactored to use the Result<Success, Failure> syntax and simplified some of the implementation style. |
NeedleInAJayStack
approved these changes
Sep 11, 2025
Member
NeedleInAJayStack
left a comment
There was a problem hiding this comment.
This looks great to me. I like the change from 'for' to 'popLast' as well. Thanks Brandon!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 thestate.removeAllWaitersresumes 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
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.