/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// The ThrottledEventQueue is designed with inner and outer objects: // // XPCOM code base event target // | | // v v // +-------+ +--------+ // | Outer | +-->|executor| // +-------+ | +--------+ // | | | // | +-------+ | // +-->| Inner |<--+ // +-------+ // // Client code references the outer nsIEventTarget which in turn references // an inner object, which actually holds the queue of runnables. // // Whenever the queue is non-empty (and not paused), it keeps an "executor" // runnable dispatched to the base event target. Each time the executor is run, // it draws the next event from Inner's queue and runs it. If that queue has // more events, the executor is dispatched to the base again. // // The executor holds a strong reference to the Inner object. This means that if // the outer object is dereferenced and destroyed, the Inner object will remain // live for as long as the executor exists - that is, until the Inner's queue is // empty. // // A Paused ThrottledEventQueue does not enqueue an executor when new events are // added. Any executor previously queued on the base event target draws no // events from a Paused ThrottledEventQueue, and returns without re-enqueueing // itself. Since there is no executor keeping the Inner object alive until its // queue is empty, dropping a Paused ThrottledEventQueue may drop the Inner // while it still owns events. This is the correct behavior: if there are no // references to it, it will never be Resumed, and thus it will never dispatch // events again. // // Resuming a ThrottledEventQueue must dispatch an executor, so calls to Resume // are fallible for the same reasons as calls to Dispatch. // // The xpcom shutdown process drains the main thread's event queue several // times, so if a ThrottledEventQueue is being driven by the main thread, it // should get emptied out by the time we reach the "eventq shutdown" phase. class ThrottledEventQueue::Inner final : public nsISupports { // The runnable which is dispatched to the underlying base target. Since // we only execute one event at a time we just re-use a single instance // of this class while there are events left in the queue. class Executor final : public Runnable, public nsIRunnablePriority { // The Inner whose runnables we execute. mInner->mExecutor points // to this executor, forming a reference loop.
RefPtr<Inner> mInner;
// As-of-yet unexecuted runnables queued on this ThrottledEventQueue. // // Used from any thread; protected by mMutex. Signals mIdleCondVar when // emptied.
EventQueueSized<64> mEventQueue MOZ_GUARDED_BY(mMutex);
// The event target we dispatch our events (actually, just our Executor) to. // // Written only during construction. Readable by any thread without locking. const nsCOMPtr<nsISerialEventTarget> mBaseTarget;
// The Executor that we dispatch to mBaseTarget to draw runnables from our // queue. mExecutor->mInner points to this Inner, forming a reference loop. // // Used from any thread; protected by mMutex.
nsCOMPtr<nsIRunnable> mExecutor MOZ_GUARDED_BY(mMutex);
constchar* const mName;
const uint32_t mPriority;
// True if this queue is currently paused. // Used from any thread; protected by mMutex. bool mIsPaused MOZ_GUARDED_BY(mMutex);
// As long as an executor exists, it had better keep us alive, since it's // going to call ExecuteRunnable on us.
MOZ_ASSERT(!mExecutor);
// If we have any events in our queue, there should be an executor queued // for them, and that should have kept us alive. The exception is that, if // we're paused, we don't enqueue an executor.
MOZ_ASSERT(mEventQueue.IsEmpty(lock) || IsPaused(lock));
// If we are cleaning up prematurely (i.e. with still some pending // runnables) must be doing so from the target thread.
MOZ_ASSERT_IF(!mEventQueue.IsEmpty(lock), IsOnCurrentThread()); #endif
}
// Make sure an executor has been queued on our base target. If we already // have one, do nothing; otherwise, create and dispatch it.
nsresult EnsureExecutor(MutexAutoLock& lock) MOZ_REQUIRES(mMutex) { if (mExecutor) return NS_OK;
// Note, this creates a ref cycle keeping the inner alive // until the queue is drained.
mExecutor = new Executor(this);
nsresult rv = mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) {
mExecutor = nullptr; return rv;
}
{
MutexAutoLock lock(mMutex);
event = mEventQueue.PeekEvent(lock); // It is possible that mEventQueue wasn't empty when the executor // was added to the queue, but someone processed events from mEventQueue // before the executor, this is why mEventQueue is empty here if (!event) {
aName.AssignLiteral("no runnables left in the ThrottledEventQueue"); return NS_OK;
}
}
if (nsCOMPtr<nsINamed> named = do_QueryInterface(event)) {
nsresult rv = named->GetName(aName); return rv;
}
aName.AssignASCII(mName); return NS_OK;
}
void ExecuteRunnable() { // Any thread
nsCOMPtr<nsIRunnable> event;
// Normally, a paused queue doesn't dispatch any executor, but we might // have been paused after the executor was already in flight. There's no // way to yank the executor out of the base event target, so we just check // for a paused queue here and return without running anything. We'll // create a new executor when we're resumed. if (IsPaused(lock)) { // Note, this breaks a ref cycle.
mExecutor = nullptr; return;
}
// We only dispatch an executor runnable when we know there is something // in the queue, so this should never fail.
event = mEventQueue.GetEvent(lock);
MOZ_ASSERT(event);
// If there are more events in the queue, then dispatch the next // executor. We do this now, before running the event, because // the event might spin the event loop and we don't want to stall // the queue. if (mEventQueue.HasReadyEvent(lock)) { // Dispatch the next base target runnable to attempt to execute // the next throttled event. We must do this before executing // the event in case the event spins the event loop.
MOZ_ALWAYS_SUCCEEDS(
mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL));
}
// Otherwise the queue is empty and we can stop dispatching the // executor. else { // Break the Executor::mInner / Inner::mExecutor reference loop.
mExecutor = nullptr;
mIdleCondVar.NotifyAll();
}
}
// Execute the event now that we have unlocked.
LogRunnable::Run log(event);
Unused << event->Run();
// To cover the event's destructor code in the LogRunnable log
event = nullptr;
}
public: static already_AddRefed<Inner> Create(nsISerialEventTarget* aBaseTarget, constchar* aName, uint32_t aPriority) { // FIXME: This assertion only worked when `sCurrentShutdownPhase` was not // being updated. // MOZ_ASSERT(ClearOnShutdown_Internal::sCurrentShutdownPhase == // ShutdownPhase::NotInShutdown);
RefPtr<Inner> ref = new Inner(aBaseTarget, aName, aPriority); return ref.forget();
}
void AwaitIdle() const { // Any thread, except the main thread or our base target. Blocking the // main thread is forbidden. Blocking the base target is guaranteed to // produce a deadlock.
MOZ_ASSERT(!NS_IsMainThread()); #ifdef DEBUG bool onBaseTarget = false;
Unused << mBaseTarget->IsOnCurrentThread(&onBaseTarget);
MOZ_ASSERT(!onBaseTarget); #endif
MutexAutoLock lock(mMutex); while (mExecutor || IsPaused(lock)) {
mIdleCondVar.Wait();
}
}
// If we will be unpaused, and we have events in our queue, make sure we // have an executor queued on the base event target to run them. Do this // before we actually change mIsPaused, since this is fallible. if (!aIsPaused && !mEventQueue.IsEmpty(lock)) {
nsresult rv = EnsureExecutor(lock); if (NS_FAILED(rv)) { return rv;
}
}
mIsPaused = aIsPaused; return NS_OK;
}
nsresult DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { // Any thread
nsCOMPtr<nsIRunnable> r = aEvent; return Dispatch(r.forget(), aFlags);
}
if (!IsPaused(lock)) { // Make sure we have an executor in flight to process events. This is // fallible, so do it first. Our lock will prevent the executor from // accessing the event queue before we add the event below.
nsresult rv = EnsureExecutor(lock); if (NS_FAILED(rv)) return rv;
}
// Only add the event to the underlying queue if are able to // dispatch to our base target.
nsCOMPtr<nsIRunnable> event(aEvent);
LogRunnable::LogDispatch(event);
mEventQueue.PutEvent(event.forget(), EventQueuePriority::Normal, lock); return NS_OK;
}
nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
uint32_t aDelay) { // The base target may implement this, but we don't. Always fail // to provide consistent behavior. return NS_ERROR_NOT_IMPLEMENTED;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.