Skip to content

Commit 0e58df9

Browse files
committed
Support work-or-wait patterns a little better in AtomicWaitQueue.
Add a tryReloadAndWait method to Worker (which can only be used when not the worker thread). Revise the docs to describe this sort of pattern as the more standard pattern, which I think is likely to better reflect common use.
1 parent 7372e7e commit 0e58df9

File tree

1 file changed

+57
-15
lines changed

1 file changed

+57
-15
lines changed

include/swift/Runtime/AtomicWaitQueue.h

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ class AtomicWaitQueue {
120120
/// An RAII helper class for signalling that the current thread is a
121121
/// worker thread which has acquired the lock.
122122
///
123+
/// `AtomicWaitQueue` does not require the global lock to be held
124+
/// while creating or publishing the queue. Clients taking advantage
125+
/// of this should inform the Worker class that a created queue has
126+
/// been published by calling `flagQueueIsPublished`. Clients who
127+
/// wish to publish the queue while holding the global lock, perhaps
128+
/// to get a rule that all stores are done under the lock, may instead
129+
/// use `tryPublishQueue`.
130+
///
123131
/// The expected use pattern is something like:
124132
///
125133
/// ```
@@ -128,26 +136,47 @@ class AtomicWaitQueue {
128136
/// while (true) {
129137
/// if (oldStatus.isDone()) return;
130138
///
131-
/// // Try to publish the wait queue. If this succeeds, we've become
132-
/// // the worker thread.
133-
/// bool published = worker.tryPublishQueue([&] {
134-
/// auto newStatus = oldStatus.withLock(worker.createQueue());
135-
/// return myAtomic.compare_exchange_weak(oldStatus, newStatus,
136-
/// /*success*/ std::memory_order_release,
137-
/// /*failure*/ std::memory_order_acquire);
138-
/// });
139-
/// if (!published) continue;
139+
/// if (oldStatus.hasWaitQueue()) {
140+
/// bool waited = worker.tryReloadAndWait([&] {
141+
/// oldStatus = myAtomic.load(std::memory_order_acquire);
142+
/// return (oldStatus.hasWaitQueue() ? oldStatus.getWaitQueue()
143+
/// : nullptr);
144+
/// });
140145
///
141-
/// // Do the actual work here.
146+
/// // If we waited, `oldStatus` will be out of date; reload it.
147+
/// //
148+
/// // (For the pattern in this example, where the worker thread
149+
/// // always transitions the status to done, this is actually
150+
/// // unnecessary: by virtue of having successfully waited, we've
151+
/// // synchronized with the worker thread and know that the status
152+
/// // is done, so we could just return. But in general, this
153+
/// // reload is necessary.)
154+
/// if (waited)
155+
/// oldStatus = myAtomic.load(std::memory_order_acquire);
156+
///
157+
/// // Go back and reconsider the updated status.
158+
/// continue;
159+
/// }
142160
///
143-
/// // "Unpublish" the queue from the the atomic.
144-
/// while (true) {
145-
/// auto newStatus = oldStatus.withDone(true);
146-
/// if (myAtomic.compare_exchange_weak(oldStatus, newStatus,
161+
/// // Create a queue and try to publish it. If this succeeds,
162+
/// // we've become the worker thread. We don't have to worry
163+
/// // about the queue leaking if we don't use it; that's managed
164+
/// // by the Worker class.
165+
/// {
166+
/// auto queue = worker.createQueue();
167+
/// auto newStatus = oldStatus.withWaitQueue(queue);
168+
/// if (!myAtomic.compare_exchange_weak(oldStatus, newStatus,
147169
/// /*success*/ std::memory_order_release,
148170
/// /*failure*/ std::memory_order_acquire))
149-
/// break;
171+
/// continue;
172+
/// worker.flagQueueIsPublished(queue);
150173
/// }
174+
///
175+
/// // Do the actual work here.
176+
///
177+
/// // Report that the work is done and "unpublish" the queue from
178+
/// // the atomic.
179+
/// myAtomic.store(oldStatus.withDone(true), std::memory_order_release);
151180
/// worker.finishAndUnpublishQueue([]{});
152181
/// return;
153182
/// }
@@ -193,6 +222,19 @@ class AtomicWaitQueue {
193222
return Published;
194223
}
195224

225+
/// Given that this thread is not the worker thread and there seems
226+
/// to be a wait queue in place, try to wait on it.
227+
///
228+
/// Acquire the global lock and call the given function. If it
229+
/// returns a wait queue, wait on that queue and return true;
230+
/// otherwise, return false.
231+
template <class Fn>
232+
bool tryReloadAndWait(Fn &&fn) {
233+
assert(!isWorkerThread());
234+
typename Impl::Waiter waiter(GlobalLock);
235+
return waiter.tryReloadAndWait(std::forward<Fn>(fn));
236+
}
237+
196238
/// Given that this thread is the worker thread, return the queue
197239
/// that's been created and published for it.
198240
Impl *getPublishedQueue() const {

0 commit comments

Comments
 (0)