gecko-dev/xpcom/threads/ThreadEventTarget.cpp

207 строки
5.7 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 "mozilla/TimeStamp.h"
#include "nsComponentManagerUtils.h"
#include "nsITimer.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()
{
return NS_NewTimerWithCallback(getter_AddRefs(mTimer),
this, mDelay, nsITimer::TYPE_ONE_SHOT,
mTarget);
}
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");
}