-
Notifications
You must be signed in to change notification settings - Fork 113
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 data race in LocalServer's invocation pool #479
Conversation
// if the iterator is waiting for an element, give it to it | ||
// otherwise, enqueue the element | ||
if let continuation = _continuation { | ||
continuation.resume(returning: invocation) |
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.
Not sure if it is a good idea to resume a continuation while holding a lock... usually I would try to keep the withLock
body free of side-effects.
if let element = self.popFirst() { | ||
// if there is an element in the buffer, dequeue it | ||
return element | ||
} else { | ||
// we can't return nil if there is nothing to dequeue otherwise the async for loop will stop | ||
// wait for an element to be enqueued | ||
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<T, any Error>) in | ||
// store the continuation for later, when an element is enqueued | ||
self._continuation.withLock { | ||
$0 = continuation | ||
// so, wait for an element to be enqueued | ||
return try await withCheckedThrowingContinuation { | ||
(continuation: CheckedContinuation<T, any Error>) in | ||
self.mutex.withLock { mutexContent in | ||
// store the continuation for later, when an element is enqueued | ||
mutexContent.1 = continuation |
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 is still racy: between "popFirst" returning nil and storing the continuation, somebody could have already put sth in the buffer. Really, to implement this correctly, I think you need to acquire the lock, check your invariants, apply the necessary state change, release the lock and then run side-effects.
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.
The invariant here is: There can only be a stored continuation if the buffer is empty. If the buffer is non-empty, there cannot be a stored continuation.
Thank you @t089 for your help. |
Use a single Mutex to store both the Invocation buffer and the continuation to avoid a possible data race condition as described here.
https://forums.swift.org/t/how-to-use-withcheckedcontinuation-with-an-async-closure/77852/8