Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / js / src / vm / OffThreadPromiseRuntimeState.cpp
blob004c50492a16b22368eebe1509dc87369292229d
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "vm/OffThreadPromiseRuntimeState.h"
9 #include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
11 #include <utility> // mozilla::Swap
13 #include "jspubtd.h" // js::CurrentThreadCanAccessRuntime
15 #include "js/AllocPolicy.h" // js::ReportOutOfMemory
16 #include "js/HeapAPI.h" // JS::shadow::Zone
17 #include "js/Promise.h" // JS::Dispatchable, JS::DispatchToEventLoopCallback
18 #include "js/Utility.h" // js_delete, js::AutoEnterOOMUnsafeRegion
19 #include "threading/ProtectedData.h" // js::UnprotectedData
20 #include "vm/HelperThreads.h" // js::AutoLockHelperThreadState
21 #include "vm/JSContext.h" // JSContext
22 #include "vm/PromiseObject.h" // js::PromiseObject
23 #include "vm/Realm.h" // js::AutoRealm
24 #include "vm/Runtime.h" // JSRuntime
26 #include "vm/Realm-inl.h" // js::AutoRealm::AutoRealm
28 using JS::Handle;
30 using js::OffThreadPromiseRuntimeState;
31 using js::OffThreadPromiseTask;
33 OffThreadPromiseTask::OffThreadPromiseTask(JSContext* cx,
34 JS::Handle<PromiseObject*> promise)
35 : runtime_(cx->runtime()), promise_(cx, promise), registered_(false) {
36 MOZ_ASSERT(runtime_ == promise_->zone()->runtimeFromMainThread());
37 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
38 MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized());
41 OffThreadPromiseTask::~OffThreadPromiseTask() {
42 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
44 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
45 MOZ_ASSERT(state.initialized());
47 if (registered_) {
48 unregister(state);
52 bool OffThreadPromiseTask::init(JSContext* cx) {
53 MOZ_ASSERT(cx->runtime() == runtime_);
54 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
56 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
57 MOZ_ASSERT(state.initialized());
59 AutoLockHelperThreadState lock;
61 if (!state.live().putNew(this)) {
62 ReportOutOfMemory(cx);
63 return false;
66 registered_ = true;
67 return true;
70 void OffThreadPromiseTask::unregister(OffThreadPromiseRuntimeState& state) {
71 MOZ_ASSERT(registered_);
72 AutoLockHelperThreadState lock;
73 state.live().remove(this);
74 registered_ = false;
77 void OffThreadPromiseTask::run(JSContext* cx,
78 MaybeShuttingDown maybeShuttingDown) {
79 MOZ_ASSERT(cx->runtime() == runtime_);
80 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
81 MOZ_ASSERT(registered_);
83 // Remove this task from live_ before calling `resolve`, so that if `resolve`
84 // itself drains the queue reentrantly, the queue will not think this task is
85 // yet to be queued and block waiting for it.
87 // The unregister method synchronizes on the helper thread lock and ensures
88 // that we don't delete the task while the helper thread is still running.
89 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
90 MOZ_ASSERT(state.initialized());
91 unregister(state);
93 if (maybeShuttingDown == JS::Dispatchable::NotShuttingDown) {
94 // We can't leave a pending exception when returning to the caller so do
95 // the same thing as Gecko, which is to ignore the error. This should
96 // only happen due to OOM or interruption.
97 AutoRealm ar(cx, promise_);
98 if (!resolve(cx, promise_)) {
99 cx->clearPendingException();
103 js_delete(this);
106 void OffThreadPromiseTask::dispatchResolveAndDestroy() {
107 AutoLockHelperThreadState lock;
108 dispatchResolveAndDestroy(lock);
111 void OffThreadPromiseTask::dispatchResolveAndDestroy(
112 const AutoLockHelperThreadState& lock) {
113 MOZ_ASSERT(registered_);
115 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
116 MOZ_ASSERT(state.initialized());
117 MOZ_ASSERT(state.live().has(this));
119 // If the dispatch succeeds, then we are guaranteed that run() will be
120 // called on an active JSContext of runtime_.
121 if (state.dispatchToEventLoopCallback_(state.dispatchToEventLoopClosure_,
122 this)) {
123 return;
126 // The DispatchToEventLoopCallback has rejected this task, indicating that
127 // shutdown has begun. Count the number of rejected tasks that have called
128 // dispatchResolveAndDestroy, and when they account for the entire contents of
129 // live_, notify OffThreadPromiseRuntimeState::shutdown that it is safe to
130 // destruct them.
131 state.numCanceled_++;
132 if (state.numCanceled_ == state.live().count()) {
133 state.allCanceled().notify_one();
137 OffThreadPromiseRuntimeState::OffThreadPromiseRuntimeState()
138 : dispatchToEventLoopCallback_(nullptr),
139 dispatchToEventLoopClosure_(nullptr),
140 numCanceled_(0),
141 internalDispatchQueueClosed_(false) {}
143 OffThreadPromiseRuntimeState::~OffThreadPromiseRuntimeState() {
144 MOZ_ASSERT(live_.refNoCheck().empty());
145 MOZ_ASSERT(numCanceled_ == 0);
146 MOZ_ASSERT(internalDispatchQueue_.refNoCheck().empty());
147 MOZ_ASSERT(!initialized());
150 void OffThreadPromiseRuntimeState::init(
151 JS::DispatchToEventLoopCallback callback, void* closure) {
152 MOZ_ASSERT(!initialized());
154 dispatchToEventLoopCallback_ = callback;
155 dispatchToEventLoopClosure_ = closure;
157 MOZ_ASSERT(initialized());
160 /* static */
161 bool OffThreadPromiseRuntimeState::internalDispatchToEventLoop(
162 void* closure, JS::Dispatchable* d) {
163 OffThreadPromiseRuntimeState& state =
164 *reinterpret_cast<OffThreadPromiseRuntimeState*>(closure);
165 MOZ_ASSERT(state.usingInternalDispatchQueue());
166 gHelperThreadLock.assertOwnedByCurrentThread();
168 if (state.internalDispatchQueueClosed_) {
169 return false;
172 // The JS API contract is that 'false' means shutdown, so be infallible
173 // here (like Gecko).
174 AutoEnterOOMUnsafeRegion noOOM;
175 if (!state.internalDispatchQueue().pushBack(d)) {
176 noOOM.crash("internalDispatchToEventLoop");
179 // Wake up internalDrain() if it is waiting for a job to finish.
180 state.internalDispatchQueueAppended().notify_one();
181 return true;
184 bool OffThreadPromiseRuntimeState::usingInternalDispatchQueue() const {
185 return dispatchToEventLoopCallback_ == internalDispatchToEventLoop;
188 void OffThreadPromiseRuntimeState::initInternalDispatchQueue() {
189 init(internalDispatchToEventLoop, this);
190 MOZ_ASSERT(usingInternalDispatchQueue());
193 bool OffThreadPromiseRuntimeState::initialized() const {
194 return !!dispatchToEventLoopCallback_;
197 void OffThreadPromiseRuntimeState::internalDrain(JSContext* cx) {
198 MOZ_ASSERT(usingInternalDispatchQueue());
200 for (;;) {
201 JS::Dispatchable* d;
203 AutoLockHelperThreadState lock;
205 MOZ_ASSERT(!internalDispatchQueueClosed_);
206 MOZ_ASSERT_IF(!internalDispatchQueue().empty(), !live().empty());
207 if (live().empty()) {
208 return;
211 // There are extant live OffThreadPromiseTasks. If none are in the queue,
212 // block until one of them finishes and enqueues a dispatchable.
213 while (internalDispatchQueue().empty()) {
214 internalDispatchQueueAppended().wait(lock);
217 d = internalDispatchQueue().popCopyFront();
220 // Don't call run() with lock held to avoid deadlock.
221 d->run(cx, JS::Dispatchable::NotShuttingDown);
225 bool OffThreadPromiseRuntimeState::internalHasPending() {
226 MOZ_ASSERT(usingInternalDispatchQueue());
228 AutoLockHelperThreadState lock;
229 MOZ_ASSERT(!internalDispatchQueueClosed_);
230 MOZ_ASSERT_IF(!internalDispatchQueue().empty(), !live().empty());
231 return !live().empty();
234 void OffThreadPromiseRuntimeState::shutdown(JSContext* cx) {
235 if (!initialized()) {
236 return;
239 AutoLockHelperThreadState lock;
241 // When the shell is using the internal event loop, we must simulate our
242 // requirement of the embedding that, before shutdown, all successfully-
243 // dispatched-to-event-loop tasks have been run.
244 if (usingInternalDispatchQueue()) {
245 DispatchableFifo dispatchQueue;
247 std::swap(dispatchQueue, internalDispatchQueue());
248 MOZ_ASSERT(internalDispatchQueue().empty());
249 internalDispatchQueueClosed_ = true;
252 // Don't call run() with lock held to avoid deadlock.
253 AutoUnlockHelperThreadState unlock(lock);
254 for (JS::Dispatchable* d : dispatchQueue) {
255 d->run(cx, JS::Dispatchable::ShuttingDown);
259 // An OffThreadPromiseTask may only be safely deleted on its JSContext's
260 // thread (since it contains a PersistentRooted holding its promise), and
261 // only after it has called dispatchResolveAndDestroy (since that is our
262 // only indication that its owner is done writing into it).
264 // OffThreadPromiseTasks accepted by the DispatchToEventLoopCallback are
265 // deleted by their 'run' methods. Only dispatchResolveAndDestroy invokes
266 // the callback, and the point of the callback is to call 'run' on the
267 // JSContext's thread, so the conditions above are met.
269 // But although the embedding's DispatchToEventLoopCallback promises to run
270 // every task it accepts before shutdown, when shutdown does begin it starts
271 // rejecting tasks; we cannot count on 'run' to clean those up for us.
272 // Instead, dispatchResolveAndDestroy keeps a count of rejected ('canceled')
273 // tasks; once that count covers everything in live_, this function itself
274 // runs only on the JSContext's thread, so we can delete them all here.
275 while (live().count() != numCanceled_) {
276 MOZ_ASSERT(numCanceled_ < live().count());
277 allCanceled().wait(lock);
280 // Now that live_ contains only cancelled tasks, we can just delete
281 // everything.
282 for (OffThreadPromiseTaskSet::Range r = live().all(); !r.empty();
283 r.popFront()) {
284 OffThreadPromiseTask* task = r.front();
286 // We don't want 'task' to unregister itself (which would mutate live_ while
287 // we are iterating over it) so reset its internal registered_ flag.
288 MOZ_ASSERT(task->registered_);
289 task->registered_ = false;
290 js_delete(task);
292 live().clear();
293 numCanceled_ = 0;
295 // After shutdown, there should be no OffThreadPromiseTask activity in this
296 // JSRuntime. Revert to the !initialized() state to catch bugs.
297 dispatchToEventLoopCallback_ = nullptr;
298 MOZ_ASSERT(!initialized());