-
Couldn't load subscription status.
- Fork 15
fix: avoid leaking CheckedContinuations by removing possible Channel actor reentrancy #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: avoid leaking CheckedContinuations by removing possible Channel actor reentrancy #27
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 |
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. |
e26c023 to
49e76b8
Compare
…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
|
I've refactored to use the Result<Success, Failure> syntax and simplified some of the implementation style. |
There was a problem hiding this 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!
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.