Skip to content
File

Blob: src/workerd/util/wait-list.c++

5.4 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "wait-list.h"
6 
7#include <kj/debug.h>
8 
9namespace workerd {
10 
11namespace {
12// Optimization: If the same wait list is waited multiple times in the same thread, we want to
13// share the signal rather than send two cross-thread signals.
14static const kj::EventLoopLocal<CrossThreadWaitList::WaiterMap> threadLocalWaiters;
15 
16void END_WAIT_LIST_CANCELER_STACK_START_CANCELEE_STACK() {}
17} // namespace
18 
19CrossThreadWaitList::CrossThreadWaitList(Options options)
20 : state(kj::atomicRefcounted<State>(options)) {}
21 
22void CrossThreadWaitList::destroyed() {
23 if (!createdFulfiller) state->lostFulfiller();
24}
25 
26CrossThreadWaitList::Waiter::Waiter(
27 const State& state, kj::Own<kj::CrossThreadPromiseFulfiller<void>> fulfillerArg)
28 : state(kj::atomicAddRef(state)),
29 fulfiller(kj::mv(fulfillerArg)) {
30 auto lock = state.waiters.lockExclusive();
31 if (__atomic_load_n(&state.done, __ATOMIC_ACQUIRE)) {
32 KJ_IF_SOME(e, state.exception) {
33 fulfiller->reject(e.clone());
34 } else {
35 fulfiller->fulfill();
36 }
37 } else {
38 lock->add(*this);
39 }
40}
41CrossThreadWaitList::Waiter::~Waiter() noexcept(false) {
42 if (__atomic_load_n(&unlinked, __ATOMIC_ACQUIRE)) {
43 // No need to take a lock, already unlinked.
44 KJ_ASSERT(!link.isLinked());
45 } else {
46 auto lock = state->waiters.lockExclusive();
47 if (link.isLinked()) {
48 lock->remove(*this);
49 }
50 }
51 
52 if (state->useThreadLocalOptimization) {
53 auto& entry = KJ_ASSERT_NONNULL(threadLocalWaiters->findEntry(state.get()));
54 KJ_ASSERT(entry.value == this);
55 threadLocalWaiters->erase(entry);
56 }
57}
58 
59kj::Promise<void> CrossThreadWaitList::addWaiter() const {
60 if (__atomic_load_n(&state->done, __ATOMIC_ACQUIRE)) {
61 KJ_IF_SOME(e, state->exception) {
62 return e.clone();
63 } else {
64 return kj::READY_NOW;
65 }
66 }
67 
68 if (state->useThreadLocalOptimization) {
69 kj::Own<Waiter> ownWaiter;
70 
71 auto& waiter = threadLocalWaiters->findOrCreate(
72 state.get(), [&]() -> CrossThreadWaitList::WaiterMap::Entry {
73 auto paf = kj::newPromiseAndCrossThreadFulfiller<void>();
74 ownWaiter = kj::refcounted<Waiter>(*state, kj::mv(paf.fulfiller));
75 ownWaiter->forkedPromise = paf.promise.fork();
76 return {state.get(), ownWaiter.get()};
77 });
78 
79 if (ownWaiter.get() == nullptr) {
80 ownWaiter = kj::addRef(*waiter);
81 }
82 
83 return waiter->forkedPromise.addBranch().attach(kj::mv(ownWaiter));
84 } else {
85 // No refcounting, no forked promise.
86 auto paf = kj::newPromiseAndCrossThreadFulfiller<void>();
87 auto waiter = kj::heap<Waiter>(*state, kj::mv(paf.fulfiller));
88 return paf.promise.attach(kj::mv(waiter));
89 }
90}
91 
92kj::Own<kj::CrossThreadPromiseFulfiller<void>> CrossThreadWaitList::makeSeparateFulfiller() {
93 class FulfillerImpl final: public kj::CrossThreadPromiseFulfiller<void> {
94 public:
95 FulfillerImpl(kj::Own<const State> state): state(kj::mv(state)) {}
96 ~FulfillerImpl() noexcept(false) {
97 state->lostFulfiller();
98 }
99 void fulfill(kj::_::Void&&) const override {
100 state->fulfill();
101 }
102 void reject(kj::Exception&& exception) const override {
103 state->reject(kj::mv(exception));
104 }
105 bool isWaiting() const override {
106 // Note that it would be incorrect for isWaiting() to return false when `done` is false
107 // even if the waiter list is empty, because the waiter list could become non-empty later.
108 // In theory if we could determine that there will never be a waiter, then isWaiting()
109 // could return false.
110 return !__atomic_load_n(&state->done, __ATOMIC_ACQUIRE);
111 }
112 
113 private:
114 kj::Own<const State> state;
115 };
116 
117 KJ_REQUIRE(!createdFulfiller, "makeSeparateFulfiller() can only be called once");
118 createdFulfiller = true;
119 return kj::heap<FulfillerImpl>(kj::atomicAddRef(*state));
120}
121 
122void CrossThreadWaitList::State::fulfill() const {
123 if (__atomic_load_n(&done, __ATOMIC_ACQUIRE)) return;
124 auto lock = waiters.lockExclusive();
125 if (done) return;
126 __atomic_store_n(&done, true, __ATOMIC_RELEASE);
127 
128 for (auto& waiter: *lock) {
129 lock->remove(waiter);
130 waiter.fulfiller->fulfill();
131 __atomic_store_n(&waiter.unlinked, true, __ATOMIC_RELEASE);
132 }
133}
134 
135void CrossThreadWaitList::State::reject(kj::Exception&& e) const {
136 if (__atomic_load_n(&done, __ATOMIC_ACQUIRE)) return;
137 auto lock = waiters.lockExclusive();
138 if (done) return;
139 auto& exceptionRef = exception.emplace(kj::mv(e));
140 __atomic_store_n(&done, true, __ATOMIC_RELEASE);
141 
142 for (auto& waiter: *lock) {
143 lock->remove(waiter);
144 waiter.fulfiller->reject(exceptionRef.clone());
145 __atomic_store_n(&waiter.unlinked, true, __ATOMIC_RELEASE);
146 }
147}
148 
149void CrossThreadWaitList::State::lostFulfiller() const {
150 if (__atomic_load_n(&done, __ATOMIC_ACQUIRE)) return;
151 auto lock = waiters.lockExclusive();
152 if (done) return;
153 auto& exceptionRef = exception.emplace(kj::getDestructionReason(
154 reinterpret_cast<void*>(&END_WAIT_LIST_CANCELER_STACK_START_CANCELEE_STACK),
155 kj::Exception::Type::FAILED, __FILE__, __LINE__, "wait list was never fulfilled"_kj));
156 __atomic_store_n(&done, true, __ATOMIC_RELEASE);
157 
158 if (!lock->empty()) {
159 for (auto& waiter: *lock) {
160 lock->remove(waiter);
161 waiter.fulfiller->reject(exceptionRef.clone());
162 __atomic_store_n(&waiter.unlinked, true, __ATOMIC_RELEASE);
163 }
164 }
165}
166 
167} // namespace workerd