bool ForkJoinShared::check(ForkJoinSlice &slice) { if (abort_) return false; if (slice.isMainThread()) { JS_ASSERT(!cx_->runtime->gcIsNeeded); if (cx_->runtime->interrupt) { // The GC Needed flag should not be set during parallel // execution. Instead, one of the requestGC() or // requestZoneGC() methods should be invoked. JS_ASSERT(!cx_->runtime->gcIsNeeded); // If interrupt is requested, bring worker threads to a halt, // service the interrupt, then let them start back up again. // AutoRendezvous autoRendezvous(slice); // if (!js_HandleExecutionInterrupt(cx_)) // return setFatal(); setAbortFlag(); return false; } } else if (rendezvous_) { joinRendezvous(slice); } return true; }
void ForkJoinShared::endRendezvous(ForkJoinSlice &slice) { JS_ASSERT(slice.isMainThread()); AutoLockMonitor lock(*this); rendezvous_ = false; blocked_ = 0; rendezvousIndex_++; // Signal other threads that rendezvous is over. PR_NotifyAllCondVar(rendezvousEnd_); }
void ForkJoinShared::initiateRendezvous(ForkJoinSlice &slice) { // The rendezvous protocol is always initiated by the main thread. The // main thread sets the rendezvous flag to true. Seeing this flag, other // threads will invoke |joinRendezvous()|, which causes them to (1) read // |rendezvousIndex| and (2) increment the |blocked| counter. Once the // |blocked| counter is equal to |uncompleted|, all parallel threads have // joined the rendezvous, and so the main thread is signaled. That will // cause this function to return. // // Some subtle points: // // - Worker threads may potentially terminate their work before they see // the rendezvous flag. In this case, they would decrement // |uncompleted| rather than incrementing |blocked|. Either way, if the // two variables become equal, the main thread will be notified // // - The |rendezvousIndex| counter is used to detect the case where the // main thread signals the end of the rendezvous and then starts another // rendezvous before the workers have a chance to exit. We circumvent // this by having the workers read the |rendezvousIndex| counter as they // enter the rendezvous, and then they only block until that counter is // incremented. Another alternative would be for the main thread to // block in |endRendezvous()| until all workers have exited, but that // would be slower and involve unnecessary synchronization. // // Note that the main thread cannot ever get more than one rendezvous // ahead of the workers, because it must wait for all of them to enter // the rendezvous before it can end it, so the solution of using a // counter is perfectly general and we need not fear rollover. JS_ASSERT(slice.isMainThread()); JS_ASSERT(!rendezvous_ && blocked_ == 0); JS_ASSERT(cx_->runtime->interrupt); AutoLockMonitor lock(*this); // Signal other threads we want to start a rendezvous. rendezvous_ = true; // Wait until all the other threads blocked themselves. while (blocked_ != uncompleted_) lock.wait(); }
bool ForkJoinShared::check(ForkJoinSlice &slice) { if (abort_) return false; if (slice.isMainThread()) { if (cx_->runtime->interrupt) { // If interrupt is requested, bring worker threads to a halt, // service the interrupt, then let them start back up again. AutoRendezvous autoRendezvous(slice); if (!js_HandleExecutionInterrupt(cx_)) return setFatal(); } } else if (rendezvous_) { joinRendezvous(slice); } return true; }
void ForkJoinShared::joinRendezvous(ForkJoinSlice &slice) { JS_ASSERT(!slice.isMainThread()); JS_ASSERT(rendezvous_); AutoLockMonitor lock(*this); const uint32_t index = rendezvousIndex_; blocked_ += 1; // If we're the last to arrive, let the main thread know about it. if (blocked_ == uncompleted_) lock.notify(); // Wait until the main thread terminates the rendezvous. We use a // separate condition variable here to distinguish between workers // notifying the main thread that they have completed and the main // thread notifying the workers to resume. while (rendezvousIndex_ == index) PR_WaitCondVar(rendezvousEnd_, PR_INTERVAL_NO_TIMEOUT); }
bool ForkJoinShared::check(ForkJoinSlice &slice) { JS_ASSERT(cx_->runtime->interrupt); if (abort_) return false; if (slice.isMainThread()) { // We are the main thread: therefore we must // (1) initiate the rendezvous; // (2) if GC was requested, reinvoke trigger // which will do various non-thread-safe // preparatory steps. We then invoke // a non-incremental GC manually. // (3) run the operation callback, which // would normally run the GC but // incrementally, which we do not want. JSRuntime *rt = cx_->runtime; // Calls to js::TriggerGC() should have been redirected to // requestGC(), and thus the gcIsNeeded flag is not set yet. JS_ASSERT(!rt->gcIsNeeded); if (gcRequested_ && rt->isHeapBusy()) { // Cannot call GCSlice when heap busy, so abort. Easier // right now to abort rather than prove it cannot arise, // and safer for short-term than asserting !isHeapBusy. setAbortFlag(false); records_->setCause(ParallelBailoutHeapBusy, NULL, NULL); return false; } // (1). Initiaize the rendezvous and record stack extents. AutoRendezvous autoRendezvous(slice); AutoMarkWorldStoppedForGC autoMarkSTWFlag(slice); slice.recordStackExtent(); AutoInstallForkJoinStackExtents extents(rt, &stackExtents_[0]); // (2). Note that because we are in a STW section, calls to // js::TriggerGC() etc will not re-invoke // ForkJoinSlice::requestGC(). triggerGCIfRequested(); // (2b) Run the GC if it is required. This would occur as // part of js_InvokeOperationCallback(), but we want to avoid // an incremental GC. if (rt->gcIsNeeded) { GC(rt, GC_NORMAL, gcReason_); } // (3). Invoke the callback and abort if it returns false. if (!js_InvokeOperationCallback(cx_)) { records_->setCause(ParallelBailoutInterrupt, NULL, NULL); setAbortFlag(true); return false; } return true; } else if (rendezvous_) { slice.recordStackExtent(); joinRendezvous(slice); } return true; }