mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 01:05:45 +00:00
9edd615af7
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
211 lines
5.8 KiB
C++
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");
|
|
}
|