gecko-dev/xpcom/threads/ThreadEventTarget.cpp
Bill McCloskey 9edd615af7 Bug 1382922 - Refactor event queue to allow multiple implementations (r=erahm)
This patch refactors the nsThread event queue to clean it up and to make it easier to restructure. The fundamental concepts are as follows:

Each nsThread will have a pointer to a refcounted SynchronizedEventQueue. A SynchronizedEQ takes care of doing the locking and condition variable work when posting and popping events. For the actual storage of events, it delegates to an AbstractEventQueue data structure. It keeps a UniquePtr to the AbstractEventQueue that it uses for storage.

Both SynchronizedEQ and AbstractEventQueue are abstract classes. There is only one concrete implementation of SynchronizedEQ in this patch, which is called ThreadEventQueue. ThreadEventQueue uses locks and condition variables to post and pop events the same way nsThread does. It also encapsulates the functionality that DOM workers need to implement their special event loops (PushEventQueue and PopEventQueue). In later Quantum DOM work, I plan to have another SynchronizedEQ implementation for the main thread, called SchedulerEventQueue. It will have special code for the cooperatively scheduling threads in Quantum DOM.

There are two concrete implementations of AbstractEventQueue in this patch: EventQueue and PrioritizedEventQueue. EventQueue replaces the old nsEventQueue. The other AbstractEventQueue implementation is PrioritizedEventQueue, which uses multiple queues for different event priorities.

The final major piece here is ThreadEventTarget, which splits some of the code for posting events out of nsThread. Eventually, my plan is for multiple cooperatively scheduled nsThreads to be able to share a ThreadEventTarget. In this patch, though, each nsThread has its own ThreadEventTarget. The class's purpose is just to collect some related code together.

One final note: I tried to avoid virtual dispatch overhead as much as possible. Calls to SynchronizedEQ methods do use virtual dispatch, since I plan to use different implementations for different threads with Quantum DOM. But all the calls to EventQueue methods should be non-virtual. Although the methods are declared virtual, all the classes used are final and the concrete classes involved should all be known through templatization.

MozReview-Commit-ID: 9Evtr9oIJvx
2017-08-16 20:55:43 -07:00

211 lines
5.8 KiB
C++

/* -*- 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/. */
#include "ThreadEventTarget.h"
#include "mozilla/ThreadEventQueue.h"
#include "LeakRefPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsThreadManager.h"
#include "nsThreadSyncDispatch.h"
#include "nsThreadUtils.h"
#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
#ifdef MOZ_TASK_TRACER
#include "GeckoTaskTracer.h"
#include "TracedTaskCommon.h"
using namespace mozilla::tasktracer;
#endif
using namespace mozilla;
namespace {
class DelayedRunnable : public Runnable,
public nsITimerCallback
{
public:
DelayedRunnable(already_AddRefed<nsIEventTarget> aTarget,
already_AddRefed<nsIRunnable> aRunnable,
uint32_t aDelay)
: mozilla::Runnable("DelayedRunnable")
, mTarget(aTarget)
, mWrappedRunnable(aRunnable)
, mDelayedFrom(TimeStamp::NowLoRes())
, mDelay(aDelay)
{ }
NS_DECL_ISUPPORTS_INHERITED
nsresult Init()
{
nsresult rv;
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(mTimer);
rv = mTimer->SetTarget(mTarget);
NS_ENSURE_SUCCESS(rv, rv);
return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
}
nsresult DoRun()
{
nsCOMPtr<nsIRunnable> r = mWrappedRunnable.forget();
return r->Run();
}
NS_IMETHOD Run() override
{
// Already ran?
if (!mWrappedRunnable) {
return NS_OK;
}
// Are we too early?
if ((TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < mDelay) {
return NS_OK; // Let the nsITimer run us.
}
mTimer->Cancel();
return DoRun();
}
NS_IMETHOD Notify(nsITimer* aTimer) override
{
// If we already ran, the timer should have been canceled.
MOZ_ASSERT(mWrappedRunnable);
MOZ_ASSERT(aTimer == mTimer);
return DoRun();
}
private:
~DelayedRunnable() {}
nsCOMPtr<nsIEventTarget> mTarget;
nsCOMPtr<nsIRunnable> mWrappedRunnable;
nsCOMPtr<nsITimer> mTimer;
TimeStamp mDelayedFrom;
uint32_t mDelay;
};
NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback)
} // anonymous namespace
ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink,
bool aIsMainThread)
: mSink(aSink)
, mIsMainThread(aIsMainThread)
{
mVirtualThread = GetCurrentVirtualThread();
}
void
ThreadEventTarget::SetCurrentThread()
{
mVirtualThread = GetCurrentVirtualThread();
}
NS_IMPL_ISUPPORTS(ThreadEventTarget,
nsIEventTarget,
nsISerialEventTarget)
NS_IMETHODIMP
ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
{
return Dispatch(do_AddRef(aRunnable), aFlags);
}
NS_IMETHODIMP
ThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
{
// We want to leak the reference when we fail to dispatch it, so that
// we won't release the event in a wrong thread.
LeakRefPtr<nsIRunnable> event(Move(aEvent));
if (NS_WARN_IF(!event)) {
return NS_ERROR_INVALID_ARG;
}
if (gXPCOMThreadsShutDown && !mIsMainThread) {
NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads");
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
#ifdef MOZ_TASK_TRACER
nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event.take());
(static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask();
// XXX tracedRunnable will always leaked when we fail to disptch.
event = tracedRunnable.forget();
#endif
if (aFlags & DISPATCH_SYNC) {
nsCOMPtr<nsIEventTarget> current = GetCurrentThreadEventTarget();
if (NS_WARN_IF(!current)) {
return NS_ERROR_NOT_AVAILABLE;
}
// XXX we should be able to do something better here... we should
// be able to monitor the slot occupied by this event and use
// that to tell us when the event has been processed.
RefPtr<nsThreadSyncDispatch> wrapper =
new nsThreadSyncDispatch(current.forget(), event.take());
bool success = mSink->PutEvent(do_AddRef(wrapper), EventPriority::Normal); // hold a ref
if (!success) {
// PutEvent leaked the wrapper runnable object on failure, so we
// explicitly release this object once for that. Note that this
// object will be released again soon because it exits the scope.
wrapper.get()->Release();
return NS_ERROR_UNEXPECTED;
}
// Allows waiting; ensure no locks are held that would deadlock us!
SpinEventLoopUntil([&, wrapper]() -> bool {
return !wrapper->IsPending();
});
return NS_OK;
}
NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
if (!mSink->PutEvent(event.take(), EventPriority::Normal)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
NS_IMETHODIMP
ThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs)
{
NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);
RefPtr<DelayedRunnable> r = new DelayedRunnable(do_AddRef(this),
Move(aEvent),
aDelayMs);
nsresult rv = r->Init();
NS_ENSURE_SUCCESS(rv, rv);
return Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}
NS_IMETHODIMP
ThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
{
*aIsOnCurrentThread = IsOnCurrentThread();
return NS_OK;
}
NS_IMETHODIMP_(bool)
ThreadEventTarget::IsOnCurrentThreadInfallible()
{
// Rely on mVirtualThread being correct.
MOZ_CRASH("IsOnCurrentThreadInfallible should never be called on nsIThread");
}