OffThreadPromiseRuntimeState.cpp (21118B)
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/. */ 6 7 #include "vm/OffThreadPromiseRuntimeState.h" 8 9 #include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF} 10 11 #include <utility> // mozilla::Swap 12 13 #include "jspubtd.h" // js::CurrentThreadCanAccessRuntime 14 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 // JS::DelayedDispatchToEventLoopCallback 19 #include "js/Utility.h" // js_delete, js::AutoEnterOOMUnsafeRegion 20 #include "threading/ProtectedData.h" // js::UnprotectedData 21 #include "vm/HelperThreads.h" // js::AutoLockHelperThreadState 22 #include "vm/JSContext.h" // JSContext 23 #include "vm/PromiseObject.h" // js::PromiseObject 24 #include "vm/Realm.h" // js::AutoRealm 25 #include "vm/Runtime.h" // JSRuntime 26 27 #include "vm/Realm-inl.h" // js::AutoRealm::AutoRealm 28 29 using JS::Handle; 30 31 using js::OffThreadPromiseRuntimeState; 32 using js::OffThreadPromiseTask; 33 34 OffThreadPromiseTask::OffThreadPromiseTask(JSContext* cx, 35 JS::Handle<PromiseObject*> promise) 36 : runtime_(cx->runtime()), promise_(cx, promise), cancellable_(false) { 37 MOZ_ASSERT(runtime_ == promise_->zone()->runtimeFromMainThread()); 38 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); 39 MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized()); 40 } 41 42 OffThreadPromiseTask::~OffThreadPromiseTask() { 43 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); 44 45 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref(); 46 MOZ_ASSERT(state.initialized()); 47 48 if (registered_) { 49 unregister(state); 50 } 51 } 52 53 bool OffThreadPromiseTask::init(JSContext* cx) { 54 AutoLockHelperThreadState lock; 55 return init(cx, lock); 56 } 57 58 bool OffThreadPromiseTask::init(JSContext* cx, 59 const AutoLockHelperThreadState& lock) { 60 MOZ_ASSERT(cx->runtime() == runtime_); 61 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); 62 63 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref(); 64 MOZ_ASSERT(state.initialized()); 65 66 state.registerTask(cx, this); 67 return true; 68 } 69 70 bool OffThreadPromiseTask::InitCancellable( 71 JSContext* cx, js::UniquePtr<OffThreadPromiseTask>&& task) { 72 AutoLockHelperThreadState lock; 73 return InitCancellable(cx, lock, std::move(task)); 74 } 75 76 bool OffThreadPromiseTask::InitCancellable( 77 JSContext* cx, const AutoLockHelperThreadState& lock, 78 js::UniquePtr<OffThreadPromiseTask>&& task) { 79 MOZ_ASSERT(cx->runtime() == task->runtime_); 80 MOZ_ASSERT(CurrentThreadCanAccessRuntime(task->runtime_)); 81 OffThreadPromiseRuntimeState& state = 82 task->runtime_->offThreadPromiseState.ref(); 83 MOZ_ASSERT(state.initialized()); 84 85 if (!task->init(cx, lock)) { 86 ReportOutOfMemory(cx); 87 return false; 88 } 89 90 if (!state.cancellable().putNew(task.get())) { 91 // The task will be freed because it's only owned by this function. Eagerly 92 // unregister it now using the provided helper thread lock so that it 93 // doesn't need to be reacquired. 94 task->unregister(state, lock); 95 ReportOutOfMemory(cx); 96 return false; 97 } 98 99 // We're infallible from this point on. 100 OffThreadPromiseTask* rawTask = task.release(); 101 102 // Only mark the task as cancellable once we've added it to the cancellable 103 // set. The destructor will remove from the set if this flag is set. 104 rawTask->cancellable_ = true; 105 106 return true; 107 } 108 109 void OffThreadPromiseTask::unregister(OffThreadPromiseRuntimeState& state) { 110 AutoLockHelperThreadState lock; 111 unregister(state, lock); 112 } 113 void OffThreadPromiseTask::unregister(OffThreadPromiseRuntimeState& state, 114 const AutoLockHelperThreadState& lock) { 115 MOZ_ASSERT(registered_); 116 state.unregisterTask(this); 117 } 118 119 void OffThreadPromiseTask::run(JSContext* cx, 120 MaybeShuttingDown maybeShuttingDown) { 121 MOZ_ASSERT(cx->runtime() == runtime_); 122 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); 123 MOZ_ASSERT(registered_); 124 125 // Remove this task from numRegistered_ before calling `resolve`, so that if 126 // `resolve` itself drains the queue reentrantly, the queue will not think 127 // this task is yet to be queued and block waiting for it. 128 // 129 // The unregister method synchronizes on the helper thread lock and ensures 130 // that we don't delete the task while the helper thread is still running. 131 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref(); 132 MOZ_ASSERT(state.initialized()); 133 unregister(state); 134 135 if (maybeShuttingDown == JS::Dispatchable::NotShuttingDown) { 136 // We can't leave a pending exception when returning to the caller so do 137 // the same thing as Gecko, which is to ignore the error. This should 138 // only happen due to OOM or interruption. 139 AutoRealm ar(cx, promise_); 140 if (!resolve(cx, promise_)) { 141 cx->clearPendingException(); 142 } 143 } 144 145 js_delete(this); 146 } 147 148 void OffThreadPromiseTask::transferToRuntime() { 149 MOZ_ASSERT(registered_); 150 151 // The unregister method synchronizes on the helper thread lock and ensures 152 // that we don't delete the task while the helper thread is still running. 153 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref(); 154 MOZ_ASSERT(state.initialized()); 155 156 // Task is now owned by the state and will be deleted on ::shutdown. 157 state.stealFailedTask(this); 158 } 159 160 /* static */ 161 void OffThreadPromiseTask::DestroyUndispatchedTask( 162 OffThreadPromiseTask* task, OffThreadPromiseRuntimeState& state, 163 const AutoLockHelperThreadState& lock) { 164 MOZ_ASSERT(CurrentThreadCanAccessRuntime(task->runtime_)); 165 MOZ_ASSERT(task->registered_); 166 MOZ_ASSERT(task->cancellable_); 167 // Cleanup Steps from 4. in SMDOC for Atomics.waitAsync 168 task->prepareForCancel(); 169 // unregister with the passed lock. Necessary so that 170 // there is no conflict in js_delete with shutdown code. 171 task->unregister(state, lock); 172 js_delete(task); 173 } 174 175 void OffThreadPromiseTask::dispatchResolveAndDestroy() { 176 AutoLockHelperThreadState lock; 177 js::UniquePtr<OffThreadPromiseTask> task(this); 178 DispatchResolveAndDestroy(std::move(task), lock); 179 } 180 181 void OffThreadPromiseTask::dispatchResolveAndDestroy( 182 const AutoLockHelperThreadState& lock) { 183 js::UniquePtr<OffThreadPromiseTask> task(this); 184 DispatchResolveAndDestroy(std::move(task), lock); 185 } 186 187 void OffThreadPromiseTask::removeFromCancellableListAndDispatch() { 188 AutoLockHelperThreadState lock; 189 removeFromCancellableListAndDispatch(lock); 190 } 191 192 void OffThreadPromiseTask::removeFromCancellableListAndDispatch( 193 const AutoLockHelperThreadState& lock) { 194 OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref(); 195 MOZ_ASSERT(state.initialized()); 196 MOZ_ASSERT(state.cancellable().has(this)); 197 198 MOZ_ASSERT(registered_); 199 MOZ_ASSERT(cancellable_); 200 cancellable_ = false; 201 // remove this task from the runnable's cancellable list. This ends the 202 // runtime's ownership of the the task. 203 state.cancellable().remove(this); 204 205 // Create a UniquePtr that will be passed to the embedding. 206 js::UniquePtr<OffThreadPromiseTask> task; 207 // move ownership of this task to the newly created pointer 208 task.reset(this); 209 DispatchResolveAndDestroy(std::move(task), lock); 210 } 211 212 /* static */ 213 void OffThreadPromiseTask::DispatchResolveAndDestroy( 214 js::UniquePtr<OffThreadPromiseTask>&& task) { 215 AutoLockHelperThreadState lock; 216 DispatchResolveAndDestroy(std::move(task), lock); 217 } 218 219 /* static */ 220 void OffThreadPromiseTask::DispatchResolveAndDestroy( 221 js::UniquePtr<OffThreadPromiseTask>&& task, 222 const AutoLockHelperThreadState& lock) { 223 OffThreadPromiseRuntimeState& state = 224 task->runtime()->offThreadPromiseState.ref(); 225 MOZ_ASSERT(state.initialized()); 226 227 MOZ_ASSERT(task->registered_); 228 MOZ_ASSERT(!task->cancellable_); 229 // If the dispatch succeeds, then we are guaranteed that run() will be 230 // called on an active JSContext of runtime_. 231 { 232 // Hazard analysis can't tell that the callback does not GC. 233 JS::AutoSuppressGCAnalysis nogc; 234 if (state.dispatchToEventLoop(std::move(task))) { 235 return; 236 } 237 } 238 239 // The DispatchToEventLoopCallback has failed to dispatch this task, 240 // indicating that shutdown has begun. Compare the number of failed tasks that 241 // have called dispatchResolveAndDestroy, and when they account for all of 242 // numRegistered_, notify OffThreadPromiseRuntimeState::shutdown that it is 243 // safe to destruct them. 244 if (state.failed().length() == state.numRegistered_) { 245 state.allFailed().notify_one(); 246 } 247 } 248 249 OffThreadPromiseRuntimeState::OffThreadPromiseRuntimeState() 250 : dispatchToEventLoopCallback_(nullptr), 251 delayedDispatchToEventLoopCallback_(nullptr), 252 asyncTaskStartedCallback_(nullptr), 253 asyncTaskFinishedCallback_(nullptr), 254 dispatchToEventLoopClosure_(nullptr), 255 #ifdef DEBUG 256 forceQuitting_(false), 257 #endif 258 numRegistered_(0), 259 internalDispatchQueueClosed_(false) { 260 } 261 262 OffThreadPromiseRuntimeState::~OffThreadPromiseRuntimeState() { 263 MOZ_ASSERT_IF(!forceQuitting_, numRegistered_ == 0); 264 MOZ_ASSERT_IF(!forceQuitting_, numDelayed_ == 0); 265 MOZ_ASSERT_IF(!forceQuitting_, internalDispatchQueue_.refNoCheck().empty()); 266 MOZ_ASSERT(!initialized()); 267 } 268 269 void OffThreadPromiseRuntimeState::init( 270 JS::DispatchToEventLoopCallback dispatchCallback, 271 JS::DelayedDispatchToEventLoopCallback delayedDispatchCallback, 272 JS::AsyncTaskStartedCallback asyncTaskStartedCallback, 273 JS::AsyncTaskFinishedCallback asyncTaskFinishedCallback, void* closure) { 274 MOZ_ASSERT(!initialized()); 275 276 dispatchToEventLoopCallback_ = dispatchCallback; 277 delayedDispatchToEventLoopCallback_ = delayedDispatchCallback; 278 asyncTaskStartedCallback_ = asyncTaskStartedCallback; 279 asyncTaskFinishedCallback_ = asyncTaskFinishedCallback; 280 dispatchToEventLoopClosure_ = closure; 281 282 MOZ_ASSERT(initialized()); 283 } 284 285 bool OffThreadPromiseRuntimeState::dispatchToEventLoop( 286 js::UniquePtr<JS::Dispatchable>&& dispatchable) { 287 return dispatchToEventLoopCallback_(dispatchToEventLoopClosure_, 288 std::move(dispatchable)); 289 } 290 291 bool OffThreadPromiseRuntimeState::delayedDispatchToEventLoop( 292 js::UniquePtr<JS::Dispatchable>&& dispatchable, uint32_t delay) { 293 return delayedDispatchToEventLoopCallback_(dispatchToEventLoopClosure_, 294 std::move(dispatchable), delay); 295 } 296 297 void OffThreadPromiseRuntimeState::registerTask(JSContext* cx, 298 OffThreadPromiseTask* task) { 299 // Track the total number of pending async tasks 300 numRegistered_++; 301 302 // Mark the task as registered 303 task->registered_ = true; 304 305 if (!asyncTaskStartedCallback_) { 306 return; 307 } 308 309 // The embedder must not perform a GC, suppress GC analysis here. 310 JS::AutoSuppressGCAnalysis nogc(cx); 311 asyncTaskStartedCallback_(dispatchToEventLoopClosure_, task); 312 } 313 314 void OffThreadPromiseRuntimeState::unregisterTask(OffThreadPromiseTask* task) { 315 // Track the total number of pending async tasks 316 MOZ_ASSERT(numRegistered_ != 0); 317 numRegistered_--; 318 319 // Mark the task as unregistered 320 task->registered_ = false; 321 322 // If the task was cancellable, remove from our cancellable set. 323 if (task->cancellable_) { 324 task->cancellable_ = false; 325 cancellable().remove(task); 326 } 327 328 if (!asyncTaskFinishedCallback_) { 329 return; 330 } 331 332 // The embedder must not perform a GC, suppress GC analysis here. 333 JS::AutoSuppressGCAnalysis nogc; 334 asyncTaskFinishedCallback_(dispatchToEventLoopClosure_, task); 335 } 336 337 /* static */ 338 bool OffThreadPromiseRuntimeState::internalDispatchToEventLoop( 339 void* closure, js::UniquePtr<JS::Dispatchable>&& d) { 340 OffThreadPromiseRuntimeState& state = 341 *reinterpret_cast<OffThreadPromiseRuntimeState*>(closure); 342 MOZ_ASSERT(state.usingInternalDispatchQueue()); 343 gHelperThreadLock.assertOwnedByCurrentThread(); 344 345 if (state.internalDispatchQueueClosed_) { 346 JS::Dispatchable::ReleaseFailedTask(std::move(d)); 347 return false; 348 } 349 350 state.dispatchDelayedTasks(); 351 352 // The JS API contract is that 'false' means shutdown, so be infallible 353 // here (like Gecko). 354 AutoEnterOOMUnsafeRegion noOOM; 355 if (!state.internalDispatchQueue().pushBack(std::move(d))) { 356 noOOM.crash("internalDispatchToEventLoop"); 357 } 358 359 // Wake up internalDrain() if it is waiting for a job to finish. 360 state.internalDispatchQueueAppended().notify_one(); 361 return true; 362 } 363 364 // This function (and all related delayedDispatch data structures), are in place 365 // in order to make the JS Shell work as expected with delayed tasks such as 366 // Atomics.waitAsync. 367 bool OffThreadPromiseRuntimeState::internalDelayedDispatchToEventLoop( 368 void* closure, js::UniquePtr<JS::Dispatchable>&& d, uint32_t delay) { 369 OffThreadPromiseRuntimeState& state = 370 *reinterpret_cast<OffThreadPromiseRuntimeState*>(closure); 371 MOZ_ASSERT(state.usingInternalDispatchQueue()); 372 gHelperThreadLock.assertOwnedByCurrentThread(); 373 374 if (state.internalDispatchQueueClosed_) { 375 return false; 376 } 377 378 state.dispatchDelayedTasks(); 379 380 // endTime is calculated synchronously from the moment we call 381 // internalDelayedDispatchToEventLoop. The only current use-case is 382 // Atomics.waitAsync. 383 mozilla::TimeStamp endTime = mozilla::TimeStamp::Now() + 384 mozilla::TimeDuration::FromMilliseconds(delay); 385 if (!state.internalDelayedDispatchPriorityQueue().reserveOne()) { 386 JS::Dispatchable::ReleaseFailedTask(std::move(d)); 387 return false; 388 } 389 390 state.internalDelayedDispatchPriorityQueue().infallibleInsert( 391 DelayedDispatchable(std::move(d), endTime)); 392 393 return true; 394 } 395 396 void OffThreadPromiseRuntimeState::dispatchDelayedTasks() { 397 MOZ_ASSERT(usingInternalDispatchQueue()); 398 gHelperThreadLock.assertOwnedByCurrentThread(); 399 400 if (internalDispatchQueueClosed_) { 401 return; 402 } 403 404 auto& queue = internalDelayedDispatchPriorityQueue(); 405 406 if (queue.empty()) { 407 return; 408 } 409 410 mozilla::TimeStamp now = mozilla::TimeStamp::Now(); 411 412 while (!queue.empty() && queue.highest().endTime() <= now) { 413 DelayedDispatchable d(std::move(queue.highest())); 414 queue.popHighest(); 415 416 AutoEnterOOMUnsafeRegion noOOM; 417 numDelayed_++; 418 if (!internalDispatchQueue().pushBack(d.dispatchable())) { 419 noOOM.crash("dispatchDelayedTasks"); 420 } 421 internalDispatchQueueAppended().notify_one(); 422 } 423 } 424 425 bool OffThreadPromiseRuntimeState::usingInternalDispatchQueue() const { 426 return dispatchToEventLoopCallback_ == internalDispatchToEventLoop; 427 } 428 429 void OffThreadPromiseRuntimeState::initInternalDispatchQueue() { 430 init(internalDispatchToEventLoop, internalDelayedDispatchToEventLoop, nullptr, 431 nullptr, this); 432 MOZ_ASSERT(usingInternalDispatchQueue()); 433 } 434 435 bool OffThreadPromiseRuntimeState::initialized() const { 436 return !!dispatchToEventLoopCallback_; 437 } 438 439 void OffThreadPromiseRuntimeState::internalDrain(JSContext* cx) { 440 MOZ_ASSERT(usingInternalDispatchQueue()); 441 442 for (;;) { 443 js::UniquePtr<JS::Dispatchable> d; 444 { 445 AutoLockHelperThreadState lock; 446 dispatchDelayedTasks(); 447 448 MOZ_ASSERT(!internalDispatchQueueClosed_); 449 MOZ_ASSERT_IF(!internalDispatchQueue().empty(), 450 numRegistered_ + numDelayed_ > 0); 451 if (internalDispatchQueue().empty() && !internalHasPending(lock)) { 452 return; 453 } 454 455 // There are extant live dispatched OffThreadPromiseTasks. 456 // If none are in the queue, block until one of them finishes 457 // and enqueues a dispatchable. 458 while (internalDispatchQueue().empty()) { 459 internalDispatchQueueAppended().wait(lock); 460 } 461 462 d = std::move(internalDispatchQueue().front()); 463 internalDispatchQueue().popFront(); 464 if (!d->registered()) { 465 numDelayed_--; 466 } 467 } 468 469 // Don't call Run() with lock held to avoid deadlock. 470 OffThreadPromiseTask::Run(cx, std::move(d), 471 JS::Dispatchable::NotShuttingDown); 472 } 473 } 474 475 bool OffThreadPromiseRuntimeState::internalHasPending() { 476 AutoLockHelperThreadState lock; 477 return internalHasPending(lock); 478 } 479 480 bool OffThreadPromiseRuntimeState::internalHasPending( 481 AutoLockHelperThreadState& lock) { 482 MOZ_ASSERT(usingInternalDispatchQueue()); 483 484 MOZ_ASSERT(!internalDispatchQueueClosed_); 485 MOZ_ASSERT_IF(!internalDispatchQueue().empty(), 486 numRegistered_ + numDelayed_ > 0); 487 return numDelayed_ > 0 || numRegistered_ > cancellable().count(); 488 } 489 490 void OffThreadPromiseRuntimeState::stealFailedTask(JS::Dispatchable* task) { 491 js::AutoEnterOOMUnsafeRegion noOOM; 492 if (!failed().pushBack(task)) { 493 noOOM.crash("stealFailedTask"); 494 } 495 } 496 497 void OffThreadPromiseRuntimeState::cancelTasks( 498 js::AutoLockHelperThreadState& lock, JSContext* cx) { 499 MOZ_ASSERT(initialized()); 500 if (!initialized()) { 501 return; 502 } 503 504 // Cancel all undispatched tasks that are cancellable. 505 for (auto iter = cancellable().modIter(); !iter.done(); iter.next()) { 506 OffThreadPromiseTask* task = iter.get(); 507 MOZ_ASSERT(task->cancellable_); 508 iter.remove(); 509 510 OffThreadPromiseTask::DestroyUndispatchedTask(task, *this, lock); 511 } 512 } 513 514 void OffThreadPromiseRuntimeState::cancelTasks(JSContext* cx) { 515 if (!initialized()) { 516 return; 517 } 518 519 AutoLockHelperThreadState lock; 520 cancelTasks(lock, cx); 521 } 522 523 void OffThreadPromiseRuntimeState::shutdown(JSContext* cx) { 524 if (!initialized()) { 525 return; 526 } 527 528 AutoLockHelperThreadState lock; 529 530 // Cancel all undispatched tasks. 531 cancelTasks(lock, cx); 532 MOZ_ASSERT(cancellable().empty()); 533 534 // When the shell is using the internal event loop, we must simulate our 535 // requirement of the embedding that, before shutdown, all successfully- 536 // dispatched-to-event-loop tasks have been run. 537 if (usingInternalDispatchQueue()) { 538 DispatchableFifo dispatchQueue; 539 { 540 std::swap(dispatchQueue, internalDispatchQueue()); 541 MOZ_ASSERT(internalDispatchQueue().empty()); 542 internalDispatchQueueClosed_ = true; 543 } 544 545 // Don't call run() with lock held to avoid deadlock. 546 AutoUnlockHelperThreadState unlock(lock); 547 while (!dispatchQueue.empty()) { 548 js::UniquePtr<JS::Dispatchable> d = std::move(dispatchQueue.front()); 549 dispatchQueue.popFront(); 550 OffThreadPromiseTask::Run(cx, std::move(d), 551 JS::Dispatchable::ShuttingDown); 552 } 553 } 554 555 // An OffThreadPromiseTask may only be safely deleted on its JSContext's 556 // thread (since it contains a PersistentRooted holding its promise), and 557 // only after it has called DispatchResolveAndDestroy (since that is our 558 // only indication that its owner is done writing into it). 559 // 560 // OffThreadPromiseTasks accepted by the DispatchToEventLoopCallback are 561 // deleted by their 'run' methods. Only DispatchResolveAndDestroy invokes 562 // the callback, and the point of the callback is to call 'run' on the 563 // JSContext's thread, so the conditions above are met. 564 // 565 // But although the embedding's DispatchToEventLoopCallback promises to run 566 // every task it accepts before shutdown, when shutdown does begin it starts 567 // rejecting tasks; we cannot count on 'run' to clean those up for us. 568 // Instead, tasks which fail to run have their ownership passed to the failed_ 569 // list; once that count covers everything in numRegisterd_, this function 570 // itself runs only on the JSContext's thread, so we can delete them all here. 571 while (numRegistered_ != failed().length()) { 572 MOZ_ASSERT(failed().length() < numRegistered_); 573 allFailed().wait(lock); 574 } 575 576 { 577 DispatchableFifo failedQueue; 578 { 579 std::swap(failedQueue, failed()); 580 MOZ_ASSERT(failed().empty()); 581 } 582 583 AutoUnlockHelperThreadState unlock(lock); 584 while (!failedQueue.empty()) { 585 js::UniquePtr<JS::Dispatchable> d = std::move(failedQueue.front()); 586 failedQueue.popFront(); 587 js_delete(d.release()); 588 } 589 } 590 591 // Everything should be empty at this point. 592 MOZ_ASSERT(numRegistered_ == 0); 593 594 // After shutdown, there should be no OffThreadPromiseTask activity in this 595 // JSRuntime. Revert to the !initialized() state to catch bugs. 596 dispatchToEventLoopCallback_ = nullptr; 597 MOZ_ASSERT(!initialized()); 598 } 599 600 /* static */ 601 js::PromiseObject* OffThreadPromiseTask::ExtractAndForget( 602 OffThreadPromiseTask* task, const AutoLockHelperThreadState& lock) { 603 OffThreadPromiseRuntimeState& state = 604 task->runtime()->offThreadPromiseState.ref(); 605 MOZ_ASSERT(state.initialized()); 606 MOZ_ASSERT(task->registered_); 607 js::PromiseObject* promise = task->promise_; 608 // Call the unregister method manually with our provided lock, otherwise the 609 // destructor will try to acquire it and fail. 610 task->unregister(state, lock); 611 // Now we can call the destructor. 612 js_delete(task); 613 return promise; 614 }