mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-30 20:37:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
192
xpcom/threads/AbstractThread.cpp
Normal file
192
xpcom/threads/AbstractThread.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/* -*- 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 "mozilla/AbstractThread.h"
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MozPromise.h" // We initialize the MozPromise logging in this file.
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/StateWatching.h" // We initialize the StateWatching logging in this file.
|
||||
#include "mozilla/TaskQueue.h"
|
||||
#include "mozilla/TaskDispatcher.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
LazyLogModule gMozPromiseLog("MozPromise");
|
||||
LazyLogModule gStateWatchingLog("StateWatching");
|
||||
|
||||
StaticRefPtr<AbstractThread> sMainThread;
|
||||
MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS;
|
||||
|
||||
class XPCOMThreadWrapper : public AbstractThread
|
||||
{
|
||||
public:
|
||||
explicit XPCOMThreadWrapper(nsIThread* aTarget, bool aRequireTailDispatch)
|
||||
: AbstractThread(aRequireTailDispatch)
|
||||
, mTarget(aTarget)
|
||||
{
|
||||
// Our current mechanism of implementing tail dispatch is appshell-specific.
|
||||
// This is because a very similar mechanism already exists on the main
|
||||
// thread, and we want to avoid making event dispatch on the main thread
|
||||
// more complicated than it already is.
|
||||
//
|
||||
// If you need to use tail dispatch on other XPCOM threads, you'll need to
|
||||
// implement an nsIThreadObserver to fire the tail dispatcher at the
|
||||
// appropriate times.
|
||||
MOZ_ASSERT_IF(aRequireTailDispatch,
|
||||
NS_IsMainThread() && NS_GetCurrentThread() == aTarget);
|
||||
}
|
||||
|
||||
virtual void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
|
||||
DispatchReason aReason = NormalDispatch) override
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> r = aRunnable;
|
||||
AbstractThread* currentThread;
|
||||
if (aReason != TailDispatch && (currentThread = GetCurrent()) && RequiresTailDispatch(currentThread)) {
|
||||
currentThread->TailDispatcher().AddTask(this, r.forget(), aFailureHandling);
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv));
|
||||
Unused << rv;
|
||||
}
|
||||
|
||||
virtual bool IsCurrentThreadIn() override
|
||||
{
|
||||
// Compare NSPR threads so that this works after shutdown when
|
||||
// NS_GetCurrentThread starts returning null.
|
||||
PRThread* thread = nullptr;
|
||||
mTarget->GetPRThread(&thread);
|
||||
bool in = PR_GetCurrentThread() == thread;
|
||||
return in;
|
||||
}
|
||||
|
||||
void FireTailDispatcher()
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(mTailDispatcher.isSome());
|
||||
mTailDispatcher.ref().DrainDirectTasks();
|
||||
mTailDispatcher.reset();
|
||||
}
|
||||
|
||||
virtual TaskDispatcher& TailDispatcher() override
|
||||
{
|
||||
MOZ_ASSERT(this == sMainThread); // See the comment in the constructor.
|
||||
MOZ_ASSERT(IsCurrentThreadIn());
|
||||
if (!mTailDispatcher.isSome()) {
|
||||
mTailDispatcher.emplace(/* aIsTailDispatcher = */ true);
|
||||
|
||||
nsCOMPtr<nsIRunnable> event = NewRunnableMethod(this, &XPCOMThreadWrapper::FireTailDispatcher);
|
||||
nsContentUtils::RunInStableState(event.forget());
|
||||
}
|
||||
|
||||
return mTailDispatcher.ref();
|
||||
}
|
||||
|
||||
virtual bool MightHaveTailTasks() override
|
||||
{
|
||||
return mTailDispatcher.isSome();
|
||||
}
|
||||
|
||||
virtual nsIThread* AsXPCOMThread() override { return mTarget; }
|
||||
|
||||
private:
|
||||
RefPtr<nsIThread> mTarget;
|
||||
Maybe<AutoTaskDispatcher> mTailDispatcher;
|
||||
};
|
||||
|
||||
void
|
||||
AbstractThread::TailDispatchTasksFor(AbstractThread* aThread)
|
||||
{
|
||||
if (MightHaveTailTasks()) {
|
||||
TailDispatcher().DispatchTasksFor(aThread);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
AbstractThread::HasTailTasksFor(AbstractThread* aThread)
|
||||
{
|
||||
if (!MightHaveTailTasks()) {
|
||||
return false;
|
||||
}
|
||||
return TailDispatcher().HasTasksFor(aThread);
|
||||
}
|
||||
|
||||
bool
|
||||
AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const
|
||||
{
|
||||
MOZ_ASSERT(aThread);
|
||||
// We require tail dispatch if both the source and destination
|
||||
// threads support it.
|
||||
return SupportsTailDispatch() && aThread->SupportsTailDispatch();
|
||||
}
|
||||
|
||||
bool
|
||||
AbstractThread::RequiresTailDispatchFromCurrentThread() const
|
||||
{
|
||||
AbstractThread* current = GetCurrent();
|
||||
return current && RequiresTailDispatch(current);
|
||||
}
|
||||
|
||||
AbstractThread*
|
||||
AbstractThread::MainThread()
|
||||
{
|
||||
MOZ_ASSERT(sMainThread);
|
||||
return sMainThread;
|
||||
}
|
||||
|
||||
void
|
||||
AbstractThread::InitStatics()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!sMainThread);
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
MOZ_DIAGNOSTIC_ASSERT(mainThread);
|
||||
sMainThread = new XPCOMThreadWrapper(mainThread.get(), /* aRequireTailDispatch = */ true);
|
||||
ClearOnShutdown(&sMainThread);
|
||||
|
||||
if (!sCurrentThreadTLS.init()) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
sCurrentThreadTLS.set(sMainThread);
|
||||
}
|
||||
|
||||
void
|
||||
AbstractThread::DispatchStateChange(already_AddRefed<nsIRunnable> aRunnable)
|
||||
{
|
||||
GetCurrent()->TailDispatcher().AddStateChangeTask(this, Move(aRunnable));
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
AbstractThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aRunnable)
|
||||
{
|
||||
GetCurrent()->TailDispatcher().AddDirectTask(Move(aRunnable));
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<AbstractThread>
|
||||
AbstractThread::CreateXPCOMThreadWrapper(nsIThread* aThread, bool aRequireTailDispatch)
|
||||
{
|
||||
RefPtr<XPCOMThreadWrapper> wrapper = new XPCOMThreadWrapper(aThread, aRequireTailDispatch);
|
||||
// Set the thread-local sCurrentThreadTLS to point to the wrapper on the
|
||||
// target thread. This ensures that sCurrentThreadTLS is as expected by
|
||||
// AbstractThread::GetCurrent() on the target thread.
|
||||
nsCOMPtr<nsIRunnable> r =
|
||||
NS_NewRunnableFunction([wrapper]() { sCurrentThreadTLS.set(wrapper); });
|
||||
aThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
return wrapper.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
111
xpcom/threads/AbstractThread.h
Normal file
111
xpcom/threads/AbstractThread.h
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#if !defined(AbstractThread_h_)
|
||||
#define AbstractThread_h_
|
||||
|
||||
#include "nscore.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsIThread.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class TaskQueue;
|
||||
class TaskDispatcher;
|
||||
|
||||
/*
|
||||
* We often want to run tasks on a target that guarantees that events will never
|
||||
* run in parallel. There are various target types that achieve this - namely
|
||||
* nsIThread and TaskQueue. Note that nsIThreadPool (which implements
|
||||
* nsIEventTarget) does not have this property, so we do not want to use
|
||||
* nsIEventTarget for this purpose. This class encapsulates the specifics of
|
||||
* the structures we might use here and provides a consistent interface.
|
||||
*
|
||||
* At present, the supported AbstractThread implementations are TaskQueue
|
||||
* and AbstractThread::MainThread. If you add support for another thread that is
|
||||
* not the MainThread, you'll need to figure out how to make it unique such that
|
||||
* comparing AbstractThread pointers is equivalent to comparing nsIThread pointers.
|
||||
*/
|
||||
class AbstractThread
|
||||
{
|
||||
public:
|
||||
// Returns the AbstractThread that the caller is currently running in, or null
|
||||
// if the caller is not running in an AbstractThread.
|
||||
static AbstractThread* GetCurrent() { return sCurrentThreadTLS.get(); }
|
||||
|
||||
AbstractThread(bool aSupportsTailDispatch) : mSupportsTailDispatch(aSupportsTailDispatch) {}
|
||||
|
||||
static already_AddRefed<AbstractThread>
|
||||
CreateXPCOMThreadWrapper(nsIThread* aThread, bool aRequireTailDispatch);
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractThread);
|
||||
|
||||
enum DispatchFailureHandling { AssertDispatchSuccess, DontAssertDispatchSuccess };
|
||||
enum DispatchReason { NormalDispatch, TailDispatch };
|
||||
virtual void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aHandling = AssertDispatchSuccess,
|
||||
DispatchReason aReason = NormalDispatch) = 0;
|
||||
|
||||
virtual bool IsCurrentThreadIn() = 0;
|
||||
|
||||
// Returns true if dispatch is generally reliable. This is used to guard
|
||||
// against FlushableTaskQueues, which should go away.
|
||||
virtual bool IsDispatchReliable() { return true; }
|
||||
|
||||
// Returns a TaskDispatcher that will dispatch its tasks when the currently-
|
||||
// running tasks pops off the stack.
|
||||
//
|
||||
// May only be called when running within the it is invoked up, and only on
|
||||
// threads which support it.
|
||||
virtual TaskDispatcher& TailDispatcher() = 0;
|
||||
|
||||
// Returns true if we have tail tasks scheduled, or if this isn't known.
|
||||
// Returns false if we definitely don't have any tail tasks.
|
||||
virtual bool MightHaveTailTasks() { return true; }
|
||||
|
||||
// Helper functions for methods on the tail TasklDispatcher. These check
|
||||
// HasTailTasks to avoid allocating a TailDispatcher if it isn't
|
||||
// needed.
|
||||
void TailDispatchTasksFor(AbstractThread* aThread);
|
||||
bool HasTailTasksFor(AbstractThread* aThread);
|
||||
|
||||
// Returns true if this supports the tail dispatcher.
|
||||
bool SupportsTailDispatch() const { return mSupportsTailDispatch; }
|
||||
|
||||
// Returns true if this thread requires all dispatches originating from
|
||||
// aThread go through the tail dispatcher.
|
||||
bool RequiresTailDispatch(AbstractThread* aThread) const;
|
||||
bool RequiresTailDispatchFromCurrentThread() const;
|
||||
|
||||
virtual TaskQueue* AsTaskQueue() { MOZ_CRASH("Not a task queue!"); }
|
||||
virtual nsIThread* AsXPCOMThread() { MOZ_CRASH("Not an XPCOM thread!"); }
|
||||
|
||||
// Convenience method for getting an AbstractThread for the main thread.
|
||||
static AbstractThread* MainThread();
|
||||
|
||||
// Must be called exactly once during startup.
|
||||
static void InitStatics();
|
||||
|
||||
void DispatchStateChange(already_AddRefed<nsIRunnable> aRunnable);
|
||||
|
||||
static void DispatchDirectTask(already_AddRefed<nsIRunnable> aRunnable);
|
||||
|
||||
protected:
|
||||
virtual ~AbstractThread() {}
|
||||
static MOZ_THREAD_LOCAL(AbstractThread*) sCurrentThreadTLS;
|
||||
|
||||
// True if we want to require that every task dispatched from tasks running in
|
||||
// this queue go through our queue's tail dispatcher.
|
||||
const bool mSupportsTailDispatch;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
734
xpcom/threads/BackgroundHangMonitor.cpp
Normal file
734
xpcom/threads/BackgroundHangMonitor.cpp
Normal file
|
|
@ -0,0 +1,734 @@
|
|||
/* -*- 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 "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/BackgroundHangMonitor.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/ThreadHangStats.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
#include "prinrval.h"
|
||||
#include "prthread.h"
|
||||
#include "ThreadStackHelper.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// Activate BHR only for one every BHR_BETA_MOD users.
|
||||
// This is now 100% of Beta population for the Beta 45/46 e10s A/B trials
|
||||
// It can be scaled back again in the future
|
||||
#define BHR_BETA_MOD 1;
|
||||
|
||||
// Maximum depth of the call stack in the reported thread hangs. This value represents
|
||||
// the 99.9th percentile of the thread hangs stack depths reported by Telemetry.
|
||||
static const size_t kMaxThreadHangStackDepth = 30;
|
||||
|
||||
// An utility comparator function used by std::unique to collapse "(* script)" entries in
|
||||
// a vector representing a call stack.
|
||||
bool StackScriptEntriesCollapser(const char* aStackEntry, const char *aAnotherStackEntry)
|
||||
{
|
||||
return !strcmp(aStackEntry, aAnotherStackEntry) &&
|
||||
(!strcmp(aStackEntry, "(chrome script)") || !strcmp(aStackEntry, "(content script)"));
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* BackgroundHangManager is the global object that
|
||||
* manages all instances of BackgroundHangThread.
|
||||
*/
|
||||
class BackgroundHangManager : public nsIObserver
|
||||
{
|
||||
private:
|
||||
// Background hang monitor thread function
|
||||
static void MonitorThread(void* aData)
|
||||
{
|
||||
PR_SetCurrentThreadName("BgHangManager");
|
||||
|
||||
/* We do not hold a reference to BackgroundHangManager here
|
||||
because the monitor thread only exists as long as the
|
||||
BackgroundHangManager instance exists. We stop the monitor
|
||||
thread in the BackgroundHangManager destructor, and we can
|
||||
only get to the destructor if we don't hold a reference here. */
|
||||
static_cast<BackgroundHangManager*>(aData)->RunMonitorThread();
|
||||
}
|
||||
|
||||
// Hang monitor thread
|
||||
PRThread* mHangMonitorThread;
|
||||
// Stop hang monitoring
|
||||
bool mShutdown;
|
||||
|
||||
BackgroundHangManager(const BackgroundHangManager&);
|
||||
BackgroundHangManager& operator=(const BackgroundHangManager&);
|
||||
void RunMonitorThread();
|
||||
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
static StaticRefPtr<BackgroundHangManager> sInstance;
|
||||
static bool sDisabled;
|
||||
|
||||
// Lock for access to members of this class
|
||||
Monitor mLock;
|
||||
// Current time as seen by hang monitors
|
||||
PRIntervalTime mIntervalNow;
|
||||
// List of BackgroundHangThread instances associated with each thread
|
||||
LinkedList<BackgroundHangThread> mHangThreads;
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
MonitorAutoLock autoLock(mLock);
|
||||
mShutdown = true;
|
||||
autoLock.Notify();
|
||||
}
|
||||
|
||||
void Wakeup()
|
||||
{
|
||||
// PR_CreateThread could have failed earlier
|
||||
if (mHangMonitorThread) {
|
||||
// Use PR_Interrupt to avoid potentially taking a lock
|
||||
PR_Interrupt(mHangMonitorThread);
|
||||
}
|
||||
}
|
||||
|
||||
BackgroundHangManager();
|
||||
private:
|
||||
virtual ~BackgroundHangManager();
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(BackgroundHangManager, nsIObserver)
|
||||
|
||||
NS_IMETHODIMP
|
||||
BackgroundHangManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) {
|
||||
NS_ENSURE_TRUE(!strcmp(aTopic, "profile-after-change"), NS_ERROR_UNEXPECTED);
|
||||
BackgroundHangMonitor::DisableOnBeta();
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
||||
MOZ_ASSERT(observerService);
|
||||
observerService->RemoveObserver(this, "profile-after-change");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* BackgroundHangThread is a per-thread object that is used
|
||||
* by all instances of BackgroundHangMonitor to monitor hangs.
|
||||
*/
|
||||
class BackgroundHangThread : public LinkedListElement<BackgroundHangThread>
|
||||
{
|
||||
private:
|
||||
static MOZ_THREAD_LOCAL(BackgroundHangThread*) sTlsKey;
|
||||
static bool sTlsKeyInitialized;
|
||||
|
||||
BackgroundHangThread(const BackgroundHangThread&);
|
||||
BackgroundHangThread& operator=(const BackgroundHangThread&);
|
||||
~BackgroundHangThread();
|
||||
|
||||
/* Keep a reference to the manager, so we can keep going even
|
||||
after BackgroundHangManager::Shutdown is called. */
|
||||
const RefPtr<BackgroundHangManager> mManager;
|
||||
// Unique thread ID for identification
|
||||
const PRThread* mThreadID;
|
||||
|
||||
void Update();
|
||||
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(BackgroundHangThread)
|
||||
/**
|
||||
* Returns the BackgroundHangThread associated with the
|
||||
* running thread. Note that this will not find private
|
||||
* BackgroundHangThread threads.
|
||||
*
|
||||
* @return BackgroundHangThread*, or nullptr if no thread
|
||||
* is found.
|
||||
*/
|
||||
static BackgroundHangThread* FindThread();
|
||||
|
||||
static void Startup()
|
||||
{
|
||||
/* We can tolerate init() failing. */
|
||||
sTlsKeyInitialized = sTlsKey.init();
|
||||
}
|
||||
|
||||
// Hang timeout in ticks
|
||||
const PRIntervalTime mTimeout;
|
||||
// PermaHang timeout in ticks
|
||||
const PRIntervalTime mMaxTimeout;
|
||||
// Time at last activity
|
||||
PRIntervalTime mInterval;
|
||||
// Time when a hang started
|
||||
PRIntervalTime mHangStart;
|
||||
// Is the thread in a hang
|
||||
bool mHanging;
|
||||
// Is the thread in a waiting state
|
||||
bool mWaiting;
|
||||
// Is the thread dedicated to a single BackgroundHangMonitor
|
||||
BackgroundHangMonitor::ThreadType mThreadType;
|
||||
// Platform-specific helper to get hang stacks
|
||||
ThreadStackHelper mStackHelper;
|
||||
// Stack of current hang
|
||||
Telemetry::HangStack mHangStack;
|
||||
// Statistics for telemetry
|
||||
Telemetry::ThreadHangStats mStats;
|
||||
// Annotations for the current hang
|
||||
UniquePtr<HangMonitor::HangAnnotations> mAnnotations;
|
||||
// Annotators registered for this thread
|
||||
HangMonitor::Observer::Annotators mAnnotators;
|
||||
|
||||
BackgroundHangThread(const char* aName,
|
||||
uint32_t aTimeoutMs,
|
||||
uint32_t aMaxTimeoutMs,
|
||||
BackgroundHangMonitor::ThreadType aThreadType = BackgroundHangMonitor::THREAD_SHARED);
|
||||
|
||||
// Report a hang; aManager->mLock IS locked
|
||||
Telemetry::HangHistogram& ReportHang(PRIntervalTime aHangTime);
|
||||
// Report a permanent hang; aManager->mLock IS locked
|
||||
void ReportPermaHang();
|
||||
// Called by BackgroundHangMonitor::NotifyActivity
|
||||
void NotifyActivity()
|
||||
{
|
||||
MonitorAutoLock autoLock(mManager->mLock);
|
||||
Update();
|
||||
}
|
||||
// Called by BackgroundHangMonitor::NotifyWait
|
||||
void NotifyWait()
|
||||
{
|
||||
MonitorAutoLock autoLock(mManager->mLock);
|
||||
|
||||
if (mWaiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
Update();
|
||||
mWaiting = true;
|
||||
}
|
||||
|
||||
// Returns true if this thread is (or might be) shared between other
|
||||
// BackgroundHangMonitors for the monitored thread.
|
||||
bool IsShared() {
|
||||
return mThreadType == BackgroundHangMonitor::THREAD_SHARED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
StaticRefPtr<BackgroundHangManager> BackgroundHangManager::sInstance;
|
||||
bool BackgroundHangManager::sDisabled = false;
|
||||
|
||||
MOZ_THREAD_LOCAL(BackgroundHangThread*) BackgroundHangThread::sTlsKey;
|
||||
bool BackgroundHangThread::sTlsKeyInitialized;
|
||||
|
||||
BackgroundHangManager::BackgroundHangManager()
|
||||
: mShutdown(false)
|
||||
, mLock("BackgroundHangManager")
|
||||
, mIntervalNow(0)
|
||||
{
|
||||
// Lock so we don't race against the new monitor thread
|
||||
MonitorAutoLock autoLock(mLock);
|
||||
mHangMonitorThread = PR_CreateThread(
|
||||
PR_USER_THREAD, MonitorThread, this,
|
||||
PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
|
||||
|
||||
MOZ_ASSERT(mHangMonitorThread, "Failed to create monitor thread");
|
||||
}
|
||||
|
||||
BackgroundHangManager::~BackgroundHangManager()
|
||||
{
|
||||
MOZ_ASSERT(mShutdown, "Destruction without Shutdown call");
|
||||
MOZ_ASSERT(mHangThreads.isEmpty(), "Destruction with outstanding monitors");
|
||||
MOZ_ASSERT(mHangMonitorThread, "No monitor thread");
|
||||
|
||||
// PR_CreateThread could have failed above due to resource limitation
|
||||
if (mHangMonitorThread) {
|
||||
// The monitor thread can only live as long as the instance lives
|
||||
PR_JoinThread(mHangMonitorThread);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BackgroundHangManager::RunMonitorThread()
|
||||
{
|
||||
// Keep us locked except when waiting
|
||||
MonitorAutoLock autoLock(mLock);
|
||||
|
||||
/* mIntervalNow is updated at various intervals determined by waitTime.
|
||||
However, if an update latency is too long (due to CPU scheduling, system
|
||||
sleep, etc.), we don't update mIntervalNow at all. This is done so that
|
||||
long latencies in our timing are not detected as hangs. systemTime is
|
||||
used to track PR_IntervalNow() and determine our latency. */
|
||||
|
||||
PRIntervalTime systemTime = PR_IntervalNow();
|
||||
// Default values for the first iteration of thread loop
|
||||
PRIntervalTime waitTime = PR_INTERVAL_NO_WAIT;
|
||||
PRIntervalTime recheckTimeout = PR_INTERVAL_NO_WAIT;
|
||||
|
||||
while (!mShutdown) {
|
||||
|
||||
PR_ClearInterrupt();
|
||||
nsresult rv = autoLock.Wait(waitTime);
|
||||
|
||||
PRIntervalTime newTime = PR_IntervalNow();
|
||||
PRIntervalTime systemInterval = newTime - systemTime;
|
||||
systemTime = newTime;
|
||||
|
||||
/* waitTime is a quarter of the shortest timeout value; If our timing
|
||||
latency is low enough (less than half the shortest timeout value),
|
||||
we can update mIntervalNow. */
|
||||
if (MOZ_LIKELY(waitTime != PR_INTERVAL_NO_TIMEOUT &&
|
||||
systemInterval < 2 * waitTime)) {
|
||||
mIntervalNow += systemInterval;
|
||||
}
|
||||
|
||||
/* If it's before the next recheck timeout, and our wait did not
|
||||
get interrupted (either through Notify or PR_Interrupt), we can
|
||||
keep the current waitTime and skip iterating through hang monitors. */
|
||||
if (MOZ_LIKELY(systemInterval < recheckTimeout &&
|
||||
systemInterval >= waitTime &&
|
||||
rv == NS_OK)) {
|
||||
recheckTimeout -= systemInterval;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We are in one of the following scenarios,
|
||||
- Hang or permahang recheck timeout
|
||||
- Thread added/removed
|
||||
- Thread wait or hang ended
|
||||
In all cases, we want to go through our list of hang
|
||||
monitors and update waitTime and recheckTimeout. */
|
||||
waitTime = PR_INTERVAL_NO_TIMEOUT;
|
||||
recheckTimeout = PR_INTERVAL_NO_TIMEOUT;
|
||||
|
||||
// Locally hold mIntervalNow
|
||||
PRIntervalTime intervalNow = mIntervalNow;
|
||||
|
||||
// iterate through hang monitors
|
||||
for (BackgroundHangThread* currentThread = mHangThreads.getFirst();
|
||||
currentThread; currentThread = currentThread->getNext()) {
|
||||
|
||||
if (currentThread->mWaiting) {
|
||||
// Thread is waiting, not hanging
|
||||
continue;
|
||||
}
|
||||
PRIntervalTime interval = currentThread->mInterval;
|
||||
PRIntervalTime hangTime = intervalNow - interval;
|
||||
if (MOZ_UNLIKELY(hangTime >= currentThread->mMaxTimeout)) {
|
||||
// A permahang started
|
||||
// Skip subsequent iterations and tolerate a race on mWaiting here
|
||||
currentThread->mWaiting = true;
|
||||
currentThread->mHanging = false;
|
||||
currentThread->ReportPermaHang();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (MOZ_LIKELY(!currentThread->mHanging)) {
|
||||
if (MOZ_UNLIKELY(hangTime >= currentThread->mTimeout)) {
|
||||
// A hang started
|
||||
currentThread->mStackHelper.GetStack(currentThread->mHangStack);
|
||||
currentThread->mHangStart = interval;
|
||||
currentThread->mHanging = true;
|
||||
currentThread->mAnnotations =
|
||||
currentThread->mAnnotators.GatherAnnotations();
|
||||
}
|
||||
} else {
|
||||
if (MOZ_LIKELY(interval != currentThread->mHangStart)) {
|
||||
// A hang ended
|
||||
currentThread->ReportHang(intervalNow - currentThread->mHangStart);
|
||||
currentThread->mHanging = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we are hanging, the next time we check for hang status is when
|
||||
the hang turns into a permahang. If we're not hanging, the next
|
||||
recheck timeout is when we may be entering a hang. */
|
||||
PRIntervalTime nextRecheck;
|
||||
if (currentThread->mHanging) {
|
||||
nextRecheck = currentThread->mMaxTimeout;
|
||||
} else {
|
||||
nextRecheck = currentThread->mTimeout;
|
||||
}
|
||||
recheckTimeout = std::min(recheckTimeout, nextRecheck - hangTime);
|
||||
|
||||
if (currentThread->mTimeout != PR_INTERVAL_NO_TIMEOUT) {
|
||||
/* We wait for a quarter of the shortest timeout
|
||||
value to give mIntervalNow enough granularity. */
|
||||
waitTime = std::min(waitTime, currentThread->mTimeout / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* We are shutting down now.
|
||||
Wait for all outstanding monitors to unregister. */
|
||||
while (!mHangThreads.isEmpty()) {
|
||||
autoLock.Wait(PR_INTERVAL_NO_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BackgroundHangThread::BackgroundHangThread(const char* aName,
|
||||
uint32_t aTimeoutMs,
|
||||
uint32_t aMaxTimeoutMs,
|
||||
BackgroundHangMonitor::ThreadType aThreadType)
|
||||
: mManager(BackgroundHangManager::sInstance)
|
||||
, mThreadID(PR_GetCurrentThread())
|
||||
, mTimeout(aTimeoutMs == BackgroundHangMonitor::kNoTimeout
|
||||
? PR_INTERVAL_NO_TIMEOUT
|
||||
: PR_MillisecondsToInterval(aTimeoutMs))
|
||||
, mMaxTimeout(aMaxTimeoutMs == BackgroundHangMonitor::kNoTimeout
|
||||
? PR_INTERVAL_NO_TIMEOUT
|
||||
: PR_MillisecondsToInterval(aMaxTimeoutMs))
|
||||
, mInterval(mManager->mIntervalNow)
|
||||
, mHangStart(mInterval)
|
||||
, mHanging(false)
|
||||
, mWaiting(true)
|
||||
, mThreadType(aThreadType)
|
||||
, mStats(aName)
|
||||
{
|
||||
if (sTlsKeyInitialized && IsShared()) {
|
||||
sTlsKey.set(this);
|
||||
}
|
||||
// Lock here because LinkedList is not thread-safe
|
||||
MonitorAutoLock autoLock(mManager->mLock);
|
||||
// Add to thread list
|
||||
mManager->mHangThreads.insertBack(this);
|
||||
// Wake up monitor thread to process new thread
|
||||
autoLock.Notify();
|
||||
}
|
||||
|
||||
BackgroundHangThread::~BackgroundHangThread()
|
||||
{
|
||||
// Lock here because LinkedList is not thread-safe
|
||||
MonitorAutoLock autoLock(mManager->mLock);
|
||||
// Remove from thread list
|
||||
remove();
|
||||
// Wake up monitor thread to process removed thread
|
||||
autoLock.Notify();
|
||||
|
||||
// We no longer have a thread
|
||||
if (sTlsKeyInitialized && IsShared()) {
|
||||
sTlsKey.set(nullptr);
|
||||
}
|
||||
|
||||
// Move our copy of ThreadHangStats to Telemetry storage
|
||||
Telemetry::RecordThreadHangStats(mStats);
|
||||
}
|
||||
|
||||
Telemetry::HangHistogram&
|
||||
BackgroundHangThread::ReportHang(PRIntervalTime aHangTime)
|
||||
{
|
||||
// Recovered from a hang; called on the monitor thread
|
||||
// mManager->mLock IS locked
|
||||
|
||||
// Remove unwanted "js::RunScript" frame from the stack
|
||||
for (size_t i = 0; i < mHangStack.length(); ) {
|
||||
const char** f = mHangStack.begin() + i;
|
||||
if (!mHangStack.IsInBuffer(*f) && !strcmp(*f, "js::RunScript")) {
|
||||
mHangStack.erase(f);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse duplicated "(chrome script)" and "(content script)" entries in the stack.
|
||||
auto it = std::unique(mHangStack.begin(), mHangStack.end(), StackScriptEntriesCollapser);
|
||||
mHangStack.erase(it, mHangStack.end());
|
||||
|
||||
// Limit the depth of the reported stack if greater than our limit. Only keep its
|
||||
// last entries, since the most recent frames are at the end of the vector.
|
||||
if (mHangStack.length() > kMaxThreadHangStackDepth) {
|
||||
const int elementsToRemove = mHangStack.length() - kMaxThreadHangStackDepth;
|
||||
// Replace the oldest frame with a known label so that we can tell this stack
|
||||
// was limited.
|
||||
mHangStack[0] = "(reduced stack)";
|
||||
mHangStack.erase(mHangStack.begin() + 1, mHangStack.begin() + elementsToRemove);
|
||||
}
|
||||
|
||||
Telemetry::HangHistogram newHistogram(Move(mHangStack));
|
||||
for (Telemetry::HangHistogram* oldHistogram = mStats.mHangs.begin();
|
||||
oldHistogram != mStats.mHangs.end(); oldHistogram++) {
|
||||
if (newHistogram == *oldHistogram) {
|
||||
// New histogram matches old one
|
||||
oldHistogram->Add(aHangTime, Move(mAnnotations));
|
||||
return *oldHistogram;
|
||||
}
|
||||
}
|
||||
// Add new histogram
|
||||
newHistogram.Add(aHangTime, Move(mAnnotations));
|
||||
if (!mStats.mHangs.append(Move(newHistogram))) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
return mStats.mHangs.back();
|
||||
}
|
||||
|
||||
void
|
||||
BackgroundHangThread::ReportPermaHang()
|
||||
{
|
||||
// Permanently hanged; called on the monitor thread
|
||||
// mManager->mLock IS locked
|
||||
|
||||
Telemetry::HangHistogram& hang = ReportHang(mMaxTimeout);
|
||||
Telemetry::HangStack& stack = hang.GetNativeStack();
|
||||
if (stack.empty()) {
|
||||
mStackHelper.GetNativeStack(stack);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE void
|
||||
BackgroundHangThread::Update()
|
||||
{
|
||||
PRIntervalTime intervalNow = mManager->mIntervalNow;
|
||||
if (mWaiting) {
|
||||
mInterval = intervalNow;
|
||||
mWaiting = false;
|
||||
/* We have to wake up the manager thread because when all threads
|
||||
are waiting, the manager thread waits indefinitely as well. */
|
||||
mManager->Wakeup();
|
||||
} else {
|
||||
PRIntervalTime duration = intervalNow - mInterval;
|
||||
mStats.mActivity.Add(duration);
|
||||
if (MOZ_UNLIKELY(duration >= mTimeout)) {
|
||||
/* Wake up the manager thread to tell it that a hang ended */
|
||||
mManager->Wakeup();
|
||||
}
|
||||
mInterval = intervalNow;
|
||||
}
|
||||
}
|
||||
|
||||
BackgroundHangThread*
|
||||
BackgroundHangThread::FindThread()
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
if (BackgroundHangManager::sInstance == nullptr) {
|
||||
MOZ_ASSERT(BackgroundHangManager::sDisabled,
|
||||
"BackgroundHandleManager is not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (sTlsKeyInitialized) {
|
||||
// Use TLS if available
|
||||
return sTlsKey.get();
|
||||
}
|
||||
// If TLS is unavailable, we can search through the thread list
|
||||
RefPtr<BackgroundHangManager> manager(BackgroundHangManager::sInstance);
|
||||
MOZ_ASSERT(manager, "Creating BackgroundHangMonitor after shutdown");
|
||||
|
||||
PRThread* threadID = PR_GetCurrentThread();
|
||||
// Lock thread list for traversal
|
||||
MonitorAutoLock autoLock(manager->mLock);
|
||||
for (BackgroundHangThread* thread = manager->mHangThreads.getFirst();
|
||||
thread; thread = thread->getNext()) {
|
||||
if (thread->mThreadID == threadID && thread->IsShared()) {
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Current thread is not initialized
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
BackgroundHangMonitor::ShouldDisableOnBeta(const nsCString &clientID) {
|
||||
MOZ_ASSERT(clientID.Length() == 36, "clientID is invalid");
|
||||
const char *suffix = clientID.get() + clientID.Length() - 4;
|
||||
return strtol(suffix, NULL, 16) % BHR_BETA_MOD;
|
||||
}
|
||||
|
||||
bool
|
||||
BackgroundHangMonitor::IsDisabled() {
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
return BackgroundHangManager::sDisabled;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
BackgroundHangMonitor::DisableOnBeta() {
|
||||
nsAdoptingCString clientID = Preferences::GetCString("toolkit.telemetry.cachedClientID");
|
||||
bool telemetryEnabled = Preferences::GetBool("toolkit.telemetry.enabled");
|
||||
|
||||
if (!telemetryEnabled || !clientID || BackgroundHangMonitor::ShouldDisableOnBeta(clientID)) {
|
||||
if (XRE_IsParentProcess()) {
|
||||
BackgroundHangMonitor::Shutdown();
|
||||
} else {
|
||||
BackgroundHangManager::sDisabled = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
BackgroundHangMonitor::Startup()
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
MOZ_ASSERT(!BackgroundHangManager::sInstance, "Already initialized");
|
||||
|
||||
if (!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta")) {
|
||||
if (XRE_IsParentProcess()) { // cached ClientID hasn't been read yet
|
||||
ThreadStackHelper::Startup();
|
||||
BackgroundHangThread::Startup();
|
||||
BackgroundHangManager::sInstance = new BackgroundHangManager();
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
||||
MOZ_ASSERT(observerService);
|
||||
|
||||
observerService->AddObserver(BackgroundHangManager::sInstance, "profile-after-change", false);
|
||||
return;
|
||||
} else if(DisableOnBeta()){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ThreadStackHelper::Startup();
|
||||
BackgroundHangThread::Startup();
|
||||
BackgroundHangManager::sInstance = new BackgroundHangManager();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
BackgroundHangMonitor::Shutdown()
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
if (BackgroundHangManager::sDisabled) {
|
||||
MOZ_ASSERT(!BackgroundHangManager::sInstance, "Initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(BackgroundHangManager::sInstance, "Not initialized");
|
||||
/* Scope our lock inside Shutdown() because the sInstance object can
|
||||
be destroyed as soon as we set sInstance to nullptr below, and
|
||||
we don't want to hold the lock when it's being destroyed. */
|
||||
BackgroundHangManager::sInstance->Shutdown();
|
||||
BackgroundHangManager::sInstance = nullptr;
|
||||
ThreadStackHelper::Shutdown();
|
||||
BackgroundHangManager::sDisabled = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
BackgroundHangMonitor::BackgroundHangMonitor(const char* aName,
|
||||
uint32_t aTimeoutMs,
|
||||
uint32_t aMaxTimeoutMs,
|
||||
ThreadType aThreadType)
|
||||
: mThread(aThreadType == THREAD_SHARED ? BackgroundHangThread::FindThread() : nullptr)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
if (!BackgroundHangManager::sDisabled && !mThread) {
|
||||
mThread = new BackgroundHangThread(aName, aTimeoutMs, aMaxTimeoutMs,
|
||||
aThreadType);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
BackgroundHangMonitor::BackgroundHangMonitor()
|
||||
: mThread(BackgroundHangThread::FindThread())
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
if (BackgroundHangManager::sDisabled) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
BackgroundHangMonitor::~BackgroundHangMonitor()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
BackgroundHangMonitor::NotifyActivity()
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
if (mThread == nullptr) {
|
||||
MOZ_ASSERT(BackgroundHangManager::sDisabled,
|
||||
"This thread is not initialized for hang monitoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Telemetry::CanRecordExtended()) {
|
||||
mThread->NotifyActivity();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
BackgroundHangMonitor::NotifyWait()
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
if (mThread == nullptr) {
|
||||
MOZ_ASSERT(BackgroundHangManager::sDisabled,
|
||||
"This thread is not initialized for hang monitoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Telemetry::CanRecordExtended()) {
|
||||
mThread->NotifyWait();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
BackgroundHangMonitor::RegisterAnnotator(HangMonitor::Annotator& aAnnotator)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
BackgroundHangThread* thisThread = BackgroundHangThread::FindThread();
|
||||
if (!thisThread) {
|
||||
return false;
|
||||
}
|
||||
return thisThread->mAnnotators.Register(aAnnotator);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
BackgroundHangMonitor::UnregisterAnnotator(HangMonitor::Annotator& aAnnotator)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
BackgroundHangThread* thisThread = BackgroundHangThread::FindThread();
|
||||
if (!thisThread) {
|
||||
return false;
|
||||
}
|
||||
return thisThread->mAnnotators.Unregister(aAnnotator);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Because we are iterating through the BackgroundHangThread linked list,
|
||||
we need to take a lock. Using MonitorAutoLock as a base class makes
|
||||
sure all of that is taken care of for us. */
|
||||
BackgroundHangMonitor::ThreadHangStatsIterator::ThreadHangStatsIterator()
|
||||
: MonitorAutoLock(BackgroundHangManager::sInstance->mLock)
|
||||
, mThread(BackgroundHangManager::sInstance ?
|
||||
BackgroundHangManager::sInstance->mHangThreads.getFirst() :
|
||||
nullptr)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
|
||||
MOZ_ASSERT(BackgroundHangManager::sInstance ||
|
||||
BackgroundHangManager::sDisabled,
|
||||
"Inconsistent state");
|
||||
#endif
|
||||
}
|
||||
|
||||
Telemetry::ThreadHangStats*
|
||||
BackgroundHangMonitor::ThreadHangStatsIterator::GetNext()
|
||||
{
|
||||
if (!mThread) {
|
||||
return nullptr;
|
||||
}
|
||||
Telemetry::ThreadHangStats* stats = &mThread->mStats;
|
||||
mThread = mThread->getNext();
|
||||
return stats;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
246
xpcom/threads/BackgroundHangMonitor.h
Normal file
246
xpcom/threads/BackgroundHangMonitor.h
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_BackgroundHangMonitor_h
|
||||
#define mozilla_BackgroundHangMonitor_h
|
||||
|
||||
#include "mozilla/HangAnnotations.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#include "nsString.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace Telemetry {
|
||||
class ThreadHangStats;
|
||||
} // namespace Telemetry
|
||||
|
||||
class BackgroundHangThread;
|
||||
class BackgroundHangManager;
|
||||
|
||||
/**
|
||||
* The background hang monitor is responsible for detecting and reporting
|
||||
* hangs in main and background threads. A thread registers itself using
|
||||
* the BackgroundHangMonitor object and periodically calls its methods to
|
||||
* inform the hang monitor of the thread's activity. Each thread is given
|
||||
* a thread name, a timeout, and a maximum timeout. If one of the thread's
|
||||
* tasks runs for longer than the timeout duration but shorter than the
|
||||
* maximum timeout, a (transient) hang is reported. On the other hand, if
|
||||
* a task runs for longer than the maximum timeout duration or never
|
||||
* finishes (e.g. in a deadlock), a permahang is reported.
|
||||
*
|
||||
* Tasks are defined arbitrarily, but are typically represented by events
|
||||
* in an event loop -- processing one event is equivalent to running one
|
||||
* task. To ensure responsiveness, tasks in a thread often have a target
|
||||
* running time. This is a good starting point for determining the timeout
|
||||
* and maximum timeout values. For example, the Compositor thread has a
|
||||
* responsiveness goal of 60Hz or 17ms, so a starting timeout could be
|
||||
* 100ms. Considering some platforms (e.g. Android) can terminate the app
|
||||
* when a critical thread hangs for longer than a few seconds, a good
|
||||
* starting maximum timeout is 4 or 5 seconds.
|
||||
*
|
||||
* A thread registers itself through the BackgroundHangMonitor constructor.
|
||||
* Multiple BackgroundHangMonitor objects can be used in one thread. The
|
||||
* constructor without arguments can be used when it is known that the thread
|
||||
* already has a BackgroundHangMonitor registered. When all instances of
|
||||
* BackgroundHangMonitor are destroyed, the thread is unregistered.
|
||||
*
|
||||
* The thread then uses two methods to inform BackgroundHangMonitor of the
|
||||
* thread's activity:
|
||||
*
|
||||
* > BackgroundHangMonitor::NotifyActivity should be called *before*
|
||||
* starting a task. The task run time is determined by the interval
|
||||
* between this call and the next NotifyActivity call.
|
||||
*
|
||||
* > BackgroundHangMonitor::NotifyWait should be called *before* the
|
||||
* thread enters a wait state (e.g. to wait for a new event). This
|
||||
* prevents a waiting thread from being detected as hanging. The wait
|
||||
* state is automatically cleared at the next NotifyActivity call.
|
||||
*
|
||||
* The following example shows hang monitoring in a simple event loop:
|
||||
*
|
||||
* void thread_main()
|
||||
* {
|
||||
* mozilla::BackgroundHangMonitor hangMonitor("example1", 100, 1000);
|
||||
* while (!exiting) {
|
||||
* hangMonitor.NotifyActivity();
|
||||
* process_next_event();
|
||||
* hangMonitor.NotifyWait();
|
||||
* wait_for_next_event();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* The following example shows reentrancy in nested event loops:
|
||||
*
|
||||
* void thread_main()
|
||||
* {
|
||||
* mozilla::BackgroundHangMonitor hangMonitor("example2", 100, 1000);
|
||||
* while (!exiting) {
|
||||
* hangMonitor.NotifyActivity();
|
||||
* process_next_event();
|
||||
* hangMonitor.NotifyWait();
|
||||
* wait_for_next_event();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* void process_next_event()
|
||||
* {
|
||||
* mozilla::BackgroundHangMonitor hangMonitor();
|
||||
* if (is_sync_event) {
|
||||
* while (!finished_event) {
|
||||
* hangMonitor.NotifyActivity();
|
||||
* process_next_event();
|
||||
* hangMonitor.NotifyWait();
|
||||
* wait_for_next_event();
|
||||
* }
|
||||
* } else {
|
||||
* process_nonsync_event();
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class BackgroundHangMonitor
|
||||
{
|
||||
private:
|
||||
friend BackgroundHangManager;
|
||||
|
||||
RefPtr<BackgroundHangThread> mThread;
|
||||
|
||||
static bool ShouldDisableOnBeta(const nsCString &);
|
||||
static bool DisableOnBeta();
|
||||
|
||||
public:
|
||||
static const uint32_t kNoTimeout = 0;
|
||||
enum ThreadType {
|
||||
// For a new BackgroundHangMonitor for thread T, only create a new
|
||||
// monitoring thread for T if one doesn't already exist. If one does,
|
||||
// share that pre-existing monitoring thread.
|
||||
THREAD_SHARED,
|
||||
// For a new BackgroundHangMonitor for thread T, create a new
|
||||
// monitoring thread for T even if there are other, pre-existing
|
||||
// monitoring threads for T.
|
||||
THREAD_PRIVATE
|
||||
};
|
||||
|
||||
/**
|
||||
* ThreadHangStatsIterator is used to iterate through the ThreadHangStats
|
||||
* associated with each active monitored thread. Because of an internal
|
||||
* lock while this object is alive, a thread must use only one instance
|
||||
* of this class at a time and must iterate through the list as fast as
|
||||
* possible. The following example shows using the iterator:
|
||||
*
|
||||
* {
|
||||
* // Scope the iter variable so it's destroyed as soon as we're done
|
||||
* BackgroundHangMonitor::ThreadHangStatsIterator iter;
|
||||
* for (ThreadHangStats* histogram = iter.GetNext();
|
||||
* histogram; histogram = iter.GetNext()) {
|
||||
* // Process histogram
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class ThreadHangStatsIterator : public MonitorAutoLock
|
||||
{
|
||||
private:
|
||||
BackgroundHangThread* mThread;
|
||||
|
||||
ThreadHangStatsIterator(const ThreadHangStatsIterator&);
|
||||
ThreadHangStatsIterator& operator=(const ThreadHangStatsIterator&);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create an ThreadHangStatsIterator instance and take the internal lock.
|
||||
* Internal lock is released on destruction.
|
||||
*/
|
||||
ThreadHangStatsIterator();
|
||||
|
||||
/**
|
||||
* Get the next item in the list; the first call returns the first item.
|
||||
* Returns nullptr at the end of the list.
|
||||
*/
|
||||
Telemetry::ThreadHangStats* GetNext();
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable hang monitoring.
|
||||
* Must return before using BackgroundHangMonitor.
|
||||
*/
|
||||
static void Startup();
|
||||
|
||||
/**
|
||||
* Disable hang monitoring.
|
||||
* Can be called without destroying all BackgroundHangMonitors first.
|
||||
*/
|
||||
static void Shutdown();
|
||||
|
||||
/**
|
||||
* Returns true if BHR is disabled.
|
||||
*/
|
||||
static bool IsDisabled();
|
||||
|
||||
/**
|
||||
* Start monitoring hangs for the current thread.
|
||||
*
|
||||
* @param aName Name to identify the thread with
|
||||
* @param aTimeoutMs Amount of time in milliseconds without
|
||||
* activity before registering a hang
|
||||
* @param aMaxTimeoutMs Amount of time in milliseconds without
|
||||
* activity before registering a permanent hang
|
||||
* @param aThreadType
|
||||
* The ThreadType type of monitoring thread that should be created
|
||||
* for this monitor. See the documentation for ThreadType.
|
||||
*/
|
||||
BackgroundHangMonitor(const char* aName,
|
||||
uint32_t aTimeoutMs,
|
||||
uint32_t aMaxTimeoutMs,
|
||||
ThreadType aThreadType = THREAD_SHARED);
|
||||
|
||||
/**
|
||||
* Monitor hangs using an existing monitor
|
||||
* associated with the current thread.
|
||||
*/
|
||||
BackgroundHangMonitor();
|
||||
|
||||
/**
|
||||
* Destroys the hang monitor; hang monitoring for a thread stops
|
||||
* when all monitors associated with the thread are destroyed.
|
||||
*/
|
||||
~BackgroundHangMonitor();
|
||||
|
||||
/**
|
||||
* Notify the hang monitor of pending current thread activity.
|
||||
* Call this method before starting an "activity" or after
|
||||
* exiting from a wait state.
|
||||
*/
|
||||
void NotifyActivity();
|
||||
|
||||
/**
|
||||
* Notify the hang monitor of current thread wait.
|
||||
* Call this method before entering a wait state; call
|
||||
* NotifyActivity when subsequently exiting the wait state.
|
||||
*/
|
||||
void NotifyWait();
|
||||
|
||||
/**
|
||||
* Register an annotator with BHR for the current thread.
|
||||
* @param aAnnotator annotator to register
|
||||
* @return true if the annotator was registered, otherwise false.
|
||||
*/
|
||||
static bool RegisterAnnotator(HangMonitor::Annotator& aAnnotator);
|
||||
|
||||
/**
|
||||
* Unregister an annotator that was previously registered via
|
||||
* RegisterAnnotator.
|
||||
* @param aAnnotator annotator to unregister
|
||||
* @return true if there are still remaining annotators registered
|
||||
*/
|
||||
static bool UnregisterAnnotator(HangMonitor::Annotator& aAnnotator);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_BackgroundHangMonitor_h
|
||||
262
xpcom/threads/HangAnnotations.cpp
Normal file
262
xpcom/threads/HangAnnotations.cpp
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/* -*- 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 "mozilla/HangAnnotations.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "MainThreadUtils.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace HangMonitor {
|
||||
|
||||
// Chrome hang annotators. This can go away once BHR has completely replaced
|
||||
// ChromeHangs.
|
||||
static StaticAutoPtr<Observer::Annotators> gChromehangAnnotators;
|
||||
|
||||
class BrowserHangAnnotations : public HangAnnotations
|
||||
{
|
||||
public:
|
||||
BrowserHangAnnotations();
|
||||
~BrowserHangAnnotations();
|
||||
|
||||
void AddAnnotation(const nsAString& aName, const int32_t aData) override;
|
||||
void AddAnnotation(const nsAString& aName, const double aData) override;
|
||||
void AddAnnotation(const nsAString& aName, const nsAString& aData) override;
|
||||
void AddAnnotation(const nsAString& aName, const nsACString& aData) override;
|
||||
void AddAnnotation(const nsAString& aName, const bool aData) override;
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
|
||||
bool IsEmpty() const override;
|
||||
UniquePtr<Enumerator> GetEnumerator() override;
|
||||
|
||||
typedef std::pair<nsString, nsString> AnnotationType;
|
||||
typedef std::vector<AnnotationType> VectorType;
|
||||
typedef VectorType::const_iterator IteratorType;
|
||||
|
||||
private:
|
||||
VectorType mAnnotations;
|
||||
};
|
||||
|
||||
BrowserHangAnnotations::BrowserHangAnnotations()
|
||||
{
|
||||
MOZ_COUNT_CTOR(BrowserHangAnnotations);
|
||||
}
|
||||
|
||||
BrowserHangAnnotations::~BrowserHangAnnotations()
|
||||
{
|
||||
MOZ_COUNT_DTOR(BrowserHangAnnotations);
|
||||
}
|
||||
|
||||
void
|
||||
BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const int32_t aData)
|
||||
{
|
||||
nsString dataString;
|
||||
dataString.AppendInt(aData);
|
||||
AnnotationType annotation = std::make_pair(nsString(aName), dataString);
|
||||
mAnnotations.push_back(annotation);
|
||||
}
|
||||
|
||||
void
|
||||
BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const double aData)
|
||||
{
|
||||
nsString dataString;
|
||||
dataString.AppendFloat(aData);
|
||||
AnnotationType annotation = std::make_pair(nsString(aName), dataString);
|
||||
mAnnotations.push_back(annotation);
|
||||
}
|
||||
|
||||
void
|
||||
BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const nsAString& aData)
|
||||
{
|
||||
AnnotationType annotation = std::make_pair(nsString(aName), nsString(aData));
|
||||
mAnnotations.push_back(annotation);
|
||||
}
|
||||
|
||||
void
|
||||
BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const nsACString& aData)
|
||||
{
|
||||
nsString dataString;
|
||||
AppendUTF8toUTF16(aData, dataString);
|
||||
AnnotationType annotation = std::make_pair(nsString(aName), dataString);
|
||||
mAnnotations.push_back(annotation);
|
||||
}
|
||||
|
||||
void
|
||||
BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const bool aData)
|
||||
{
|
||||
nsString dataString;
|
||||
dataString += aData ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false");
|
||||
AnnotationType annotation = std::make_pair(nsString(aName), dataString);
|
||||
mAnnotations.push_back(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class itself does not use synchronization but it (and its parent object)
|
||||
* should be protected by mutual exclusion in some way. In Telemetry the chrome
|
||||
* hang data is protected via TelemetryImpl::mHangReportsMutex.
|
||||
*/
|
||||
class ChromeHangAnnotationEnumerator : public HangAnnotations::Enumerator
|
||||
{
|
||||
public:
|
||||
explicit ChromeHangAnnotationEnumerator(const BrowserHangAnnotations::VectorType& aAnnotations);
|
||||
~ChromeHangAnnotationEnumerator();
|
||||
|
||||
virtual bool Next(nsAString& aOutName, nsAString& aOutValue);
|
||||
|
||||
private:
|
||||
BrowserHangAnnotations::IteratorType mIterator;
|
||||
BrowserHangAnnotations::IteratorType mEnd;
|
||||
};
|
||||
|
||||
ChromeHangAnnotationEnumerator::ChromeHangAnnotationEnumerator(
|
||||
const BrowserHangAnnotations::VectorType& aAnnotations)
|
||||
: mIterator(aAnnotations.begin())
|
||||
, mEnd(aAnnotations.end())
|
||||
{
|
||||
MOZ_COUNT_CTOR(ChromeHangAnnotationEnumerator);
|
||||
}
|
||||
|
||||
ChromeHangAnnotationEnumerator::~ChromeHangAnnotationEnumerator()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ChromeHangAnnotationEnumerator);
|
||||
}
|
||||
|
||||
bool
|
||||
ChromeHangAnnotationEnumerator::Next(nsAString& aOutName, nsAString& aOutValue)
|
||||
{
|
||||
aOutName.Truncate();
|
||||
aOutValue.Truncate();
|
||||
if (mIterator == mEnd) {
|
||||
return false;
|
||||
}
|
||||
aOutName = mIterator->first;
|
||||
aOutValue = mIterator->second;
|
||||
++mIterator;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BrowserHangAnnotations::IsEmpty() const
|
||||
{
|
||||
return mAnnotations.empty();
|
||||
}
|
||||
|
||||
size_t
|
||||
BrowserHangAnnotations::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t result = sizeof(mAnnotations) +
|
||||
mAnnotations.capacity() * sizeof(AnnotationType);
|
||||
for (IteratorType i = mAnnotations.begin(), e = mAnnotations.end(); i != e;
|
||||
++i) {
|
||||
result += i->first.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
result += i->second.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UniquePtr<HangAnnotations::Enumerator>
|
||||
BrowserHangAnnotations::GetEnumerator()
|
||||
{
|
||||
if (mAnnotations.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return MakeUnique<ChromeHangAnnotationEnumerator>(mAnnotations);
|
||||
}
|
||||
|
||||
namespace Observer {
|
||||
|
||||
Annotators::Annotators()
|
||||
: mMutex("HangMonitor::Annotators::mMutex")
|
||||
{
|
||||
MOZ_COUNT_CTOR(Annotators);
|
||||
}
|
||||
|
||||
Annotators::~Annotators()
|
||||
{
|
||||
MOZ_ASSERT(mAnnotators.empty());
|
||||
MOZ_COUNT_DTOR(Annotators);
|
||||
}
|
||||
|
||||
bool
|
||||
Annotators::Register(Annotator& aAnnotator)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
auto result = mAnnotators.insert(&aAnnotator);
|
||||
return result.second;
|
||||
}
|
||||
|
||||
bool
|
||||
Annotators::Unregister(Annotator& aAnnotator)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
DebugOnly<std::set<Annotator*>::size_type> numErased;
|
||||
numErased = mAnnotators.erase(&aAnnotator);
|
||||
MOZ_ASSERT(numErased == 1);
|
||||
return mAnnotators.empty();
|
||||
}
|
||||
|
||||
UniquePtr<HangAnnotations>
|
||||
Annotators::GatherAnnotations()
|
||||
{
|
||||
auto annotations = MakeUnique<BrowserHangAnnotations>();
|
||||
{ // Scope for lock
|
||||
MutexAutoLock lock(mMutex);
|
||||
for (std::set<Annotator*>::iterator i = mAnnotators.begin(),
|
||||
e = mAnnotators.end();
|
||||
i != e; ++i) {
|
||||
(*i)->AnnotateHang(*annotations);
|
||||
}
|
||||
}
|
||||
if (annotations->IsEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return Move(annotations);
|
||||
}
|
||||
|
||||
} // namespace Observer
|
||||
|
||||
void
|
||||
RegisterAnnotator(Annotator& aAnnotator)
|
||||
{
|
||||
BackgroundHangMonitor::RegisterAnnotator(aAnnotator);
|
||||
// We still register annotators for ChromeHangs
|
||||
if (NS_IsMainThread() &&
|
||||
GeckoProcessType_Default == XRE_GetProcessType()) {
|
||||
if (!gChromehangAnnotators) {
|
||||
gChromehangAnnotators = new Observer::Annotators();
|
||||
}
|
||||
gChromehangAnnotators->Register(aAnnotator);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
UnregisterAnnotator(Annotator& aAnnotator)
|
||||
{
|
||||
BackgroundHangMonitor::UnregisterAnnotator(aAnnotator);
|
||||
// We still register annotators for ChromeHangs
|
||||
if (NS_IsMainThread() &&
|
||||
GeckoProcessType_Default == XRE_GetProcessType()) {
|
||||
if (gChromehangAnnotators->Unregister(aAnnotator)) {
|
||||
gChromehangAnnotators = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UniquePtr<HangAnnotations>
|
||||
ChromeHangAnnotatorCallout()
|
||||
{
|
||||
if (!gChromehangAnnotators) {
|
||||
return nullptr;
|
||||
}
|
||||
return gChromehangAnnotators->GatherAnnotations();
|
||||
}
|
||||
|
||||
} // namespace HangMonitor
|
||||
} // namespace mozilla
|
||||
104
xpcom/threads/HangAnnotations.h
Normal file
104
xpcom/threads/HangAnnotations.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_HangAnnotations_h
|
||||
#define mozilla_HangAnnotations_h
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace HangMonitor {
|
||||
|
||||
/**
|
||||
* This class declares an abstraction for a data type that encapsulates all
|
||||
* of the annotations being reported by a registered hang Annotator.
|
||||
*/
|
||||
class HangAnnotations
|
||||
{
|
||||
public:
|
||||
virtual ~HangAnnotations() {}
|
||||
|
||||
virtual void AddAnnotation(const nsAString& aName, const int32_t aData) = 0;
|
||||
virtual void AddAnnotation(const nsAString& aName, const double aData) = 0;
|
||||
virtual void AddAnnotation(const nsAString& aName, const nsAString& aData) = 0;
|
||||
virtual void AddAnnotation(const nsAString& aName, const nsACString& aData) = 0;
|
||||
virtual void AddAnnotation(const nsAString& aName, const bool aData) = 0;
|
||||
|
||||
class Enumerator
|
||||
{
|
||||
public:
|
||||
virtual ~Enumerator() {}
|
||||
virtual bool Next(nsAString& aOutName, nsAString& aOutValue) = 0;
|
||||
};
|
||||
|
||||
virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0;
|
||||
virtual bool IsEmpty() const = 0;
|
||||
virtual UniquePtr<Enumerator> GetEnumerator() = 0;
|
||||
};
|
||||
|
||||
typedef UniquePtr<HangAnnotations> HangAnnotationsPtr;
|
||||
typedef Vector<HangAnnotationsPtr> HangAnnotationsVector;
|
||||
|
||||
class Annotator
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* NB: This function is always called by the HangMonitor thread.
|
||||
* Plan accordingly.
|
||||
*/
|
||||
virtual void AnnotateHang(HangAnnotations& aAnnotations) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers an Annotator to be called when a hang is detected.
|
||||
* @param aAnnotator Reference to an object that implements the
|
||||
* HangMonitor::Annotator interface.
|
||||
*/
|
||||
void RegisterAnnotator(Annotator& aAnnotator);
|
||||
|
||||
/**
|
||||
* Registers an Annotator that was previously registered via RegisterAnnotator.
|
||||
* @param aAnnotator Reference to an object that implements the
|
||||
* HangMonitor::Annotator interface.
|
||||
*/
|
||||
void UnregisterAnnotator(Annotator& aAnnotator);
|
||||
|
||||
/**
|
||||
* Gathers annotations. This function should be called by ChromeHangs.
|
||||
* @return UniquePtr to HangAnnotations object or nullptr if none.
|
||||
*/
|
||||
HangAnnotationsPtr ChromeHangAnnotatorCallout();
|
||||
|
||||
namespace Observer {
|
||||
|
||||
class Annotators
|
||||
{
|
||||
public:
|
||||
Annotators();
|
||||
~Annotators();
|
||||
|
||||
bool Register(Annotator& aAnnotator);
|
||||
bool Unregister(Annotator& aAnnotator);
|
||||
|
||||
HangAnnotationsPtr GatherAnnotations();
|
||||
|
||||
private:
|
||||
Mutex mMutex;
|
||||
std::set<Annotator*> mAnnotators;
|
||||
};
|
||||
|
||||
} // namespace Observer
|
||||
|
||||
} // namespace HangMonitor
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_HangAnnotations_h
|
||||
434
xpcom/threads/HangMonitor.cpp
Normal file
434
xpcom/threads/HangMonitor.cpp
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
/* -*- 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 "mozilla/HangMonitor.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/BackgroundHangMonitor.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ProcessedStack.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "mozilla/StackWalk.h"
|
||||
#ifdef _WIN64
|
||||
#include "mozilla/StackWalk_windows.h"
|
||||
#endif
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
#include "nsExceptionHandler.h"
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS) && defined(MOZ_PROFILING) && defined(XP_WIN)
|
||||
#define REPORT_CHROME_HANGS
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace HangMonitor {
|
||||
|
||||
/**
|
||||
* A flag which may be set from within a debugger to disable the hang
|
||||
* monitor.
|
||||
*/
|
||||
volatile bool gDebugDisableHangMonitor = false;
|
||||
|
||||
const char kHangMonitorPrefName[] = "hangmonitor.timeout";
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
const char kTelemetryPrefName[] = "toolkit.telemetry.enabled";
|
||||
#endif
|
||||
|
||||
// Monitor protects gShutdown and gTimeout, but not gTimestamp which rely on
|
||||
// being atomically set by the processor; synchronization doesn't really matter
|
||||
// in this use case.
|
||||
Monitor* gMonitor;
|
||||
|
||||
// The timeout preference, in seconds.
|
||||
int32_t gTimeout;
|
||||
|
||||
PRThread* gThread;
|
||||
|
||||
// Set when shutdown begins to signal the thread to exit immediately.
|
||||
bool gShutdown;
|
||||
|
||||
// The timestamp of the last event notification, or PR_INTERVAL_NO_WAIT if
|
||||
// we're currently not processing events.
|
||||
Atomic<PRIntervalTime> gTimestamp(PR_INTERVAL_NO_WAIT);
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
// Main thread ID used in reporting chrome hangs under Windows
|
||||
static HANDLE winMainThreadHandle = nullptr;
|
||||
|
||||
// Default timeout for reporting chrome hangs to Telemetry (5 seconds)
|
||||
static const int32_t DEFAULT_CHROME_HANG_INTERVAL = 5;
|
||||
|
||||
// Maximum number of PCs to gather from the stack
|
||||
static const int32_t MAX_CALL_STACK_PCS = 400;
|
||||
#endif
|
||||
|
||||
// PrefChangedFunc
|
||||
void
|
||||
PrefChanged(const char*, void*)
|
||||
{
|
||||
int32_t newval = Preferences::GetInt(kHangMonitorPrefName);
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
// Monitor chrome hangs on the profiling branch if Telemetry enabled
|
||||
if (newval == 0) {
|
||||
bool telemetryEnabled = Preferences::GetBool(kTelemetryPrefName);
|
||||
if (telemetryEnabled) {
|
||||
newval = DEFAULT_CHROME_HANG_INTERVAL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
if (newval != gTimeout) {
|
||||
gTimeout = newval;
|
||||
lock.Notify();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Crash()
|
||||
{
|
||||
if (gDebugDisableHangMonitor) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
if (::IsDebuggerPresent()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
// If you change this, you must also deal with the threadsafety of AnnotateCrashReport in
|
||||
// non-chrome processes!
|
||||
if (GeckoProcessType_Default == XRE_GetProcessType()) {
|
||||
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Hang"),
|
||||
NS_LITERAL_CSTRING("1"));
|
||||
}
|
||||
#endif
|
||||
|
||||
NS_RUNTIMEABORT("HangMonitor triggered");
|
||||
}
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
|
||||
static void
|
||||
ChromeStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
|
||||
{
|
||||
MOZ_ASSERT(aClosure);
|
||||
std::vector<uintptr_t>* stack =
|
||||
static_cast<std::vector<uintptr_t>*>(aClosure);
|
||||
if (stack->size() == MAX_CALL_STACK_PCS) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(stack->size() < MAX_CALL_STACK_PCS);
|
||||
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
|
||||
}
|
||||
|
||||
static void
|
||||
GetChromeHangReport(Telemetry::ProcessedStack& aStack,
|
||||
int32_t& aSystemUptime,
|
||||
int32_t& aFirefoxUptime)
|
||||
{
|
||||
MOZ_ASSERT(winMainThreadHandle);
|
||||
|
||||
// The thread we're about to suspend might have the alloc lock
|
||||
// so allocate ahead of time
|
||||
std::vector<uintptr_t> rawStack;
|
||||
rawStack.reserve(MAX_CALL_STACK_PCS);
|
||||
|
||||
// Workaround possible deadlock where the main thread is running a
|
||||
// long-standing JS job, and happens to be in the JIT allocator when we
|
||||
// suspend it. Since, on win 64, this requires holding a process lock that
|
||||
// MozStackWalk requires, take this "workaround lock" to avoid deadlock.
|
||||
#ifdef _WIN64
|
||||
AcquireStackWalkWorkaroundLock();
|
||||
#endif
|
||||
DWORD ret = ::SuspendThread(winMainThreadHandle);
|
||||
bool suspended = false;
|
||||
if (ret != -1) {
|
||||
// SuspendThread is asynchronous, so the thread may still be running. Use
|
||||
// GetThreadContext to ensure it's really suspended.
|
||||
// See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743.
|
||||
CONTEXT context;
|
||||
context.ContextFlags = CONTEXT_CONTROL;
|
||||
if (::GetThreadContext(winMainThreadHandle, &context)) {
|
||||
suspended = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
ReleaseStackWalkWorkaroundLock();
|
||||
#endif
|
||||
|
||||
if (!suspended) {
|
||||
if (ret != -1) {
|
||||
MOZ_ALWAYS_TRUE(::ResumeThread(winMainThreadHandle) != DWORD(-1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MozStackWalk(ChromeStackWalker, /* skipFrames */ 0, /* maxFrames */ 0,
|
||||
reinterpret_cast<void*>(&rawStack),
|
||||
reinterpret_cast<uintptr_t>(winMainThreadHandle), nullptr);
|
||||
ret = ::ResumeThread(winMainThreadHandle);
|
||||
if (ret == -1) {
|
||||
return;
|
||||
}
|
||||
aStack = Telemetry::GetStackAndModules(rawStack);
|
||||
|
||||
// Record system uptime (in minutes) at the time of the hang
|
||||
aSystemUptime = ((GetTickCount() / 1000) - (gTimeout * 2)) / 60;
|
||||
|
||||
// Record Firefox uptime (in minutes) at the time of the hang
|
||||
bool error;
|
||||
TimeStamp processCreation = TimeStamp::ProcessCreation(error);
|
||||
if (!error) {
|
||||
TimeDuration td = TimeStamp::Now() - processCreation;
|
||||
aFirefoxUptime = (static_cast<int32_t>(td.ToSeconds()) - (gTimeout * 2)) / 60;
|
||||
} else {
|
||||
aFirefoxUptime = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
ThreadMain(void*)
|
||||
{
|
||||
PR_SetCurrentThreadName("Hang Monitor");
|
||||
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
|
||||
// In order to avoid issues with the hang monitor incorrectly triggering
|
||||
// during a general system stop such as sleeping, the monitor thread must
|
||||
// run twice to trigger hang protection.
|
||||
PRIntervalTime lastTimestamp = 0;
|
||||
int waitCount = 0;
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
Telemetry::ProcessedStack stack;
|
||||
int32_t systemUptime = -1;
|
||||
int32_t firefoxUptime = -1;
|
||||
UniquePtr<HangAnnotations> annotations;
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
if (gShutdown) {
|
||||
return; // Exit the thread
|
||||
}
|
||||
|
||||
// avoid rereading the volatile value in this loop
|
||||
PRIntervalTime timestamp = gTimestamp;
|
||||
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
|
||||
if (timestamp != PR_INTERVAL_NO_WAIT &&
|
||||
now < timestamp) {
|
||||
// 32-bit overflow, reset for another waiting period
|
||||
timestamp = 1; // lowest legal PRInterval value
|
||||
}
|
||||
|
||||
if (timestamp != PR_INTERVAL_NO_WAIT &&
|
||||
timestamp == lastTimestamp &&
|
||||
gTimeout > 0) {
|
||||
++waitCount;
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
// Capture the chrome-hang stack + Firefox & system uptimes after
|
||||
// the minimum hang duration has been reached (not when the hang ends)
|
||||
if (waitCount == 2) {
|
||||
GetChromeHangReport(stack, systemUptime, firefoxUptime);
|
||||
annotations = ChromeHangAnnotatorCallout();
|
||||
}
|
||||
#else
|
||||
// This is the crash-on-hang feature.
|
||||
// See bug 867313 for the quirk in the waitCount comparison
|
||||
if (waitCount >= 2) {
|
||||
int32_t delay =
|
||||
int32_t(PR_IntervalToSeconds(now - timestamp));
|
||||
if (delay >= gTimeout) {
|
||||
MonitorAutoUnlock unlock(*gMonitor);
|
||||
Crash();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
if (waitCount >= 2) {
|
||||
uint32_t hangDuration = PR_IntervalToSeconds(now - lastTimestamp);
|
||||
Telemetry::RecordChromeHang(hangDuration, stack, systemUptime,
|
||||
firefoxUptime, Move(annotations));
|
||||
stack.Clear();
|
||||
}
|
||||
#endif
|
||||
lastTimestamp = timestamp;
|
||||
waitCount = 0;
|
||||
}
|
||||
|
||||
PRIntervalTime timeout;
|
||||
if (gTimeout <= 0) {
|
||||
timeout = PR_INTERVAL_NO_TIMEOUT;
|
||||
} else {
|
||||
timeout = PR_MillisecondsToInterval(gTimeout * 500);
|
||||
}
|
||||
lock.Wait(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Startup()
|
||||
{
|
||||
if (GeckoProcessType_Default != XRE_GetProcessType() &&
|
||||
GeckoProcessType_Content != XRE_GetProcessType()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!gMonitor, "Hang monitor already initialized");
|
||||
gMonitor = new Monitor("HangMonitor");
|
||||
|
||||
Preferences::RegisterCallback(PrefChanged, kHangMonitorPrefName, nullptr);
|
||||
PrefChanged(nullptr, nullptr);
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
Preferences::RegisterCallback(PrefChanged, kTelemetryPrefName, nullptr);
|
||||
winMainThreadHandle =
|
||||
OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId());
|
||||
if (!winMainThreadHandle) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Don't actually start measuring hangs until we hit the main event loop.
|
||||
// This potentially misses a small class of really early startup hangs,
|
||||
// but avoids dealing with some xpcshell tests and other situations which
|
||||
// start XPCOM but don't ever start the event loop.
|
||||
Suspend();
|
||||
|
||||
gThread = PR_CreateThread(PR_USER_THREAD,
|
||||
ThreadMain,
|
||||
nullptr, PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
|
||||
PR_JOINABLE_THREAD, 0);
|
||||
}
|
||||
|
||||
void
|
||||
Shutdown()
|
||||
{
|
||||
if (GeckoProcessType_Default != XRE_GetProcessType() &&
|
||||
GeckoProcessType_Content != XRE_GetProcessType()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(gMonitor, "Hang monitor not started");
|
||||
|
||||
{
|
||||
// Scope the lock we're going to delete later
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
gShutdown = true;
|
||||
lock.Notify();
|
||||
}
|
||||
|
||||
// thread creation could theoretically fail
|
||||
if (gThread) {
|
||||
PR_JoinThread(gThread);
|
||||
gThread = nullptr;
|
||||
}
|
||||
|
||||
delete gMonitor;
|
||||
gMonitor = nullptr;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsUIMessageWaiting()
|
||||
{
|
||||
#ifndef XP_WIN
|
||||
return false;
|
||||
#else
|
||||
#define NS_WM_IMEFIRST WM_IME_SETCONTEXT
|
||||
#define NS_WM_IMELAST WM_IME_KEYUP
|
||||
BOOL haveUIMessageWaiting = FALSE;
|
||||
MSG msg;
|
||||
haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, WM_KEYFIRST,
|
||||
WM_IME_KEYLAST, PM_NOREMOVE);
|
||||
haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, NS_WM_IMEFIRST,
|
||||
NS_WM_IMELAST, PM_NOREMOVE);
|
||||
haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, WM_MOUSEFIRST,
|
||||
WM_MOUSELAST, PM_NOREMOVE);
|
||||
return haveUIMessageWaiting;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
NotifyActivity(ActivityType aActivityType)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(),
|
||||
"HangMonitor::Notify called from off the main thread.");
|
||||
|
||||
// Determine the activity type more specifically
|
||||
if (aActivityType == kGeneralActivity) {
|
||||
aActivityType = IsUIMessageWaiting() ? kActivityUIAVail :
|
||||
kActivityNoUIAVail;
|
||||
}
|
||||
|
||||
// Calculate the cumulative amount of lag time since the last UI message
|
||||
static uint32_t cumulativeUILagMS = 0;
|
||||
switch (aActivityType) {
|
||||
case kActivityNoUIAVail:
|
||||
cumulativeUILagMS = 0;
|
||||
break;
|
||||
case kActivityUIAVail:
|
||||
case kUIActivity:
|
||||
if (gTimestamp != PR_INTERVAL_NO_WAIT) {
|
||||
cumulativeUILagMS += PR_IntervalToMilliseconds(PR_IntervalNow() -
|
||||
gTimestamp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// This is not a locked activity because PRTimeStamp is a 32-bit quantity
|
||||
// which can be read/written atomically, and we don't want to pay locking
|
||||
// penalties here.
|
||||
gTimestamp = PR_IntervalNow();
|
||||
|
||||
// If we have UI activity we should reset the timer and report it
|
||||
if (aActivityType == kUIActivity) {
|
||||
mozilla::Telemetry::Accumulate(mozilla::Telemetry::EVENTLOOP_UI_ACTIVITY_EXP_MS,
|
||||
cumulativeUILagMS);
|
||||
cumulativeUILagMS = 0;
|
||||
}
|
||||
|
||||
if (gThread && !gShutdown) {
|
||||
mozilla::BackgroundHangMonitor().NotifyActivity();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Suspend()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(),
|
||||
"HangMonitor::Suspend called from off the main thread.");
|
||||
|
||||
// Because gTimestamp changes this resets the wait count.
|
||||
gTimestamp = PR_INTERVAL_NO_WAIT;
|
||||
|
||||
if (gThread && !gShutdown) {
|
||||
mozilla::BackgroundHangMonitor().NotifyWait();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace HangMonitor
|
||||
} // namespace mozilla
|
||||
58
xpcom/threads/HangMonitor.h
Normal file
58
xpcom/threads/HangMonitor.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_HangMonitor_h
|
||||
#define mozilla_HangMonitor_h
|
||||
|
||||
namespace mozilla {
|
||||
namespace HangMonitor {
|
||||
|
||||
/**
|
||||
* Signifies the type of activity in question
|
||||
*/
|
||||
enum ActivityType
|
||||
{
|
||||
/* There is activity and it is known to be UI related activity. */
|
||||
kUIActivity,
|
||||
|
||||
/* There is non UI activity and no UI activity is pending */
|
||||
kActivityNoUIAVail,
|
||||
|
||||
/* There is non UI activity and UI activity is known to be pending */
|
||||
kActivityUIAVail,
|
||||
|
||||
/* There is non UI activity and UI activity pending is unknown */
|
||||
kGeneralActivity
|
||||
};
|
||||
|
||||
/**
|
||||
* Start monitoring hangs. Should be called by the XPCOM startup process only.
|
||||
*/
|
||||
void Startup();
|
||||
|
||||
/**
|
||||
* Stop monitoring hangs and join the thread.
|
||||
*/
|
||||
void Shutdown();
|
||||
|
||||
/**
|
||||
* Notify the hang monitor of activity which will reset its internal timer.
|
||||
*
|
||||
* @param activityType The type of activity being reported.
|
||||
* @see ActivityType
|
||||
*/
|
||||
void NotifyActivity(ActivityType activityType = kGeneralActivity);
|
||||
|
||||
/*
|
||||
* Notify the hang monitor that the browser is now idle and no detection should
|
||||
* be done.
|
||||
*/
|
||||
void Suspend();
|
||||
|
||||
} // namespace HangMonitor
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_HangMonitor_h
|
||||
624
xpcom/threads/LazyIdleThread.cpp
Normal file
624
xpcom/threads/LazyIdleThread.cpp
Normal file
|
|
@ -0,0 +1,624 @@
|
|||
/* -*- 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 "LazyIdleThread.h"
|
||||
|
||||
#include "nsIObserverService.h"
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsIIdlePeriod.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/Services.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#define ASSERT_OWNING_THREAD() \
|
||||
PR_BEGIN_MACRO \
|
||||
nsIThread* currentThread = NS_GetCurrentThread(); \
|
||||
if (currentThread) { \
|
||||
nsCOMPtr<nsISupports> current(do_QueryInterface(currentThread)); \
|
||||
nsCOMPtr<nsISupports> test(do_QueryInterface(mOwningThread)); \
|
||||
MOZ_ASSERT(current == test, "Wrong thread!"); \
|
||||
} \
|
||||
PR_END_MACRO
|
||||
#else
|
||||
#define ASSERT_OWNING_THREAD() /* nothing */
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS,
|
||||
const nsCSubstring& aName,
|
||||
ShutdownMethod aShutdownMethod,
|
||||
nsIObserver* aIdleObserver)
|
||||
: mMutex("LazyIdleThread::mMutex")
|
||||
, mOwningThread(NS_GetCurrentThread())
|
||||
, mIdleObserver(aIdleObserver)
|
||||
, mQueuedRunnables(nullptr)
|
||||
, mIdleTimeoutMS(aIdleTimeoutMS)
|
||||
, mPendingEventCount(0)
|
||||
, mIdleNotificationCount(0)
|
||||
, mShutdownMethod(aShutdownMethod)
|
||||
, mShutdown(false)
|
||||
, mThreadIsShuttingDown(false)
|
||||
, mIdleTimeoutEnabled(true)
|
||||
, mName(aName)
|
||||
{
|
||||
MOZ_ASSERT(mOwningThread, "Need owning thread!");
|
||||
}
|
||||
|
||||
LazyIdleThread::~LazyIdleThread()
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void
|
||||
LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver)
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
|
||||
if (mShutdown) {
|
||||
NS_WARNING_ASSERTION(!aObserver,
|
||||
"Setting an observer after Shutdown was called!");
|
||||
return;
|
||||
}
|
||||
|
||||
mIdleObserver = aObserver;
|
||||
}
|
||||
|
||||
void
|
||||
LazyIdleThread::DisableIdleTimeout()
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
if (!mIdleTimeoutEnabled) {
|
||||
return;
|
||||
}
|
||||
mIdleTimeoutEnabled = false;
|
||||
|
||||
if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) {
|
||||
NS_WARNING("Failed to cancel timer!");
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
// Pretend we have a pending event to keep the idle timer from firing.
|
||||
MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
|
||||
mPendingEventCount++;
|
||||
}
|
||||
|
||||
void
|
||||
LazyIdleThread::EnableIdleTimeout()
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
if (mIdleTimeoutEnabled) {
|
||||
return;
|
||||
}
|
||||
mIdleTimeoutEnabled = true;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
|
||||
--mPendingEventCount;
|
||||
}
|
||||
|
||||
if (mThread) {
|
||||
nsCOMPtr<nsIRunnable> runnable(new Runnable());
|
||||
if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) {
|
||||
NS_WARNING("Failed to dispatch!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LazyIdleThread::PreDispatch()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
|
||||
mPendingEventCount++;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LazyIdleThread::EnsureThread()
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
|
||||
if (mShutdown) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (mThread) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!");
|
||||
MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!");
|
||||
MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!");
|
||||
MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!");
|
||||
|
||||
nsresult rv;
|
||||
|
||||
if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIObserverService> obs =
|
||||
do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = obs->AddObserver(this, "xpcom-shutdown-threads", false);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
|
||||
if (NS_WARN_IF(!mIdleTimer)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NewRunnableMethod(this, &LazyIdleThread::InitThread);
|
||||
if (NS_WARN_IF(!runnable)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
rv = NS_NewThread(getter_AddRefs(mThread), runnable);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
LazyIdleThread::InitThread()
|
||||
{
|
||||
char aLocal;
|
||||
profiler_register_thread(mName.get(), &aLocal);
|
||||
|
||||
PR_SetCurrentThreadName(mName.get());
|
||||
|
||||
// Happens on mThread but mThread may not be set yet...
|
||||
|
||||
nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
|
||||
MOZ_ASSERT(thread, "This should always succeed!");
|
||||
|
||||
if (NS_FAILED(thread->SetObserver(this))) {
|
||||
NS_WARNING("Failed to set thread observer!");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LazyIdleThread::CleanupThread()
|
||||
{
|
||||
nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
|
||||
MOZ_ASSERT(thread, "This should always succeed!");
|
||||
|
||||
if (NS_FAILED(thread->SetObserver(nullptr))) {
|
||||
NS_WARNING("Failed to set thread observer!");
|
||||
}
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!");
|
||||
mThreadIsShuttingDown = true;
|
||||
}
|
||||
|
||||
profiler_unregister_thread();
|
||||
}
|
||||
|
||||
void
|
||||
LazyIdleThread::ScheduleTimer()
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
|
||||
bool shouldSchedule;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!");
|
||||
--mIdleNotificationCount;
|
||||
|
||||
shouldSchedule = !mIdleNotificationCount && !mPendingEventCount;
|
||||
}
|
||||
|
||||
if (mIdleTimer) {
|
||||
if (NS_FAILED(mIdleTimer->Cancel())) {
|
||||
NS_WARNING("Failed to cancel timer!");
|
||||
}
|
||||
|
||||
if (shouldSchedule &&
|
||||
NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS,
|
||||
nsITimer::TYPE_ONE_SHOT))) {
|
||||
NS_WARNING("Failed to schedule timer!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
LazyIdleThread::ShutdownThread()
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
|
||||
// Before calling Shutdown() on the real thread we need to put a queue in
|
||||
// place in case a runnable is posted to the thread while it's in the
|
||||
// process of shutting down. This will be our queue.
|
||||
AutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables;
|
||||
|
||||
nsresult rv;
|
||||
|
||||
// Make sure to cancel the shutdown timer before spinning the event loop
|
||||
// during |mThread->Shutdown()| below. Otherwise the timer might fire and we
|
||||
// could reenter here.
|
||||
if (mIdleTimer) {
|
||||
rv = mIdleTimer->Cancel();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mIdleTimer = nullptr;
|
||||
}
|
||||
|
||||
if (mThread) {
|
||||
if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIObserverService> obs =
|
||||
mozilla::services::GetObserverService();
|
||||
NS_WARNING_ASSERTION(obs, "Failed to get observer service!");
|
||||
|
||||
if (obs &&
|
||||
NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) {
|
||||
NS_WARNING("Failed to remove observer!");
|
||||
}
|
||||
}
|
||||
|
||||
if (mIdleObserver) {
|
||||
mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!");
|
||||
}
|
||||
#endif
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NewRunnableMethod(this, &LazyIdleThread::CleanupThread);
|
||||
if (NS_WARN_IF(!runnable)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
PreDispatch();
|
||||
|
||||
rv = mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Put the temporary queue in place before calling Shutdown().
|
||||
mQueuedRunnables = &queuedRunnables;
|
||||
|
||||
if (NS_FAILED(mThread->Shutdown())) {
|
||||
NS_ERROR("Failed to shutdown the thread!");
|
||||
}
|
||||
|
||||
// Now unset the queue.
|
||||
mQueuedRunnables = nullptr;
|
||||
|
||||
mThread = nullptr;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
MOZ_ASSERT(!mPendingEventCount, "Huh?!");
|
||||
MOZ_ASSERT(!mIdleNotificationCount, "Huh?!");
|
||||
MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!");
|
||||
mThreadIsShuttingDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If our temporary queue has any runnables then we need to dispatch them.
|
||||
if (queuedRunnables.Length()) {
|
||||
// If the thread manager has gone away then these runnables will never run.
|
||||
if (mShutdown) {
|
||||
NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Re-dispatch the queued runnables.
|
||||
for (uint32_t index = 0; index < queuedRunnables.Length(); index++) {
|
||||
nsCOMPtr<nsIRunnable> runnable;
|
||||
runnable.swap(queuedRunnables[index]);
|
||||
MOZ_ASSERT(runnable, "Null runnable?!");
|
||||
|
||||
if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) {
|
||||
NS_ERROR("Failed to re-dispatch queued runnable!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
LazyIdleThread::SelfDestruct()
|
||||
{
|
||||
MOZ_ASSERT(mRefCnt == 1, "Bad refcount!");
|
||||
delete this;
|
||||
}
|
||||
|
||||
NS_IMPL_ADDREF(LazyIdleThread)
|
||||
|
||||
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||
LazyIdleThread::Release()
|
||||
{
|
||||
nsrefcnt count = --mRefCnt;
|
||||
NS_LOG_RELEASE(this, count, "LazyIdleThread");
|
||||
|
||||
if (!count) {
|
||||
// Stabilize refcount.
|
||||
mRefCnt = 1;
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NewNonOwningRunnableMethod(this, &LazyIdleThread::SelfDestruct);
|
||||
NS_WARNING_ASSERTION(runnable, "Couldn't make runnable!");
|
||||
|
||||
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
||||
// The only way this could fail is if we're in shutdown, and in that case
|
||||
// threads should have been joined already. Deleting here isn't dangerous
|
||||
// anymore because we won't spin the event loop waiting to join the
|
||||
// thread.
|
||||
SelfDestruct();
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread,
|
||||
nsIEventTarget,
|
||||
nsITimerCallback,
|
||||
nsIThreadObserver,
|
||||
nsIObserver)
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event(aEvent);
|
||||
return Dispatch(event.forget(), aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
nsCOMPtr<nsIRunnable> event(aEvent); // avoid leaks
|
||||
|
||||
// LazyIdleThread can't always support synchronous dispatch currently.
|
||||
if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(mShutdown)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
// If our thread is shutting down then we can't actually dispatch right now.
|
||||
// Queue this runnable for later.
|
||||
if (UseRunnableQueue()) {
|
||||
mQueuedRunnables->AppendElement(event);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv = EnsureThread();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
PreDispatch();
|
||||
|
||||
return mThread->Dispatch(event.forget(), aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread)
|
||||
{
|
||||
if (mThread) {
|
||||
return mThread->IsOnCurrentThread(aIsOnCurrentThread);
|
||||
}
|
||||
|
||||
*aIsOnCurrentThread = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::GetPRThread(PRThread** aPRThread)
|
||||
{
|
||||
if (mThread) {
|
||||
return mThread->GetPRThread(aPRThread);
|
||||
}
|
||||
|
||||
*aPRThread = nullptr;
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::GetCanInvokeJS(bool* aCanInvokeJS)
|
||||
{
|
||||
*aCanInvokeJS = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::SetCanInvokeJS(bool aCanInvokeJS)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::AsyncShutdown()
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::Shutdown()
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
|
||||
mShutdown = true;
|
||||
|
||||
nsresult rv = ShutdownThread();
|
||||
MOZ_ASSERT(!mThread, "Should have destroyed this by now!");
|
||||
|
||||
mIdleObserver = nullptr;
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents)
|
||||
{
|
||||
// This is only supposed to be called from the thread itself so it's not
|
||||
// implemented here.
|
||||
NS_NOTREACHED("Shouldn't ever call this!");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::ProcessNextEvent(bool aMayWait,
|
||||
bool* aEventWasProcessed)
|
||||
{
|
||||
// This is only supposed to be called from the thread itself so it's not
|
||||
// implemented here.
|
||||
NS_NOTREACHED("Shouldn't ever call this!");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::Notify(nsITimer* aTimer)
|
||||
{
|
||||
ASSERT_OWNING_THREAD();
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (mPendingEventCount || mIdleNotificationCount) {
|
||||
// Another event was scheduled since this timer was set. Don't do
|
||||
// anything and wait for the timer to fire again.
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = ShutdownThread();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */)
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread, "Wrong thread!");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
bool /* aMayWait */)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
bool aEventWasProcessed)
|
||||
{
|
||||
bool shouldNotifyIdle;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (aEventWasProcessed) {
|
||||
MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
|
||||
--mPendingEventCount;
|
||||
}
|
||||
|
||||
if (mThreadIsShuttingDown) {
|
||||
// We're shutting down, no need to fire any timer.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
shouldNotifyIdle = !mPendingEventCount;
|
||||
if (shouldNotifyIdle) {
|
||||
MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!");
|
||||
mIdleNotificationCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldNotifyIdle) {
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer);
|
||||
if (NS_WARN_IF(!runnable)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
nsresult rv = mOwningThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::Observe(nsISupports* /* aSubject */,
|
||||
const char* aTopic,
|
||||
const char16_t* /* aData */)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
||||
MOZ_ASSERT(mShutdownMethod == AutomaticShutdown,
|
||||
"Should not receive notifications if not AutomaticShutdown!");
|
||||
MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!");
|
||||
|
||||
Shutdown();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
226
xpcom/threads/LazyIdleThread.h
Normal file
226
xpcom/threads/LazyIdleThread.h
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_lazyidlethread_h__
|
||||
#define mozilla_lazyidlethread_h__
|
||||
|
||||
#ifndef MOZILLA_INTERNAL_API
|
||||
#error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)."
|
||||
#endif
|
||||
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIThreadInternal.h"
|
||||
#include "nsITimer.h"
|
||||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsString.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#define IDLE_THREAD_TOPIC "thread-shutting-down"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* This class provides a basic event target that creates its thread lazily and
|
||||
* destroys its thread after a period of inactivity. It may be created on any
|
||||
* thread but it may only be used from the thread on which it is created. If it
|
||||
* is created on the main thread then it will automatically join its thread on
|
||||
* XPCOM shutdown using the Observer Service.
|
||||
*/
|
||||
class LazyIdleThread final
|
||||
: public nsIThread
|
||||
, public nsITimerCallback
|
||||
, public nsIThreadObserver
|
||||
, public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIEVENTTARGET
|
||||
NS_DECL_NSITHREAD
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
NS_DECL_NSITHREADOBSERVER
|
||||
NS_DECL_NSIOBSERVER
|
||||
using nsIEventTarget::Dispatch;
|
||||
|
||||
enum ShutdownMethod
|
||||
{
|
||||
AutomaticShutdown = 0,
|
||||
ManualShutdown
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new LazyIdleThread that will destroy its thread after the given
|
||||
* number of milliseconds.
|
||||
*/
|
||||
LazyIdleThread(uint32_t aIdleTimeoutMS,
|
||||
const nsCSubstring& aName,
|
||||
ShutdownMethod aShutdownMethod = AutomaticShutdown,
|
||||
nsIObserver* aIdleObserver = nullptr);
|
||||
|
||||
/**
|
||||
* Add an observer that will be notified when the thread is idle and about to
|
||||
* be shut down. The aSubject argument can be QueryInterface'd to an nsIThread
|
||||
* that can be used to post cleanup events. The aTopic argument will be
|
||||
* IDLE_THREAD_TOPIC, and aData will be null. The LazyIdleThread does not add
|
||||
* a reference to the observer to avoid circular references as it is assumed
|
||||
* to be the owner. It is the caller's responsibility to clear this observer
|
||||
* if the pointer becomes invalid.
|
||||
*/
|
||||
void SetWeakIdleObserver(nsIObserver* aObserver);
|
||||
|
||||
/**
|
||||
* Disable the idle timeout for this thread. No effect if the timeout is
|
||||
* already disabled.
|
||||
*/
|
||||
void DisableIdleTimeout();
|
||||
|
||||
/**
|
||||
* Enable the idle timeout. No effect if the timeout is already enabled.
|
||||
*/
|
||||
void EnableIdleTimeout();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Calls Shutdown().
|
||||
*/
|
||||
~LazyIdleThread();
|
||||
|
||||
/**
|
||||
* Called just before dispatching to mThread.
|
||||
*/
|
||||
void PreDispatch();
|
||||
|
||||
/**
|
||||
* Makes sure a valid thread lives in mThread.
|
||||
*/
|
||||
nsresult EnsureThread();
|
||||
|
||||
/**
|
||||
* Called on mThread to set up the thread observer.
|
||||
*/
|
||||
void InitThread();
|
||||
|
||||
/**
|
||||
* Called on mThread to clean up the thread observer.
|
||||
*/
|
||||
void CleanupThread();
|
||||
|
||||
/**
|
||||
* Called on the main thread when mThread believes itself to be idle. Sets up
|
||||
* the idle timer.
|
||||
*/
|
||||
void ScheduleTimer();
|
||||
|
||||
/**
|
||||
* Called when we are shutting down mThread.
|
||||
*/
|
||||
nsresult ShutdownThread();
|
||||
|
||||
/**
|
||||
* Deletes this object. Used to delay calling mThread->Shutdown() during the
|
||||
* final release (during a GC, for instance).
|
||||
*/
|
||||
void SelfDestruct();
|
||||
|
||||
/**
|
||||
* Returns true if events should be queued rather than immediately dispatched
|
||||
* to mThread. Currently only happens when the thread is shutting down.
|
||||
*/
|
||||
bool UseRunnableQueue()
|
||||
{
|
||||
return !!mQueuedRunnables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protects data that is accessed on both threads.
|
||||
*/
|
||||
mozilla::Mutex mMutex;
|
||||
|
||||
/**
|
||||
* Touched on both threads but set before mThread is created. Used to direct
|
||||
* timer events to the owning thread.
|
||||
*/
|
||||
nsCOMPtr<nsIThread> mOwningThread;
|
||||
|
||||
/**
|
||||
* Only accessed on the owning thread. Set by EnsureThread().
|
||||
*/
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
|
||||
/**
|
||||
* Protected by mMutex. Created when mThread has no pending events and fired
|
||||
* at mOwningThread. Any thread that dispatches to mThread will take ownership
|
||||
* of the timer and fire a separate cancel event to the owning thread.
|
||||
*/
|
||||
nsCOMPtr<nsITimer> mIdleTimer;
|
||||
|
||||
/**
|
||||
* Idle observer. Called when the thread is about to be shut down. Released
|
||||
* only when Shutdown() is called.
|
||||
*/
|
||||
nsIObserver* MOZ_UNSAFE_REF("See the documentation for SetWeakIdleObserver for "
|
||||
"how the owner of LazyIdleThread should manage the "
|
||||
"lifetime information of this field") mIdleObserver;
|
||||
|
||||
/**
|
||||
* Temporary storage for events that happen to be dispatched while we're in
|
||||
* the process of shutting down our real thread.
|
||||
*/
|
||||
nsTArray<nsCOMPtr<nsIRunnable>>* mQueuedRunnables;
|
||||
|
||||
/**
|
||||
* The number of milliseconds a thread should be idle before dying.
|
||||
*/
|
||||
const uint32_t mIdleTimeoutMS;
|
||||
|
||||
/**
|
||||
* The number of events that are pending on mThread. A nonzero value means
|
||||
* that the thread cannot be cleaned up.
|
||||
*/
|
||||
uint32_t mPendingEventCount;
|
||||
|
||||
/**
|
||||
* The number of times that mThread has dispatched an idle notification. Any
|
||||
* timer that fires while this count is nonzero can safely be ignored as
|
||||
* another timer will be on the way.
|
||||
*/
|
||||
uint32_t mIdleNotificationCount;
|
||||
|
||||
/**
|
||||
* Whether or not the thread should automatically shutdown. If the owner
|
||||
* specified ManualShutdown at construction time then the owner should take
|
||||
* care to call Shutdown() manually when appropriate.
|
||||
*/
|
||||
ShutdownMethod mShutdownMethod;
|
||||
|
||||
/**
|
||||
* Only accessed on the owning thread. Set to true when Shutdown() has been
|
||||
* called and prevents EnsureThread() from recreating mThread.
|
||||
*/
|
||||
bool mShutdown;
|
||||
|
||||
/**
|
||||
* Set from CleanupThread and lasting until the thread has shut down. Prevents
|
||||
* further idle notifications during the shutdown process.
|
||||
*/
|
||||
bool mThreadIsShuttingDown;
|
||||
|
||||
/**
|
||||
* Whether or not the idle timeout is enabled.
|
||||
*/
|
||||
bool mIdleTimeoutEnabled;
|
||||
|
||||
/**
|
||||
* Name of the thread, set on the actual thread after it gets created.
|
||||
*/
|
||||
nsCString mName;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_lazyidlethread_h__
|
||||
52
xpcom/threads/LeakRefPtr.h
Normal file
52
xpcom/threads/LeakRefPtr.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/* Smart pointer which leaks its owning refcounted object by default. */
|
||||
|
||||
#ifndef LeakRefPtr_h
|
||||
#define LeakRefPtr_h
|
||||
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* Instance of this class behaves like a raw pointer which leaks the
|
||||
* resource it's owning if not explicitly released.
|
||||
*/
|
||||
template<class T>
|
||||
class LeakRefPtr
|
||||
{
|
||||
public:
|
||||
explicit LeakRefPtr(already_AddRefed<T>&& aPtr)
|
||||
: mRawPtr(aPtr.take()) { }
|
||||
|
||||
explicit operator bool() const { return !!mRawPtr; }
|
||||
|
||||
LeakRefPtr<T>& operator=(already_AddRefed<T>&& aPtr)
|
||||
{
|
||||
mRawPtr = aPtr.take();
|
||||
return *this;
|
||||
}
|
||||
|
||||
T* get() const { return mRawPtr; }
|
||||
|
||||
already_AddRefed<T> take()
|
||||
{
|
||||
T* rawPtr = mRawPtr;
|
||||
mRawPtr = nullptr;
|
||||
return already_AddRefed<T>(rawPtr);
|
||||
}
|
||||
|
||||
void release() { NS_RELEASE(mRawPtr); }
|
||||
|
||||
private:
|
||||
T* MOZ_OWNING_REF mRawPtr;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // LeakRefPtr_h
|
||||
76
xpcom/threads/MainThreadIdlePeriod.cpp
Normal file
76
xpcom/threads/MainThreadIdlePeriod.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* -*- 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 "MainThreadIdlePeriod.h"
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
|
||||
#define DEFAULT_LONG_IDLE_PERIOD 50.0f
|
||||
#define DEFAULT_MIN_IDLE_PERIOD 3.0f
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMETHODIMP
|
||||
MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aIdleDeadline);
|
||||
|
||||
Maybe<TimeStamp> deadline = nsRefreshDriver::GetIdleDeadlineHint();
|
||||
|
||||
if (deadline.isSome()) {
|
||||
// If the idle period is too small, then just return a null time
|
||||
// to indicate we are busy. Otherwise return the actual deadline.
|
||||
TimeDuration minIdlePeriod =
|
||||
TimeDuration::FromMilliseconds(GetMinIdlePeriod());
|
||||
bool busySoon = deadline.value().IsNull() ||
|
||||
(TimeStamp::Now() >= (deadline.value() - minIdlePeriod));
|
||||
*aIdleDeadline = busySoon ? TimeStamp() : deadline.value();
|
||||
} else {
|
||||
*aIdleDeadline =
|
||||
TimeStamp::Now() + TimeDuration::FromMilliseconds(GetLongIdlePeriod());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */ float
|
||||
MainThreadIdlePeriod::GetLongIdlePeriod()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
static float sLongIdlePeriod = DEFAULT_LONG_IDLE_PERIOD;
|
||||
static bool sInitialized = false;
|
||||
|
||||
if (!sInitialized && Preferences::IsServiceAvailable()) {
|
||||
sInitialized = true;
|
||||
Preferences::AddFloatVarCache(&sLongIdlePeriod, "idle_queue.long_period",
|
||||
DEFAULT_LONG_IDLE_PERIOD);
|
||||
}
|
||||
|
||||
return sLongIdlePeriod;
|
||||
}
|
||||
|
||||
/* static */ float
|
||||
MainThreadIdlePeriod::GetMinIdlePeriod()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
static float sMinIdlePeriod = DEFAULT_MIN_IDLE_PERIOD;
|
||||
static bool sInitialized = false;
|
||||
|
||||
if (!sInitialized && Preferences::IsServiceAvailable()) {
|
||||
sInitialized = true;
|
||||
Preferences::AddFloatVarCache(&sMinIdlePeriod, "idle_queue.min_period",
|
||||
DEFAULT_MIN_IDLE_PERIOD);
|
||||
}
|
||||
|
||||
return sMinIdlePeriod;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
28
xpcom/threads/MainThreadIdlePeriod.h
Normal file
28
xpcom/threads/MainThreadIdlePeriod.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_dom_mainthreadidleperiod_h
|
||||
#define mozilla_dom_mainthreadidleperiod_h
|
||||
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MainThreadIdlePeriod final : public IdlePeriod
|
||||
{
|
||||
public:
|
||||
NS_DECL_NSIIDLEPERIOD
|
||||
|
||||
static float GetLongIdlePeriod();
|
||||
static float GetMinIdlePeriod();
|
||||
private:
|
||||
virtual ~MainThreadIdlePeriod() {}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_mainthreadidleperiod_h
|
||||
1067
xpcom/threads/MozPromise.h
Normal file
1067
xpcom/threads/MozPromise.h
Normal file
File diff suppressed because it is too large
Load diff
224
xpcom/threads/SharedThreadPool.cpp
Normal file
224
xpcom/threads/SharedThreadPool.cpp
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/* -*- 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 "mozilla/SharedThreadPool.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsXPCOMCIDInternal.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
#ifdef XP_WIN
|
||||
#include "ThreadPoolCOMListener.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Created and destroyed on the main thread.
|
||||
static StaticAutoPtr<ReentrantMonitor> sMonitor;
|
||||
|
||||
// Hashtable, maps thread pool name to SharedThreadPool instance.
|
||||
// Modified only on the main thread.
|
||||
static StaticAutoPtr<nsDataHashtable<nsCStringHashKey, SharedThreadPool*>> sPools;
|
||||
|
||||
static already_AddRefed<nsIThreadPool>
|
||||
CreateThreadPool(const nsCString& aName);
|
||||
|
||||
class SharedThreadPoolShutdownObserver : public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
protected:
|
||||
virtual ~SharedThreadPoolShutdownObserver() {}
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver, nsIObserver, nsISupports)
|
||||
|
||||
NS_IMETHODIMP
|
||||
SharedThreadPoolShutdownObserver::Observe(nsISupports* aSubject, const char *aTopic,
|
||||
const char16_t *aData)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!strcmp(aTopic, "xpcom-shutdown-threads"));
|
||||
SharedThreadPool::SpinUntilEmpty();
|
||||
sMonitor = nullptr;
|
||||
sPools = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
SharedThreadPool::InitStatics()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!sMonitor && !sPools);
|
||||
sMonitor = new ReentrantMonitor("SharedThreadPool");
|
||||
sPools = new nsDataHashtable<nsCStringHashKey, SharedThreadPool*>();
|
||||
nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
|
||||
nsCOMPtr<nsIObserver> obs = new SharedThreadPoolShutdownObserver();
|
||||
obsService->AddObserver(obs, "xpcom-shutdown-threads", false);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
SharedThreadPool::IsEmpty()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(*sMonitor);
|
||||
return !sPools->Count();
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
SharedThreadPool::SpinUntilEmpty()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
while (!IsEmpty()) {
|
||||
sMonitor->AssertNotCurrentThreadIn();
|
||||
NS_ProcessNextEvent(NS_GetCurrentThread(), true);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<SharedThreadPool>
|
||||
SharedThreadPool::Get(const nsCString& aName, uint32_t aThreadLimit)
|
||||
{
|
||||
MOZ_ASSERT(sMonitor && sPools);
|
||||
ReentrantMonitorAutoEnter mon(*sMonitor);
|
||||
SharedThreadPool* pool = nullptr;
|
||||
nsresult rv;
|
||||
if (!sPools->Get(aName, &pool)) {
|
||||
nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName));
|
||||
NS_ENSURE_TRUE(threadPool, nullptr);
|
||||
pool = new SharedThreadPool(aName, threadPool);
|
||||
|
||||
// Set the thread and idle limits. Note that we don't rely on the
|
||||
// EnsureThreadLimitIsAtLeast() call below, as the default thread limit
|
||||
// is 4, and if aThreadLimit is less than 4 we'll end up with a pool
|
||||
// with 4 threads rather than what we expected; so we'll have unexpected
|
||||
// behaviour.
|
||||
rv = pool->SetThreadLimit(aThreadLimit);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
rv = pool->SetIdleThreadLimit(aThreadLimit);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
sPools->Put(aName, pool);
|
||||
} else if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) {
|
||||
NS_WARNING("Failed to set limits on thread pool");
|
||||
}
|
||||
|
||||
MOZ_ASSERT(pool);
|
||||
RefPtr<SharedThreadPool> instance(pool);
|
||||
return instance.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void)
|
||||
{
|
||||
MOZ_ASSERT(sMonitor);
|
||||
ReentrantMonitorAutoEnter mon(*sMonitor);
|
||||
MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
|
||||
nsrefcnt count = ++mRefCnt;
|
||||
NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this));
|
||||
return count;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void)
|
||||
{
|
||||
MOZ_ASSERT(sMonitor);
|
||||
ReentrantMonitorAutoEnter mon(*sMonitor);
|
||||
nsrefcnt count = --mRefCnt;
|
||||
NS_LOG_RELEASE(this, count, "SharedThreadPool");
|
||||
if (count) {
|
||||
return count;
|
||||
}
|
||||
|
||||
// Remove SharedThreadPool from table of pools.
|
||||
sPools->Remove(mName);
|
||||
MOZ_ASSERT(!sPools->Get(mName));
|
||||
|
||||
// Dispatch an event to the main thread to call Shutdown() on
|
||||
// the nsIThreadPool. The Runnable here will add a refcount to the pool,
|
||||
// and when the Runnable releases the nsIThreadPool it will be deleted.
|
||||
NS_DispatchToMainThread(NewRunnableMethod(mPool, &nsIThreadPool::Shutdown));
|
||||
|
||||
// Stabilize refcount, so that if something in the dtor QIs, it won't explode.
|
||||
mRefCnt = 1;
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget)
|
||||
|
||||
SharedThreadPool::SharedThreadPool(const nsCString& aName,
|
||||
nsIThreadPool* aPool)
|
||||
: mName(aName)
|
||||
, mPool(aPool)
|
||||
, mRefCnt(0)
|
||||
{
|
||||
MOZ_COUNT_CTOR(SharedThreadPool);
|
||||
mEventTarget = do_QueryInterface(aPool);
|
||||
}
|
||||
|
||||
SharedThreadPool::~SharedThreadPool()
|
||||
{
|
||||
MOZ_COUNT_DTOR(SharedThreadPool);
|
||||
}
|
||||
|
||||
nsresult
|
||||
SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit)
|
||||
{
|
||||
// We limit the number of threads that we use. Note that we
|
||||
// set the thread limit to the same as the idle limit so that we're not
|
||||
// constantly creating and destroying threads (see Bug 881954). When the
|
||||
// thread pool threads shutdown they dispatch an event to the main thread
|
||||
// to call nsIThread::Shutdown(), and if we're very busy that can take a
|
||||
// while to run, and we end up with dozens of extra threads. Note that
|
||||
// threads that are idle for 60 seconds are shutdown naturally.
|
||||
uint32_t existingLimit = 0;
|
||||
nsresult rv;
|
||||
|
||||
rv = mPool->GetThreadLimit(&existingLimit);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (aLimit > existingLimit) {
|
||||
rv = mPool->SetThreadLimit(aLimit);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
rv = mPool->GetIdleThreadLimit(&existingLimit);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (aLimit > existingLimit) {
|
||||
rv = mPool->SetIdleThreadLimit(aLimit);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static already_AddRefed<nsIThreadPool>
|
||||
CreateThreadPool(const nsCString& aName)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
rv = pool->SetName(aName);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
rv = pool->SetThreadStackSize(SharedThreadPool::kStackSize);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
#ifdef XP_WIN
|
||||
// Ensure MSCOM is initialized on the thread pools threads.
|
||||
nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener();
|
||||
rv = pool->SetListener(listener);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
#endif
|
||||
|
||||
return pool.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
129
xpcom/threads/SharedThreadPool.h
Normal file
129
xpcom/threads/SharedThreadPool.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef SharedThreadPool_h_
|
||||
#define SharedThreadPool_h_
|
||||
|
||||
#include <queue>
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsIThreadManager.h"
|
||||
#include "nsIThreadPool.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Wrapper that makes an nsIThreadPool a singleton, and provides a
|
||||
// consistent threadsafe interface to get instances. Callers simply get a
|
||||
// SharedThreadPool by the name of its nsIThreadPool. All get requests of
|
||||
// the same name get the same SharedThreadPool. Users must store a reference
|
||||
// to the pool, and when the last reference to a SharedThreadPool is dropped
|
||||
// the pool is shutdown and deleted. Users aren't required to manually
|
||||
// shutdown the pool, and can release references on any thread. This can make
|
||||
// it significantly easier to use thread pools, because the caller doesn't need
|
||||
// to worry about joining and tearing it down.
|
||||
//
|
||||
// On Windows all threads in the pool have MSCOM initialized with
|
||||
// COINIT_MULTITHREADED. Note that not all users of MSCOM use this mode see [1],
|
||||
// and mixing MSCOM objects between the two is terrible for performance, and can
|
||||
// cause some functions to fail. So be careful when using Win32 APIs on a
|
||||
// SharedThreadPool, and avoid sharing objects if at all possible.
|
||||
//
|
||||
// [1] https://dxr.mozilla.org/mozilla-central/search?q=coinitialize&redirect=false
|
||||
class SharedThreadPool : public nsIThreadPool
|
||||
{
|
||||
public:
|
||||
|
||||
// Gets (possibly creating) the shared thread pool singleton instance with
|
||||
// thread pool named aName.
|
||||
static already_AddRefed<SharedThreadPool> Get(const nsCString& aName,
|
||||
uint32_t aThreadLimit = 4);
|
||||
|
||||
// We implement custom threadsafe AddRef/Release pair, that destroys the
|
||||
// the shared pool singleton when the refcount drops to 0. The addref/release
|
||||
// are implemented using locking, so it's not recommended that you use them
|
||||
// in a tight loop.
|
||||
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override;
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release(void) override;
|
||||
|
||||
// Forward behaviour to wrapped thread pool implementation.
|
||||
NS_FORWARD_SAFE_NSITHREADPOOL(mPool);
|
||||
|
||||
// Call this when dispatching from an event on the same
|
||||
// threadpool that is about to complete. We should not create a new thread
|
||||
// in that case since a thread is about to become idle.
|
||||
nsresult DispatchFromEndOfTaskInThisPool(nsIRunnable *event)
|
||||
{
|
||||
return Dispatch(event, NS_DISPATCH_AT_END);
|
||||
}
|
||||
|
||||
NS_IMETHOD DispatchFromScript(nsIRunnable *event, uint32_t flags) override {
|
||||
return Dispatch(event, flags);
|
||||
}
|
||||
|
||||
NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> event, uint32_t flags) override
|
||||
{ return !mEventTarget ? NS_ERROR_NULL_POINTER : mEventTarget->Dispatch(Move(event), flags); }
|
||||
|
||||
NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override
|
||||
{ return NS_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
using nsIEventTarget::Dispatch;
|
||||
|
||||
NS_IMETHOD IsOnCurrentThread(bool *_retval) override { return !mEventTarget ? NS_ERROR_NULL_POINTER : mEventTarget->IsOnCurrentThread(_retval); }
|
||||
|
||||
// Creates necessary statics. Called once at startup.
|
||||
static void InitStatics();
|
||||
|
||||
// Spins the event loop until all thread pools are shutdown.
|
||||
// *Must* be called on the main thread.
|
||||
static void SpinUntilEmpty();
|
||||
|
||||
#if defined(MOZ_ASAN)
|
||||
// Use the system default in ASAN builds, because the default is assumed to be
|
||||
// larger than the size we want to use and is hopefully sufficient for ASAN.
|
||||
static const uint32_t kStackSize = nsIThreadManager::DEFAULT_STACK_SIZE;
|
||||
#elif defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX)
|
||||
static const uint32_t kStackSize = (256 * 1024);
|
||||
#else
|
||||
// All other platforms use their system defaults.
|
||||
static const uint32_t kStackSize = nsIThreadManager::DEFAULT_STACK_SIZE;
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
// Returns whether there are no pools in existence at the moment.
|
||||
static bool IsEmpty();
|
||||
|
||||
// Creates a singleton SharedThreadPool wrapper around aPool.
|
||||
// aName is the name of the aPool, and is used to lookup the
|
||||
// SharedThreadPool in the hash table of all created pools.
|
||||
SharedThreadPool(const nsCString& aName,
|
||||
nsIThreadPool* aPool);
|
||||
virtual ~SharedThreadPool();
|
||||
|
||||
nsresult EnsureThreadLimitIsAtLeast(uint32_t aThreadLimit);
|
||||
|
||||
// Name of mPool.
|
||||
const nsCString mName;
|
||||
|
||||
// Thread pool being wrapped.
|
||||
nsCOMPtr<nsIThreadPool> mPool;
|
||||
|
||||
// Refcount. We implement custom ref counting so that the thread pool is
|
||||
// shutdown in a threadsafe manner and singletonness is preserved.
|
||||
nsrefcnt mRefCnt;
|
||||
|
||||
// mPool QI'd to nsIEventTarget. We cache this, so that we can use
|
||||
// NS_FORWARD_SAFE_NSIEVENTTARGET above.
|
||||
nsCOMPtr<nsIEventTarget> mEventTarget;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // SharedThreadPool_h_
|
||||
378
xpcom/threads/StateMirroring.h
Normal file
378
xpcom/threads/StateMirroring.h
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#if !defined(StateMirroring_h_)
|
||||
#define StateMirroring_h_
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/StateWatching.h"
|
||||
#include "mozilla/TaskDispatcher.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "mozilla/Logging.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
||||
/*
|
||||
* The state-mirroring machinery allows pieces of interesting state to be
|
||||
* observed on multiple thread without locking. The basic strategy is to track
|
||||
* changes in a canonical value and post updates to other threads that hold
|
||||
* mirrors for that value.
|
||||
*
|
||||
* One problem with the naive implementation of such a system is that some pieces
|
||||
* of state need to be updated atomically, and certain other operations need to
|
||||
* wait for these atomic updates to complete before executing. The state-mirroring
|
||||
* machinery solves this problem by requiring that its owner thread uses tail
|
||||
* dispatch, and posting state update events (which should always be run first by
|
||||
* TaskDispatcher implementations) to that tail dispatcher. This ensures that
|
||||
* state changes are always atomic from the perspective of observing threads.
|
||||
*
|
||||
* Given that state-mirroring is an automatic background process, we try to avoid
|
||||
* burdening the caller with worrying too much about teardown. To that end, we
|
||||
* don't assert dispatch success for any of the notifications, and assume that
|
||||
* any canonical or mirror owned by a thread for whom dispatch fails will soon
|
||||
* be disconnected by its holder anyway.
|
||||
*
|
||||
* Given that semantics may change and comments tend to go out of date, we
|
||||
* deliberately don't provide usage examples here. Grep around to find them.
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Mirror<T> and Canonical<T> inherit WatchTarget, so we piggy-back on the
|
||||
// logging that WatchTarget already does. Given that, it makes sense to share
|
||||
// the same log module.
|
||||
#define MIRROR_LOG(x, ...) \
|
||||
MOZ_ASSERT(gStateWatchingLog); \
|
||||
MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))
|
||||
|
||||
template<typename T> class AbstractMirror;
|
||||
|
||||
/*
|
||||
* AbstractCanonical is a superclass from which all Canonical values must
|
||||
* inherit. It serves as the interface of operations which may be performed (via
|
||||
* asynchronous dispatch) by other threads, in particular by the corresponding
|
||||
* Mirror value.
|
||||
*/
|
||||
template<typename T>
|
||||
class AbstractCanonical
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractCanonical)
|
||||
AbstractCanonical(AbstractThread* aThread) : mOwnerThread(aThread) {}
|
||||
virtual void AddMirror(AbstractMirror<T>* aMirror) = 0;
|
||||
virtual void RemoveMirror(AbstractMirror<T>* aMirror) = 0;
|
||||
|
||||
AbstractThread* OwnerThread() const { return mOwnerThread; }
|
||||
protected:
|
||||
virtual ~AbstractCanonical() {}
|
||||
RefPtr<AbstractThread> mOwnerThread;
|
||||
};
|
||||
|
||||
/*
|
||||
* AbstractMirror is a superclass from which all Mirror values must
|
||||
* inherit. It serves as the interface of operations which may be performed (via
|
||||
* asynchronous dispatch) by other threads, in particular by the corresponding
|
||||
* Canonical value.
|
||||
*/
|
||||
template<typename T>
|
||||
class AbstractMirror
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractMirror)
|
||||
AbstractMirror(AbstractThread* aThread) : mOwnerThread(aThread) {}
|
||||
virtual void UpdateValue(const T& aNewValue) = 0;
|
||||
virtual void NotifyDisconnected() = 0;
|
||||
|
||||
AbstractThread* OwnerThread() const { return mOwnerThread; }
|
||||
protected:
|
||||
virtual ~AbstractMirror() {}
|
||||
RefPtr<AbstractThread> mOwnerThread;
|
||||
};
|
||||
|
||||
/*
|
||||
* Canonical<T> is a wrapper class that allows a given value to be mirrored by other
|
||||
* threads. It maintains a list of active mirrors, and queues updates for them
|
||||
* when the internal value changes. When changing the value, the caller needs to
|
||||
* pass a TaskDispatcher object, which fires the updates at the appropriate time.
|
||||
* Canonical<T> is also a WatchTarget, and may be set up to trigger other routines
|
||||
* (on the same thread) when the canonical value changes.
|
||||
*
|
||||
* Canonical<T> is intended to be used as a member variable, so it doesn't actually
|
||||
* inherit AbstractCanonical<T> (a refcounted type). Rather, it contains an inner
|
||||
* class called |Impl| that implements most of the interesting logic.
|
||||
*/
|
||||
template<typename T>
|
||||
class Canonical
|
||||
{
|
||||
public:
|
||||
Canonical(AbstractThread* aThread, const T& aInitialValue, const char* aName)
|
||||
{
|
||||
mImpl = new Impl(aThread, aInitialValue, aName);
|
||||
}
|
||||
|
||||
|
||||
~Canonical() {}
|
||||
|
||||
private:
|
||||
class Impl : public AbstractCanonical<T>, public WatchTarget
|
||||
{
|
||||
public:
|
||||
using AbstractCanonical<T>::OwnerThread;
|
||||
|
||||
Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName)
|
||||
: AbstractCanonical<T>(aThread), WatchTarget(aName), mValue(aInitialValue)
|
||||
{
|
||||
MIRROR_LOG("%s [%p] initialized", mName, this);
|
||||
MOZ_ASSERT(aThread->SupportsTailDispatch(), "Can't get coherency without tail dispatch");
|
||||
}
|
||||
|
||||
void AddMirror(AbstractMirror<T>* aMirror) override
|
||||
{
|
||||
MIRROR_LOG("%s [%p] adding mirror %p", mName, this, aMirror);
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
MOZ_ASSERT(!mMirrors.Contains(aMirror));
|
||||
mMirrors.AppendElement(aMirror);
|
||||
aMirror->OwnerThread()->DispatchStateChange(MakeNotifier(aMirror));
|
||||
}
|
||||
|
||||
void RemoveMirror(AbstractMirror<T>* aMirror) override
|
||||
{
|
||||
MIRROR_LOG("%s [%p] removing mirror %p", mName, this, aMirror);
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
MOZ_ASSERT(mMirrors.Contains(aMirror));
|
||||
mMirrors.RemoveElement(aMirror);
|
||||
}
|
||||
|
||||
void DisconnectAll()
|
||||
{
|
||||
MIRROR_LOG("%s [%p] Disconnecting all mirrors", mName, this);
|
||||
for (size_t i = 0; i < mMirrors.Length(); ++i) {
|
||||
mMirrors[i]->OwnerThread()->Dispatch(NewRunnableMethod(mMirrors[i],
|
||||
&AbstractMirror<T>::NotifyDisconnected),
|
||||
AbstractThread::DontAssertDispatchSuccess);
|
||||
}
|
||||
mMirrors.Clear();
|
||||
}
|
||||
|
||||
operator const T&()
|
||||
{
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
return mValue;
|
||||
}
|
||||
|
||||
void Set(const T& aNewValue)
|
||||
{
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
|
||||
if (aNewValue == mValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify same-thread watchers. The state watching machinery will make sure
|
||||
// that notifications run at the right time.
|
||||
NotifyWatchers();
|
||||
|
||||
// Check if we've already got a pending update. If so we won't schedule another
|
||||
// one.
|
||||
bool alreadyNotifying = mInitialValue.isSome();
|
||||
|
||||
// Stash the initial value if needed, then update to the new value.
|
||||
if (mInitialValue.isNothing()) {
|
||||
mInitialValue.emplace(mValue);
|
||||
}
|
||||
mValue = aNewValue;
|
||||
|
||||
// We wait until things have stablized before sending state updates so that
|
||||
// we can avoid sending multiple updates, and possibly avoid sending any
|
||||
// updates at all if the value ends up where it started.
|
||||
if (!alreadyNotifying) {
|
||||
AbstractThread::DispatchDirectTask(NewRunnableMethod(this, &Impl::DoNotify));
|
||||
}
|
||||
}
|
||||
|
||||
Impl& operator=(const T& aNewValue) { Set(aNewValue); return *this; }
|
||||
Impl& operator=(const Impl& aOther) { Set(aOther); return *this; }
|
||||
Impl(const Impl& aOther) = delete;
|
||||
|
||||
protected:
|
||||
~Impl() { MOZ_DIAGNOSTIC_ASSERT(mMirrors.IsEmpty()); }
|
||||
|
||||
private:
|
||||
void DoNotify()
|
||||
{
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
MOZ_ASSERT(mInitialValue.isSome());
|
||||
bool same = mInitialValue.ref() == mValue;
|
||||
mInitialValue.reset();
|
||||
|
||||
if (same) {
|
||||
MIRROR_LOG("%s [%p] unchanged - not sending update", mName, this);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mMirrors.Length(); ++i) {
|
||||
mMirrors[i]->OwnerThread()->DispatchStateChange(MakeNotifier(mMirrors[i]));
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<nsIRunnable> MakeNotifier(AbstractMirror<T>* aMirror)
|
||||
{
|
||||
return NewRunnableMethod<T>(aMirror, &AbstractMirror<T>::UpdateValue, mValue);;
|
||||
}
|
||||
|
||||
T mValue;
|
||||
Maybe<T> mInitialValue;
|
||||
nsTArray<RefPtr<AbstractMirror<T>>> mMirrors;
|
||||
};
|
||||
public:
|
||||
|
||||
// NB: Because mirror-initiated disconnection can race with canonical-
|
||||
// initiated disconnection, a canonical should never be reinitialized.
|
||||
// Forward control operations to the Impl.
|
||||
void DisconnectAll() { return mImpl->DisconnectAll(); }
|
||||
|
||||
// Access to the Impl.
|
||||
operator Impl&() { return *mImpl; }
|
||||
Impl* operator&() { return mImpl; }
|
||||
|
||||
// Access to the T.
|
||||
const T& Ref() const { return *mImpl; }
|
||||
operator const T&() const { return Ref(); }
|
||||
void Set(const T& aNewValue) { mImpl->Set(aNewValue); }
|
||||
Canonical& operator=(const T& aNewValue) { Set(aNewValue); return *this; }
|
||||
Canonical& operator=(const Canonical& aOther) { Set(aOther); return *this; }
|
||||
Canonical(const Canonical& aOther) = delete;
|
||||
|
||||
private:
|
||||
RefPtr<Impl> mImpl;
|
||||
};
|
||||
|
||||
/*
|
||||
* Mirror<T> is a wrapper class that allows a given value to mirror that of a
|
||||
* Canonical<T> owned by another thread. It registers itself with a Canonical<T>,
|
||||
* and is periodically updated with new values. Mirror<T> is also a WatchTarget,
|
||||
* and may be set up to trigger other routines (on the same thread) when the
|
||||
* mirrored value changes.
|
||||
*
|
||||
* Mirror<T> is intended to be used as a member variable, so it doesn't actually
|
||||
* inherit AbstractMirror<T> (a refcounted type). Rather, it contains an inner
|
||||
* class called |Impl| that implements most of the interesting logic.
|
||||
*/
|
||||
template<typename T>
|
||||
class Mirror
|
||||
{
|
||||
public:
|
||||
Mirror(AbstractThread* aThread, const T& aInitialValue, const char* aName)
|
||||
{
|
||||
mImpl = new Impl(aThread, aInitialValue, aName);
|
||||
}
|
||||
|
||||
~Mirror()
|
||||
{
|
||||
// As a member of complex objects, a Mirror<T> may be destroyed on a
|
||||
// different thread than its owner, or late in shutdown during CC. Given
|
||||
// that, we require manual disconnection so that callers can put things in
|
||||
// the right place.
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected());
|
||||
}
|
||||
|
||||
private:
|
||||
class Impl : public AbstractMirror<T>, public WatchTarget
|
||||
{
|
||||
public:
|
||||
using AbstractMirror<T>::OwnerThread;
|
||||
|
||||
Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName)
|
||||
: AbstractMirror<T>(aThread), WatchTarget(aName), mValue(aInitialValue)
|
||||
{
|
||||
MIRROR_LOG("%s [%p] initialized", mName, this);
|
||||
MOZ_ASSERT(aThread->SupportsTailDispatch(), "Can't get coherency without tail dispatch");
|
||||
}
|
||||
|
||||
operator const T&()
|
||||
{
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
return mValue;
|
||||
}
|
||||
|
||||
virtual void UpdateValue(const T& aNewValue) override
|
||||
{
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
if (mValue != aNewValue) {
|
||||
mValue = aNewValue;
|
||||
WatchTarget::NotifyWatchers();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void NotifyDisconnected() override
|
||||
{
|
||||
MIRROR_LOG("%s [%p] Notifed of disconnection from %p", mName, this, mCanonical.get());
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
mCanonical = nullptr;
|
||||
}
|
||||
|
||||
bool IsConnected() const { return !!mCanonical; }
|
||||
|
||||
void Connect(AbstractCanonical<T>* aCanonical)
|
||||
{
|
||||
MIRROR_LOG("%s [%p] Connecting to %p", mName, this, aCanonical);
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
MOZ_ASSERT(!IsConnected());
|
||||
MOZ_ASSERT(OwnerThread()->RequiresTailDispatch(aCanonical->OwnerThread()), "Can't get coherency without tail dispatch");
|
||||
|
||||
nsCOMPtr<nsIRunnable> r = NewRunnableMethod<StorensRefPtrPassByPtr<AbstractMirror<T>>>
|
||||
(aCanonical, &AbstractCanonical<T>::AddMirror, this);
|
||||
aCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
|
||||
mCanonical = aCanonical;
|
||||
}
|
||||
public:
|
||||
|
||||
void DisconnectIfConnected()
|
||||
{
|
||||
MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
|
||||
if (!IsConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MIRROR_LOG("%s [%p] Disconnecting from %p", mName, this, mCanonical.get());
|
||||
nsCOMPtr<nsIRunnable> r = NewRunnableMethod<StorensRefPtrPassByPtr<AbstractMirror<T>>>
|
||||
(mCanonical, &AbstractCanonical<T>::RemoveMirror, this);
|
||||
mCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
|
||||
mCanonical = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
~Impl() { MOZ_DIAGNOSTIC_ASSERT(!IsConnected()); }
|
||||
|
||||
private:
|
||||
T mValue;
|
||||
RefPtr<AbstractCanonical<T>> mCanonical;
|
||||
};
|
||||
public:
|
||||
|
||||
// Forward control operations to the Impl<T>.
|
||||
void Connect(AbstractCanonical<T>* aCanonical) { mImpl->Connect(aCanonical); }
|
||||
void DisconnectIfConnected() { mImpl->DisconnectIfConnected(); }
|
||||
|
||||
// Access to the Impl<T>.
|
||||
operator Impl&() { return *mImpl; }
|
||||
Impl* operator&() { return mImpl; }
|
||||
|
||||
// Access to the T.
|
||||
const T& Ref() const { return *mImpl; }
|
||||
operator const T&() const { return Ref(); }
|
||||
|
||||
private:
|
||||
RefPtr<Impl> mImpl;
|
||||
};
|
||||
|
||||
#undef MIRROR_LOG
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
317
xpcom/threads/StateWatching.h
Normal file
317
xpcom/threads/StateWatching.h
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#if !defined(StateWatching_h_)
|
||||
#define StateWatching_h_
|
||||
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/TaskDispatcher.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "nsISupportsImpl.h"
|
||||
|
||||
/*
|
||||
* The state-watching machinery automates the process of responding to changes
|
||||
* in various pieces of state.
|
||||
*
|
||||
* A standard programming pattern is as follows:
|
||||
*
|
||||
* mFoo = ...;
|
||||
* NotifyStuffChanged();
|
||||
* ...
|
||||
* mBar = ...;
|
||||
* NotifyStuffChanged();
|
||||
*
|
||||
* This pattern is error-prone and difficult to audit because it requires the
|
||||
* programmer to manually trigger the update routine. This can be especially
|
||||
* problematic when the update routine depends on numerous pieces of state, and
|
||||
* when that state is modified across a variety of helper methods. In these
|
||||
* cases the responsibility for invoking the routine is often unclear, causing
|
||||
* developers to scatter calls to it like pixie dust. This can result in
|
||||
* duplicate invocations (which is wasteful) and missing invocations in corner-
|
||||
* cases (which is a source of bugs).
|
||||
*
|
||||
* This file provides a set of primitives that automatically handle updates and
|
||||
* allow the programmers to explicitly construct a graph of state dependencies.
|
||||
* When used correctly, it eliminates the guess-work and wasted cycles described
|
||||
* above.
|
||||
*
|
||||
* There are two basic pieces:
|
||||
* (1) Objects that can be watched for updates. These inherit WatchTarget.
|
||||
* (2) Objects that receive objects and trigger processing. These inherit
|
||||
* AbstractWatcher. In the current machinery, these exist only internally
|
||||
* within the WatchManager, though that could change.
|
||||
*
|
||||
* Note that none of this machinery is thread-safe - it must all happen on the
|
||||
* same owning thread. To solve multi-threaded use-cases, use state mirroring
|
||||
* and watch the mirrored value.
|
||||
*
|
||||
* Given that semantics may change and comments tend to go out of date, we
|
||||
* deliberately don't provide usage examples here. Grep around to find them.
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern LazyLogModule gStateWatchingLog;
|
||||
|
||||
#define WATCH_LOG(x, ...) \
|
||||
MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))
|
||||
|
||||
/*
|
||||
* AbstractWatcher is a superclass from which all watchers must inherit.
|
||||
*/
|
||||
class AbstractWatcher
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
|
||||
AbstractWatcher() : mDestroyed(false) {}
|
||||
bool IsDestroyed() { return mDestroyed; }
|
||||
virtual void Notify() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
|
||||
bool mDestroyed;
|
||||
};
|
||||
|
||||
/*
|
||||
* WatchTarget is a superclass from which all watchable things must inherit.
|
||||
* Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
|
||||
* needs only to invoke NotifyWatchers when something changes.
|
||||
*
|
||||
* The functionality that this class provides is not threadsafe, and should only
|
||||
* be used on the thread that owns that WatchTarget.
|
||||
*/
|
||||
class WatchTarget
|
||||
{
|
||||
public:
|
||||
explicit WatchTarget(const char* aName) : mName(aName) {}
|
||||
|
||||
void AddWatcher(AbstractWatcher* aWatcher)
|
||||
{
|
||||
MOZ_ASSERT(!mWatchers.Contains(aWatcher));
|
||||
mWatchers.AppendElement(aWatcher);
|
||||
}
|
||||
|
||||
void RemoveWatcher(AbstractWatcher* aWatcher)
|
||||
{
|
||||
MOZ_ASSERT(mWatchers.Contains(aWatcher));
|
||||
mWatchers.RemoveElement(aWatcher);
|
||||
}
|
||||
|
||||
protected:
|
||||
void NotifyWatchers()
|
||||
{
|
||||
WATCH_LOG("%s[%p] notifying watchers\n", mName, this);
|
||||
PruneWatchers();
|
||||
for (size_t i = 0; i < mWatchers.Length(); ++i) {
|
||||
mWatchers[i]->Notify();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// We don't have Watchers explicitly unregister themselves when they die,
|
||||
// because then they'd need back-references to all the WatchTargets they're
|
||||
// subscribed to, and WatchTargets aren't reference-counted. So instead we
|
||||
// just prune dead ones at appropriate times, which works just fine.
|
||||
void PruneWatchers()
|
||||
{
|
||||
for (int i = mWatchers.Length() - 1; i >= 0; --i) {
|
||||
if (mWatchers[i]->IsDestroyed()) {
|
||||
mWatchers.RemoveElementAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<AbstractWatcher>> mWatchers;
|
||||
|
||||
protected:
|
||||
const char* mName;
|
||||
};
|
||||
|
||||
/*
|
||||
* Watchable is a wrapper class that turns any primitive into a WatchTarget.
|
||||
*/
|
||||
template<typename T>
|
||||
class Watchable : public WatchTarget
|
||||
{
|
||||
public:
|
||||
Watchable(const T& aInitialValue, const char* aName)
|
||||
: WatchTarget(aName), mValue(aInitialValue) {}
|
||||
|
||||
const T& Ref() const { return mValue; }
|
||||
operator const T&() const { return Ref(); }
|
||||
Watchable& operator=(const T& aNewValue)
|
||||
{
|
||||
if (aNewValue != mValue) {
|
||||
mValue = aNewValue;
|
||||
NotifyWatchers();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Watchable(const Watchable& aOther); // Not implemented
|
||||
Watchable& operator=(const Watchable& aOther); // Not implemented
|
||||
|
||||
T mValue;
|
||||
};
|
||||
|
||||
// Manager class for state-watching. Declare one of these in any class for which
|
||||
// you want to invoke method callbacks.
|
||||
//
|
||||
// Internally, WatchManager maintains one AbstractWatcher per callback method.
|
||||
// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
|
||||
// This causes an AbstractWatcher for |Callback| to be instantiated if it doesn't
|
||||
// already exist, and registers it with |WatchTarget|.
|
||||
//
|
||||
// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
|
||||
// watch callbacks no more than once per task, once all other operations for that
|
||||
// task have been completed.
|
||||
//
|
||||
// WatchManager<OwnerType> is intended to be declared as a member of |OwnerType|
|
||||
// objects. Given that, it and its owned objects can't hold permanent strong refs to
|
||||
// the owner, since that would keep the owner alive indefinitely. Instead, it
|
||||
// _only_ holds strong refs while waiting for Direct Tasks to fire. This ensures
|
||||
// that everything is kept alive just long enough.
|
||||
template <typename OwnerType>
|
||||
class WatchManager
|
||||
{
|
||||
public:
|
||||
typedef void(OwnerType::*CallbackMethod)();
|
||||
explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
|
||||
: mOwner(aOwner), mOwnerThread(aOwnerThread) {}
|
||||
|
||||
~WatchManager()
|
||||
{
|
||||
if (!IsShutdown()) {
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsShutdown() const { return !mOwner; }
|
||||
|
||||
// Shutdown needs to happen on mOwnerThread. If the WatchManager will be
|
||||
// destroyed on a different thread, Shutdown() must be called manually.
|
||||
void Shutdown()
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
for (size_t i = 0; i < mWatchers.Length(); ++i) {
|
||||
mWatchers[i]->Destroy();
|
||||
}
|
||||
mWatchers.Clear();
|
||||
mOwner = nullptr;
|
||||
}
|
||||
|
||||
void Watch(WatchTarget& aTarget, CallbackMethod aMethod)
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
aTarget.AddWatcher(&EnsureWatcher(aMethod));
|
||||
}
|
||||
|
||||
void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod)
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
PerCallbackWatcher* watcher = GetWatcher(aMethod);
|
||||
MOZ_ASSERT(watcher);
|
||||
aTarget.RemoveWatcher(watcher);
|
||||
}
|
||||
|
||||
void ManualNotify(CallbackMethod aMethod)
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
PerCallbackWatcher* watcher = GetWatcher(aMethod);
|
||||
MOZ_ASSERT(watcher);
|
||||
watcher->Notify();
|
||||
}
|
||||
|
||||
private:
|
||||
class PerCallbackWatcher : public AbstractWatcher
|
||||
{
|
||||
public:
|
||||
PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread, CallbackMethod aMethod)
|
||||
: mOwner(aOwner), mOwnerThread(aOwnerThread), mCallbackMethod(aMethod) {}
|
||||
|
||||
void Destroy()
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
mDestroyed = true;
|
||||
mOwner = nullptr;
|
||||
}
|
||||
|
||||
void Notify() override
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
MOZ_DIAGNOSTIC_ASSERT(mOwner, "mOwner is only null after destruction, "
|
||||
"at which point we shouldn't be notified");
|
||||
if (mStrongRef) {
|
||||
// We've already got a notification job in the pipe.
|
||||
return;
|
||||
}
|
||||
mStrongRef = mOwner; // Hold the owner alive while notifying.
|
||||
|
||||
// Queue up our notification jobs to run in a stable state.
|
||||
mOwnerThread->TailDispatcher().AddDirectTask(NewRunnableMethod(this, &PerCallbackWatcher::DoNotify));
|
||||
}
|
||||
|
||||
bool CallbackMethodIs(CallbackMethod aMethod) const
|
||||
{
|
||||
return mCallbackMethod == aMethod;
|
||||
}
|
||||
|
||||
private:
|
||||
~PerCallbackWatcher() {}
|
||||
|
||||
void DoNotify()
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
MOZ_ASSERT(mStrongRef);
|
||||
RefPtr<OwnerType> ref = mStrongRef.forget();
|
||||
if (!mDestroyed) {
|
||||
((*ref).*mCallbackMethod)();
|
||||
}
|
||||
}
|
||||
|
||||
OwnerType* mOwner; // Never null.
|
||||
RefPtr<OwnerType> mStrongRef; // Only non-null when notifying.
|
||||
RefPtr<AbstractThread> mOwnerThread;
|
||||
CallbackMethod mCallbackMethod;
|
||||
};
|
||||
|
||||
PerCallbackWatcher* GetWatcher(CallbackMethod aMethod)
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
for (size_t i = 0; i < mWatchers.Length(); ++i) {
|
||||
if (mWatchers[i]->CallbackMethodIs(aMethod)) {
|
||||
return mWatchers[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod)
|
||||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
PerCallbackWatcher* watcher = GetWatcher(aMethod);
|
||||
if (watcher) {
|
||||
return *watcher;
|
||||
}
|
||||
watcher = mWatchers.AppendElement(new PerCallbackWatcher(mOwner, mOwnerThread, aMethod))->get();
|
||||
return *watcher;
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<PerCallbackWatcher>> mWatchers;
|
||||
OwnerType* mOwner;
|
||||
RefPtr<AbstractThread> mOwnerThread;
|
||||
};
|
||||
|
||||
#undef WATCH_LOG
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
129
xpcom/threads/SyncRunnable.h
Normal file
129
xpcom/threads/SyncRunnable.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_SyncRunnable_h
|
||||
#define mozilla_SyncRunnable_h
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Move.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* This class will wrap a nsIRunnable and dispatch it to the main thread
|
||||
* synchronously. This is different from nsIEventTarget.DISPATCH_SYNC:
|
||||
* this class does not spin the event loop waiting for the event to be
|
||||
* dispatched. This means that you don't risk reentrance from pending
|
||||
* messages, but you must be sure that the target thread does not ever block
|
||||
* on this thread, or else you will deadlock.
|
||||
*
|
||||
* Typical usage:
|
||||
* RefPtr<SyncRunnable> sr = new SyncRunnable(new myrunnable...());
|
||||
* sr->DispatchToThread(t);
|
||||
*
|
||||
* We also provide a convenience wrapper:
|
||||
* SyncRunnable::DispatchToThread(new myrunnable...());
|
||||
*
|
||||
*/
|
||||
class SyncRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
explicit SyncRunnable(nsIRunnable* aRunnable)
|
||||
: mRunnable(aRunnable)
|
||||
, mMonitor("SyncRunnable")
|
||||
, mDone(false)
|
||||
{
|
||||
}
|
||||
|
||||
explicit SyncRunnable(already_AddRefed<nsIRunnable> aRunnable)
|
||||
: mRunnable(Move(aRunnable))
|
||||
, mMonitor("SyncRunnable")
|
||||
, mDone(false)
|
||||
{
|
||||
}
|
||||
|
||||
void DispatchToThread(nsIEventTarget* aThread, bool aForceDispatch = false)
|
||||
{
|
||||
nsresult rv;
|
||||
bool on;
|
||||
|
||||
if (!aForceDispatch) {
|
||||
rv = aThread->IsOnCurrentThread(&on);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_SUCCEEDED(rv) && on) {
|
||||
mRunnable->Run();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rv = aThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mozilla::MonitorAutoLock lock(mMonitor);
|
||||
while (!mDone) {
|
||||
lock.Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DispatchToThread(AbstractThread* aThread, bool aForceDispatch = false)
|
||||
{
|
||||
if (!aForceDispatch && aThread->IsCurrentThreadIn()) {
|
||||
mRunnable->Run();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check we don't have tail dispatching here. Otherwise we will deadlock
|
||||
// ourself when spinning the loop below.
|
||||
MOZ_ASSERT(!aThread->RequiresTailDispatchFromCurrentThread());
|
||||
|
||||
aThread->Dispatch(RefPtr<nsIRunnable>(this).forget());
|
||||
mozilla::MonitorAutoLock lock(mMonitor);
|
||||
while (!mDone) {
|
||||
lock.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
static void DispatchToThread(nsIEventTarget* aThread,
|
||||
nsIRunnable* aRunnable,
|
||||
bool aForceDispatch = false)
|
||||
{
|
||||
RefPtr<SyncRunnable> s(new SyncRunnable(aRunnable));
|
||||
s->DispatchToThread(aThread, aForceDispatch);
|
||||
}
|
||||
|
||||
static void DispatchToThread(AbstractThread* aThread,
|
||||
nsIRunnable* aRunnable,
|
||||
bool aForceDispatch = false)
|
||||
{
|
||||
RefPtr<SyncRunnable> s(new SyncRunnable(aRunnable));
|
||||
s->DispatchToThread(aThread, aForceDispatch);
|
||||
}
|
||||
|
||||
protected:
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
mRunnable->Run();
|
||||
|
||||
mozilla::MonitorAutoLock lock(mMonitor);
|
||||
MOZ_ASSERT(!mDone);
|
||||
|
||||
mDone = true;
|
||||
mMonitor.Notify();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIRunnable> mRunnable;
|
||||
mozilla::Monitor mMonitor;
|
||||
bool mDone;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_SyncRunnable_h
|
||||
276
xpcom/threads/TaskDispatcher.h
Normal file
276
xpcom/threads/TaskDispatcher.h
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#if !defined(TaskDispatcher_h_)
|
||||
#define TaskDispatcher_h_
|
||||
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/*
|
||||
* A classic approach to cross-thread communication is to dispatch asynchronous
|
||||
* runnables to perform updates on other threads. This generally works well, but
|
||||
* there are sometimes reasons why we might want to delay the actual dispatch of
|
||||
* these tasks until a specified moment. At present, this is primarily useful to
|
||||
* ensure that mirrored state gets updated atomically - but there may be other
|
||||
* applications as well.
|
||||
*
|
||||
* TaskDispatcher is a general abstract class that accepts tasks and dispatches
|
||||
* them at some later point. These groups of tasks are per-target-thread, and
|
||||
* contain separate queues for several kinds of tasks (see comments below). - "state change tasks" (which
|
||||
* run first, and are intended to be used to update the value held by mirrors),
|
||||
* and regular tasks, which are other arbitrary operations that the are gated
|
||||
* to run after all the state changes have completed.
|
||||
*/
|
||||
class TaskDispatcher
|
||||
{
|
||||
public:
|
||||
TaskDispatcher() {}
|
||||
virtual ~TaskDispatcher() {}
|
||||
|
||||
// Direct tasks are run directly (rather than dispatched asynchronously) when
|
||||
// the tail dispatcher fires. A direct task may cause other tasks to be added
|
||||
// to the tail dispatcher.
|
||||
virtual void AddDirectTask(already_AddRefed<nsIRunnable> aRunnable) = 0;
|
||||
|
||||
// State change tasks are dispatched asynchronously always run before regular
|
||||
// tasks. They are intended to be used to update the value held by mirrors
|
||||
// before any other dispatched tasks are run on the target thread.
|
||||
virtual void AddStateChangeTask(AbstractThread* aThread,
|
||||
already_AddRefed<nsIRunnable> aRunnable) = 0;
|
||||
|
||||
// Regular tasks are dispatched asynchronously, and run after state change
|
||||
// tasks.
|
||||
virtual void AddTask(AbstractThread* aThread,
|
||||
already_AddRefed<nsIRunnable> aRunnable,
|
||||
AbstractThread::DispatchFailureHandling aFailureHandling = AbstractThread::AssertDispatchSuccess) = 0;
|
||||
|
||||
virtual void DispatchTasksFor(AbstractThread* aThread) = 0;
|
||||
virtual bool HasTasksFor(AbstractThread* aThread) = 0;
|
||||
virtual void DrainDirectTasks() = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* AutoTaskDispatcher is a stack-scoped TaskDispatcher implementation that fires
|
||||
* its queued tasks when it is popped off the stack.
|
||||
*/
|
||||
class AutoTaskDispatcher : public TaskDispatcher
|
||||
{
|
||||
public:
|
||||
explicit AutoTaskDispatcher(bool aIsTailDispatcher = false)
|
||||
: mIsTailDispatcher(aIsTailDispatcher)
|
||||
{}
|
||||
|
||||
~AutoTaskDispatcher()
|
||||
{
|
||||
// Given that direct tasks may trigger other code that uses the tail
|
||||
// dispatcher, it's better to avoid processing them in the tail dispatcher's
|
||||
// destructor. So we require TailDispatchers to manually invoke
|
||||
// DrainDirectTasks before the AutoTaskDispatcher gets destroyed. In truth,
|
||||
// this is only necessary in the case where this AutoTaskDispatcher can be
|
||||
// accessed by the direct tasks it dispatches (true for TailDispatchers, but
|
||||
// potentially not true for other hypothetical AutoTaskDispatchers). Feel
|
||||
// free to loosen this restriction to apply only to mIsTailDispatcher if a
|
||||
// use-case requires it.
|
||||
MOZ_ASSERT(!HaveDirectTasks());
|
||||
|
||||
for (size_t i = 0; i < mTaskGroups.Length(); ++i) {
|
||||
DispatchTaskGroup(Move(mTaskGroups[i]));
|
||||
}
|
||||
}
|
||||
|
||||
bool HaveDirectTasks() const
|
||||
{
|
||||
return mDirectTasks.isSome() && !mDirectTasks->empty();
|
||||
}
|
||||
|
||||
void DrainDirectTasks() override
|
||||
{
|
||||
while (HaveDirectTasks()) {
|
||||
nsCOMPtr<nsIRunnable> r = mDirectTasks->front();
|
||||
mDirectTasks->pop();
|
||||
r->Run();
|
||||
}
|
||||
}
|
||||
|
||||
void AddDirectTask(already_AddRefed<nsIRunnable> aRunnable) override
|
||||
{
|
||||
if (mDirectTasks.isNothing()) {
|
||||
mDirectTasks.emplace();
|
||||
}
|
||||
mDirectTasks->push(Move(aRunnable));
|
||||
}
|
||||
|
||||
void AddStateChangeTask(AbstractThread* aThread,
|
||||
already_AddRefed<nsIRunnable> aRunnable) override
|
||||
{
|
||||
EnsureTaskGroup(aThread).mStateChangeTasks.AppendElement(aRunnable);
|
||||
}
|
||||
|
||||
void AddTask(AbstractThread* aThread,
|
||||
already_AddRefed<nsIRunnable> aRunnable,
|
||||
AbstractThread::DispatchFailureHandling aFailureHandling) override
|
||||
{
|
||||
PerThreadTaskGroup& group = EnsureTaskGroup(aThread);
|
||||
group.mRegularTasks.AppendElement(aRunnable);
|
||||
|
||||
// The task group needs to assert dispatch success if any of the runnables
|
||||
// it's dispatching want to assert it.
|
||||
if (aFailureHandling == AbstractThread::AssertDispatchSuccess) {
|
||||
group.mFailureHandling = AbstractThread::AssertDispatchSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
bool HasTasksFor(AbstractThread* aThread) override
|
||||
{
|
||||
return !!GetTaskGroup(aThread) ||
|
||||
(aThread == AbstractThread::GetCurrent() && HaveDirectTasks());
|
||||
}
|
||||
|
||||
void DispatchTasksFor(AbstractThread* aThread) override
|
||||
{
|
||||
for (size_t i = 0; i < mTaskGroups.Length(); ++i) {
|
||||
if (mTaskGroups[i]->mThread == aThread) {
|
||||
DispatchTaskGroup(Move(mTaskGroups[i]));
|
||||
mTaskGroups.RemoveElementAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
struct PerThreadTaskGroup
|
||||
{
|
||||
public:
|
||||
explicit PerThreadTaskGroup(AbstractThread* aThread)
|
||||
: mThread(aThread), mFailureHandling(AbstractThread::DontAssertDispatchSuccess)
|
||||
{
|
||||
MOZ_COUNT_CTOR(PerThreadTaskGroup);
|
||||
}
|
||||
|
||||
~PerThreadTaskGroup() { MOZ_COUNT_DTOR(PerThreadTaskGroup); }
|
||||
|
||||
RefPtr<AbstractThread> mThread;
|
||||
nsTArray<nsCOMPtr<nsIRunnable>> mStateChangeTasks;
|
||||
nsTArray<nsCOMPtr<nsIRunnable>> mRegularTasks;
|
||||
AbstractThread::DispatchFailureHandling mFailureHandling;
|
||||
};
|
||||
|
||||
class TaskGroupRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
explicit TaskGroupRunnable(UniquePtr<PerThreadTaskGroup>&& aTasks) : mTasks(Move(aTasks)) {}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
// State change tasks get run all together before any code is run, so
|
||||
// that all state changes are made in an atomic unit.
|
||||
for (size_t i = 0; i < mTasks->mStateChangeTasks.Length(); ++i) {
|
||||
mTasks->mStateChangeTasks[i]->Run();
|
||||
}
|
||||
|
||||
// Once the state changes have completed, drain any direct tasks
|
||||
// generated by those state changes (i.e. watcher notification tasks).
|
||||
// This needs to be outside the loop because we don't want to run code
|
||||
// that might observe intermediate states.
|
||||
MaybeDrainDirectTasks();
|
||||
|
||||
for (size_t i = 0; i < mTasks->mRegularTasks.Length(); ++i) {
|
||||
mTasks->mRegularTasks[i]->Run();
|
||||
|
||||
// Scope direct tasks tightly to the task that generated them.
|
||||
MaybeDrainDirectTasks();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
void MaybeDrainDirectTasks()
|
||||
{
|
||||
AbstractThread* currentThread = AbstractThread::GetCurrent();
|
||||
if (currentThread) {
|
||||
currentThread->TailDispatcher().DrainDirectTasks();
|
||||
}
|
||||
}
|
||||
|
||||
UniquePtr<PerThreadTaskGroup> mTasks;
|
||||
};
|
||||
|
||||
PerThreadTaskGroup& EnsureTaskGroup(AbstractThread* aThread)
|
||||
{
|
||||
PerThreadTaskGroup* existing = GetTaskGroup(aThread);
|
||||
if (existing) {
|
||||
return *existing;
|
||||
}
|
||||
|
||||
mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread));
|
||||
return *mTaskGroups.LastElement();
|
||||
}
|
||||
|
||||
PerThreadTaskGroup* GetTaskGroup(AbstractThread* aThread)
|
||||
{
|
||||
for (size_t i = 0; i < mTaskGroups.Length(); ++i) {
|
||||
if (mTaskGroups[i]->mThread == aThread) {
|
||||
return mTaskGroups[i].get();
|
||||
}
|
||||
}
|
||||
|
||||
// Not found.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DispatchTaskGroup(UniquePtr<PerThreadTaskGroup> aGroup)
|
||||
{
|
||||
RefPtr<AbstractThread> thread = aGroup->mThread;
|
||||
|
||||
AbstractThread::DispatchFailureHandling failureHandling = aGroup->mFailureHandling;
|
||||
AbstractThread::DispatchReason reason = mIsTailDispatcher ? AbstractThread::TailDispatch
|
||||
: AbstractThread::NormalDispatch;
|
||||
nsCOMPtr<nsIRunnable> r = new TaskGroupRunnable(Move(aGroup));
|
||||
thread->Dispatch(r.forget(), failureHandling, reason);
|
||||
}
|
||||
|
||||
// Direct tasks. We use a Maybe<> because (a) this class is hot, (b)
|
||||
// mDirectTasks often doesn't get anything put into it, and (c) the
|
||||
// std::queue implementation in GNU libstdc++ does two largish heap
|
||||
// allocations when creating a new std::queue.
|
||||
mozilla::Maybe<std::queue<nsCOMPtr<nsIRunnable>>> mDirectTasks;
|
||||
|
||||
// Task groups, organized by thread.
|
||||
nsTArray<UniquePtr<PerThreadTaskGroup>> mTaskGroups;
|
||||
|
||||
// True if this TaskDispatcher represents the tail dispatcher for the thread
|
||||
// upon which it runs.
|
||||
const bool mIsTailDispatcher;
|
||||
};
|
||||
|
||||
// Little utility class to allow declaring AutoTaskDispatcher as a default
|
||||
// parameter for methods that take a TaskDispatcher&.
|
||||
template<typename T>
|
||||
class PassByRef
|
||||
{
|
||||
public:
|
||||
PassByRef() {}
|
||||
operator T&() { return mVal; }
|
||||
private:
|
||||
T mVal;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
271
xpcom/threads/TaskQueue.cpp
Normal file
271
xpcom/threads/TaskQueue.cpp
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
/* -*- 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 "mozilla/TaskQueue.h"
|
||||
|
||||
#include "nsIEventTarget.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class TaskQueue::EventTargetWrapper final : public nsIEventTarget
|
||||
{
|
||||
RefPtr<TaskQueue> mTaskQueue;
|
||||
|
||||
~EventTargetWrapper()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
explicit EventTargetWrapper(TaskQueue* aTaskQueue)
|
||||
: mTaskQueue(aTaskQueue)
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue);
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) override
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> ref = aEvent;
|
||||
return Dispatch(ref.forget(), aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> runnable = aEvent;
|
||||
MonitorAutoLock mon(mTaskQueue->mQueueMonitor);
|
||||
return mTaskQueue->DispatchLocked(/* passed by ref */runnable,
|
||||
DontAssertDispatchSuccess,
|
||||
NormalDispatch);
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t aFlags) override
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
IsOnCurrentThread(bool* aResult) override
|
||||
{
|
||||
*aResult = mTaskQueue->IsCurrentThreadIn();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(TaskQueue::EventTargetWrapper, nsIEventTarget)
|
||||
|
||||
TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
|
||||
bool aRequireTailDispatch)
|
||||
: AbstractThread(aRequireTailDispatch)
|
||||
, mTarget(aTarget)
|
||||
, mQueueMonitor("TaskQueue::Queue")
|
||||
, mTailDispatcher(nullptr)
|
||||
, mIsRunning(false)
|
||||
, mIsShutdown(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(TaskQueue);
|
||||
}
|
||||
|
||||
TaskQueue::~TaskQueue()
|
||||
{
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
MOZ_ASSERT(mIsShutdown);
|
||||
MOZ_COUNT_DTOR(TaskQueue);
|
||||
}
|
||||
|
||||
TaskDispatcher&
|
||||
TaskQueue::TailDispatcher()
|
||||
{
|
||||
MOZ_ASSERT(IsCurrentThreadIn());
|
||||
MOZ_ASSERT(mTailDispatcher);
|
||||
return *mTailDispatcher;
|
||||
}
|
||||
|
||||
// Note aRunnable is passed by ref to support conditional ownership transfer.
|
||||
// See Dispatch() in TaskQueue.h for more details.
|
||||
nsresult
|
||||
TaskQueue::DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
|
||||
DispatchFailureHandling aFailureHandling,
|
||||
DispatchReason aReason)
|
||||
{
|
||||
mQueueMonitor.AssertCurrentThreadOwns();
|
||||
if (mIsShutdown) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AbstractThread* currentThread;
|
||||
if (aReason != TailDispatch && (currentThread = GetCurrent()) && RequiresTailDispatch(currentThread)) {
|
||||
currentThread->TailDispatcher().AddTask(this, aRunnable.forget(), aFailureHandling);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mTasks.push(aRunnable.forget());
|
||||
if (mIsRunning) {
|
||||
return NS_OK;
|
||||
}
|
||||
RefPtr<nsIRunnable> runner(new Runner(this));
|
||||
nsresult rv = mTarget->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to dispatch runnable to run TaskQueue");
|
||||
return rv;
|
||||
}
|
||||
mIsRunning = true;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
TaskQueue::AwaitIdle()
|
||||
{
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
AwaitIdleLocked();
|
||||
}
|
||||
|
||||
void
|
||||
TaskQueue::AwaitIdleLocked()
|
||||
{
|
||||
// Make sure there are no tasks for this queue waiting in the caller's tail
|
||||
// dispatcher.
|
||||
MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
|
||||
!AbstractThread::GetCurrent()->HasTailTasksFor(this));
|
||||
|
||||
mQueueMonitor.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mIsRunning || mTasks.empty());
|
||||
while (mIsRunning) {
|
||||
mQueueMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TaskQueue::AwaitShutdownAndIdle()
|
||||
{
|
||||
MOZ_ASSERT(!IsCurrentThreadIn());
|
||||
// Make sure there are no tasks for this queue waiting in the caller's tail
|
||||
// dispatcher.
|
||||
MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
|
||||
!AbstractThread::GetCurrent()->HasTailTasksFor(this));
|
||||
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
while (!mIsShutdown) {
|
||||
mQueueMonitor.Wait();
|
||||
}
|
||||
AwaitIdleLocked();
|
||||
}
|
||||
|
||||
RefPtr<ShutdownPromise>
|
||||
TaskQueue::BeginShutdown()
|
||||
{
|
||||
// Dispatch any tasks for this queue waiting in the caller's tail dispatcher,
|
||||
// since this is the last opportunity to do so.
|
||||
if (AbstractThread* currentThread = AbstractThread::GetCurrent()) {
|
||||
currentThread->TailDispatchTasksFor(this);
|
||||
}
|
||||
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
mIsShutdown = true;
|
||||
RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
|
||||
MaybeResolveShutdown();
|
||||
mon.NotifyAll();
|
||||
return p;
|
||||
}
|
||||
|
||||
bool
|
||||
TaskQueue::IsEmpty()
|
||||
{
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
return mTasks.empty();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
TaskQueue::ImpreciseLengthForHeuristics()
|
||||
{
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
return mTasks.size();
|
||||
}
|
||||
|
||||
bool
|
||||
TaskQueue::IsCurrentThreadIn()
|
||||
{
|
||||
bool in = NS_GetCurrentThread() == mRunningThread;
|
||||
return in;
|
||||
}
|
||||
|
||||
already_AddRefed<nsIEventTarget>
|
||||
TaskQueue::WrapAsEventTarget()
|
||||
{
|
||||
nsCOMPtr<nsIEventTarget> ref = new EventTargetWrapper(this);
|
||||
return ref.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
TaskQueue::Runner::Run()
|
||||
{
|
||||
RefPtr<nsIRunnable> event;
|
||||
{
|
||||
MonitorAutoLock mon(mQueue->mQueueMonitor);
|
||||
MOZ_ASSERT(mQueue->mIsRunning);
|
||||
if (mQueue->mTasks.size() == 0) {
|
||||
mQueue->mIsRunning = false;
|
||||
mQueue->MaybeResolveShutdown();
|
||||
mon.NotifyAll();
|
||||
return NS_OK;
|
||||
}
|
||||
event = mQueue->mTasks.front().forget();
|
||||
mQueue->mTasks.pop();
|
||||
}
|
||||
MOZ_ASSERT(event);
|
||||
|
||||
// Note that dropping the queue monitor before running the task, and
|
||||
// taking the monitor again after the task has run ensures we have memory
|
||||
// fences enforced. This means that if the object we're calling wasn't
|
||||
// designed to be threadsafe, it will be, provided we're only calling it
|
||||
// in this task queue.
|
||||
{
|
||||
AutoTaskGuard g(mQueue);
|
||||
event->Run();
|
||||
}
|
||||
|
||||
// Drop the reference to event. The event will hold a reference to the
|
||||
// object it's calling, and we don't want to keep it alive, it may be
|
||||
// making assumptions what holds references to it. This is especially
|
||||
// the case if the object is waiting for us to shutdown, so that it
|
||||
// can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case).
|
||||
event = nullptr;
|
||||
|
||||
{
|
||||
MonitorAutoLock mon(mQueue->mQueueMonitor);
|
||||
if (mQueue->mTasks.size() == 0) {
|
||||
// No more events to run. Exit the task runner.
|
||||
mQueue->mIsRunning = false;
|
||||
mQueue->MaybeResolveShutdown();
|
||||
mon.NotifyAll();
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// There's at least one more event that we can run. Dispatch this Runner
|
||||
// to the target again to ensure it runs again. Note that we don't just
|
||||
// run in a loop here so that we don't hog the target. This means we may
|
||||
// run on another thread next time, but we rely on the memory fences from
|
||||
// mQueueMonitor for thread safety of non-threadsafe tasks.
|
||||
nsresult rv = mQueue->mTarget->Dispatch(this, NS_DISPATCH_AT_END);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Failed to dispatch, shutdown!
|
||||
MonitorAutoLock mon(mQueue->mQueueMonitor);
|
||||
mQueue->mIsRunning = false;
|
||||
mQueue->mIsShutdown = true;
|
||||
mQueue->MaybeResolveShutdown();
|
||||
mon.NotifyAll();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
203
xpcom/threads/TaskQueue.h
Normal file
203
xpcom/threads/TaskQueue.h
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef TaskQueue_h_
|
||||
#define TaskQueue_h_
|
||||
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/TaskDispatcher.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
class nsIEventTarget;
|
||||
class nsIRunnable;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
typedef MozPromise<bool, bool, false> ShutdownPromise;
|
||||
|
||||
// Abstracts executing runnables in order on an arbitrary event target. The
|
||||
// runnables dispatched to the TaskQueue will be executed in the order in which
|
||||
// they're received, and are guaranteed to not be executed concurrently.
|
||||
// They may be executed on different threads, and a memory barrier is used
|
||||
// to make this threadsafe for objects that aren't already threadsafe.
|
||||
//
|
||||
// Note, since a TaskQueue can also be converted to an nsIEventTarget using
|
||||
// WrapAsEventTarget() its possible to construct a hierarchy of TaskQueues.
|
||||
// Consider these three TaskQueues:
|
||||
//
|
||||
// TQ1 dispatches to the main thread
|
||||
// TQ2 dispatches to TQ1
|
||||
// TQ3 dispatches to TQ1
|
||||
//
|
||||
// This ensures there is only ever a single runnable from the entire chain on
|
||||
// the main thread. It also ensures that TQ2 and TQ3 only have a single runnable
|
||||
// in TQ1 at any time.
|
||||
//
|
||||
// This arrangement lets you prioritize work by dispatching runnables directly
|
||||
// to TQ1. You can issue many runnables for important work. Meanwhile the TQ2
|
||||
// and TQ3 work will always execute at most one runnable and then yield.
|
||||
class TaskQueue : public AbstractThread
|
||||
{
|
||||
class EventTargetWrapper;
|
||||
|
||||
public:
|
||||
explicit TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
|
||||
bool aSupportsTailDispatch = false);
|
||||
|
||||
TaskDispatcher& TailDispatcher() override;
|
||||
|
||||
TaskQueue* AsTaskQueue() override { return this; }
|
||||
|
||||
void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
|
||||
DispatchReason aReason = NormalDispatch) override
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> r = aRunnable;
|
||||
{
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
nsresult rv = DispatchLocked(/* passed by ref */r, aFailureHandling, aReason);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv));
|
||||
Unused << rv;
|
||||
}
|
||||
// If the ownership of |r| is not transferred in DispatchLocked() due to
|
||||
// dispatch failure, it will be deleted here outside the lock. We do so
|
||||
// since the destructor of the runnable might access TaskQueue and result
|
||||
// in deadlocks.
|
||||
}
|
||||
|
||||
// Puts the queue in a shutdown state and returns immediately. The queue will
|
||||
// remain alive at least until all the events are drained, because the Runners
|
||||
// hold a strong reference to the task queue, and one of them is always held
|
||||
// by the target event queue when the task queue is non-empty.
|
||||
//
|
||||
// The returned promise is resolved when the queue goes empty.
|
||||
RefPtr<ShutdownPromise> BeginShutdown();
|
||||
|
||||
// Blocks until all task finish executing.
|
||||
void AwaitIdle();
|
||||
|
||||
// Blocks until the queue is flagged for shutdown and all tasks have finished
|
||||
// executing.
|
||||
void AwaitShutdownAndIdle();
|
||||
|
||||
bool IsEmpty();
|
||||
uint32_t ImpreciseLengthForHeuristics();
|
||||
|
||||
// Returns true if the current thread is currently running a Runnable in
|
||||
// the task queue.
|
||||
bool IsCurrentThreadIn() override;
|
||||
|
||||
// Create a new nsIEventTarget wrapper object that dispatches to this
|
||||
// TaskQueue.
|
||||
already_AddRefed<nsIEventTarget> WrapAsEventTarget();
|
||||
|
||||
protected:
|
||||
virtual ~TaskQueue();
|
||||
|
||||
|
||||
// Blocks until all task finish executing. Called internally by methods
|
||||
// that need to wait until the task queue is idle.
|
||||
// mQueueMonitor must be held.
|
||||
void AwaitIdleLocked();
|
||||
|
||||
nsresult DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
|
||||
DispatchFailureHandling aFailureHandling,
|
||||
DispatchReason aReason = NormalDispatch);
|
||||
|
||||
void MaybeResolveShutdown()
|
||||
{
|
||||
mQueueMonitor.AssertCurrentThreadOwns();
|
||||
if (mIsShutdown && !mIsRunning) {
|
||||
mShutdownPromise.ResolveIfExists(true, __func__);
|
||||
mTarget = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIEventTarget> mTarget;
|
||||
|
||||
// Monitor that protects the queue and mIsRunning;
|
||||
Monitor mQueueMonitor;
|
||||
|
||||
// Queue of tasks to run.
|
||||
std::queue<nsCOMPtr<nsIRunnable>> mTasks;
|
||||
|
||||
// The thread currently running the task queue. We store a reference
|
||||
// to this so that IsCurrentThreadIn() can tell if the current thread
|
||||
// is the thread currently running in the task queue.
|
||||
//
|
||||
// This may be read on any thread, but may only be written on mRunningThread.
|
||||
// The thread can't die while we're running in it, and we only use it for
|
||||
// pointer-comparison with the current thread anyway - so we make it atomic
|
||||
// and don't refcount it.
|
||||
Atomic<nsIThread*> mRunningThread;
|
||||
|
||||
// RAII class that gets instantiated for each dispatched task.
|
||||
class AutoTaskGuard : public AutoTaskDispatcher
|
||||
{
|
||||
public:
|
||||
explicit AutoTaskGuard(TaskQueue* aQueue)
|
||||
: AutoTaskDispatcher(/* aIsTailDispatcher = */ true), mQueue(aQueue)
|
||||
, mLastCurrentThread(nullptr)
|
||||
{
|
||||
// NB: We don't hold the lock to aQueue here. Don't do anything that
|
||||
// might require it.
|
||||
MOZ_ASSERT(!mQueue->mTailDispatcher);
|
||||
mQueue->mTailDispatcher = this;
|
||||
|
||||
mLastCurrentThread = sCurrentThreadTLS.get();
|
||||
sCurrentThreadTLS.set(aQueue);
|
||||
|
||||
MOZ_ASSERT(mQueue->mRunningThread == nullptr);
|
||||
mQueue->mRunningThread = NS_GetCurrentThread();
|
||||
}
|
||||
|
||||
~AutoTaskGuard()
|
||||
{
|
||||
DrainDirectTasks();
|
||||
|
||||
MOZ_ASSERT(mQueue->mRunningThread == NS_GetCurrentThread());
|
||||
mQueue->mRunningThread = nullptr;
|
||||
|
||||
sCurrentThreadTLS.set(mLastCurrentThread);
|
||||
mQueue->mTailDispatcher = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
TaskQueue* mQueue;
|
||||
AbstractThread* mLastCurrentThread;
|
||||
};
|
||||
|
||||
TaskDispatcher* mTailDispatcher;
|
||||
|
||||
// True if we've dispatched an event to the target to execute events from
|
||||
// the queue.
|
||||
bool mIsRunning;
|
||||
|
||||
// True if we've started our shutdown process.
|
||||
bool mIsShutdown;
|
||||
MozPromiseHolder<ShutdownPromise> mShutdownPromise;
|
||||
|
||||
class Runner : public Runnable {
|
||||
public:
|
||||
explicit Runner(TaskQueue* aQueue)
|
||||
: mQueue(aQueue)
|
||||
{
|
||||
}
|
||||
NS_IMETHOD Run() override;
|
||||
private:
|
||||
RefPtr<TaskQueue> mQueue;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // TaskQueue_h_
|
||||
726
xpcom/threads/ThreadStackHelper.cpp
Normal file
726
xpcom/threads/ThreadStackHelper.cpp
Normal file
|
|
@ -0,0 +1,726 @@
|
|||
/* -*- 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 "ThreadStackHelper.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "nsJSPrincipals.h"
|
||||
#include "nsScriptSecurityManager.h"
|
||||
#include "jsfriendapi.h"
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
#include "shared-libraries.h"
|
||||
#endif
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/MemoryChecking.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#ifdef __GNUC__
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wshadow"
|
||||
#endif
|
||||
|
||||
#if defined(MOZ_VALGRIND)
|
||||
# include <valgrind/valgrind.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef XP_LINUX
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
# pragma GCC diagnostic pop // -Wshadow
|
||||
#endif
|
||||
|
||||
#if defined(XP_LINUX) || defined(XP_MACOSX)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifndef SYS_gettid
|
||||
#define SYS_gettid __NR_gettid
|
||||
#endif
|
||||
#if defined(__arm__) && !defined(__NR_rt_tgsigqueueinfo)
|
||||
// Some NDKs don't define this constant even though the kernel supports it.
|
||||
#define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363)
|
||||
#endif
|
||||
#ifndef SYS_rt_tgsigqueueinfo
|
||||
#define SYS_rt_tgsigqueueinfo __NR_rt_tgsigqueueinfo
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
#if defined(MOZ_THREADSTACKHELPER_X86) || \
|
||||
defined(MOZ_THREADSTACKHELPER_X64) || \
|
||||
defined(MOZ_THREADSTACKHELPER_ARM)
|
||||
// On these architectures, the stack grows downwards (toward lower addresses).
|
||||
#define MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif
|
||||
#endif // MOZ_THREADSTACKHELPER_NATIVE
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void
|
||||
ThreadStackHelper::Startup()
|
||||
{
|
||||
#if defined(XP_LINUX)
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!sInitialized) {
|
||||
// TODO: centralize signal number allocation
|
||||
sFillStackSignum = SIGRTMIN + 4;
|
||||
if (sFillStackSignum > SIGRTMAX) {
|
||||
// Leave uninitialized
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
struct sigaction sigact = {};
|
||||
sigact.sa_sigaction = FillStackHandler;
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
MOZ_ALWAYS_TRUE(!::sigaction(sFillStackSignum, &sigact, nullptr));
|
||||
}
|
||||
sInitialized++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
ThreadStackHelper::Shutdown()
|
||||
{
|
||||
#if defined(XP_LINUX)
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInitialized == 1) {
|
||||
struct sigaction sigact = {};
|
||||
sigact.sa_handler = SIG_DFL;
|
||||
MOZ_ALWAYS_TRUE(!::sigaction(sFillStackSignum, &sigact, nullptr));
|
||||
}
|
||||
sInitialized--;
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadStackHelper::ThreadStackHelper()
|
||||
: mStackToFill(nullptr)
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
, mPseudoStack(mozilla_get_pseudo_stack())
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
, mContextToFill(nullptr)
|
||||
#endif
|
||||
, mMaxStackSize(Stack::sMaxInlineStorage)
|
||||
, mMaxBufferSize(512)
|
||||
#endif
|
||||
{
|
||||
#if defined(XP_LINUX)
|
||||
MOZ_ALWAYS_TRUE(!::sem_init(&mSem, 0, 0));
|
||||
mThreadID = ::syscall(SYS_gettid);
|
||||
#elif defined(XP_WIN)
|
||||
mInitialized = !!::DuplicateHandle(
|
||||
::GetCurrentProcess(), ::GetCurrentThread(),
|
||||
::GetCurrentProcess(), &mThreadID,
|
||||
THREAD_SUSPEND_RESUME
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
| THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION
|
||||
#endif
|
||||
, FALSE, 0);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
#elif defined(XP_MACOSX)
|
||||
mThreadID = mach_thread_self();
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
GetThreadStackBase();
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadStackHelper::~ThreadStackHelper()
|
||||
{
|
||||
#if defined(XP_LINUX)
|
||||
MOZ_ALWAYS_TRUE(!::sem_destroy(&mSem));
|
||||
#elif defined(XP_WIN)
|
||||
if (mInitialized) {
|
||||
MOZ_ALWAYS_TRUE(!!::CloseHandle(mThreadID));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
void ThreadStackHelper::GetThreadStackBase()
|
||||
{
|
||||
mThreadStackBase = 0;
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
void* stackAddr;
|
||||
size_t stackSize;
|
||||
::pthread_t pthr = ::pthread_self();
|
||||
::pthread_attr_t pthr_attr;
|
||||
NS_ENSURE_TRUE_VOID(!::pthread_getattr_np(pthr, &pthr_attr));
|
||||
if (!::pthread_attr_getstack(&pthr_attr, &stackAddr, &stackSize)) {
|
||||
#ifdef MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN
|
||||
mThreadStackBase = intptr_t(stackAddr) + stackSize;
|
||||
#else
|
||||
mThreadStackBase = intptr_t(stackAddr);
|
||||
#endif
|
||||
}
|
||||
MOZ_ALWAYS_TRUE(!::pthread_attr_destroy(&pthr_attr));
|
||||
|
||||
#elif defined(XP_WIN)
|
||||
::MEMORY_BASIC_INFORMATION meminfo = {};
|
||||
NS_ENSURE_TRUE_VOID(::VirtualQuery(&meminfo, &meminfo, sizeof(meminfo)));
|
||||
#ifdef MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN
|
||||
mThreadStackBase = intptr_t(meminfo.BaseAddress) + meminfo.RegionSize;
|
||||
#else
|
||||
mThreadStackBase = intptr_t(meminfo.AllocationBase);
|
||||
#endif
|
||||
|
||||
#elif defined(XP_MACOSX)
|
||||
::pthread_t pthr = ::pthread_self();
|
||||
mThreadStackBase = intptr_t(::pthread_get_stackaddr_np(pthr));
|
||||
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif // platform
|
||||
}
|
||||
#endif // MOZ_THREADSTACKHELPER_NATIVE
|
||||
|
||||
namespace {
|
||||
template<typename T>
|
||||
class ScopedSetPtr
|
||||
{
|
||||
private:
|
||||
T*& mPtr;
|
||||
public:
|
||||
ScopedSetPtr(T*& p, T* val) : mPtr(p) { mPtr = val; }
|
||||
~ScopedSetPtr() { mPtr = nullptr; }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void
|
||||
ThreadStackHelper::GetStack(Stack& aStack)
|
||||
{
|
||||
// Always run PrepareStackBuffer first to clear aStack
|
||||
if (!PrepareStackBuffer(aStack)) {
|
||||
// Skip and return empty aStack
|
||||
return;
|
||||
}
|
||||
|
||||
ScopedSetPtr<Stack> stackPtr(mStackToFill, &aStack);
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
if (!sInitialized) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
siginfo_t uinfo = {};
|
||||
uinfo.si_signo = sFillStackSignum;
|
||||
uinfo.si_code = SI_QUEUE;
|
||||
uinfo.si_pid = getpid();
|
||||
uinfo.si_uid = getuid();
|
||||
uinfo.si_value.sival_ptr = this;
|
||||
if (::syscall(SYS_rt_tgsigqueueinfo, uinfo.si_pid,
|
||||
mThreadID, sFillStackSignum, &uinfo)) {
|
||||
// rt_tgsigqueueinfo was added in Linux 2.6.31.
|
||||
// Could have failed because the syscall did not exist.
|
||||
return;
|
||||
}
|
||||
MOZ_ALWAYS_TRUE(!::sem_wait(&mSem));
|
||||
|
||||
#elif defined(XP_WIN)
|
||||
if (!mInitialized) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
if (::SuspendThread(mThreadID) == DWORD(-1)) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// SuspendThread is asynchronous, so the thread may still be running. Use
|
||||
// GetThreadContext to ensure it's really suspended.
|
||||
// See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743.
|
||||
CONTEXT context;
|
||||
context.ContextFlags = CONTEXT_CONTROL;
|
||||
if (::GetThreadContext(mThreadID, &context)) {
|
||||
FillStackBuffer();
|
||||
FillThreadContext();
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1));
|
||||
|
||||
#elif defined(XP_MACOSX)
|
||||
# if defined(MOZ_VALGRIND) && defined(RUNNING_ON_VALGRIND)
|
||||
if (RUNNING_ON_VALGRIND) {
|
||||
/* thread_suspend and thread_resume sometimes hang runs on Valgrind,
|
||||
for unknown reasons. So, just avoid them. See bug 1100911. */
|
||||
return;
|
||||
}
|
||||
# endif
|
||||
|
||||
if (::thread_suspend(mThreadID) != KERN_SUCCESS) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
FillStackBuffer();
|
||||
FillThreadContext();
|
||||
|
||||
MOZ_ALWAYS_TRUE(::thread_resume(mThreadID) == KERN_SUCCESS);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
class ThreadStackHelper::ThreadContext final
|
||||
{
|
||||
public:
|
||||
// TODO: provide per-platform definition of Context.
|
||||
typedef struct {} Context;
|
||||
|
||||
// Limit copied stack to 4kB
|
||||
static const size_t kMaxStackSize = 0x1000;
|
||||
// Limit unwound stack to 32 frames
|
||||
static const unsigned int kMaxStackFrames = 32;
|
||||
// Whether this structure contains valid data
|
||||
bool mValid;
|
||||
// Processor context
|
||||
Context mContext;
|
||||
// Stack area
|
||||
UniquePtr<uint8_t[]> mStack;
|
||||
// Start of stack area
|
||||
uintptr_t mStackBase;
|
||||
// Size of stack area
|
||||
size_t mStackSize;
|
||||
// End of stack area
|
||||
const void* mStackEnd;
|
||||
|
||||
ThreadContext()
|
||||
: mValid(false)
|
||||
, mStackBase(0)
|
||||
, mStackSize(0)
|
||||
, mStackEnd(nullptr) {}
|
||||
};
|
||||
#endif // MOZ_THREADSTACKHELPER_NATIVE
|
||||
|
||||
void
|
||||
ThreadStackHelper::GetNativeStack(Stack& aStack)
|
||||
{
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
ThreadContext context;
|
||||
context.mStack = MakeUnique<uint8_t[]>(ThreadContext::kMaxStackSize);
|
||||
|
||||
ScopedSetPtr<ThreadContext> contextPtr(mContextToFill, &context);
|
||||
|
||||
// Get pseudostack first and fill the thread context.
|
||||
GetStack(aStack);
|
||||
NS_ENSURE_TRUE_VOID(context.mValid);
|
||||
|
||||
// TODO: walk the saved stack frames.
|
||||
#endif // MOZ_THREADSTACKHELPER_NATIVE
|
||||
}
|
||||
|
||||
#ifdef XP_LINUX
|
||||
|
||||
int ThreadStackHelper::sInitialized;
|
||||
int ThreadStackHelper::sFillStackSignum;
|
||||
|
||||
void
|
||||
ThreadStackHelper::FillStackHandler(int aSignal, siginfo_t* aInfo,
|
||||
void* aContext)
|
||||
{
|
||||
ThreadStackHelper* const helper =
|
||||
reinterpret_cast<ThreadStackHelper*>(aInfo->si_value.sival_ptr);
|
||||
helper->FillStackBuffer();
|
||||
helper->FillThreadContext(aContext);
|
||||
::sem_post(&helper->mSem);
|
||||
}
|
||||
|
||||
#endif // XP_LINUX
|
||||
|
||||
bool
|
||||
ThreadStackHelper::PrepareStackBuffer(Stack& aStack)
|
||||
{
|
||||
// Return false to skip getting the stack and return an empty stack
|
||||
aStack.clear();
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
/* Normally, provided the profiler is enabled, it would be an error if we
|
||||
don't have a pseudostack here (the thread probably forgot to call
|
||||
profiler_register_thread). However, on B2G, profiling secondary threads
|
||||
may be disabled despite profiler being enabled. This is by-design and
|
||||
is not an error. */
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
if (!mPseudoStack) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
MOZ_ASSERT(mPseudoStack);
|
||||
if (!aStack.reserve(mMaxStackSize) ||
|
||||
!aStack.reserve(aStack.capacity()) || // reserve up to the capacity
|
||||
!aStack.EnsureBufferCapacity(mMaxBufferSize)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
IsChromeJSScript(JSScript* aScript)
|
||||
{
|
||||
// May be called from another thread or inside a signal handler.
|
||||
// We assume querying the script is safe but we must not manipulate it.
|
||||
|
||||
nsIScriptSecurityManager* const secman =
|
||||
nsScriptSecurityManager::GetScriptSecurityManager();
|
||||
NS_ENSURE_TRUE(secman, false);
|
||||
|
||||
JSPrincipals* const principals = JS_GetScriptPrincipals(aScript);
|
||||
return secman->IsSystemPrincipal(nsJSPrincipals::get(principals));
|
||||
}
|
||||
|
||||
// Get the full path after the URI scheme, if the URI matches the scheme.
|
||||
// For example, GetFullPathForScheme("a://b/c/d/e", "a://") returns "b/c/d/e".
|
||||
template <size_t LEN>
|
||||
const char*
|
||||
GetFullPathForScheme(const char* filename, const char (&scheme)[LEN]) {
|
||||
// Account for the null terminator included in LEN.
|
||||
if (!strncmp(filename, scheme, LEN - 1)) {
|
||||
return filename + LEN - 1;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the full path after a URI component, if the URI contains the component.
|
||||
// For example, GetPathAfterComponent("a://b/c/d/e", "/c/") returns "d/e".
|
||||
template <size_t LEN>
|
||||
const char*
|
||||
GetPathAfterComponent(const char* filename, const char (&component)[LEN]) {
|
||||
const char* found = nullptr;
|
||||
const char* next = strstr(filename, component);
|
||||
while (next) {
|
||||
// Move 'found' to end of the component, after the separator '/'.
|
||||
// 'LEN - 1' accounts for the null terminator included in LEN,
|
||||
found = next + LEN - 1;
|
||||
// Resume searching before the separator '/'.
|
||||
next = strstr(found - 1, component);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char*
|
||||
ThreadStackHelper::AppendJSEntry(const volatile StackEntry* aEntry,
|
||||
intptr_t& aAvailableBufferSize,
|
||||
const char* aPrevLabel)
|
||||
{
|
||||
// May be called from another thread or inside a signal handler.
|
||||
// We assume querying the script is safe but we must not manupulate it.
|
||||
// Also we must not allocate any memory from heap.
|
||||
MOZ_ASSERT(aEntry->isJs());
|
||||
|
||||
const char* label;
|
||||
JSScript* script = aEntry->script();
|
||||
if (!script) {
|
||||
label = "(profiling suppressed)";
|
||||
} else if (IsChromeJSScript(aEntry->script())) {
|
||||
const char* filename = JS_GetScriptFilename(aEntry->script());
|
||||
const unsigned lineno = JS_PCToLineNumber(aEntry->script(), aEntry->pc());
|
||||
MOZ_ASSERT(filename);
|
||||
|
||||
char buffer[128]; // Enough to fit longest js file name from the tree
|
||||
|
||||
// Some script names are in the form "foo -> bar -> baz".
|
||||
// Here we find the origin of these redirected scripts.
|
||||
const char* basename = GetPathAfterComponent(filename, " -> ");
|
||||
if (basename) {
|
||||
filename = basename;
|
||||
}
|
||||
|
||||
basename = GetFullPathForScheme(filename, "chrome://");
|
||||
if (!basename) {
|
||||
basename = GetFullPathForScheme(filename, "resource://");
|
||||
}
|
||||
if (!basename) {
|
||||
// If the (add-on) script is located under the {profile}/extensions
|
||||
// directory, extract the path after the /extensions/ part.
|
||||
basename = GetPathAfterComponent(filename, "/extensions/");
|
||||
}
|
||||
if (!basename) {
|
||||
// Only keep the file base name for paths outside the above formats.
|
||||
basename = strrchr(filename, '/');
|
||||
basename = basename ? basename + 1 : filename;
|
||||
// Look for Windows path separator as well.
|
||||
filename = strrchr(basename, '\\');
|
||||
if (filename) {
|
||||
basename = filename + 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t len = SprintfLiteral(buffer, "%s:%u", basename, lineno);
|
||||
if (len < sizeof(buffer)) {
|
||||
if (mStackToFill->IsSameAsEntry(aPrevLabel, buffer)) {
|
||||
return aPrevLabel;
|
||||
}
|
||||
|
||||
// Keep track of the required buffer size
|
||||
aAvailableBufferSize -= (len + 1);
|
||||
if (aAvailableBufferSize >= 0) {
|
||||
// Buffer is big enough.
|
||||
return mStackToFill->InfallibleAppendViaBuffer(buffer, len);
|
||||
}
|
||||
// Buffer is not big enough; fall through to using static label below.
|
||||
}
|
||||
// snprintf failed or buffer is not big enough.
|
||||
label = "(chrome script)";
|
||||
} else {
|
||||
label = "(content script)";
|
||||
}
|
||||
|
||||
if (mStackToFill->IsSameAsEntry(aPrevLabel, label)) {
|
||||
return aPrevLabel;
|
||||
}
|
||||
mStackToFill->infallibleAppend(label);
|
||||
return label;
|
||||
}
|
||||
|
||||
#endif // MOZ_THREADSTACKHELPER_PSEUDO
|
||||
|
||||
void
|
||||
ThreadStackHelper::FillStackBuffer()
|
||||
{
|
||||
MOZ_ASSERT(mStackToFill->empty());
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
size_t reservedSize = mStackToFill->capacity();
|
||||
size_t reservedBufferSize = mStackToFill->AvailableBufferSize();
|
||||
intptr_t availableBufferSize = intptr_t(reservedBufferSize);
|
||||
|
||||
// Go from front to back
|
||||
const volatile StackEntry* entry = mPseudoStack->mStack;
|
||||
const volatile StackEntry* end = entry + mPseudoStack->stackSize();
|
||||
// Deduplicate identical, consecutive frames
|
||||
const char* prevLabel = nullptr;
|
||||
for (; reservedSize-- && entry != end; entry++) {
|
||||
/* We only accept non-copy labels, including js::RunScript,
|
||||
because we only want static labels in the hang stack. */
|
||||
if (entry->isCopyLabel()) {
|
||||
continue;
|
||||
}
|
||||
if (entry->isJs()) {
|
||||
prevLabel = AppendJSEntry(entry, availableBufferSize, prevLabel);
|
||||
continue;
|
||||
}
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
if (mContextToFill) {
|
||||
mContextToFill->mStackEnd = entry->stackAddress();
|
||||
}
|
||||
#endif
|
||||
const char* const label = entry->label();
|
||||
if (mStackToFill->IsSameAsEntry(prevLabel, label)) {
|
||||
// Avoid duplicate labels to save space in the stack.
|
||||
continue;
|
||||
}
|
||||
mStackToFill->infallibleAppend(label);
|
||||
prevLabel = label;
|
||||
}
|
||||
|
||||
// end != entry if we exited early due to not enough reserved frames.
|
||||
// Expand the number of reserved frames for next time.
|
||||
mMaxStackSize = mStackToFill->capacity() + (end - entry);
|
||||
|
||||
// availableBufferSize < 0 if we needed a larger buffer than we reserved.
|
||||
// Calculate a new reserve size for next time.
|
||||
if (availableBufferSize < 0) {
|
||||
mMaxBufferSize = reservedBufferSize - availableBufferSize;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MOZ_ASAN_BLACKLIST void
|
||||
ThreadStackHelper::FillThreadContext(void* aContext)
|
||||
{
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
if (!mContextToFill) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if 0 // TODO: remove dependency on Breakpad structs.
|
||||
#if defined(XP_LINUX)
|
||||
const ucontext_t& context = *reinterpret_cast<ucontext_t*>(aContext);
|
||||
#if defined(MOZ_THREADSTACKHELPER_X86)
|
||||
mContextToFill->mContext.context_flags = MD_CONTEXT_X86_FULL;
|
||||
mContextToFill->mContext.edi = context.uc_mcontext.gregs[REG_EDI];
|
||||
mContextToFill->mContext.esi = context.uc_mcontext.gregs[REG_ESI];
|
||||
mContextToFill->mContext.ebx = context.uc_mcontext.gregs[REG_EBX];
|
||||
mContextToFill->mContext.edx = context.uc_mcontext.gregs[REG_EDX];
|
||||
mContextToFill->mContext.ecx = context.uc_mcontext.gregs[REG_ECX];
|
||||
mContextToFill->mContext.eax = context.uc_mcontext.gregs[REG_EAX];
|
||||
mContextToFill->mContext.ebp = context.uc_mcontext.gregs[REG_EBP];
|
||||
mContextToFill->mContext.eip = context.uc_mcontext.gregs[REG_EIP];
|
||||
mContextToFill->mContext.eflags = context.uc_mcontext.gregs[REG_EFL];
|
||||
mContextToFill->mContext.esp = context.uc_mcontext.gregs[REG_ESP];
|
||||
#elif defined(MOZ_THREADSTACKHELPER_X64)
|
||||
mContextToFill->mContext.context_flags = MD_CONTEXT_AMD64_FULL;
|
||||
mContextToFill->mContext.eflags = uint32_t(context.uc_mcontext.gregs[REG_EFL]);
|
||||
mContextToFill->mContext.rax = context.uc_mcontext.gregs[REG_RAX];
|
||||
mContextToFill->mContext.rcx = context.uc_mcontext.gregs[REG_RCX];
|
||||
mContextToFill->mContext.rdx = context.uc_mcontext.gregs[REG_RDX];
|
||||
mContextToFill->mContext.rbx = context.uc_mcontext.gregs[REG_RBX];
|
||||
mContextToFill->mContext.rsp = context.uc_mcontext.gregs[REG_RSP];
|
||||
mContextToFill->mContext.rbp = context.uc_mcontext.gregs[REG_RBP];
|
||||
mContextToFill->mContext.rsi = context.uc_mcontext.gregs[REG_RSI];
|
||||
mContextToFill->mContext.rdi = context.uc_mcontext.gregs[REG_RDI];
|
||||
memcpy(&mContextToFill->mContext.r8,
|
||||
&context.uc_mcontext.gregs[REG_R8], 8 * sizeof(int64_t));
|
||||
mContextToFill->mContext.rip = context.uc_mcontext.gregs[REG_RIP];
|
||||
#elif defined(MOZ_THREADSTACKHELPER_ARM)
|
||||
mContextToFill->mContext.context_flags = MD_CONTEXT_ARM_FULL;
|
||||
memcpy(&mContextToFill->mContext.iregs[0],
|
||||
&context.uc_mcontext.arm_r0, 17 * sizeof(int32_t));
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif // architecture
|
||||
|
||||
#elif defined(XP_WIN)
|
||||
// Breakpad context struct is based off of the Windows CONTEXT struct,
|
||||
// so we assume they are the same; do some sanity checks to make sure.
|
||||
static_assert(sizeof(ThreadContext::Context) == sizeof(::CONTEXT),
|
||||
"Context struct mismatch");
|
||||
static_assert(offsetof(ThreadContext::Context, context_flags) ==
|
||||
offsetof(::CONTEXT, ContextFlags),
|
||||
"Context struct mismatch");
|
||||
mContextToFill->mContext.context_flags = CONTEXT_FULL;
|
||||
NS_ENSURE_TRUE_VOID(::GetThreadContext(mThreadID,
|
||||
reinterpret_cast<::CONTEXT*>(&mContextToFill->mContext)));
|
||||
|
||||
#elif defined(XP_MACOSX)
|
||||
#if defined(MOZ_THREADSTACKHELPER_X86)
|
||||
const thread_state_flavor_t flavor = x86_THREAD_STATE32;
|
||||
x86_thread_state32_t state = {};
|
||||
mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT;
|
||||
#elif defined(MOZ_THREADSTACKHELPER_X64)
|
||||
const thread_state_flavor_t flavor = x86_THREAD_STATE64;
|
||||
x86_thread_state64_t state = {};
|
||||
mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT;
|
||||
#elif defined(MOZ_THREADSTACKHELPER_ARM)
|
||||
const thread_state_flavor_t flavor = ARM_THREAD_STATE;
|
||||
arm_thread_state_t state = {};
|
||||
mach_msg_type_number_t count = ARM_THREAD_STATE_COUNT;
|
||||
#endif
|
||||
NS_ENSURE_TRUE_VOID(KERN_SUCCESS == ::thread_get_state(
|
||||
mThreadID, flavor, reinterpret_cast<thread_state_t>(&state), &count));
|
||||
#if __DARWIN_UNIX03
|
||||
#define GET_REGISTER(s, r) ((s).__##r)
|
||||
#else
|
||||
#define GET_REGISTER(s, r) ((s).r)
|
||||
#endif
|
||||
#if defined(MOZ_THREADSTACKHELPER_X86)
|
||||
mContextToFill->mContext.context_flags = MD_CONTEXT_X86_FULL;
|
||||
mContextToFill->mContext.edi = GET_REGISTER(state, edi);
|
||||
mContextToFill->mContext.esi = GET_REGISTER(state, esi);
|
||||
mContextToFill->mContext.ebx = GET_REGISTER(state, ebx);
|
||||
mContextToFill->mContext.edx = GET_REGISTER(state, edx);
|
||||
mContextToFill->mContext.ecx = GET_REGISTER(state, ecx);
|
||||
mContextToFill->mContext.eax = GET_REGISTER(state, eax);
|
||||
mContextToFill->mContext.ebp = GET_REGISTER(state, ebp);
|
||||
mContextToFill->mContext.eip = GET_REGISTER(state, eip);
|
||||
mContextToFill->mContext.eflags = GET_REGISTER(state, eflags);
|
||||
mContextToFill->mContext.esp = GET_REGISTER(state, esp);
|
||||
#elif defined(MOZ_THREADSTACKHELPER_X64)
|
||||
mContextToFill->mContext.context_flags = MD_CONTEXT_AMD64_FULL;
|
||||
mContextToFill->mContext.eflags = uint32_t(GET_REGISTER(state, rflags));
|
||||
mContextToFill->mContext.rax = GET_REGISTER(state, rax);
|
||||
mContextToFill->mContext.rcx = GET_REGISTER(state, rcx);
|
||||
mContextToFill->mContext.rdx = GET_REGISTER(state, rdx);
|
||||
mContextToFill->mContext.rbx = GET_REGISTER(state, rbx);
|
||||
mContextToFill->mContext.rsp = GET_REGISTER(state, rsp);
|
||||
mContextToFill->mContext.rbp = GET_REGISTER(state, rbp);
|
||||
mContextToFill->mContext.rsi = GET_REGISTER(state, rsi);
|
||||
mContextToFill->mContext.rdi = GET_REGISTER(state, rdi);
|
||||
memcpy(&mContextToFill->mContext.r8,
|
||||
&GET_REGISTER(state, r8), 8 * sizeof(int64_t));
|
||||
mContextToFill->mContext.rip = GET_REGISTER(state, rip);
|
||||
#elif defined(MOZ_THREADSTACKHELPER_ARM)
|
||||
mContextToFill->mContext.context_flags = MD_CONTEXT_ARM_FULL;
|
||||
memcpy(mContextToFill->mContext.iregs,
|
||||
GET_REGISTER(state, r), 17 * sizeof(int32_t));
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif // architecture
|
||||
#undef GET_REGISTER
|
||||
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif // platform
|
||||
|
||||
intptr_t sp = 0;
|
||||
#if defined(MOZ_THREADSTACKHELPER_X86)
|
||||
sp = mContextToFill->mContext.esp;
|
||||
#elif defined(MOZ_THREADSTACKHELPER_X64)
|
||||
sp = mContextToFill->mContext.rsp;
|
||||
#elif defined(MOZ_THREADSTACKHELPER_ARM)
|
||||
sp = mContextToFill->mContext.iregs[13];
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif // architecture
|
||||
NS_ENSURE_TRUE_VOID(sp);
|
||||
NS_ENSURE_TRUE_VOID(mThreadStackBase);
|
||||
|
||||
size_t stackSize = std::min(intptr_t(ThreadContext::kMaxStackSize),
|
||||
std::abs(sp - mThreadStackBase));
|
||||
|
||||
if (mContextToFill->mStackEnd) {
|
||||
// Limit the start of stack to a certain location if specified.
|
||||
stackSize = std::min(intptr_t(stackSize),
|
||||
std::abs(sp - intptr_t(mContextToFill->mStackEnd)));
|
||||
}
|
||||
|
||||
#ifndef MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN
|
||||
// If if the stack grows upwards, and we need to recalculate our
|
||||
// stack copy's base address. Subtract sizeof(void*) so that the
|
||||
// location pointed to by sp is included.
|
||||
sp -= stackSize - sizeof(void*);
|
||||
#endif
|
||||
|
||||
#ifndef MOZ_ASAN
|
||||
memcpy(mContextToFill->mStack.get(), reinterpret_cast<void*>(sp), stackSize);
|
||||
// Valgrind doesn't care about the access outside the stack frame, but
|
||||
// the presence of uninitialised values on the stack does cause it to
|
||||
// later report a lot of false errors when Breakpad comes to unwind it.
|
||||
// So mark the extracted data as defined.
|
||||
MOZ_MAKE_MEM_DEFINED(mContextToFill->mStack.get(), stackSize);
|
||||
#else
|
||||
// ASan will flag memcpy for access outside of stack frames,
|
||||
// so roll our own memcpy here.
|
||||
intptr_t* dst = reinterpret_cast<intptr_t*>(&mContextToFill->mStack[0]);
|
||||
const intptr_t* src = reinterpret_cast<intptr_t*>(sp);
|
||||
for (intptr_t len = stackSize; len > 0; len -= sizeof(*src)) {
|
||||
*(dst++) = *(src++);
|
||||
}
|
||||
#endif
|
||||
|
||||
mContextToFill->mStackBase = uintptr_t(sp);
|
||||
mContextToFill->mStackSize = stackSize;
|
||||
mContextToFill->mValid = true;
|
||||
#endif
|
||||
#endif // MOZ_THREADSTACKHELPER_NATIVE
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
147
xpcom/threads/ThreadStackHelper.h
Normal file
147
xpcom/threads/ThreadStackHelper.h
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_ThreadStackHelper_h
|
||||
#define mozilla_ThreadStackHelper_h
|
||||
|
||||
#include "mozilla/ThreadHangStats.h"
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
#include <signal.h>
|
||||
#include <semaphore.h>
|
||||
#include <sys/types.h>
|
||||
#elif defined(XP_WIN)
|
||||
#include <windows.h>
|
||||
#elif defined(XP_MACOSX)
|
||||
#include <mach/mach.h>
|
||||
#endif
|
||||
|
||||
// Support pseudostack on these platforms.
|
||||
#if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX)
|
||||
# ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
# define MOZ_THREADSTACKHELPER_PSEUDO
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
# define MOZ_THREADSTACKHELPER_NATIVE
|
||||
# if defined(__i386__) || defined(_M_IX86)
|
||||
# define MOZ_THREADSTACKHELPER_X86
|
||||
# elif defined(__x86_64__) || defined(_M_X64)
|
||||
# define MOZ_THREADSTACKHELPER_X64
|
||||
# elif defined(__arm__) || defined(_M_ARM)
|
||||
# define MOZ_THREADSTACKHELPER_ARM
|
||||
# else
|
||||
// Unsupported architecture
|
||||
# undef MOZ_THREADSTACKHELPER_NATIVE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* ThreadStackHelper is used to retrieve the profiler pseudo-stack of a
|
||||
* thread, as an alternative of using the profiler to take a profile.
|
||||
* The target thread first declares an ThreadStackHelper instance;
|
||||
* then another thread can call ThreadStackHelper::GetStack to retrieve
|
||||
* the pseudo-stack of the target thread at that instant.
|
||||
*
|
||||
* Only non-copying labels are included in the stack, which means labels
|
||||
* with custom text and markers are not included.
|
||||
*/
|
||||
class ThreadStackHelper
|
||||
{
|
||||
public:
|
||||
typedef Telemetry::HangStack Stack;
|
||||
|
||||
private:
|
||||
Stack* mStackToFill;
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
const PseudoStack* const mPseudoStack;
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
class ThreadContext;
|
||||
// Set to non-null if GetStack should get the thread context.
|
||||
ThreadContext* mContextToFill;
|
||||
intptr_t mThreadStackBase;
|
||||
#endif
|
||||
size_t mMaxStackSize;
|
||||
size_t mMaxBufferSize;
|
||||
#endif
|
||||
|
||||
bool PrepareStackBuffer(Stack& aStack);
|
||||
void FillStackBuffer();
|
||||
void FillThreadContext(void* aContext = nullptr);
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
const char* AppendJSEntry(const volatile StackEntry* aEntry,
|
||||
intptr_t& aAvailableBufferSize,
|
||||
const char* aPrevLabel);
|
||||
#endif
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
void GetThreadStackBase();
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initialize ThreadStackHelper. Must be called from main thread.
|
||||
*/
|
||||
static void Startup();
|
||||
/**
|
||||
* Uninitialize ThreadStackHelper. Must be called from main thread.
|
||||
*/
|
||||
static void Shutdown();
|
||||
|
||||
/**
|
||||
* Create a ThreadStackHelper instance targeting the current thread.
|
||||
*/
|
||||
ThreadStackHelper();
|
||||
|
||||
~ThreadStackHelper();
|
||||
|
||||
/**
|
||||
* Retrieve the current pseudostack of the thread associated
|
||||
* with this ThreadStackHelper.
|
||||
*
|
||||
* @param aStack Stack instance to be filled.
|
||||
*/
|
||||
void GetStack(Stack& aStack);
|
||||
|
||||
/**
|
||||
* Retrieve the current native stack of the thread associated
|
||||
* with this ThreadStackHelper.
|
||||
*
|
||||
* @param aNativeStack Stack instance to be filled.
|
||||
*/
|
||||
void GetNativeStack(Stack& aStack);
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
private:
|
||||
static int sInitialized;
|
||||
static int sFillStackSignum;
|
||||
|
||||
static void FillStackHandler(int aSignal, siginfo_t* aInfo, void* aContext);
|
||||
|
||||
sem_t mSem;
|
||||
pid_t mThreadID;
|
||||
|
||||
#elif defined(XP_WIN)
|
||||
private:
|
||||
bool mInitialized;
|
||||
HANDLE mThreadID;
|
||||
|
||||
#elif defined(XP_MACOSX)
|
||||
private:
|
||||
thread_act_t mThreadID;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_ThreadStackHelper_h
|
||||
446
xpcom/threads/ThrottledEventQueue.cpp
Normal file
446
xpcom/threads/ThrottledEventQueue.cpp
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
/* -*- 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 "ThrottledEventQueue.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsEventQueue.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using mozilla::services::GetObserverService;
|
||||
|
||||
namespace {
|
||||
|
||||
static const char kShutdownTopic[] = "xpcom-shutdown";
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// The ThrottledEventQueue is designed with inner and outer objects:
|
||||
//
|
||||
// XPCOM code nsObserverService
|
||||
// | |
|
||||
// | |
|
||||
// v |
|
||||
// +-------+ |
|
||||
// | Outer | |
|
||||
// +-------+ |
|
||||
// | |
|
||||
// | +-------+ |
|
||||
// +-->| Inner |<--+
|
||||
// +-------+
|
||||
//
|
||||
// Client code references the outer nsIEventTarget which in turn references
|
||||
// an inner object. The inner object is also held alive by the observer
|
||||
// service.
|
||||
//
|
||||
// If the outer object is dereferenced and destroyed, it will trigger a
|
||||
// shutdown operation on the inner object. Similarly if the observer
|
||||
// service notifies that the browser is shutting down, then the inner
|
||||
// object also starts shutting down.
|
||||
//
|
||||
// Once the queue has drained we unregister from the observer service. If
|
||||
// the outer object is already gone, then the inner object is free'd at this
|
||||
// point. If the outer object still exists then calls fall back to the
|
||||
// ThrottledEventQueue's base target. We just don't queue things
|
||||
// any more. The inner is then released once the outer object is released.
|
||||
//
|
||||
// Note, we must keep the inner object alive and attached to the observer
|
||||
// service until the TaskQueue is fully shutdown and idle. We must delay
|
||||
// xpcom shutdown if the TaskQueue is in the middle of draining.
|
||||
class ThrottledEventQueue::Inner final : public nsIObserver
|
||||
{
|
||||
// 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
|
||||
{
|
||||
RefPtr<Inner> mInner;
|
||||
|
||||
public:
|
||||
explicit Executor(Inner* aInner)
|
||||
: mInner(aInner)
|
||||
{ }
|
||||
|
||||
NS_IMETHODIMP
|
||||
Run()
|
||||
{
|
||||
mInner->ExecuteRunnable();
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
mutable Mutex mMutex;
|
||||
mutable CondVar mIdleCondVar;
|
||||
|
||||
mozilla::CondVar mEventsAvailable;
|
||||
|
||||
// any thread, protected by mutex
|
||||
nsEventQueue mEventQueue;
|
||||
|
||||
// written on main thread, read on any thread
|
||||
nsCOMPtr<nsIEventTarget> mBaseTarget;
|
||||
|
||||
// any thread, protected by mutex
|
||||
nsCOMPtr<nsIRunnable> mExecutor;
|
||||
|
||||
// any thread, atomic
|
||||
Atomic<uint32_t> mExecutionDepth;
|
||||
|
||||
// any thread, protected by mutex
|
||||
bool mShutdownStarted;
|
||||
|
||||
explicit Inner(nsIEventTarget* aBaseTarget)
|
||||
: mMutex("ThrottledEventQueue")
|
||||
, mIdleCondVar(mMutex, "ThrottledEventQueue:Idle")
|
||||
, mEventsAvailable(mMutex, "[ThrottledEventQueue::Inner.mEventsAvailable]")
|
||||
, mEventQueue(mEventsAvailable, nsEventQueue::eNormalQueue)
|
||||
, mBaseTarget(aBaseTarget)
|
||||
, mExecutionDepth(0)
|
||||
, mShutdownStarted(false)
|
||||
{
|
||||
}
|
||||
|
||||
~Inner()
|
||||
{
|
||||
MOZ_ASSERT(!mExecutor);
|
||||
MOZ_ASSERT(mShutdownStarted);
|
||||
}
|
||||
|
||||
void
|
||||
ExecuteRunnable()
|
||||
{
|
||||
// Any thread
|
||||
nsCOMPtr<nsIRunnable> event;
|
||||
bool shouldShutdown = false;
|
||||
|
||||
#ifdef DEBUG
|
||||
bool currentThread = false;
|
||||
mBaseTarget->IsOnCurrentThread(¤tThread);
|
||||
MOZ_ASSERT(currentThread);
|
||||
#endif
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
// We only dispatch an executor runnable when we know there is something
|
||||
// in the queue, so this should never fail.
|
||||
MOZ_ALWAYS_TRUE(mEventQueue.GetPendingEvent(getter_AddRefs(event), lock));
|
||||
|
||||
// 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.HasPendingEvent(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. We might also need to shutdown after running the
|
||||
// last event.
|
||||
else {
|
||||
shouldShutdown = mShutdownStarted;
|
||||
// Note, this breaks a ref cycle.
|
||||
mExecutor = nullptr;
|
||||
mIdleCondVar.NotifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the event now that we have unlocked.
|
||||
++mExecutionDepth;
|
||||
Unused << event->Run();
|
||||
--mExecutionDepth;
|
||||
|
||||
// If shutdown was started and the queue is now empty we can now
|
||||
// finalize the shutdown. This is performed separately at the end
|
||||
// of the method in order to wait for the event to finish running.
|
||||
if (shouldShutdown) {
|
||||
MOZ_ASSERT(IsEmpty());
|
||||
NS_DispatchToMainThread(NewRunnableMethod(this, &Inner::ShutdownComplete));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ShutdownComplete()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(IsEmpty());
|
||||
nsCOMPtr<nsIObserverService> obs = GetObserverService();
|
||||
obs->RemoveObserver(this, kShutdownTopic);
|
||||
}
|
||||
|
||||
public:
|
||||
static already_AddRefed<Inner>
|
||||
Create(nsIEventTarget* aBaseTarget)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (ClearOnShutdown_Internal::sCurrentShutdownPhase != ShutdownPhase::NotInShutdown) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = GetObserverService();
|
||||
if (NS_WARN_IF(!obs)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Inner> ref = new Inner(aBaseTarget);
|
||||
|
||||
nsresult rv = obs->AddObserver(ref, kShutdownTopic,
|
||||
false /* means OS will hold a strong ref */);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
ref->MaybeStartShutdown();
|
||||
MOZ_ASSERT(ref->IsEmpty());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ref.forget();
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
Observe(nsISupports*, const char* aTopic, const char16_t*) override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!strcmp(aTopic, kShutdownTopic));
|
||||
|
||||
MaybeStartShutdown();
|
||||
|
||||
// Once shutdown begins we set the Atomic<bool> mShutdownStarted flag.
|
||||
// This prevents any new runnables from being dispatched into the
|
||||
// TaskQueue. Therefore this loop should be finite.
|
||||
while (!IsEmpty()) {
|
||||
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
MaybeStartShutdown()
|
||||
{
|
||||
// Any thread
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (mShutdownStarted) {
|
||||
return;
|
||||
}
|
||||
mShutdownStarted = true;
|
||||
|
||||
// We are marked for shutdown now, but we are still processing runnables.
|
||||
// Return for now. The shutdown will be completed once the queue is
|
||||
// drained.
|
||||
if (mExecutor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The queue is empty, so we can complete immediately.
|
||||
NS_DispatchToMainThread(NewRunnableMethod(this, &Inner::ShutdownComplete));
|
||||
}
|
||||
|
||||
bool
|
||||
IsEmpty() const
|
||||
{
|
||||
// Any thread
|
||||
return Length() == 0;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Length() const
|
||||
{
|
||||
// Any thread
|
||||
MutexAutoLock lock(mMutex);
|
||||
return mEventQueue.Count(lock);
|
||||
}
|
||||
|
||||
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) {
|
||||
mIdleCondVar.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
|
||||
{
|
||||
// Any thread
|
||||
nsCOMPtr<nsIRunnable> r = aEvent;
|
||||
return Dispatch(r.forget(), aFlags);
|
||||
}
|
||||
|
||||
nsresult
|
||||
Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
|
||||
{
|
||||
MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL ||
|
||||
aFlags == NS_DISPATCH_AT_END);
|
||||
|
||||
// Any thread
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
// If we are shutting down, just fall back to our base target
|
||||
// directly.
|
||||
if (mShutdownStarted) {
|
||||
return mBaseTarget->Dispatch(Move(aEvent), aFlags);
|
||||
}
|
||||
|
||||
// We are not currently processing events, so we must start
|
||||
// operating on our base target. This is fallible, so do
|
||||
// it first. Our lock will prevent the executor from accessing
|
||||
// the event queue before we add the event below.
|
||||
if (!mExecutor) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Only add the event to the underlying queue if are able to
|
||||
// dispatch to our base target.
|
||||
mEventQueue.PutEvent(Move(aEvent), 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;
|
||||
}
|
||||
|
||||
nsresult
|
||||
IsOnCurrentThread(bool* aResult)
|
||||
{
|
||||
// Any thread
|
||||
|
||||
bool shutdownAndIdle = false;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
shutdownAndIdle = mShutdownStarted && mEventQueue.Count(lock) == 0;
|
||||
}
|
||||
|
||||
bool onBaseTarget = false;
|
||||
nsresult rv = mBaseTarget->IsOnCurrentThread(&onBaseTarget);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We consider the current stack on this event target if are on
|
||||
// the base target and one of the following is true
|
||||
// 1) We are currently running an event OR
|
||||
// 2) We are both shutting down and the queue is idle
|
||||
*aResult = onBaseTarget && (mExecutionDepth || shutdownAndIdle);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsIObserver);
|
||||
|
||||
NS_IMPL_ISUPPORTS(ThrottledEventQueue, nsIEventTarget);
|
||||
|
||||
ThrottledEventQueue::ThrottledEventQueue(already_AddRefed<Inner> aInner)
|
||||
: mInner(aInner)
|
||||
{
|
||||
MOZ_ASSERT(mInner);
|
||||
}
|
||||
|
||||
ThrottledEventQueue::~ThrottledEventQueue()
|
||||
{
|
||||
mInner->MaybeStartShutdown();
|
||||
}
|
||||
|
||||
void
|
||||
ThrottledEventQueue::MaybeStartShutdown()
|
||||
{
|
||||
return mInner->MaybeStartShutdown();
|
||||
}
|
||||
|
||||
already_AddRefed<ThrottledEventQueue>
|
||||
ThrottledEventQueue::Create(nsIEventTarget* aBaseTarget)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aBaseTarget);
|
||||
|
||||
RefPtr<Inner> inner = Inner::Create(aBaseTarget);
|
||||
if (NS_WARN_IF(!inner)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<ThrottledEventQueue> ref =
|
||||
new ThrottledEventQueue(inner.forget());
|
||||
return ref.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
ThrottledEventQueue::IsEmpty() const
|
||||
{
|
||||
return mInner->IsEmpty();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ThrottledEventQueue::Length() const
|
||||
{
|
||||
return mInner->Length();
|
||||
}
|
||||
|
||||
void
|
||||
ThrottledEventQueue::AwaitIdle() const
|
||||
{
|
||||
return mInner->AwaitIdle();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottledEventQueue::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
|
||||
{
|
||||
return mInner->DispatchFromScript(aEvent, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottledEventQueue::Dispatch(already_AddRefed<nsIRunnable> aEvent,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return mInner->Dispatch(Move(aEvent), aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottledEventQueue::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return mInner->DelayedDispatch(Move(aEvent), aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottledEventQueue::IsOnCurrentThread(bool* aResult)
|
||||
{
|
||||
return mInner->IsOnCurrentThread(aResult);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
94
xpcom/threads/ThrottledEventQueue.h
Normal file
94
xpcom/threads/ThrottledEventQueue.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// nsIEventTarget wrapper for throttling event dispatch.
|
||||
|
||||
#ifndef mozilla_ThrottledEventQueue_h
|
||||
#define mozilla_ThrottledEventQueue_h
|
||||
|
||||
#include "nsIEventTarget.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// A ThrottledEventQueue is an event target that can be used to throttle
|
||||
// events being dispatched to another base target. It maintains its
|
||||
// own queue of events and only dispatches one at a time to the wrapped
|
||||
// target. This can be used to avoid flooding the base target.
|
||||
//
|
||||
// Flooding is avoided via a very simply principal. Runnables dispatched
|
||||
// to the ThrottledEventQueue are only dispatched to the base target
|
||||
// one at a time. Only once that runnable has executed will we dispatch
|
||||
// the next runnable to the base target. This in effect makes all
|
||||
// runnables passing through the ThrottledEventQueue yield to other work
|
||||
// on the base target.
|
||||
//
|
||||
// ThrottledEventQueue keeps runnables waiting to be dispatched to the
|
||||
// base in its own internal queue. Code can query the length of this
|
||||
// queue using IsEmpty() and Length(). Further, code implement back
|
||||
// pressure by checking the depth of the queue and deciding to stop
|
||||
// issuing runnables if they see the ThrottledEventQueue is backed up.
|
||||
// Code running on other threads could even use AwaitIdle() to block
|
||||
// all operation until the ThrottledEventQueue drains.
|
||||
//
|
||||
// Note, this class is similar to TaskQueue, but also differs in a few
|
||||
// ways. First, it is a very simple nsIEventTarget implementation. It
|
||||
// does not use the AbstractThread API.
|
||||
//
|
||||
// In addition, ThrottledEventQueue currently dispatches its next
|
||||
// runnable to the base target *before* running the current event. This
|
||||
// allows the event code to spin the event loop without stalling the
|
||||
// ThrottledEventQueue. In contrast, TaskQueue only dispatches its next
|
||||
// runnable after running the current event. That approach is necessary
|
||||
// for TaskQueue in order to work with thread pool targets.
|
||||
//
|
||||
// So, if you are targeting a thread pool you probably want a TaskQueue.
|
||||
// If you are targeting a single thread or other non-concurrent event
|
||||
// target, you probably want a ThrottledEventQueue.
|
||||
//
|
||||
// ThrottledEventQueue also implements an automatic shutdown mechanism.
|
||||
// De-referencing the queue or browser shutdown will automatically begin
|
||||
// shutdown.
|
||||
//
|
||||
// Once shutdown begins all events will bypass the queue and be dispatched
|
||||
// straight to the underlying base target.
|
||||
class ThrottledEventQueue final : public nsIEventTarget
|
||||
{
|
||||
class Inner;
|
||||
RefPtr<Inner> mInner;
|
||||
|
||||
explicit ThrottledEventQueue(already_AddRefed<Inner> aInner);
|
||||
~ThrottledEventQueue();
|
||||
|
||||
// Begin shutdown of the event queue. This has no effect if shutdown
|
||||
// is already in process. After this is called nsIEventTarget methods
|
||||
// will bypass the queue and operate directly on the base target.
|
||||
// Note, this could be made public if code needs to explicitly shutdown
|
||||
// for some reason.
|
||||
void MaybeStartShutdown();
|
||||
|
||||
public:
|
||||
// Attempt to create a ThrottledEventQueue for the given target. This
|
||||
// may return nullptr if the browser is already shutting down.
|
||||
static already_AddRefed<ThrottledEventQueue>
|
||||
Create(nsIEventTarget* aBaseTarget);
|
||||
|
||||
// Determine if there are any events pending in the queue.
|
||||
bool IsEmpty() const;
|
||||
|
||||
// Determine how many events are pending in the queue.
|
||||
uint32_t Length() const;
|
||||
|
||||
// Block the current thread until the queue is empty. This may not
|
||||
// be called on the main thread or the base target.
|
||||
void AwaitIdle() const;
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIEVENTTARGET
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_ThrottledEventQueue_h
|
||||
752
xpcom/threads/TimerThread.cpp
Normal file
752
xpcom/threads/TimerThread.cpp
Normal file
|
|
@ -0,0 +1,752 @@
|
|||
/* -*- 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 "nsTimerImpl.h"
|
||||
#include "TimerThread.h"
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
#include "plarena.h"
|
||||
#include "pratom.h"
|
||||
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/ChaosMode.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/BinarySearch.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
using namespace mozilla;
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
#include "GeckoTaskTracerImpl.h"
|
||||
using namespace mozilla::tasktracer;
|
||||
#endif
|
||||
|
||||
NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver)
|
||||
|
||||
TimerThread::TimerThread() :
|
||||
mInitInProgress(false),
|
||||
mInitialized(false),
|
||||
mMonitor("TimerThread.mMonitor"),
|
||||
mShutdown(false),
|
||||
mWaiting(false),
|
||||
mNotified(false),
|
||||
mSleeping(false)
|
||||
{
|
||||
}
|
||||
|
||||
TimerThread::~TimerThread()
|
||||
{
|
||||
mThread = nullptr;
|
||||
|
||||
NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
|
||||
}
|
||||
|
||||
nsresult
|
||||
TimerThread::InitLocks()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class TimerObserverRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
explicit TimerObserverRunnable(nsIObserver* aObserver)
|
||||
: mObserver(aObserver)
|
||||
{
|
||||
}
|
||||
|
||||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIObserver> mObserver;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
TimerObserverRunnable::Run()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
observerService->AddObserver(mObserver, "sleep_notification", false);
|
||||
observerService->AddObserver(mObserver, "wake_notification", false);
|
||||
observerService->AddObserver(mObserver, "suspend_process_notification", false);
|
||||
observerService->AddObserver(mObserver, "resume_process_notification", false);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
|
||||
// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents.
|
||||
// It's needed to avoid contention over the default allocator lock when
|
||||
// firing timer events (see bug 733277). The thread-safety is required because
|
||||
// nsTimerEvent objects are allocated on the timer thread, and freed on another
|
||||
// thread. Because TimerEventAllocator has its own lock, contention over that
|
||||
// lock is limited to the allocation and deallocation of nsTimerEvent objects.
|
||||
//
|
||||
// Because this allocator is layered over PLArenaPool, it never shrinks -- even
|
||||
// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list
|
||||
// for later recycling. So the amount of memory consumed will always be equal
|
||||
// to the high-water mark consumption. But nsTimerEvents are small and it's
|
||||
// unusual to have more than a few hundred of them, so this shouldn't be a
|
||||
// problem in practice.
|
||||
|
||||
class TimerEventAllocator
|
||||
{
|
||||
private:
|
||||
struct FreeEntry
|
||||
{
|
||||
FreeEntry* mNext;
|
||||
};
|
||||
|
||||
PLArenaPool mPool;
|
||||
FreeEntry* mFirstFree;
|
||||
mozilla::Monitor mMonitor;
|
||||
|
||||
public:
|
||||
TimerEventAllocator()
|
||||
: mFirstFree(nullptr)
|
||||
, mMonitor("TimerEventAllocator")
|
||||
{
|
||||
PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0);
|
||||
}
|
||||
|
||||
~TimerEventAllocator()
|
||||
{
|
||||
PL_FinishArenaPool(&mPool);
|
||||
}
|
||||
|
||||
void* Alloc(size_t aSize);
|
||||
void Free(void* aPtr);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// This is a nsICancelableRunnable because we can dispatch it to Workers and
|
||||
// those can be shut down at any time, and in these cases, Cancel() is called
|
||||
// instead of Run().
|
||||
class nsTimerEvent final : public CancelableRunnable
|
||||
{
|
||||
public:
|
||||
NS_IMETHOD Run() override;
|
||||
|
||||
nsresult Cancel() override
|
||||
{
|
||||
// Since nsTimerImpl is not thread-safe, we should release |mTimer|
|
||||
// here in the target thread to avoid race condition. Otherwise,
|
||||
// ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the
|
||||
// timer thread and result in race condition.
|
||||
mTimer = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsTimerEvent()
|
||||
: mTimer()
|
||||
, mGeneration(0)
|
||||
{
|
||||
// Note: We override operator new for this class, and the override is
|
||||
// fallible!
|
||||
sAllocatorUsers++;
|
||||
}
|
||||
|
||||
TimeStamp mInitTime;
|
||||
|
||||
static void Init();
|
||||
static void Shutdown();
|
||||
static void DeleteAllocatorIfNeeded();
|
||||
|
||||
static void* operator new(size_t aSize) CPP_THROW_NEW
|
||||
{
|
||||
return sAllocator->Alloc(aSize);
|
||||
}
|
||||
void operator delete(void* aPtr)
|
||||
{
|
||||
sAllocator->Free(aPtr);
|
||||
DeleteAllocatorIfNeeded();
|
||||
}
|
||||
|
||||
already_AddRefed<nsTimerImpl> ForgetTimer()
|
||||
{
|
||||
return mTimer.forget();
|
||||
}
|
||||
|
||||
void SetTimer(already_AddRefed<nsTimerImpl> aTimer)
|
||||
{
|
||||
mTimer = aTimer;
|
||||
mGeneration = mTimer->GetGeneration();
|
||||
}
|
||||
|
||||
private:
|
||||
nsTimerEvent(const nsTimerEvent&) = delete;
|
||||
nsTimerEvent& operator=(const nsTimerEvent&) = delete;
|
||||
nsTimerEvent& operator=(const nsTimerEvent&&) = delete;
|
||||
|
||||
~nsTimerEvent()
|
||||
{
|
||||
MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0,
|
||||
"This will result in us attempting to deallocate the nsTimerEvent allocator twice");
|
||||
sAllocatorUsers--;
|
||||
}
|
||||
|
||||
RefPtr<nsTimerImpl> mTimer;
|
||||
int32_t mGeneration;
|
||||
|
||||
static TimerEventAllocator* sAllocator;
|
||||
static Atomic<int32_t> sAllocatorUsers;
|
||||
static bool sCanDeleteAllocator;
|
||||
};
|
||||
|
||||
TimerEventAllocator* nsTimerEvent::sAllocator = nullptr;
|
||||
Atomic<int32_t> nsTimerEvent::sAllocatorUsers;
|
||||
bool nsTimerEvent::sCanDeleteAllocator = false;
|
||||
|
||||
namespace {
|
||||
|
||||
void*
|
||||
TimerEventAllocator::Alloc(size_t aSize)
|
||||
{
|
||||
MOZ_ASSERT(aSize == sizeof(nsTimerEvent));
|
||||
|
||||
mozilla::MonitorAutoLock lock(mMonitor);
|
||||
|
||||
void* p;
|
||||
if (mFirstFree) {
|
||||
p = mFirstFree;
|
||||
mFirstFree = mFirstFree->mNext;
|
||||
} else {
|
||||
PL_ARENA_ALLOCATE(p, &mPool, aSize);
|
||||
if (!p) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
TimerEventAllocator::Free(void* aPtr)
|
||||
{
|
||||
mozilla::MonitorAutoLock lock(mMonitor);
|
||||
|
||||
FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr);
|
||||
|
||||
entry->mNext = mFirstFree;
|
||||
mFirstFree = entry;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void
|
||||
nsTimerEvent::Init()
|
||||
{
|
||||
sAllocator = new TimerEventAllocator();
|
||||
}
|
||||
|
||||
void
|
||||
nsTimerEvent::Shutdown()
|
||||
{
|
||||
sCanDeleteAllocator = true;
|
||||
DeleteAllocatorIfNeeded();
|
||||
}
|
||||
|
||||
void
|
||||
nsTimerEvent::DeleteAllocatorIfNeeded()
|
||||
{
|
||||
if (sCanDeleteAllocator && sAllocatorUsers == 0) {
|
||||
delete sAllocator;
|
||||
sAllocator = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerEvent::Run()
|
||||
{
|
||||
if (!mTimer) {
|
||||
MOZ_ASSERT(false);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("[this=%p] time between PostTimerEvent() and Fire(): %fms\n",
|
||||
this, (now - mInitTime).ToMilliseconds()));
|
||||
}
|
||||
|
||||
mTimer->Fire(mGeneration);
|
||||
|
||||
// We call Cancel() to correctly release mTimer.
|
||||
// Read more in the Cancel() implementation.
|
||||
return Cancel();
|
||||
}
|
||||
|
||||
nsresult
|
||||
TimerThread::Init()
|
||||
{
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("TimerThread::Init [%d]\n", mInitialized));
|
||||
|
||||
if (mInitialized) {
|
||||
if (!mThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsTimerEvent::Init();
|
||||
|
||||
if (mInitInProgress.exchange(true) == false) {
|
||||
// We hold on to mThread to keep the thread alive.
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(mThread), this);
|
||||
if (NS_FAILED(rv)) {
|
||||
mThread = nullptr;
|
||||
} else {
|
||||
RefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this);
|
||||
if (NS_IsMainThread()) {
|
||||
r->Run();
|
||||
} else {
|
||||
NS_DispatchToMainThread(r);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
mInitialized = true;
|
||||
mMonitor.NotifyAll();
|
||||
}
|
||||
} else {
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
while (!mInitialized) {
|
||||
mMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
TimerThread::Shutdown()
|
||||
{
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown begin\n"));
|
||||
|
||||
if (!mThread) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
nsTArray<nsTimerImpl*> timers;
|
||||
{
|
||||
// lock scope
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
|
||||
mShutdown = true;
|
||||
|
||||
// notify the cond var so that Run() can return
|
||||
if (mWaiting) {
|
||||
mNotified = true;
|
||||
mMonitor.Notify();
|
||||
}
|
||||
|
||||
// Need to copy content of mTimers array to a local array
|
||||
// because call to timers' Cancel() (and release its self)
|
||||
// must not be done under the lock. Destructor of a callback
|
||||
// might potentially call some code reentering the same lock
|
||||
// that leads to unexpected behavior or deadlock.
|
||||
// See bug 422472.
|
||||
timers.AppendElements(mTimers);
|
||||
mTimers.Clear();
|
||||
}
|
||||
|
||||
uint32_t timersCount = timers.Length();
|
||||
for (uint32_t i = 0; i < timersCount; i++) {
|
||||
nsTimerImpl* timer = timers[i];
|
||||
timer->Cancel();
|
||||
ReleaseTimerInternal(timer);
|
||||
}
|
||||
|
||||
mThread->Shutdown(); // wait for the thread to die
|
||||
|
||||
nsTimerEvent::Shutdown();
|
||||
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n"));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct MicrosecondsToInterval
|
||||
{
|
||||
PRIntervalTime operator[](size_t aMs) const {
|
||||
return PR_MicrosecondsToInterval(aMs);
|
||||
}
|
||||
};
|
||||
|
||||
struct IntervalComparator
|
||||
{
|
||||
int operator()(PRIntervalTime aInterval) const {
|
||||
return (0 < aInterval) ? -1 : 1;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
NS_IMETHODIMP
|
||||
TimerThread::Run()
|
||||
{
|
||||
PR_SetCurrentThreadName("Timer");
|
||||
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
|
||||
// We need to know how many microseconds give a positive PRIntervalTime. This
|
||||
// is platform-dependent and we calculate it at runtime, finding a value |v|
|
||||
// such that |PR_MicrosecondsToInterval(v) > 0| and then binary-searching in
|
||||
// the range [0, v) to find the ms-to-interval scale.
|
||||
uint32_t usForPosInterval = 1;
|
||||
while (PR_MicrosecondsToInterval(usForPosInterval) == 0) {
|
||||
usForPosInterval <<= 1;
|
||||
}
|
||||
|
||||
size_t usIntervalResolution;
|
||||
BinarySearchIf(MicrosecondsToInterval(), 0, usForPosInterval, IntervalComparator(), &usIntervalResolution);
|
||||
MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution - 1) == 0);
|
||||
MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution) == 1);
|
||||
|
||||
// Half of the amount of microseconds needed to get positive PRIntervalTime.
|
||||
// We use this to decide how to round our wait times later
|
||||
int32_t halfMicrosecondsIntervalResolution = usIntervalResolution / 2;
|
||||
bool forceRunNextTimer = false;
|
||||
|
||||
while (!mShutdown) {
|
||||
// Have to use PRIntervalTime here, since PR_WaitCondVar takes it
|
||||
PRIntervalTime waitFor;
|
||||
bool forceRunThisTimer = forceRunNextTimer;
|
||||
forceRunNextTimer = false;
|
||||
|
||||
if (mSleeping) {
|
||||
// Sleep for 0.1 seconds while not firing timers.
|
||||
uint32_t milliseconds = 100;
|
||||
if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) {
|
||||
milliseconds = ChaosMode::randomUint32LessThan(200);
|
||||
}
|
||||
waitFor = PR_MillisecondsToInterval(milliseconds);
|
||||
} else {
|
||||
waitFor = PR_INTERVAL_NO_TIMEOUT;
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
nsTimerImpl* timer = nullptr;
|
||||
|
||||
if (!mTimers.IsEmpty()) {
|
||||
timer = mTimers[0];
|
||||
|
||||
if (now >= timer->mTimeout || forceRunThisTimer) {
|
||||
next:
|
||||
// NB: AddRef before the Release under RemoveTimerInternal to avoid
|
||||
// mRefCnt passing through zero, in case all other refs than the one
|
||||
// from mTimers have gone away (the last non-mTimers[i]-ref's Release
|
||||
// must be racing with us, blocked in gThread->RemoveTimer waiting
|
||||
// for TimerThread::mMonitor, under nsTimerImpl::Release.
|
||||
|
||||
RefPtr<nsTimerImpl> timerRef(timer);
|
||||
RemoveTimerInternal(timer);
|
||||
timer = nullptr;
|
||||
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("Timer thread woke up %fms from when it was supposed to\n",
|
||||
fabs((now - timerRef->mTimeout).ToMilliseconds())));
|
||||
|
||||
// We are going to let the call to PostTimerEvent here handle the
|
||||
// release of the timer so that we don't end up releasing the timer
|
||||
// on the TimerThread instead of on the thread it targets.
|
||||
timerRef = PostTimerEvent(timerRef.forget());
|
||||
|
||||
if (timerRef) {
|
||||
// We got our reference back due to an error.
|
||||
// Unhook the nsRefPtr, and release manually so we can get the
|
||||
// refcount.
|
||||
nsrefcnt rc = timerRef.forget().take()->Release();
|
||||
(void)rc;
|
||||
|
||||
// The nsITimer interface requires that its users keep a reference
|
||||
// to the timers they use while those timers are initialized but
|
||||
// have not yet fired. If this ever happens, it is a bug in the
|
||||
// code that created and used the timer.
|
||||
//
|
||||
// Further, note that this should never happen even with a
|
||||
// misbehaving user, because nsTimerImpl::Release checks for a
|
||||
// refcount of 1 with an armed timer (a timer whose only reference
|
||||
// is from the timer thread) and when it hits this will remove the
|
||||
// timer from the timer thread and thus destroy the last reference,
|
||||
// preventing this situation from occurring.
|
||||
MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!");
|
||||
}
|
||||
|
||||
if (mShutdown) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Update now, as PostTimerEvent plus the locking may have taken a
|
||||
// tick or two, and we may goto next below.
|
||||
now = TimeStamp::Now();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mTimers.IsEmpty()) {
|
||||
timer = mTimers[0];
|
||||
|
||||
TimeStamp timeout = timer->mTimeout;
|
||||
|
||||
// Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
|
||||
// is due now or overdue.
|
||||
//
|
||||
// Note that we can only sleep for integer values of a certain
|
||||
// resolution. We use halfMicrosecondsIntervalResolution, calculated
|
||||
// before, to do the optimal rounding (i.e., of how to decide what
|
||||
// interval is so small we should not wait at all).
|
||||
double microseconds = (timeout - now).ToMilliseconds() * 1000;
|
||||
|
||||
if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) {
|
||||
// The mean value of sFractions must be 1 to ensure that
|
||||
// the average of a long sequence of timeouts converges to the
|
||||
// actual sum of their times.
|
||||
static const float sFractions[] = {
|
||||
0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f
|
||||
};
|
||||
microseconds *=
|
||||
sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))];
|
||||
forceRunNextTimer = true;
|
||||
}
|
||||
|
||||
if (microseconds < halfMicrosecondsIntervalResolution) {
|
||||
forceRunNextTimer = false;
|
||||
goto next; // round down; execute event now
|
||||
}
|
||||
waitFor = PR_MicrosecondsToInterval(
|
||||
static_cast<uint32_t>(microseconds)); // Floor is accurate enough.
|
||||
if (waitFor == 0) {
|
||||
waitFor = 1; // round up, wait the minimum time we can wait
|
||||
}
|
||||
}
|
||||
|
||||
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
|
||||
if (waitFor == PR_INTERVAL_NO_TIMEOUT)
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("waiting for PR_INTERVAL_NO_TIMEOUT\n"));
|
||||
else
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("waiting for %u\n", PR_IntervalToMilliseconds(waitFor)));
|
||||
}
|
||||
}
|
||||
|
||||
mWaiting = true;
|
||||
mNotified = false;
|
||||
mMonitor.Wait(waitFor);
|
||||
if (mNotified) {
|
||||
forceRunNextTimer = false;
|
||||
}
|
||||
mWaiting = false;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
TimerThread::AddTimer(nsTimerImpl* aTimer)
|
||||
{
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
|
||||
if (!aTimer->mEventTarget) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// Add the timer to our list.
|
||||
int32_t i = AddTimerInternal(aTimer);
|
||||
if (i < 0) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Awaken the timer thread.
|
||||
if (mWaiting && i == 0) {
|
||||
mNotified = true;
|
||||
mMonitor.Notify();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
TimerThread::RemoveTimer(nsTimerImpl* aTimer)
|
||||
{
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
|
||||
// Remove the timer from our array. Tell callers that aTimer was not found
|
||||
// by returning NS_ERROR_NOT_AVAILABLE.
|
||||
|
||||
if (!RemoveTimerInternal(aTimer)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
// Awaken the timer thread.
|
||||
if (mWaiting) {
|
||||
mNotified = true;
|
||||
mMonitor.Notify();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// This function must be called from within a lock
|
||||
int32_t
|
||||
TimerThread::AddTimerInternal(nsTimerImpl* aTimer)
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
if (mShutdown) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
TimerAdditionComparator c(now, aTimer);
|
||||
nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c);
|
||||
|
||||
if (!insertSlot) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
NS_ADDREF(aTimer);
|
||||
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
// Caller of AddTimer is the parent task of its timer event, so we store the
|
||||
// TraceInfo here for later used.
|
||||
aTimer->GetTLSTraceInfo();
|
||||
#endif
|
||||
|
||||
return insertSlot - mTimers.Elements();
|
||||
}
|
||||
|
||||
bool
|
||||
TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer)
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
if (!mTimers.RemoveElement(aTimer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReleaseTimerInternal(aTimer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
TimerThread::ReleaseTimerInternal(nsTimerImpl* aTimer)
|
||||
{
|
||||
if (!mShutdown) {
|
||||
// copied to a local array before releasing in shutdown
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
}
|
||||
NS_RELEASE(aTimer);
|
||||
}
|
||||
|
||||
already_AddRefed<nsTimerImpl>
|
||||
TimerThread::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef)
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
|
||||
RefPtr<nsTimerImpl> timer(aTimerRef);
|
||||
if (!timer->mEventTarget) {
|
||||
NS_ERROR("Attempt to post timer event to NULL event target");
|
||||
return timer.forget();
|
||||
}
|
||||
|
||||
// XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
|
||||
|
||||
// Since we already addref'd 'timer', we don't need to addref here.
|
||||
// We will release either in ~nsTimerEvent(), or pass the reference back to
|
||||
// the caller. We need to copy the generation number from this timer into the
|
||||
// event, so we can avoid firing a timer that was re-initialized after being
|
||||
// canceled.
|
||||
|
||||
RefPtr<nsTimerEvent> event = new nsTimerEvent;
|
||||
if (!event) {
|
||||
return timer.forget();
|
||||
}
|
||||
|
||||
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
|
||||
event->mInitTime = TimeStamp::Now();
|
||||
}
|
||||
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
// During the dispatch of TimerEvent, we overwrite the current TraceInfo
|
||||
// partially with the info saved in timer earlier, and restore it back by
|
||||
// AutoSaveCurTraceInfo.
|
||||
AutoSaveCurTraceInfo saveCurTraceInfo;
|
||||
(timer->GetTracedTask()).SetTLSTraceInfo();
|
||||
#endif
|
||||
|
||||
nsCOMPtr<nsIEventTarget> target = timer->mEventTarget;
|
||||
event->SetTimer(timer.forget());
|
||||
|
||||
nsresult rv;
|
||||
{
|
||||
// We release mMonitor around the Dispatch because if this timer is targeted
|
||||
// at the TimerThread we'll deadlock.
|
||||
MonitorAutoUnlock unlock(mMonitor);
|
||||
rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
timer = event->ForgetTimer();
|
||||
RemoveTimerInternal(timer);
|
||||
return timer.forget();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
TimerThread::DoBeforeSleep()
|
||||
{
|
||||
// Mainthread
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
mSleeping = true;
|
||||
}
|
||||
|
||||
// Note: wake may be notified without preceding sleep notification
|
||||
void
|
||||
TimerThread::DoAfterSleep()
|
||||
{
|
||||
// Mainthread
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
mSleeping = false;
|
||||
|
||||
// Wake up the timer thread to re-process the array to ensure the sleep delay is correct,
|
||||
// and fire any expired timers (perhaps quite a few)
|
||||
mNotified = true;
|
||||
mMonitor.Notify();
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic,
|
||||
const char16_t* /* aData */)
|
||||
{
|
||||
if (strcmp(aTopic, "sleep_notification") == 0 ||
|
||||
strcmp(aTopic, "suspend_process_notification") == 0) {
|
||||
DoBeforeSleep();
|
||||
} else if (strcmp(aTopic, "wake_notification") == 0 ||
|
||||
strcmp(aTopic, "resume_process_notification") == 0) {
|
||||
DoAfterSleep();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
115
xpcom/threads/TimerThread.h
Normal file
115
xpcom/threads/TimerThread.h
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef TimerThread_h___
|
||||
#define TimerThread_h___
|
||||
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsIThread.h"
|
||||
|
||||
#include "nsTimerImpl.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include "nsTArray.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
|
||||
namespace mozilla {
|
||||
class TimeStamp;
|
||||
} // namespace mozilla
|
||||
|
||||
class TimerThread final
|
||||
: public nsIRunnable
|
||||
, public nsIObserver
|
||||
{
|
||||
public:
|
||||
typedef mozilla::Monitor Monitor;
|
||||
typedef mozilla::TimeStamp TimeStamp;
|
||||
typedef mozilla::TimeDuration TimeDuration;
|
||||
|
||||
TimerThread();
|
||||
nsresult InitLocks();
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIRUNNABLE
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
nsresult Init();
|
||||
nsresult Shutdown();
|
||||
|
||||
nsresult AddTimer(nsTimerImpl* aTimer);
|
||||
nsresult RemoveTimer(nsTimerImpl* aTimer);
|
||||
|
||||
void DoBeforeSleep();
|
||||
void DoAfterSleep();
|
||||
|
||||
bool IsOnTimerThread() const
|
||||
{
|
||||
return mThread == NS_GetCurrentThread();
|
||||
}
|
||||
|
||||
private:
|
||||
~TimerThread();
|
||||
|
||||
mozilla::Atomic<bool> mInitInProgress;
|
||||
bool mInitialized;
|
||||
|
||||
// These two internal helper methods must be called while mMonitor is held.
|
||||
// AddTimerInternal returns the position where the timer was added in the
|
||||
// list, or -1 if it failed.
|
||||
int32_t AddTimerInternal(nsTimerImpl* aTimer);
|
||||
bool RemoveTimerInternal(nsTimerImpl* aTimer);
|
||||
void ReleaseTimerInternal(nsTimerImpl* aTimer);
|
||||
|
||||
already_AddRefed<nsTimerImpl> PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef);
|
||||
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
Monitor mMonitor;
|
||||
|
||||
bool mShutdown;
|
||||
bool mWaiting;
|
||||
bool mNotified;
|
||||
bool mSleeping;
|
||||
|
||||
nsTArray<nsTimerImpl*> mTimers;
|
||||
};
|
||||
|
||||
struct TimerAdditionComparator
|
||||
{
|
||||
TimerAdditionComparator(const mozilla::TimeStamp& aNow,
|
||||
nsTimerImpl* aTimerToInsert) :
|
||||
now(aNow)
|
||||
#ifdef DEBUG
|
||||
, timerToInsert(aTimerToInsert)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
bool LessThan(nsTimerImpl* aFromArray, nsTimerImpl* aNewTimer) const
|
||||
{
|
||||
MOZ_ASSERT(aNewTimer == timerToInsert, "Unexpected timer ordering");
|
||||
|
||||
// Skip any overdue timers.
|
||||
return aFromArray->mTimeout <= now ||
|
||||
aFromArray->mTimeout <= aNewTimer->mTimeout;
|
||||
}
|
||||
|
||||
bool Equals(nsTimerImpl* aFromArray, nsTimerImpl* aNewTimer) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const mozilla::TimeStamp& now;
|
||||
#ifdef DEBUG
|
||||
const nsTimerImpl* const timerToInsert;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* TimerThread_h___ */
|
||||
89
xpcom/threads/moz.build
Normal file
89
xpcom/threads/moz.build
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIEnvironment.idl',
|
||||
'nsIEventTarget.idl',
|
||||
'nsIIdlePeriod.idl',
|
||||
'nsIProcess.idl',
|
||||
'nsIRunnable.idl',
|
||||
'nsISupportsPriority.idl',
|
||||
'nsIThread.idl',
|
||||
'nsIThreadInternal.idl',
|
||||
'nsIThreadManager.idl',
|
||||
'nsIThreadPool.idl',
|
||||
'nsITimer.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'xpcom_threads'
|
||||
|
||||
EXPORTS += [
|
||||
'nsEventQueue.h',
|
||||
'nsICancelableRunnable.h',
|
||||
'nsIIncrementalRunnable.h',
|
||||
'nsMemoryPressure.h',
|
||||
'nsProcess.h',
|
||||
'nsThread.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'AbstractThread.h',
|
||||
'BackgroundHangMonitor.h',
|
||||
'HangAnnotations.h',
|
||||
'HangMonitor.h',
|
||||
'LazyIdleThread.h',
|
||||
'MainThreadIdlePeriod.h',
|
||||
'MozPromise.h',
|
||||
'SharedThreadPool.h',
|
||||
'StateMirroring.h',
|
||||
'StateWatching.h',
|
||||
'SyncRunnable.h',
|
||||
'TaskDispatcher.h',
|
||||
'TaskQueue.h',
|
||||
'ThrottledEventQueue.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AbstractThread.cpp',
|
||||
'BackgroundHangMonitor.cpp',
|
||||
'HangAnnotations.cpp',
|
||||
'HangMonitor.cpp',
|
||||
'LazyIdleThread.cpp',
|
||||
'MainThreadIdlePeriod.cpp',
|
||||
'nsEnvironment.cpp',
|
||||
'nsEventQueue.cpp',
|
||||
'nsMemoryPressure.cpp',
|
||||
'nsProcessCommon.cpp',
|
||||
'nsThread.cpp',
|
||||
'nsThreadManager.cpp',
|
||||
'nsThreadPool.cpp',
|
||||
'nsTimerImpl.cpp',
|
||||
'SharedThreadPool.cpp',
|
||||
'TaskQueue.cpp',
|
||||
'ThreadStackHelper.cpp',
|
||||
'ThrottledEventQueue.cpp',
|
||||
'TimerThread.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'../build',
|
||||
'/caps',
|
||||
'/tools/profiler',
|
||||
]
|
||||
|
||||
# BHR disabled for Release builds because of bug 965392.
|
||||
# BHR disabled for debug builds because of bug 979069.
|
||||
# BHR disabled on gonk because of bug 1180533
|
||||
# BHR disabled for TSan builds because of bug 1121216.
|
||||
if CONFIG['MOZ_UPDATE_CHANNEL'] not in ('release') and \
|
||||
not CONFIG['MOZ_DEBUG'] and \
|
||||
not CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and \
|
||||
not CONFIG['MOZ_TSAN']:
|
||||
DEFINES['MOZ_ENABLE_BACKGROUND_HANG_MONITOR'] = 1
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
163
xpcom/threads/nsEnvironment.cpp
Normal file
163
xpcom/threads/nsEnvironment.cpp
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/* -*- 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 "nsEnvironment.h"
|
||||
#include "prenv.h"
|
||||
#include "prprf.h"
|
||||
#include "nsBaseHashtable.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsPromiseFlatString.h"
|
||||
#include "nsDependentString.h"
|
||||
#include "nsNativeCharsetUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsEnvironment, nsIEnvironment)
|
||||
|
||||
nsresult
|
||||
nsEnvironment::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
|
||||
{
|
||||
nsresult rv;
|
||||
*aResult = nullptr;
|
||||
|
||||
if (aOuter) {
|
||||
return NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
|
||||
nsEnvironment* obj = new nsEnvironment();
|
||||
|
||||
rv = obj->QueryInterface(aIID, aResult);
|
||||
if (NS_FAILED(rv)) {
|
||||
delete obj;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsEnvironment::~nsEnvironment()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsEnvironment::Exists(const nsAString& aName, bool* aOutValue)
|
||||
{
|
||||
nsAutoCString nativeName;
|
||||
nsresult rv = NS_CopyUnicodeToNative(aName, nativeName);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoCString nativeVal;
|
||||
#if defined(XP_UNIX)
|
||||
/* For Unix/Linux platforms we follow the Unix definition:
|
||||
* An environment variable exists when |getenv()| returns a non-nullptr
|
||||
* value. An environment variable does not exist when |getenv()| returns
|
||||
* nullptr.
|
||||
*/
|
||||
const char* value = PR_GetEnv(nativeName.get());
|
||||
*aOutValue = value && *value;
|
||||
#else
|
||||
/* For non-Unix/Linux platforms we have to fall back to a
|
||||
* "portable" definition (which is incorrect for Unix/Linux!!!!)
|
||||
* which simply checks whether the string returned by |Get()| is empty
|
||||
* or not.
|
||||
*/
|
||||
nsAutoString value;
|
||||
Get(aName, value);
|
||||
*aOutValue = !value.IsEmpty();
|
||||
#endif /* XP_UNIX */
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsEnvironment::Get(const nsAString& aName, nsAString& aOutValue)
|
||||
{
|
||||
nsAutoCString nativeName;
|
||||
nsresult rv = NS_CopyUnicodeToNative(aName, nativeName);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoCString nativeVal;
|
||||
const char* value = PR_GetEnv(nativeName.get());
|
||||
if (value && *value) {
|
||||
rv = NS_CopyNativeToUnicode(nsDependentCString(value), aOutValue);
|
||||
} else {
|
||||
aOutValue.Truncate();
|
||||
rv = NS_OK;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Environment strings must have static duration; We're gonna leak all of this
|
||||
* at shutdown: this is by design, caused how Unix/Linux implement environment
|
||||
* vars.
|
||||
*/
|
||||
|
||||
typedef nsBaseHashtableET<nsCharPtrHashKey, char*> EnvEntryType;
|
||||
typedef nsTHashtable<EnvEntryType> EnvHashType;
|
||||
|
||||
static EnvHashType* gEnvHash = nullptr;
|
||||
|
||||
static bool
|
||||
EnsureEnvHash()
|
||||
{
|
||||
if (gEnvHash) {
|
||||
return true;
|
||||
}
|
||||
|
||||
gEnvHash = new EnvHashType;
|
||||
if (!gEnvHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsEnvironment::Set(const nsAString& aName, const nsAString& aValue)
|
||||
{
|
||||
nsAutoCString nativeName;
|
||||
nsAutoCString nativeVal;
|
||||
|
||||
nsresult rv = NS_CopyUnicodeToNative(aName, nativeName);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = NS_CopyUnicodeToNative(aValue, nativeVal);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mLock);
|
||||
|
||||
if (!EnsureEnvHash()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
EnvEntryType* entry = gEnvHash->PutEntry(nativeName.get());
|
||||
if (!entry) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
char* newData = PR_smprintf("%s=%s",
|
||||
nativeName.get(),
|
||||
nativeVal.get());
|
||||
if (!newData) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
PR_SetEnv(newData);
|
||||
if (entry->mData) {
|
||||
PR_smprintf_free(entry->mData);
|
||||
}
|
||||
entry->mData = newData;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
36
xpcom/threads/nsEnvironment.h
Normal file
36
xpcom/threads/nsEnvironment.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsEnvironment_h__
|
||||
#define nsEnvironment_h__
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsIEnvironment.h"
|
||||
|
||||
#define NS_ENVIRONMENT_CID \
|
||||
{ 0X3D68F92UL, 0X9513, 0X4E25, \
|
||||
{ 0X9B, 0XE9, 0X7C, 0XB2, 0X39, 0X87, 0X41, 0X72 } }
|
||||
#define NS_ENVIRONMENT_CONTRACTID "@mozilla.org/process/environment;1"
|
||||
|
||||
class nsEnvironment final : public nsIEnvironment
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIENVIRONMENT
|
||||
|
||||
static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
|
||||
|
||||
private:
|
||||
nsEnvironment() : mLock("nsEnvironment.mLock")
|
||||
{
|
||||
}
|
||||
~nsEnvironment();
|
||||
|
||||
mozilla::Mutex mLock;
|
||||
};
|
||||
|
||||
#endif /* !nsEnvironment_h__ */
|
||||
155
xpcom/threads/nsEventQueue.cpp
Normal file
155
xpcom/threads/nsEventQueue.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/* -*- 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 "nsEventQueue.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "prthread.h"
|
||||
#include "mozilla/ChaosMode.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
static LazyLogModule sEventQueueLog("nsEventQueue");
|
||||
#ifdef LOG
|
||||
#undef LOG
|
||||
#endif
|
||||
#define LOG(args) MOZ_LOG(sEventQueueLog, mozilla::LogLevel::Debug, args)
|
||||
|
||||
nsEventQueue::nsEventQueue(mozilla::CondVar& aCondVar, EventQueueType aType)
|
||||
: mHead(nullptr)
|
||||
, mTail(nullptr)
|
||||
, mOffsetHead(0)
|
||||
, mOffsetTail(0)
|
||||
, mEventsAvailable(aCondVar)
|
||||
, mType(aType)
|
||||
{
|
||||
}
|
||||
|
||||
nsEventQueue::~nsEventQueue()
|
||||
{
|
||||
// It'd be nice to be able to assert that no one else is holding the lock,
|
||||
// but NSPR doesn't really expose APIs for it.
|
||||
NS_ASSERTION(IsEmpty(),
|
||||
"Non-empty event queue being destroyed; events being leaked.");
|
||||
|
||||
if (mHead) {
|
||||
FreePage(mHead);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nsEventQueue::GetEvent(bool aMayWait, nsIRunnable** aResult,
|
||||
MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
if (aResult) {
|
||||
*aResult = nullptr;
|
||||
}
|
||||
|
||||
while (IsEmpty()) {
|
||||
if (!aMayWait) {
|
||||
return false;
|
||||
}
|
||||
LOG(("EVENTQ(%p): wait begin\n", this));
|
||||
mEventsAvailable.Wait();
|
||||
LOG(("EVENTQ(%p): wait end\n", this));
|
||||
|
||||
if (mType == eSharedCondVarQueue) {
|
||||
if (IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (aResult) {
|
||||
MOZ_ASSERT(mOffsetHead < EVENTS_PER_PAGE);
|
||||
MOZ_ASSERT_IF(mHead == mTail, mOffsetHead <= mOffsetTail);
|
||||
*aResult = mHead->mEvents[mOffsetHead++];
|
||||
|
||||
MOZ_ASSERT(*aResult);
|
||||
MOZ_ASSERT(mOffsetHead <= EVENTS_PER_PAGE);
|
||||
|
||||
// Check if mHead points to empty Page
|
||||
if (mOffsetHead == EVENTS_PER_PAGE) {
|
||||
Page* dead = mHead;
|
||||
mHead = mHead->mNext;
|
||||
FreePage(dead);
|
||||
mOffsetHead = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
nsEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aRunnable,
|
||||
MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
if (!mHead) {
|
||||
mHead = NewPage();
|
||||
MOZ_ASSERT(mHead);
|
||||
|
||||
mTail = mHead;
|
||||
mOffsetHead = 0;
|
||||
mOffsetTail = 0;
|
||||
} else if (mOffsetTail == EVENTS_PER_PAGE) {
|
||||
Page* page = NewPage();
|
||||
MOZ_ASSERT(page);
|
||||
|
||||
mTail->mNext = page;
|
||||
mTail = page;
|
||||
mOffsetTail = 0;
|
||||
}
|
||||
|
||||
nsIRunnable*& queueLocation = mTail->mEvents[mOffsetTail];
|
||||
MOZ_ASSERT(!queueLocation);
|
||||
queueLocation = aRunnable.take();
|
||||
++mOffsetTail;
|
||||
LOG(("EVENTQ(%p): notify\n", this));
|
||||
mEventsAvailable.Notify();
|
||||
}
|
||||
|
||||
void
|
||||
nsEventQueue::PutEvent(nsIRunnable* aRunnable, MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event(aRunnable);
|
||||
PutEvent(event.forget(), aProofOfLock);
|
||||
}
|
||||
|
||||
size_t
|
||||
nsEventQueue::Count(MutexAutoLock& aProofOfLock) const
|
||||
{
|
||||
// It is obvious count is 0 when the queue is empty.
|
||||
if (!mHead) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* How we count the number of events in the queue:
|
||||
* 1. Let pageCount(x, y) denote the number of pages excluding the tail page
|
||||
* where x is the index of head page and y is the index of the tail page.
|
||||
* 2. Then we have pageCount(x, y) = y - x.
|
||||
*
|
||||
* Ex: pageCount(0, 0) = 0 where both head and tail pages point to page 0.
|
||||
* pageCount(0, 1) = 1 where head points to page 0 and tail points page 1.
|
||||
*
|
||||
* 3. number of events = (EVENTS_PER_PAGE * pageCount(x, y))
|
||||
* - (empty slots in head page) + (non-empty slots in tail page)
|
||||
* = (EVENTS_PER_PAGE * pageCount(x, y)) - mOffsetHead + mOffsetTail
|
||||
*/
|
||||
|
||||
int count = -mOffsetHead;
|
||||
|
||||
// Compute (EVENTS_PER_PAGE * pageCount(x, y))
|
||||
for (Page* page = mHead; page != mTail; page = page->mNext) {
|
||||
count += EVENTS_PER_PAGE;
|
||||
}
|
||||
|
||||
count += mOffsetTail;
|
||||
MOZ_ASSERT(count >= 0);
|
||||
|
||||
return count;
|
||||
}
|
||||
123
xpcom/threads/nsEventQueue.h
Normal file
123
xpcom/threads/nsEventQueue.h
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsEventQueue_h__
|
||||
#define nsEventQueue_h__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "mozilla/CondVar.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
class nsThreadPool;
|
||||
|
||||
// A threadsafe FIFO event queue...
|
||||
class nsEventQueue
|
||||
{
|
||||
public:
|
||||
typedef mozilla::MutexAutoLock MutexAutoLock;
|
||||
|
||||
enum EventQueueType
|
||||
{
|
||||
eNormalQueue,
|
||||
eSharedCondVarQueue
|
||||
};
|
||||
|
||||
nsEventQueue(mozilla::CondVar& aCondVar, EventQueueType aType);
|
||||
~nsEventQueue();
|
||||
|
||||
// This method adds a new event to the pending event queue. The queue holds
|
||||
// a strong reference to the event after this method returns. This method
|
||||
// cannot fail.
|
||||
void PutEvent(nsIRunnable* aEvent, MutexAutoLock& aProofOfLock);
|
||||
void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
|
||||
MutexAutoLock& aProofOfLock);
|
||||
|
||||
// This method gets an event from the event queue. If mayWait is true, then
|
||||
// the method will block the calling thread until an event is available. If
|
||||
// the event is null, then the method returns immediately indicating whether
|
||||
// or not an event is pending. When the resulting event is non-null, the
|
||||
// caller is responsible for releasing the event object. This method does
|
||||
// not alter the reference count of the resulting event.
|
||||
bool GetEvent(bool aMayWait, nsIRunnable** aEvent,
|
||||
MutexAutoLock& aProofOfLock);
|
||||
|
||||
// This method returns true if there is a pending event.
|
||||
bool HasPendingEvent(MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
return GetEvent(false, nullptr, aProofOfLock);
|
||||
}
|
||||
|
||||
// This method returns the next pending event or null.
|
||||
bool GetPendingEvent(nsIRunnable** aRunnable, MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
return GetEvent(false, aRunnable, aProofOfLock);
|
||||
}
|
||||
|
||||
size_t Count(MutexAutoLock&) const;
|
||||
|
||||
private:
|
||||
bool IsEmpty()
|
||||
{
|
||||
return !mHead || (mHead == mTail && mOffsetHead == mOffsetTail);
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
EVENTS_PER_PAGE = 255
|
||||
};
|
||||
|
||||
// Page objects are linked together to form a simple deque.
|
||||
|
||||
struct Page
|
||||
{
|
||||
struct Page* mNext;
|
||||
nsIRunnable* mEvents[EVENTS_PER_PAGE];
|
||||
};
|
||||
|
||||
static_assert((sizeof(Page) & (sizeof(Page) - 1)) == 0,
|
||||
"sizeof(Page) should be a power of two to avoid heap slop.");
|
||||
|
||||
static Page* NewPage()
|
||||
{
|
||||
return static_cast<Page*>(moz_xcalloc(1, sizeof(Page)));
|
||||
}
|
||||
|
||||
static void FreePage(Page* aPage)
|
||||
{
|
||||
free(aPage);
|
||||
}
|
||||
|
||||
Page* mHead;
|
||||
Page* mTail;
|
||||
|
||||
uint16_t mOffsetHead; // offset into mHead where next item is removed
|
||||
uint16_t mOffsetTail; // offset into mTail where next item is added
|
||||
mozilla::CondVar& mEventsAvailable;
|
||||
|
||||
EventQueueType mType;
|
||||
|
||||
// These methods are made available to nsThreadPool as a hack, since
|
||||
// nsThreadPool needs to have its threads sleep for fixed amounts of
|
||||
// time as well as being able to wake up all threads when thread
|
||||
// limits change.
|
||||
friend class nsThreadPool;
|
||||
void Wait(PRIntervalTime aInterval)
|
||||
{
|
||||
MOZ_ASSERT(mType == eNormalQueue);
|
||||
mEventsAvailable.Wait(aInterval);
|
||||
}
|
||||
void NotifyAll()
|
||||
{
|
||||
MOZ_ASSERT(mType == eNormalQueue);
|
||||
mEventsAvailable.NotifyAll();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // nsEventQueue_h__
|
||||
38
xpcom/threads/nsICancelableRunnable.h
Normal file
38
xpcom/threads/nsICancelableRunnable.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsICancelableRunnable_h__
|
||||
#define nsICancelableRunnable_h__
|
||||
|
||||
#include "nsISupports.h"
|
||||
|
||||
#define NS_ICANCELABLERUNNABLE_IID \
|
||||
{ 0xde93dc4c, 0x5eea, 0x4eb7, \
|
||||
{ 0xb6, 0xd1, 0xdb, 0xf1, 0xe0, 0xce, 0xf6, 0x5c } }
|
||||
|
||||
class nsICancelableRunnable : public nsISupports
|
||||
{
|
||||
public:
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANCELABLERUNNABLE_IID)
|
||||
|
||||
/*
|
||||
* Cancels a pending task. If the task has already been executed this will
|
||||
* be a no-op. Calling this method twice is considered an error.
|
||||
*
|
||||
* @throws NS_ERROR_UNEXPECTED
|
||||
* Indicates that the runnable has already been canceled.
|
||||
*/
|
||||
virtual nsresult Cancel() = 0;
|
||||
|
||||
protected:
|
||||
nsICancelableRunnable() { }
|
||||
virtual ~nsICancelableRunnable() {}
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsICancelableRunnable,
|
||||
NS_ICANCELABLERUNNABLE_IID)
|
||||
|
||||
#endif // nsICancelableRunnable_h__
|
||||
55
xpcom/threads/nsIEnvironment.idl
Normal file
55
xpcom/threads/nsIEnvironment.idl
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* Scriptable access to the current process environment.
|
||||
*
|
||||
*/
|
||||
[scriptable, uuid(101d5941-d820-4e85-a266-9a3469940807)]
|
||||
interface nsIEnvironment : nsISupports
|
||||
{
|
||||
/**
|
||||
* Set the value of an environment variable.
|
||||
*
|
||||
* @param aName the variable name to set.
|
||||
* @param aValue the value to set.
|
||||
*/
|
||||
void set(in AString aName, in AString aValue);
|
||||
|
||||
/**
|
||||
* Get the value of an environment variable.
|
||||
*
|
||||
* @param aName the variable name to retrieve.
|
||||
* @return returns the value of the env variable. An empty string
|
||||
* will be returned when the env variable does not exist or
|
||||
* when the value itself is an empty string - please use
|
||||
* |exists()| to probe whether the env variable exists
|
||||
* or not.
|
||||
*/
|
||||
AString get(in AString aName);
|
||||
|
||||
/**
|
||||
* Check the existence of an environment variable.
|
||||
* This method checks whether an environment variable is present in
|
||||
* the environment or not.
|
||||
*
|
||||
* - For Unix/Linux platforms we follow the Unix definition:
|
||||
* An environment variable exists when |getenv()| returns a non-NULL value.
|
||||
* An environment variable does not exist when |getenv()| returns NULL.
|
||||
* - For non-Unix/Linux platforms we have to fall back to a
|
||||
* "portable" definition (which is incorrect for Unix/Linux!!!!)
|
||||
* which simply checks whether the string returned by |Get()| is empty
|
||||
* or not.
|
||||
*
|
||||
* @param aName the variable name to probe.
|
||||
* @return if the variable has been set, the value returned is
|
||||
* PR_TRUE. If the variable was not defined in the
|
||||
* environment PR_FALSE will be returned.
|
||||
*/
|
||||
boolean exists(in AString aName);
|
||||
};
|
||||
|
||||
127
xpcom/threads/nsIEventTarget.idl
Normal file
127
xpcom/threads/nsIEventTarget.idl
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "nsISupports.idl"
|
||||
#include "nsIRunnable.idl"
|
||||
%{C++
|
||||
#include "nsCOMPtr.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
%}
|
||||
|
||||
native alreadyAddRefed_nsIRunnable(already_AddRefed<nsIRunnable>);
|
||||
|
||||
[scriptable, uuid(88145945-3278-424e-9f37-d874cbdd9f6f)]
|
||||
interface nsIEventTarget : nsISupports
|
||||
{
|
||||
/* until we can get rid of all uses, keep the non-alreadyAddRefed<> version */
|
||||
%{C++
|
||||
nsresult Dispatch(nsIRunnable* aEvent, uint32_t aFlags) {
|
||||
return Dispatch(nsCOMPtr<nsIRunnable>(aEvent).forget(), aFlags);
|
||||
}
|
||||
%}
|
||||
|
||||
/**
|
||||
* This flag specifies the default mode of event dispatch, whereby the event
|
||||
* is simply queued for later processing. When this flag is specified,
|
||||
* dispatch returns immediately after the event is queued.
|
||||
*/
|
||||
const unsigned long DISPATCH_NORMAL = 0;
|
||||
|
||||
/**
|
||||
* This flag specifies the synchronous mode of event dispatch, in which the
|
||||
* dispatch method does not return until the event has been processed.
|
||||
*
|
||||
* NOTE: passing this flag to dispatch may have the side-effect of causing
|
||||
* other events on the current thread to be processed while waiting for the
|
||||
* given event to be processed.
|
||||
*/
|
||||
const unsigned long DISPATCH_SYNC = 1;
|
||||
|
||||
/**
|
||||
* This flag specifies that the dispatch is occurring from a running event
|
||||
* that was dispatched to the same event target, and that event is about to
|
||||
* finish.
|
||||
*
|
||||
* A thread pool can use this as an optimization hint to not spin up
|
||||
* another thread, since the current thread is about to become idle.
|
||||
*
|
||||
* These events are always async.
|
||||
*/
|
||||
const unsigned long DISPATCH_AT_END = 2;
|
||||
|
||||
/**
|
||||
* Check to see if this event target is associated with the current thread.
|
||||
*
|
||||
* @returns
|
||||
* A boolean value that if "true" indicates that events dispatched to this
|
||||
* event target will run on the current thread (i.e., the thread calling
|
||||
* this method).
|
||||
*/
|
||||
boolean isOnCurrentThread();
|
||||
|
||||
/**
|
||||
* Dispatch an event to this event target. This function may be called from
|
||||
* any thread, and it may be called re-entrantly.
|
||||
*
|
||||
* @param event
|
||||
* The alreadyAddRefed<> event to dispatch.
|
||||
* NOTE that the event will be leaked if it fails to dispatch.
|
||||
* @param flags
|
||||
* The flags modifying event dispatch. The flags are described in detail
|
||||
* below.
|
||||
*
|
||||
* @throws NS_ERROR_INVALID_ARG
|
||||
* Indicates that event is null.
|
||||
* @throws NS_ERROR_UNEXPECTED
|
||||
* Indicates that the thread is shutting down and has finished processing
|
||||
* events, so this event would never run and has not been dispatched.
|
||||
*/
|
||||
[noscript, binaryname(Dispatch)] void dispatchFromC(in alreadyAddRefed_nsIRunnable event, in unsigned long flags);
|
||||
/**
|
||||
* Version of Dispatch to expose to JS, which doesn't require an alreadyAddRefed<>
|
||||
* (it will be converted to that internally)
|
||||
*
|
||||
* @param event
|
||||
* The (raw) event to dispatch.
|
||||
* @param flags
|
||||
* The flags modifying event dispatch. The flags are described in detail
|
||||
* below.
|
||||
*
|
||||
* @throws NS_ERROR_INVALID_ARG
|
||||
* Indicates that event is null.
|
||||
* @throws NS_ERROR_UNEXPECTED
|
||||
* Indicates that the thread is shutting down and has finished processing
|
||||
* events, so this event would never run and has not been dispatched.
|
||||
*/
|
||||
[binaryname(DispatchFromScript)] void dispatch(in nsIRunnable event, in unsigned long flags);
|
||||
/**
|
||||
* Dispatch an event to this event target, but do not run it before delay
|
||||
* milliseconds have passed. This function may be called from any thread.
|
||||
*
|
||||
* @param event
|
||||
* The alreadyAddrefed<> event to dispatch.
|
||||
* @param delay
|
||||
* The delay (in ms) before running the event. If event does not rise to
|
||||
* the top of the event queue before the delay has passed, it will be set
|
||||
* aside to execute once the delay has passed. Otherwise, it will be
|
||||
* executed immediately.
|
||||
*
|
||||
* @throws NS_ERROR_INVALID_ARG
|
||||
* Indicates that event is null.
|
||||
* @throws NS_ERROR_UNEXPECTED
|
||||
* Indicates that the thread is shutting down and has finished processing
|
||||
* events, so this event would never run and has not been dispatched, or
|
||||
* that delay is zero.
|
||||
*/
|
||||
[noscript] void delayedDispatch(in alreadyAddRefed_nsIRunnable event, in unsigned long delay);
|
||||
};
|
||||
|
||||
%{C++
|
||||
// convenient aliases:
|
||||
#define NS_DISPATCH_NORMAL nsIEventTarget::DISPATCH_NORMAL
|
||||
#define NS_DISPATCH_SYNC nsIEventTarget::DISPATCH_SYNC
|
||||
#define NS_DISPATCH_AT_END nsIEventTarget::DISPATCH_AT_END
|
||||
%}
|
||||
32
xpcom/threads/nsIIdlePeriod.idl
Normal file
32
xpcom/threads/nsIIdlePeriod.idl
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "nsISupports.idl"
|
||||
|
||||
%{C++
|
||||
namespace mozilla {
|
||||
class TimeStamp;
|
||||
}
|
||||
%}
|
||||
|
||||
native TimeStamp(mozilla::TimeStamp);
|
||||
|
||||
/**
|
||||
* An instance implementing nsIIdlePeriod is used by an associated
|
||||
* nsIThread to estimate when it is likely that it will receive an
|
||||
* event.
|
||||
*/
|
||||
[builtinclass, uuid(21dd35a2-eae9-4bd8-b470-0dfa35a0e3b9)]
|
||||
interface nsIIdlePeriod : nsISupports
|
||||
{
|
||||
/**
|
||||
* Return an estimate of a point in time in the future when we
|
||||
* think that the associated thread will become busy. Should
|
||||
* return TimeStamp() (i.e. the null time) or a time less than
|
||||
* TimeStamp::Now() if the thread is currently busy or will become
|
||||
* busy very soon.
|
||||
*/
|
||||
TimeStamp getIdlePeriodHint();
|
||||
};
|
||||
41
xpcom/threads/nsIIncrementalRunnable.h
Normal file
41
xpcom/threads/nsIIncrementalRunnable.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsIIncrementalRunnable_h__
|
||||
#define nsIIncrementalRunnable_h__
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
#define NS_IINCREMENTALRUNNABLE_IID \
|
||||
{ 0x688be92e, 0x7ade, 0x4fdc, \
|
||||
{ 0x9d, 0x83, 0x74, 0xcb, 0xef, 0xf4, 0xa5, 0x2c } }
|
||||
|
||||
|
||||
/**
|
||||
* A task interface for tasks that can schedule their work to happen
|
||||
* in increments bounded by a deadline.
|
||||
*/
|
||||
class nsIIncrementalRunnable : public nsISupports
|
||||
{
|
||||
public:
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINCREMENTALRUNNABLE_IID)
|
||||
|
||||
/**
|
||||
* Notify the task of a point in time in the future when the task
|
||||
* should stop executing.
|
||||
*/
|
||||
virtual void SetDeadline(mozilla::TimeStamp aDeadline) = 0;
|
||||
|
||||
protected:
|
||||
nsIIncrementalRunnable() { }
|
||||
virtual ~nsIIncrementalRunnable() {}
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsIIncrementalRunnable,
|
||||
NS_IINCREMENTALRUNNABLE_IID)
|
||||
|
||||
#endif // nsIIncrementalRunnable_h__
|
||||
99
xpcom/threads/nsIProcess.idl
Normal file
99
xpcom/threads/nsIProcess.idl
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* 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 "nsISupports.idl"
|
||||
|
||||
interface nsIFile;
|
||||
interface nsIObserver;
|
||||
|
||||
[scriptable, uuid(609610de-9954-4a63-8a7c-346350a86403)]
|
||||
interface nsIProcess : nsISupports
|
||||
{
|
||||
/**
|
||||
* Initialises the process with an executable to be run. Call the run method
|
||||
* to run the executable.
|
||||
* @param executable The executable to run.
|
||||
*/
|
||||
void init(in nsIFile executable);
|
||||
|
||||
/**
|
||||
* Kills the running process. After exiting the process will either have
|
||||
* been killed or a failure will have been returned.
|
||||
*/
|
||||
void kill();
|
||||
|
||||
/**
|
||||
* Executes the file this object was initialized with
|
||||
* @param blocking Whether to wait until the process terminates before
|
||||
returning or not.
|
||||
* @param args An array of arguments to pass to the process in the
|
||||
* native character set.
|
||||
* @param count The length of the args array.
|
||||
*/
|
||||
void run(in boolean blocking, [array, size_is(count)] in string args,
|
||||
in unsigned long count);
|
||||
|
||||
/**
|
||||
* Executes the file this object was initialized with optionally calling
|
||||
* an observer after the process has finished running.
|
||||
* @param args An array of arguments to pass to the process in the
|
||||
* native character set.
|
||||
* @param count The length of the args array.
|
||||
* @param observer An observer to notify when the process has completed. It
|
||||
* will receive this process instance as the subject and
|
||||
* "process-finished" or "process-failed" as the topic. The
|
||||
* observer will be notified on the main thread.
|
||||
* @param holdWeak Whether to use a weak reference to hold the observer.
|
||||
*/
|
||||
void runAsync([array, size_is(count)] in string args, in unsigned long count,
|
||||
[optional] in nsIObserver observer, [optional] in boolean holdWeak);
|
||||
|
||||
/**
|
||||
* Executes the file this object was initialized with
|
||||
* @param blocking Whether to wait until the process terminates before
|
||||
returning or not.
|
||||
* @param args An array of arguments to pass to the process in UTF-16
|
||||
* @param count The length of the args array.
|
||||
*/
|
||||
void runw(in boolean blocking, [array, size_is(count)] in wstring args,
|
||||
in unsigned long count);
|
||||
|
||||
/**
|
||||
* Executes the file this object was initialized with optionally calling
|
||||
* an observer after the process has finished running.
|
||||
* @param args An array of arguments to pass to the process in UTF-16
|
||||
* @param count The length of the args array.
|
||||
* @param observer An observer to notify when the process has completed. It
|
||||
* will receive this process instance as the subject and
|
||||
* "process-finished" or "process-failed" as the topic. The
|
||||
* observer will be notified on the main thread.
|
||||
* @param holdWeak Whether to use a weak reference to hold the observer.
|
||||
*/
|
||||
void runwAsync([array, size_is(count)] in wstring args,
|
||||
in unsigned long count,
|
||||
[optional] in nsIObserver observer, [optional] in boolean holdWeak);
|
||||
|
||||
/**
|
||||
* The process identifier of the currently running process. This will only
|
||||
* be available after the process has started and may not be available on
|
||||
* some platforms.
|
||||
*/
|
||||
readonly attribute unsigned long pid;
|
||||
|
||||
/**
|
||||
* The exit value of the process. This is only valid after the process has
|
||||
* exited.
|
||||
*/
|
||||
readonly attribute long exitValue;
|
||||
|
||||
/**
|
||||
* Returns whether the process is currently running or not.
|
||||
*/
|
||||
readonly attribute boolean isRunning;
|
||||
};
|
||||
|
||||
%{C++
|
||||
|
||||
#define NS_PROCESS_CONTRACTID "@mozilla.org/process/util;1"
|
||||
%}
|
||||
27
xpcom/threads/nsIRunnable.idl
Normal file
27
xpcom/threads/nsIRunnable.idl
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* Represents a task which can be dispatched to a thread for execution.
|
||||
*/
|
||||
|
||||
[scriptable, function, uuid(4a2abaf0-6886-11d3-9382-00104ba0fd40)]
|
||||
interface nsIRunnable : nsISupports
|
||||
{
|
||||
/**
|
||||
* The function implementing the task to be run.
|
||||
*/
|
||||
void run();
|
||||
};
|
||||
|
||||
[uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)]
|
||||
interface nsIRunnablePriority : nsISupports
|
||||
{
|
||||
const unsigned short PRIORITY_NORMAL = 0;
|
||||
const unsigned short PRIORITY_HIGH = 1;
|
||||
readonly attribute unsigned long priority;
|
||||
};
|
||||
45
xpcom/threads/nsISupportsPriority.idl
Normal file
45
xpcom/threads/nsISupportsPriority.idl
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* 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 "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* This interface exposes the general notion of a scheduled object with a
|
||||
* integral priority value. Following UNIX conventions, smaller (and possibly
|
||||
* negative) values have higher priority.
|
||||
*
|
||||
* This interface does not strictly define what happens when the priority of an
|
||||
* object is changed. An implementation of this interface is free to define
|
||||
* the side-effects of changing the priority of an object. In some cases,
|
||||
* changing the priority of an object may be disallowed (resulting in an
|
||||
* exception being thrown) or may simply be ignored.
|
||||
*/
|
||||
[scriptable, uuid(aa578b44-abd5-4c19-8b14-36d4de6fdc36)]
|
||||
interface nsISupportsPriority : nsISupports
|
||||
{
|
||||
/**
|
||||
* Typical priority values.
|
||||
*/
|
||||
const long PRIORITY_HIGHEST = -20;
|
||||
const long PRIORITY_HIGH = -10;
|
||||
const long PRIORITY_NORMAL = 0;
|
||||
const long PRIORITY_LOW = 10;
|
||||
const long PRIORITY_LOWEST = 20;
|
||||
|
||||
/**
|
||||
* This attribute may be modified to change the priority of this object. The
|
||||
* implementation of this interface is free to truncate a given priority
|
||||
* value to whatever limits are appropriate. Typically, this attribute is
|
||||
* initialized to PRIORITY_NORMAL, but implementations may choose to assign a
|
||||
* different initial value.
|
||||
*/
|
||||
attribute long priority;
|
||||
|
||||
/**
|
||||
* This method adjusts the priority attribute by a given delta. It helps
|
||||
* reduce the amount of coding required to increment or decrement the value
|
||||
* of the priority attribute.
|
||||
*/
|
||||
void adjustPriority(in long delta);
|
||||
};
|
||||
149
xpcom/threads/nsIThread.idl
Normal file
149
xpcom/threads/nsIThread.idl
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "nsIEventTarget.idl"
|
||||
#include "nsIIdlePeriod.idl"
|
||||
|
||||
%{C++
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
%}
|
||||
|
||||
[ptr] native PRThread(PRThread);
|
||||
|
||||
native alreadyAddRefed_nsIIdlePeriod(already_AddRefed<nsIIdlePeriod>);
|
||||
|
||||
/**
|
||||
* This interface provides a high-level abstraction for an operating system
|
||||
* thread.
|
||||
*
|
||||
* Threads have a built-in event queue, and a thread is an event target that
|
||||
* can receive nsIRunnable objects (events) to be processed on the thread.
|
||||
*
|
||||
* See nsIThreadManager for the API used to create and locate threads.
|
||||
*/
|
||||
[scriptable, uuid(5801d193-29d1-4964-a6b7-70eb697ddf2b)]
|
||||
interface nsIThread : nsIEventTarget
|
||||
{
|
||||
/**
|
||||
* @returns
|
||||
* The NSPR thread object corresponding to this nsIThread.
|
||||
*/
|
||||
[noscript] readonly attribute PRThread PRThread;
|
||||
|
||||
/**
|
||||
* @returns
|
||||
* Whether or not this thread may call into JS. Used in the profiler
|
||||
* to avoid some unnecessary locking.
|
||||
*/
|
||||
[noscript] attribute boolean CanInvokeJS;
|
||||
|
||||
|
||||
/**
|
||||
* Shutdown the thread. This method prevents further dispatch of events to
|
||||
* the thread, and it causes any pending events to run to completion before
|
||||
* the thread joins (see PR_JoinThread) with the current thread. During this
|
||||
* method call, events for the current thread may be processed.
|
||||
*
|
||||
* This method MAY NOT be executed from the thread itself. Instead, it is
|
||||
* meant to be executed from another thread (usually the thread that created
|
||||
* this thread or the main application thread). When this function returns,
|
||||
* the thread will be shutdown, and it will no longer be possible to dispatch
|
||||
* events to the thread.
|
||||
*
|
||||
* @throws NS_ERROR_UNEXPECTED
|
||||
* Indicates that this method was erroneously called when this thread was
|
||||
* the current thread, that this thread was not created with a call to
|
||||
* nsIThreadManager::NewThread, or if this method was called more than once
|
||||
* on the thread object.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* This method may be called to determine if there are any events ready to be
|
||||
* processed. It may only be called when this thread is the current thread.
|
||||
*
|
||||
* Because events may be added to this thread by another thread, a "false"
|
||||
* result does not mean that this thread has no pending events. It only
|
||||
* means that there were no pending events when this method was called.
|
||||
*
|
||||
* @returns
|
||||
* A boolean value that if "true" indicates that this thread has one or
|
||||
* more pending events.
|
||||
*
|
||||
* @throws NS_ERROR_UNEXPECTED
|
||||
* Indicates that this method was erroneously called when this thread was
|
||||
* not the current thread.
|
||||
*/
|
||||
boolean hasPendingEvents();
|
||||
|
||||
/**
|
||||
* Process the next event. If there are no pending events, then this method
|
||||
* may wait -- depending on the value of the mayWait parameter -- until an
|
||||
* event is dispatched to this thread. This method is re-entrant but may
|
||||
* only be called if this thread is the current thread.
|
||||
*
|
||||
* @param mayWait
|
||||
* A boolean parameter that if "true" indicates that the method may block
|
||||
* the calling thread to wait for a pending event.
|
||||
*
|
||||
* @returns
|
||||
* A boolean value that if "true" indicates that an event was processed.
|
||||
*
|
||||
* @throws NS_ERROR_UNEXPECTED
|
||||
* Indicates that this method was erroneously called when this thread was
|
||||
* not the current thread.
|
||||
*/
|
||||
boolean processNextEvent(in boolean mayWait);
|
||||
|
||||
/**
|
||||
* Shutdown the thread asynchronously. This method immediately prevents
|
||||
* further dispatch of events to the thread, and it causes any pending events
|
||||
* to run to completion before this thread joins with the current thread.
|
||||
*
|
||||
* UNLIKE shutdown() this does not process events on the current thread.
|
||||
* Instead it merely ensures that the current thread continues running until
|
||||
* this thread has shut down.
|
||||
*
|
||||
* This method MAY NOT be executed from the thread itself. Instead, it is
|
||||
* meant to be executed from another thread (usually the thread that created
|
||||
* this thread or the main application thread). When this function returns,
|
||||
* the thread will continue running until it exhausts its event queue.
|
||||
*
|
||||
* @throws NS_ERROR_UNEXPECTED
|
||||
* Indicates that this method was erroneously called when this thread was
|
||||
* the current thread, that this thread was not created with a call to
|
||||
* nsIThreadManager::NewThread, or if this method was called more than once
|
||||
* on the thread object.
|
||||
*/
|
||||
void asyncShutdown();
|
||||
|
||||
/**
|
||||
* Register an instance of nsIIdlePeriod which works as a facade of
|
||||
* the abstract notion of a "current idle period". The
|
||||
* nsIIdlePeriod should always represent the "current" idle period
|
||||
* with regard to expected work for the thread. The thread is free
|
||||
* to use this when there are no higher prioritized tasks to process
|
||||
* to determine if it is reasonable to schedule tasks that could run
|
||||
* when the thread is idle. The responsibility of the registered
|
||||
* nsIIdlePeriod is to answer with an estimated deadline at which
|
||||
* the thread should expect that it could receive new higher
|
||||
* priority tasks.
|
||||
*/
|
||||
[noscript] void registerIdlePeriod(in alreadyAddRefed_nsIIdlePeriod aIdlePeriod);
|
||||
|
||||
/**
|
||||
* Dispatch an event to the thread's idle queue. This function may be called
|
||||
* from any thread, and it may be called re-entrantly.
|
||||
*
|
||||
* @param event
|
||||
* The alreadyAddRefed<> event to dispatch.
|
||||
* NOTE that the event will be leaked if it fails to dispatch.
|
||||
*
|
||||
* @throws NS_ERROR_INVALID_ARG
|
||||
* Indicates that event is null.
|
||||
*/
|
||||
[noscript] void idleDispatch(in alreadyAddRefed_nsIRunnable event);
|
||||
};
|
||||
135
xpcom/threads/nsIThreadInternal.idl
Normal file
135
xpcom/threads/nsIThreadInternal.idl
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "nsIThread.idl"
|
||||
|
||||
interface nsIRunnable;
|
||||
interface nsIThreadObserver;
|
||||
|
||||
/**
|
||||
* The XPCOM thread object implements this interface, which allows a consumer
|
||||
* to observe dispatch activity on the thread.
|
||||
*/
|
||||
[scriptable, uuid(a3a72e5f-71d9-4add-8f30-59a78fb6d5eb)]
|
||||
interface nsIThreadInternal : nsIThread
|
||||
{
|
||||
/**
|
||||
* Get/set the current thread observer (may be null). This attribute may be
|
||||
* read from any thread, but must only be set on the thread corresponding to
|
||||
* this thread object. The observer will be released on the thread
|
||||
* corresponding to this thread object after all other events have been
|
||||
* processed during a call to Shutdown.
|
||||
*/
|
||||
attribute nsIThreadObserver observer;
|
||||
|
||||
/**
|
||||
* Add an observer that will *only* receive onProcessNextEvent,
|
||||
* beforeProcessNextEvent. and afterProcessNextEvent callbacks. Always called
|
||||
* on the target thread, and the implementation does not have to be
|
||||
* threadsafe. Order of callbacks is not guaranteed (i.e.
|
||||
* afterProcessNextEvent may be called first depending on whether or not the
|
||||
* observer is added in a nested loop). Holds a strong ref.
|
||||
*/
|
||||
void addObserver(in nsIThreadObserver observer);
|
||||
|
||||
/**
|
||||
* Remove an observer added via the addObserver call. Once removed the
|
||||
* observer will never be called again by the thread.
|
||||
*/
|
||||
void removeObserver(in nsIThreadObserver observer);
|
||||
|
||||
/**
|
||||
* This method causes any events currently enqueued on the thread to be
|
||||
* suppressed until PopEventQueue is called, and any event dispatched to this
|
||||
* thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be
|
||||
* nested and must each be paired with a call to PopEventQueue in order to
|
||||
* restore the original state of the thread. The returned nsIEventTarget may
|
||||
* be used to push events onto the nested queue. Dispatching will be disabled
|
||||
* once the event queue is popped. The thread will only ever process pending
|
||||
* events for the innermost event queue. Must only be called on the target
|
||||
* thread.
|
||||
*/
|
||||
[noscript] nsIEventTarget pushEventQueue();
|
||||
|
||||
/**
|
||||
* Revert a call to PushEventQueue. When an event queue is popped, any events
|
||||
* remaining in the queue are appended to the elder queue. This also causes
|
||||
* the nsIEventTarget returned from PushEventQueue to stop dispatching events.
|
||||
* Must only be called on the target thread, and with the innermost event
|
||||
* queue.
|
||||
*/
|
||||
[noscript] void popEventQueue(in nsIEventTarget aInnermostTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* This interface provides the observer with hooks to implement a layered
|
||||
* event queue. For example, it is possible to overlay processing events
|
||||
* for a GUI toolkit on top of the events for a thread:
|
||||
*
|
||||
* var NativeQueue;
|
||||
* Observer = {
|
||||
* onDispatchedEvent(thread) {
|
||||
* NativeQueue.signal();
|
||||
* }
|
||||
* onProcessNextEvent(thread, mayWait) {
|
||||
* if (NativeQueue.hasNextEvent())
|
||||
* NativeQueue.processNextEvent();
|
||||
* while (mayWait && !thread.hasPendingEvent()) {
|
||||
* NativeQueue.wait();
|
||||
* NativeQueue.processNextEvent();
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* NOTE: The implementation of this interface must be threadsafe.
|
||||
*
|
||||
* NOTE: It is valid to change the thread's observer during a call to an
|
||||
* observer method.
|
||||
*
|
||||
* NOTE: Will be split into two interfaces soon: one for onProcessNextEvent and
|
||||
* afterProcessNextEvent, then another that inherits the first and adds
|
||||
* onDispatchedEvent.
|
||||
*/
|
||||
[uuid(cc8da053-1776-44c2-9199-b5a629d0a19d)]
|
||||
interface nsIThreadObserver : nsISupports
|
||||
{
|
||||
/**
|
||||
* This method is called after an event has been dispatched to the thread.
|
||||
* This method may be called from any thread.
|
||||
*
|
||||
* @param thread
|
||||
* The thread where the event is being dispatched.
|
||||
*/
|
||||
void onDispatchedEvent(in nsIThreadInternal thread);
|
||||
|
||||
/**
|
||||
* This method is called when nsIThread::ProcessNextEvent is called. It does
|
||||
* not guarantee that an event is actually going to be processed. This method
|
||||
* is only called on the target thread.
|
||||
*
|
||||
* @param thread
|
||||
* The thread being asked to process another event.
|
||||
* @param mayWait
|
||||
* Indicates whether or not the method is allowed to block the calling
|
||||
* thread. For example, this parameter is false during thread shutdown.
|
||||
*/
|
||||
void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait);
|
||||
|
||||
/**
|
||||
* This method is called (from nsIThread::ProcessNextEvent) after an event
|
||||
* is processed. It does not guarantee that an event was actually processed
|
||||
* (depends on the value of |eventWasProcessed|. This method is only called
|
||||
* on the target thread. DO NOT EVER RUN SCRIPT FROM THIS CALLBACK!!!
|
||||
*
|
||||
* @param thread
|
||||
* The thread that processed another event.
|
||||
* @param eventWasProcessed
|
||||
* Indicates whether an event was actually processed. May be false if the
|
||||
* |mayWait| flag was false when calling nsIThread::ProcessNextEvent().
|
||||
*/
|
||||
void afterProcessNextEvent(in nsIThreadInternal thread,
|
||||
in bool eventWasProcessed);
|
||||
};
|
||||
68
xpcom/threads/nsIThreadManager.idl
Normal file
68
xpcom/threads/nsIThreadManager.idl
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "nsISupports.idl"
|
||||
|
||||
[ptr] native PRThread(PRThread);
|
||||
|
||||
interface nsIThread;
|
||||
|
||||
/**
|
||||
* An interface for creating and locating nsIThread instances.
|
||||
*/
|
||||
[scriptable, uuid(1be89eca-e2f7-453b-8d38-c11ba247f6f3)]
|
||||
interface nsIThreadManager : nsISupports
|
||||
{
|
||||
/**
|
||||
* Default number of bytes reserved for a thread's stack, if no stack size
|
||||
* is specified in newThread(). 0 means use platform default.
|
||||
*/
|
||||
const unsigned long DEFAULT_STACK_SIZE = 0;
|
||||
|
||||
/**
|
||||
* Create a new thread (a global, user PRThread).
|
||||
*
|
||||
* @param creationFlags
|
||||
* Reserved for future use. Pass 0.
|
||||
* @param stackSize
|
||||
* Number of bytes to reserve for the thread's stack.
|
||||
*
|
||||
* @returns
|
||||
* The newly created nsIThread object.
|
||||
*/
|
||||
nsIThread newThread(in unsigned long creationFlags, [optional] in unsigned long stackSize);
|
||||
|
||||
/**
|
||||
* Get the nsIThread object (if any) corresponding to the given PRThread.
|
||||
* This method returns null if there is no corresponding nsIThread.
|
||||
*
|
||||
* @param prthread
|
||||
* The PRThread of the nsIThread being requested.
|
||||
*
|
||||
* @returns
|
||||
* The nsIThread object corresponding to the given PRThread or null if no
|
||||
* such nsIThread exists.
|
||||
*/
|
||||
[noscript] nsIThread getThreadFromPRThread(in PRThread prthread);
|
||||
|
||||
/**
|
||||
* Get the main thread.
|
||||
*/
|
||||
readonly attribute nsIThread mainThread;
|
||||
|
||||
/**
|
||||
* Get the current thread. If the calling thread does not already have a
|
||||
* nsIThread associated with it, then a new nsIThread will be created and
|
||||
* associated with the current PRThread.
|
||||
*/
|
||||
readonly attribute nsIThread currentThread;
|
||||
|
||||
/**
|
||||
* This attribute is true if the calling thread is the main thread of the
|
||||
* application process.
|
||||
*/
|
||||
readonly attribute boolean isMainThread;
|
||||
};
|
||||
88
xpcom/threads/nsIThreadPool.idl
Normal file
88
xpcom/threads/nsIThreadPool.idl
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "nsIEventTarget.idl"
|
||||
|
||||
[scriptable, uuid(ef194cab-3f86-4b61-b132-e5e96a79e5d1)]
|
||||
interface nsIThreadPoolListener : nsISupports
|
||||
{
|
||||
/**
|
||||
* Called when a new thread is created by the thread pool. The notification
|
||||
* happens on the newly-created thread.
|
||||
*/
|
||||
void onThreadCreated();
|
||||
|
||||
/**
|
||||
* Called when a thread is about to be destroyed by the thread pool. The
|
||||
* notification happens on the thread that is about to be destroyed.
|
||||
*/
|
||||
void onThreadShuttingDown();
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface to a thread pool. A thread pool creates a limited number of
|
||||
* anonymous (unnamed) worker threads. An event dispatched to the thread pool
|
||||
* will be run on the next available worker thread.
|
||||
*/
|
||||
[scriptable, uuid(76ce99c9-8e43-489a-9789-f27cc4424965)]
|
||||
interface nsIThreadPool : nsIEventTarget
|
||||
{
|
||||
/**
|
||||
* Shutdown the thread pool. This method may not be executed from any thread
|
||||
* in the thread pool. Instead, it is meant to be executed from another
|
||||
* thread (usually the thread that created this thread pool). When this
|
||||
* function returns, the thread pool and all of its threads will be shutdown,
|
||||
* and it will no longer be possible to dispatch tasks to the thread pool.
|
||||
*
|
||||
* As a side effect, events on the current thread will be processed.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Get/set the maximum number of threads allowed at one time in this pool.
|
||||
*/
|
||||
attribute unsigned long threadLimit;
|
||||
|
||||
/**
|
||||
* Get/set the maximum number of idle threads kept alive.
|
||||
*/
|
||||
attribute unsigned long idleThreadLimit;
|
||||
|
||||
/**
|
||||
* Get/set the amount of time in milliseconds before an idle thread is
|
||||
* destroyed.
|
||||
*/
|
||||
attribute unsigned long idleThreadTimeout;
|
||||
|
||||
/**
|
||||
* Get/set the number of bytes reserved for the stack of all threads in
|
||||
* the pool. By default this is nsIThreadManager::DEFAULT_STACK_SIZE.
|
||||
*/
|
||||
attribute unsigned long threadStackSize;
|
||||
|
||||
/**
|
||||
* An optional listener that will be notified when a thread is created or
|
||||
* destroyed in the course of the thread pool's operation.
|
||||
*
|
||||
* A listener will only receive notifications about threads created after the
|
||||
* listener is set so it is recommended that the consumer set the listener
|
||||
* before dispatching the first event. A listener that receives an
|
||||
* onThreadCreated() notification is guaranteed to always receive the
|
||||
* corresponding onThreadShuttingDown() notification.
|
||||
*
|
||||
* The thread pool takes ownership of the listener and releases it when the
|
||||
* shutdown() method is called. Threads created after the listener is set will
|
||||
* also take ownership of the listener so that the listener will be kept alive
|
||||
* long enough to receive the guaranteed onThreadShuttingDown() notification.
|
||||
*/
|
||||
attribute nsIThreadPoolListener listener;
|
||||
|
||||
/**
|
||||
* Set the label for threads in the pool. All threads will be named
|
||||
* "<aName> #<n>", where <n> is a serial number.
|
||||
*/
|
||||
void setName(in ACString aName);
|
||||
};
|
||||
244
xpcom/threads/nsITimer.idl
Normal file
244
xpcom/threads/nsITimer.idl
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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 "nsISupports.idl"
|
||||
|
||||
interface nsIObserver;
|
||||
interface nsIEventTarget;
|
||||
|
||||
%{C++
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
/**
|
||||
* The signature of the timer callback function passed to initWithFuncCallback.
|
||||
* This is the function that will get called when the timer expires if the
|
||||
* timer is initialized via initWithFuncCallback.
|
||||
*
|
||||
* @param aTimer the timer which has expired
|
||||
* @param aClosure opaque parameter passed to initWithFuncCallback
|
||||
*/
|
||||
class nsITimer;
|
||||
typedef void (*nsTimerCallbackFunc) (nsITimer *aTimer, void *aClosure);
|
||||
|
||||
/**
|
||||
* The signature of the timer name callback function passed to
|
||||
* initWithNameableFuncCallback.
|
||||
* This is the function that will get called when timer profiling is enabled
|
||||
* via the "TimerFirings" log module.
|
||||
*
|
||||
* @param aTimer the timer which has expired
|
||||
* @param aClosure opaque parameter passed to initWithFuncCallback
|
||||
* @param aBuf a buffer in which to put the name
|
||||
* @param aLen the length of the buffer
|
||||
*/
|
||||
typedef void (*nsTimerNameCallbackFunc) (nsITimer *aTimer, void *aClosure,
|
||||
char *aBuf, size_t aLen);
|
||||
%}
|
||||
|
||||
native nsTimerCallbackFunc(nsTimerCallbackFunc);
|
||||
native nsTimerNameCallbackFunc(nsTimerNameCallbackFunc);
|
||||
|
||||
/**
|
||||
* The callback interface for timers.
|
||||
*/
|
||||
interface nsITimer;
|
||||
|
||||
[function, scriptable, uuid(a796816d-7d47-4348-9ab8-c7aeb3216a7d)]
|
||||
interface nsITimerCallback : nsISupports
|
||||
{
|
||||
/**
|
||||
* @param aTimer the timer which has expired
|
||||
*/
|
||||
void notify(in nsITimer timer);
|
||||
};
|
||||
|
||||
%{C++
|
||||
// Two timer deadlines must differ by less than half the PRIntervalTime domain.
|
||||
#define DELAY_INTERVAL_LIMIT PR_BIT(8 * sizeof(PRIntervalTime) - 1)
|
||||
%}
|
||||
|
||||
/**
|
||||
* nsITimer instances must be initialized by calling one of the "init" methods
|
||||
* documented below. You may also re-initialize (using one of the init()
|
||||
* methods) an existing instance to avoid the overhead of destroying and
|
||||
* creating a timer. It is not necessary to cancel the timer in that case.
|
||||
*
|
||||
* By default a timer will fire on the thread that created it. Set the .target
|
||||
* attribute to fire on a different thread. Once you have set a timer's .target
|
||||
* and called one of its init functions, any further interactions with the timer
|
||||
* (calling cancel(), changing member fields, etc) should only be done by the
|
||||
* target thread, or races may occur with bad results like timers firing after
|
||||
* they've been canceled, and/or not firing after re-initiatization.
|
||||
*/
|
||||
[scriptable, uuid(3de4b105-363c-482c-a409-baac83a01bfc)]
|
||||
interface nsITimer : nsISupports
|
||||
{
|
||||
/* Timer types */
|
||||
|
||||
/**
|
||||
* Type of a timer that fires once only.
|
||||
*/
|
||||
const short TYPE_ONE_SHOT = 0;
|
||||
|
||||
/**
|
||||
* After firing, a TYPE_REPEATING_SLACK timer is stopped and not restarted
|
||||
* until its callback completes. Specified timer period will be at least
|
||||
* the time between when processing for last firing the callback completes
|
||||
* and when the next firing occurs.
|
||||
*
|
||||
* This is the preferable repeating type for most situations.
|
||||
*/
|
||||
const short TYPE_REPEATING_SLACK = 1;
|
||||
|
||||
/**
|
||||
* TYPE_REPEATING_PRECISE is just a synonym for
|
||||
* TYPE_REPEATING_PRECISE_CAN_SKIP. They used to be distinct, but the old
|
||||
* TYPE_REPEATING_PRECISE kind was similar to TYPE_REPEATING_PRECISE_CAN_SKIP
|
||||
* while also being less useful. So the distinction was removed.
|
||||
*/
|
||||
const short TYPE_REPEATING_PRECISE = 2;
|
||||
|
||||
/**
|
||||
* A TYPE_REPEATING_PRECISE_CAN_SKIP repeating timer aims to have constant
|
||||
* period between firings. The processing time for each timer callback
|
||||
* should not influence the timer period. However this timer type
|
||||
* guarantees that it will not queue up new events to fire the callback
|
||||
* until the previous callback event finishes firing. If the callback
|
||||
* takes a long time, then the next callback will be scheduled immediately
|
||||
* afterward, but only once. This is the only non-slack timer available.
|
||||
*/
|
||||
const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3;
|
||||
|
||||
/**
|
||||
* Initialize a timer that will fire after the said delay.
|
||||
* A user must keep a reference to this timer till it is
|
||||
* is no longer needed or has been cancelled.
|
||||
*
|
||||
* @param aObserver the callback object that observes the
|
||||
* ``timer-callback'' topic with the subject being
|
||||
* the timer itself when the timer fires:
|
||||
*
|
||||
* observe(nsISupports aSubject, => nsITimer
|
||||
* string aTopic, => ``timer-callback''
|
||||
* wstring data => null
|
||||
*
|
||||
* @param aDelay delay in milliseconds for timer to fire
|
||||
* @param aType timer type per TYPE* consts defined above
|
||||
*/
|
||||
void init(in nsIObserver aObserver, in unsigned long aDelay,
|
||||
in unsigned long aType);
|
||||
|
||||
|
||||
/**
|
||||
* Initialize a timer to fire after the given millisecond interval.
|
||||
* This version takes a function to call and a closure to pass to
|
||||
* that function.
|
||||
*
|
||||
* @param aFunc The function to invoke
|
||||
* @param aClosure An opaque pointer to pass to that function
|
||||
* @param aDelay The millisecond interval
|
||||
* @param aType Timer type per TYPE* consts defined above
|
||||
*/
|
||||
[noscript] void initWithFuncCallback(in nsTimerCallbackFunc aCallback,
|
||||
in voidPtr aClosure,
|
||||
in unsigned long aDelay,
|
||||
in unsigned long aType);
|
||||
|
||||
/**
|
||||
* Initialize a timer to fire after the given millisecond interval.
|
||||
* This version takes a function to call.
|
||||
*
|
||||
* @param aFunc nsITimerCallback interface to call when timer expires
|
||||
* @param aDelay The millisecond interval
|
||||
* @param aType Timer type per TYPE* consts defined above
|
||||
*/
|
||||
void initWithCallback(in nsITimerCallback aCallback,
|
||||
in unsigned long aDelay,
|
||||
in unsigned long aType);
|
||||
|
||||
/**
|
||||
* Cancel the timer. This method works on all types, not just on repeating
|
||||
* timers -- you might want to cancel a TYPE_ONE_SHOT timer, and even reuse
|
||||
* it by re-initializing it (to avoid object destruction and creation costs
|
||||
* by conserving one timer instance).
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
/**
|
||||
* Like initWithFuncCallback, but also takes a name for the timer; the name
|
||||
* will be used when timer profiling is enabled via the "TimerFirings" log
|
||||
* module.
|
||||
*
|
||||
* @param aFunc The function to invoke
|
||||
* @param aClosure An opaque pointer to pass to that function
|
||||
* @param aDelay The millisecond interval
|
||||
* @param aType Timer type per TYPE* consts defined above
|
||||
* @param aName The timer's name
|
||||
*/
|
||||
[noscript] void initWithNamedFuncCallback(in nsTimerCallbackFunc aCallback,
|
||||
in voidPtr aClosure,
|
||||
in unsigned long aDelay,
|
||||
in unsigned long aType,
|
||||
in string aName);
|
||||
|
||||
/**
|
||||
* Like initWithNamedFuncCallback, but instead of a timer name it takes a
|
||||
* callback that will provide a name when the timer fires.
|
||||
*
|
||||
* @param aFunc The function to invoke
|
||||
* @param aClosure An opaque pointer to pass to that function
|
||||
* @param aDelay The millisecond interval
|
||||
* @param aType Timer type per TYPE* consts defined above
|
||||
* @param aNameCallback The callback function
|
||||
*/
|
||||
[noscript] void initWithNameableFuncCallback(
|
||||
in nsTimerCallbackFunc aCallback,
|
||||
in voidPtr aClosure,
|
||||
in unsigned long aDelay,
|
||||
in unsigned long aType,
|
||||
in nsTimerNameCallbackFunc aNameCallback);
|
||||
|
||||
/**
|
||||
* The millisecond delay of the timeout.
|
||||
*
|
||||
* NOTE: Re-setting the delay on a one-shot timer that has already fired
|
||||
* doesn't restart the timer. Call one of the init() methods to restart
|
||||
* a one-shot timer.
|
||||
*/
|
||||
attribute unsigned long delay;
|
||||
|
||||
/**
|
||||
* The timer type - one of the above TYPE_* constants.
|
||||
*/
|
||||
attribute unsigned long type;
|
||||
|
||||
/**
|
||||
* The opaque pointer pass to initWithFuncCallback.
|
||||
*/
|
||||
[noscript] readonly attribute voidPtr closure;
|
||||
|
||||
/**
|
||||
* The nsITimerCallback object passed to initWithCallback.
|
||||
*/
|
||||
readonly attribute nsITimerCallback callback;
|
||||
|
||||
/**
|
||||
* The nsIEventTarget where the callback will be dispatched. Note that this
|
||||
* target may only be set before the call to one of the init methods above.
|
||||
*
|
||||
* By default the target is the thread that created the timer.
|
||||
*/
|
||||
attribute nsIEventTarget target;
|
||||
|
||||
%{C++
|
||||
virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0;
|
||||
%}
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_TIMER_CONTRACTID "@mozilla.org/timer;1"
|
||||
#define NS_TIMER_CALLBACK_TOPIC "timer-callback"
|
||||
%}
|
||||
|
||||
54
xpcom/threads/nsMemoryPressure.cpp
Normal file
54
xpcom/threads/nsMemoryPressure.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/* -*- 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 "nsMemoryPressure.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
static Atomic<int32_t, Relaxed> sMemoryPressurePending;
|
||||
static_assert(MemPressure_None == 0,
|
||||
"Bad static initialization with the default constructor.");
|
||||
|
||||
MemoryPressureState
|
||||
NS_GetPendingMemoryPressure()
|
||||
{
|
||||
int32_t value = sMemoryPressurePending.exchange(MemPressure_None);
|
||||
return MemoryPressureState(value);
|
||||
}
|
||||
|
||||
void
|
||||
NS_DispatchEventualMemoryPressure(MemoryPressureState aState)
|
||||
{
|
||||
/*
|
||||
* A new memory pressure event erases an ongoing memory pressure, but an
|
||||
* existing "new" memory pressure event takes precedence over a new "ongoing"
|
||||
* memory pressure event.
|
||||
*/
|
||||
switch (aState) {
|
||||
case MemPressure_None:
|
||||
sMemoryPressurePending = MemPressure_None;
|
||||
break;
|
||||
case MemPressure_New:
|
||||
sMemoryPressurePending = MemPressure_New;
|
||||
break;
|
||||
case MemPressure_Ongoing:
|
||||
sMemoryPressurePending.compareExchange(MemPressure_None,
|
||||
MemPressure_Ongoing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
NS_DispatchMemoryPressure(MemoryPressureState aState)
|
||||
{
|
||||
NS_DispatchEventualMemoryPressure(aState);
|
||||
nsCOMPtr<nsIRunnable> event = new Runnable;
|
||||
return NS_DispatchToMainThread(event);
|
||||
}
|
||||
77
xpcom/threads/nsMemoryPressure.h
Normal file
77
xpcom/threads/nsMemoryPressure.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsMemoryPressure_h__
|
||||
#define nsMemoryPressure_h__
|
||||
|
||||
#include "nscore.h"
|
||||
|
||||
enum MemoryPressureState
|
||||
{
|
||||
/*
|
||||
* No memory pressure.
|
||||
*/
|
||||
MemPressure_None = 0,
|
||||
|
||||
/*
|
||||
* New memory pressure deteced.
|
||||
*
|
||||
* On a new memory pressure, we stop everything to start cleaning
|
||||
* aggresively the memory used, in order to free as much memory as
|
||||
* possible.
|
||||
*/
|
||||
MemPressure_New,
|
||||
|
||||
/*
|
||||
* Repeated memory pressure.
|
||||
*
|
||||
* A repeated memory pressure implies to clean softly recent allocations.
|
||||
* It is supposed to happen after a new memory pressure which already
|
||||
* cleaned aggressivley. So there is no need to damage the reactivity of
|
||||
* Gecko by stopping the world again.
|
||||
*
|
||||
* In case of conflict with an new memory pressue, the new memory pressure
|
||||
* takes precedence over an ongoing memory pressure. The reason being
|
||||
* that if no events are processed between 2 notifications (new followed
|
||||
* by ongoing, or ongoing followed by a new) we want to be as aggresive as
|
||||
* possible on the clean-up of the memory. After all, we are trying to
|
||||
* keep Gecko alive as long as possible.
|
||||
*/
|
||||
MemPressure_Ongoing
|
||||
};
|
||||
|
||||
/**
|
||||
* Return and erase the latest state of the memory pressure event set by any of
|
||||
* the corresponding dispatch function.
|
||||
*/
|
||||
MemoryPressureState
|
||||
NS_GetPendingMemoryPressure();
|
||||
|
||||
/**
|
||||
* This function causes the main thread to fire a memory pressure event
|
||||
* before processing the next event, but if there are no events pending in
|
||||
* the main thread's event queue, the memory pressure event would not be
|
||||
* dispatched until one is enqueued. It is infallible and does not allocate
|
||||
* any memory.
|
||||
*
|
||||
* You may call this function from any thread.
|
||||
*/
|
||||
void
|
||||
NS_DispatchEventualMemoryPressure(MemoryPressureState aState);
|
||||
|
||||
/**
|
||||
* This function causes the main thread to fire a memory pressure event
|
||||
* before processing the next event. We wake up the main thread by adding a
|
||||
* dummy event to its event loop, so, unlike with
|
||||
* NS_DispatchEventualMemoryPressure, this memory-pressure event is always
|
||||
* fired relatively quickly, even if the event loop is otherwise empty.
|
||||
*
|
||||
* You may call this function from any thread.
|
||||
*/
|
||||
nsresult
|
||||
NS_DispatchMemoryPressure(MemoryPressureState aState);
|
||||
|
||||
#endif // nsMemoryPressure_h__
|
||||
82
xpcom/threads/nsProcess.h
Normal file
82
xpcom/threads/nsProcess.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef _nsPROCESSWIN_H_
|
||||
#define _nsPROCESSWIN_H_
|
||||
|
||||
#if defined(XP_WIN)
|
||||
#define PROCESSMODEL_WINAPI
|
||||
#endif
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsIProcess.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIWeakReferenceUtils.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsString.h"
|
||||
#ifndef XP_MACOSX
|
||||
#include "prproces.h"
|
||||
#endif
|
||||
#if defined(PROCESSMODEL_WINAPI)
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#define NS_PROCESS_CID \
|
||||
{0x7b4eeb20, 0xd781, 0x11d4, \
|
||||
{0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca}}
|
||||
|
||||
class nsProcess final
|
||||
: public nsIProcess
|
||||
, public nsIObserver
|
||||
{
|
||||
public:
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIPROCESS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
nsProcess();
|
||||
|
||||
private:
|
||||
~nsProcess();
|
||||
static void Monitor(void* aArg);
|
||||
void ProcessComplete();
|
||||
nsresult CopyArgsAndRunProcess(bool aBlocking, const char** aArgs,
|
||||
uint32_t aCount, nsIObserver* aObserver,
|
||||
bool aHoldWeak);
|
||||
nsresult CopyArgsAndRunProcessw(bool aBlocking, const char16_t** aArgs,
|
||||
uint32_t aCount, nsIObserver* aObserver,
|
||||
bool aHoldWeak);
|
||||
// The 'args' array is null-terminated.
|
||||
nsresult RunProcess(bool aBlocking, char** aArgs, nsIObserver* aObserver,
|
||||
bool aHoldWeak, bool aArgsUTF8);
|
||||
|
||||
PRThread* mThread;
|
||||
mozilla::Mutex mLock;
|
||||
bool mShutdown;
|
||||
bool mBlocking;
|
||||
|
||||
nsCOMPtr<nsIFile> mExecutable;
|
||||
nsString mTargetPath;
|
||||
int32_t mPid;
|
||||
nsCOMPtr<nsIObserver> mObserver;
|
||||
nsWeakPtr mWeakObserver;
|
||||
|
||||
// These members are modified by multiple threads, any accesses should be
|
||||
// protected with mLock.
|
||||
int32_t mExitValue;
|
||||
#if defined(PROCESSMODEL_WINAPI)
|
||||
HANDLE mProcess;
|
||||
#elif !defined(XP_MACOSX)
|
||||
PRProcess* mProcess;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
663
xpcom/threads/nsProcessCommon.cpp
Normal file
663
xpcom/threads/nsProcessCommon.cpp
Normal file
|
|
@ -0,0 +1,663 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* nsProcess is used to execute new processes and specify if you want to
|
||||
* wait (blocking) or continue (non-blocking).
|
||||
*
|
||||
*****************************************************************************
|
||||
*/
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsProcess.h"
|
||||
#include "prio.h"
|
||||
#include "prenv.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "mozilla/Services.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(PROCESSMODEL_WINAPI)
|
||||
#include "prmem.h"
|
||||
#include "nsString.h"
|
||||
#include "nsLiteralString.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#else
|
||||
#ifdef XP_MACOSX
|
||||
#include <crt_externs.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/errno.h>
|
||||
#endif
|
||||
#include <sys/types.h>
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
cpu_type_t pref_cpu_types[2] = {
|
||||
#if defined(__i386__)
|
||||
CPU_TYPE_X86,
|
||||
#elif defined(__x86_64__)
|
||||
CPU_TYPE_X86_64,
|
||||
#elif defined(__ppc__)
|
||||
CPU_TYPE_POWERPC,
|
||||
#endif
|
||||
CPU_TYPE_ANY
|
||||
};
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------------------------//
|
||||
// nsIProcess implementation
|
||||
//-------------------------------------------------------------------//
|
||||
NS_IMPL_ISUPPORTS(nsProcess, nsIProcess,
|
||||
nsIObserver)
|
||||
|
||||
//Constructor
|
||||
nsProcess::nsProcess()
|
||||
: mThread(nullptr)
|
||||
, mLock("nsProcess.mLock")
|
||||
, mShutdown(false)
|
||||
, mBlocking(false)
|
||||
, mPid(-1)
|
||||
, mObserver(nullptr)
|
||||
, mWeakObserver(nullptr)
|
||||
, mExitValue(-1)
|
||||
#if !defined(XP_MACOSX)
|
||||
, mProcess(nullptr)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
//Destructor
|
||||
nsProcess::~nsProcess()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProcess::Init(nsIFile* aExecutable)
|
||||
{
|
||||
if (mExecutable) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!aExecutable)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
bool isFile;
|
||||
|
||||
//First make sure the file exists
|
||||
nsresult rv = aExecutable->IsFile(&isFile);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (!isFile) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
//Store the nsIFile in mExecutable
|
||||
mExecutable = aExecutable;
|
||||
//Get the path because it is needed by the NSPR process creation
|
||||
#ifdef XP_WIN
|
||||
rv = mExecutable->GetTarget(mTargetPath);
|
||||
if (NS_FAILED(rv) || mTargetPath.IsEmpty())
|
||||
#endif
|
||||
rv = mExecutable->GetPath(mTargetPath);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
#if defined(XP_WIN)
|
||||
// Out param `aWideCmdLine` must be PR_Freed by the caller.
|
||||
static int
|
||||
assembleCmdLine(char* const* aArgv, wchar_t** aWideCmdLine, UINT aCodePage)
|
||||
{
|
||||
char* const* arg;
|
||||
char* p;
|
||||
char* q;
|
||||
char* cmdLine;
|
||||
int cmdLineSize;
|
||||
int numBackslashes;
|
||||
int i;
|
||||
int argNeedQuotes;
|
||||
|
||||
/*
|
||||
* Find out how large the command line buffer should be.
|
||||
*/
|
||||
cmdLineSize = 0;
|
||||
for (arg = aArgv; *arg; ++arg) {
|
||||
/*
|
||||
* \ and " need to be escaped by a \. In the worst case,
|
||||
* every character is a \ or ", so the string of length
|
||||
* may double. If we quote an argument, that needs two ".
|
||||
* Finally, we need a space between arguments, and
|
||||
* a null byte at the end of command line.
|
||||
*/
|
||||
cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */
|
||||
+ 2 /* we quote every argument */
|
||||
+ 1; /* space in between, or final null */
|
||||
}
|
||||
p = cmdLine = (char*)PR_MALLOC(cmdLineSize * sizeof(char));
|
||||
if (!p) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (arg = aArgv; *arg; ++arg) {
|
||||
/* Add a space to separates the arguments */
|
||||
if (arg != aArgv) {
|
||||
*p++ = ' ';
|
||||
}
|
||||
q = *arg;
|
||||
numBackslashes = 0;
|
||||
argNeedQuotes = 0;
|
||||
|
||||
/* If the argument contains white space, it needs to be quoted. */
|
||||
if (strpbrk(*arg, " \f\n\r\t\v")) {
|
||||
argNeedQuotes = 1;
|
||||
}
|
||||
|
||||
if (argNeedQuotes) {
|
||||
*p++ = '"';
|
||||
}
|
||||
while (*q) {
|
||||
if (*q == '\\') {
|
||||
numBackslashes++;
|
||||
q++;
|
||||
} else if (*q == '"') {
|
||||
if (numBackslashes) {
|
||||
/*
|
||||
* Double the backslashes since they are followed
|
||||
* by a quote
|
||||
*/
|
||||
for (i = 0; i < 2 * numBackslashes; i++) {
|
||||
*p++ = '\\';
|
||||
}
|
||||
numBackslashes = 0;
|
||||
}
|
||||
/* To escape the quote */
|
||||
*p++ = '\\';
|
||||
*p++ = *q++;
|
||||
} else {
|
||||
if (numBackslashes) {
|
||||
/*
|
||||
* Backslashes are not followed by a quote, so
|
||||
* don't need to double the backslashes.
|
||||
*/
|
||||
for (i = 0; i < numBackslashes; i++) {
|
||||
*p++ = '\\';
|
||||
}
|
||||
numBackslashes = 0;
|
||||
}
|
||||
*p++ = *q++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we are at the end of this argument */
|
||||
if (numBackslashes) {
|
||||
/*
|
||||
* Double the backslashes if we have a quote string
|
||||
* delimiter at the end.
|
||||
*/
|
||||
if (argNeedQuotes) {
|
||||
numBackslashes *= 2;
|
||||
}
|
||||
for (i = 0; i < numBackslashes; i++) {
|
||||
*p++ = '\\';
|
||||
}
|
||||
}
|
||||
if (argNeedQuotes) {
|
||||
*p++ = '"';
|
||||
}
|
||||
}
|
||||
|
||||
*p = '\0';
|
||||
int32_t numChars = MultiByteToWideChar(aCodePage, 0, cmdLine, -1, nullptr, 0);
|
||||
*aWideCmdLine = (wchar_t*)PR_MALLOC(numChars * sizeof(wchar_t));
|
||||
MultiByteToWideChar(aCodePage, 0, cmdLine, -1, *aWideCmdLine, numChars);
|
||||
PR_Free(cmdLine);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
nsProcess::Monitor(void* aArg)
|
||||
{
|
||||
RefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(aArg));
|
||||
|
||||
if (!process->mBlocking) {
|
||||
PR_SetCurrentThreadName("RunProcess");
|
||||
}
|
||||
|
||||
#if defined(PROCESSMODEL_WINAPI)
|
||||
DWORD dwRetVal;
|
||||
unsigned long exitCode = -1;
|
||||
|
||||
dwRetVal = WaitForSingleObject(process->mProcess, INFINITE);
|
||||
if (dwRetVal != WAIT_FAILED) {
|
||||
if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) {
|
||||
exitCode = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Lock in case Kill or GetExitCode are called during this
|
||||
{
|
||||
MutexAutoLock lock(process->mLock);
|
||||
CloseHandle(process->mProcess);
|
||||
process->mProcess = nullptr;
|
||||
process->mExitValue = exitCode;
|
||||
if (process->mShutdown) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#ifdef XP_MACOSX
|
||||
int exitCode = -1;
|
||||
int status = 0;
|
||||
pid_t result;
|
||||
do {
|
||||
result = waitpid(process->mPid, &status, 0);
|
||||
} while (result == -1 && errno == EINTR);
|
||||
if (result == process->mPid) {
|
||||
if (WIFEXITED(status)) {
|
||||
exitCode = WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
exitCode = 256; // match NSPR's signal exit status
|
||||
}
|
||||
}
|
||||
#else
|
||||
int32_t exitCode = -1;
|
||||
if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) {
|
||||
exitCode = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Lock in case Kill or GetExitCode are called during this
|
||||
{
|
||||
MutexAutoLock lock(process->mLock);
|
||||
#if !defined(XP_MACOSX)
|
||||
process->mProcess = nullptr;
|
||||
#endif
|
||||
process->mExitValue = exitCode;
|
||||
if (process->mShutdown) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// If we ran a background thread for the monitor then notify on the main
|
||||
// thread
|
||||
if (NS_IsMainThread()) {
|
||||
process->ProcessComplete();
|
||||
} else {
|
||||
NS_DispatchToMainThread(NewRunnableMethod(process, &nsProcess::ProcessComplete));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsProcess::ProcessComplete()
|
||||
{
|
||||
if (mThread) {
|
||||
nsCOMPtr<nsIObserverService> os =
|
||||
mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
os->RemoveObserver(this, "xpcom-shutdown");
|
||||
}
|
||||
PR_JoinThread(mThread);
|
||||
mThread = nullptr;
|
||||
}
|
||||
|
||||
const char* topic;
|
||||
if (mExitValue < 0) {
|
||||
topic = "process-failed";
|
||||
} else {
|
||||
topic = "process-finished";
|
||||
}
|
||||
|
||||
mPid = -1;
|
||||
nsCOMPtr<nsIObserver> observer;
|
||||
if (mWeakObserver) {
|
||||
observer = do_QueryReferent(mWeakObserver);
|
||||
} else if (mObserver) {
|
||||
observer = mObserver;
|
||||
}
|
||||
mObserver = nullptr;
|
||||
mWeakObserver = nullptr;
|
||||
|
||||
if (observer) {
|
||||
observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// XXXldb |aArgs| has the wrong const-ness
|
||||
NS_IMETHODIMP
|
||||
nsProcess::Run(bool aBlocking, const char** aArgs, uint32_t aCount)
|
||||
{
|
||||
return CopyArgsAndRunProcess(aBlocking, aArgs, aCount, nullptr, false);
|
||||
}
|
||||
|
||||
// XXXldb |aArgs| has the wrong const-ness
|
||||
NS_IMETHODIMP
|
||||
nsProcess::RunAsync(const char** aArgs, uint32_t aCount,
|
||||
nsIObserver* aObserver, bool aHoldWeak)
|
||||
{
|
||||
return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs,
|
||||
uint32_t aCount, nsIObserver* aObserver,
|
||||
bool aHoldWeak)
|
||||
{
|
||||
// Add one to the aCount for the program name and one for null termination.
|
||||
char** my_argv = nullptr;
|
||||
my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2));
|
||||
if (!my_argv) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
my_argv[0] = ToNewUTF8String(mTargetPath);
|
||||
|
||||
for (uint32_t i = 0; i < aCount; ++i) {
|
||||
my_argv[i + 1] = const_cast<char*>(aArgs[i]);
|
||||
}
|
||||
|
||||
my_argv[aCount + 1] = nullptr;
|
||||
|
||||
nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, false);
|
||||
|
||||
free(my_argv[0]);
|
||||
free(my_argv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
// XXXldb |aArgs| has the wrong const-ness
|
||||
NS_IMETHODIMP
|
||||
nsProcess::Runw(bool aBlocking, const char16_t** aArgs, uint32_t aCount)
|
||||
{
|
||||
return CopyArgsAndRunProcessw(aBlocking, aArgs, aCount, nullptr, false);
|
||||
}
|
||||
|
||||
// XXXldb |aArgs| has the wrong const-ness
|
||||
NS_IMETHODIMP
|
||||
nsProcess::RunwAsync(const char16_t** aArgs, uint32_t aCount,
|
||||
nsIObserver* aObserver, bool aHoldWeak)
|
||||
{
|
||||
return CopyArgsAndRunProcessw(false, aArgs, aCount, aObserver, aHoldWeak);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsProcess::CopyArgsAndRunProcessw(bool aBlocking, const char16_t** aArgs,
|
||||
uint32_t aCount, nsIObserver* aObserver,
|
||||
bool aHoldWeak)
|
||||
{
|
||||
// Add one to the aCount for the program name and one for null termination.
|
||||
char** my_argv = nullptr;
|
||||
my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2));
|
||||
if (!my_argv) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
my_argv[0] = ToNewUTF8String(mTargetPath);
|
||||
|
||||
for (uint32_t i = 0; i < aCount; i++) {
|
||||
my_argv[i + 1] = ToNewUTF8String(nsDependentString(aArgs[i]));
|
||||
}
|
||||
|
||||
my_argv[aCount + 1] = nullptr;
|
||||
|
||||
nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, true);
|
||||
|
||||
for (uint32_t i = 0; i <= aCount; ++i) {
|
||||
free(my_argv[i]);
|
||||
}
|
||||
free(my_argv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsProcess::RunProcess(bool aBlocking, char** aMyArgv, nsIObserver* aObserver,
|
||||
bool aHoldWeak, bool aArgsUTF8)
|
||||
{
|
||||
NS_WARNING_ASSERTION(!XRE_IsContentProcess(),
|
||||
"No launching of new processes in the content process");
|
||||
|
||||
if (NS_WARN_IF(!mExecutable)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (NS_WARN_IF(mThread)) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
if (aObserver) {
|
||||
if (aHoldWeak) {
|
||||
mWeakObserver = do_GetWeakReference(aObserver);
|
||||
if (!mWeakObserver) {
|
||||
return NS_NOINTERFACE;
|
||||
}
|
||||
} else {
|
||||
mObserver = aObserver;
|
||||
}
|
||||
}
|
||||
|
||||
mExitValue = -1;
|
||||
mPid = -1;
|
||||
|
||||
#if defined(PROCESSMODEL_WINAPI)
|
||||
BOOL retVal;
|
||||
wchar_t* cmdLine = nullptr;
|
||||
|
||||
// |aMyArgv| is null-terminated and always starts with the program path. If
|
||||
// the second slot is non-null then arguments are being passed.
|
||||
if (aMyArgv[1] && assembleCmdLine(aMyArgv + 1, &cmdLine,
|
||||
aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) {
|
||||
return NS_ERROR_FILE_EXECUTION_FAILED;
|
||||
}
|
||||
|
||||
/* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
|
||||
* from appearing. This makes behavior the same on all platforms. The flag
|
||||
* will not have any effect on non-console applications.
|
||||
*/
|
||||
|
||||
// The program name in aMyArgv[0] is always UTF-8
|
||||
NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]);
|
||||
|
||||
SHELLEXECUTEINFOW sinfo;
|
||||
memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
|
||||
sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
|
||||
sinfo.hwnd = nullptr;
|
||||
sinfo.lpFile = wideFile.get();
|
||||
sinfo.nShow = SW_SHOWNORMAL;
|
||||
sinfo.fMask = SEE_MASK_FLAG_DDEWAIT |
|
||||
SEE_MASK_NO_CONSOLE |
|
||||
SEE_MASK_NOCLOSEPROCESS;
|
||||
|
||||
if (cmdLine) {
|
||||
sinfo.lpParameters = cmdLine;
|
||||
}
|
||||
|
||||
retVal = ShellExecuteExW(&sinfo);
|
||||
if (!retVal) {
|
||||
return NS_ERROR_FILE_EXECUTION_FAILED;
|
||||
}
|
||||
|
||||
mProcess = sinfo.hProcess;
|
||||
|
||||
if (cmdLine) {
|
||||
PR_Free(cmdLine);
|
||||
}
|
||||
|
||||
mPid = GetProcessId(mProcess);
|
||||
#elif defined(XP_MACOSX)
|
||||
// Initialize spawn attributes.
|
||||
posix_spawnattr_t spawnattr;
|
||||
if (posix_spawnattr_init(&spawnattr) != 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Set spawn attributes.
|
||||
size_t attr_count = ArrayLength(pref_cpu_types);
|
||||
size_t attr_ocount = 0;
|
||||
if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types,
|
||||
&attr_ocount) != 0 ||
|
||||
attr_ocount != attr_count) {
|
||||
posix_spawnattr_destroy(&spawnattr);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Note: |aMyArgv| is already null-terminated as required by posix_spawnp.
|
||||
pid_t newPid = 0;
|
||||
int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, &spawnattr, aMyArgv,
|
||||
*_NSGetEnviron());
|
||||
mPid = static_cast<int32_t>(newPid);
|
||||
|
||||
posix_spawnattr_destroy(&spawnattr);
|
||||
|
||||
if (result != 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
#else
|
||||
mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr);
|
||||
if (!mProcess) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
struct MYProcess
|
||||
{
|
||||
uint32_t pid;
|
||||
};
|
||||
MYProcess* ptrProc = (MYProcess*)mProcess;
|
||||
mPid = ptrProc->pid;
|
||||
#endif
|
||||
|
||||
NS_ADDREF_THIS();
|
||||
mBlocking = aBlocking;
|
||||
if (aBlocking) {
|
||||
Monitor(this);
|
||||
if (mExitValue < 0) {
|
||||
return NS_ERROR_FILE_EXECUTION_FAILED;
|
||||
}
|
||||
} else {
|
||||
mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this,
|
||||
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
|
||||
PR_JOINABLE_THREAD, 0);
|
||||
if (!mThread) {
|
||||
NS_RELEASE_THIS();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// It isn't a failure if we just can't watch for shutdown
|
||||
nsCOMPtr<nsIObserverService> os =
|
||||
mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
os->AddObserver(this, "xpcom-shutdown", false);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProcess::GetIsRunning(bool* aIsRunning)
|
||||
{
|
||||
if (mThread) {
|
||||
*aIsRunning = true;
|
||||
} else {
|
||||
*aIsRunning = false;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProcess::GetPid(uint32_t* aPid)
|
||||
{
|
||||
if (!mThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (mPid < 0) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
*aPid = mPid;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProcess::Kill()
|
||||
{
|
||||
if (!mThread) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mLock);
|
||||
#if defined(PROCESSMODEL_WINAPI)
|
||||
if (TerminateProcess(mProcess, 0) == 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
#elif defined(XP_MACOSX)
|
||||
if (kill(mPid, SIGKILL) != 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
#else
|
||||
if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// We must null out mThread if we want IsRunning to return false immediately
|
||||
// after this call.
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
os->RemoveObserver(this, "xpcom-shutdown");
|
||||
}
|
||||
PR_JoinThread(mThread);
|
||||
mThread = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProcess::GetExitValue(int32_t* aExitValue)
|
||||
{
|
||||
MutexAutoLock lock(mLock);
|
||||
|
||||
*aExitValue = mExitValue;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProcess::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
// Shutting down, drop all references
|
||||
if (mThread) {
|
||||
nsCOMPtr<nsIObserverService> os =
|
||||
mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
os->RemoveObserver(this, "xpcom-shutdown");
|
||||
}
|
||||
mThread = nullptr;
|
||||
}
|
||||
|
||||
mObserver = nullptr;
|
||||
mWeakObserver = nullptr;
|
||||
|
||||
MutexAutoLock lock(mLock);
|
||||
mShutdown = true;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
1500
xpcom/threads/nsThread.cpp
Normal file
1500
xpcom/threads/nsThread.cpp
Normal file
File diff suppressed because it is too large
Load diff
284
xpcom/threads/nsThread.h
Normal file
284
xpcom/threads/nsThread.h
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsThread_h__
|
||||
#define nsThread_h__
|
||||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsIIdlePeriod.h"
|
||||
#include "nsIThreadInternal.h"
|
||||
#include "nsISupportsPriority.h"
|
||||
#include "nsEventQueue.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTObserverArray.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
class CycleCollectedJSContext;
|
||||
}
|
||||
|
||||
using mozilla::NotNull;
|
||||
|
||||
// A native thread
|
||||
class nsThread
|
||||
: public nsIThreadInternal
|
||||
, public nsISupportsPriority
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIEVENTTARGET
|
||||
NS_DECL_NSITHREAD
|
||||
NS_DECL_NSITHREADINTERNAL
|
||||
NS_DECL_NSISUPPORTSPRIORITY
|
||||
using nsIEventTarget::Dispatch;
|
||||
|
||||
enum MainThreadFlag
|
||||
{
|
||||
MAIN_THREAD,
|
||||
NOT_MAIN_THREAD
|
||||
};
|
||||
|
||||
nsThread(MainThreadFlag aMainThread, uint32_t aStackSize);
|
||||
|
||||
// Initialize this as a wrapper for a new PRThread.
|
||||
nsresult Init();
|
||||
|
||||
// Initialize this as a wrapper for the current PRThread.
|
||||
nsresult InitCurrentThread();
|
||||
|
||||
// The PRThread corresponding to this thread.
|
||||
PRThread* GetPRThread()
|
||||
{
|
||||
return mThread;
|
||||
}
|
||||
|
||||
// If this flag is true, then the nsThread was created using
|
||||
// nsIThreadManager::NewThread.
|
||||
bool ShutdownRequired()
|
||||
{
|
||||
return mShutdownRequired;
|
||||
}
|
||||
|
||||
// Clear the observer list.
|
||||
void ClearObservers()
|
||||
{
|
||||
mEventObservers.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver);
|
||||
|
||||
uint32_t
|
||||
RecursionDepth() const;
|
||||
|
||||
void ShutdownComplete(NotNull<struct nsThreadShutdownContext*> aContext);
|
||||
|
||||
void WaitForAllAsynchronousShutdowns();
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
enum class ShouldSaveMemoryReport
|
||||
{
|
||||
kMaybeReport,
|
||||
kForceReport
|
||||
};
|
||||
|
||||
static bool SaveMemoryReportNearOOM(ShouldSaveMemoryReport aShouldSave);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void DoMainThreadSpecificProcessing(bool aReallyWait);
|
||||
|
||||
void GetIdleEvent(nsIRunnable** aEvent, mozilla::MutexAutoLock& aProofOfLock);
|
||||
void GetEvent(bool aWait, nsIRunnable** aEvent,
|
||||
mozilla::MutexAutoLock& aProofOfLock);
|
||||
|
||||
protected:
|
||||
class nsChainedEventQueue;
|
||||
|
||||
class nsNestedEventTarget;
|
||||
friend class nsNestedEventTarget;
|
||||
|
||||
friend class nsThreadShutdownEvent;
|
||||
|
||||
virtual ~nsThread();
|
||||
|
||||
bool ShuttingDown()
|
||||
{
|
||||
return mShutdownContext != nullptr;
|
||||
}
|
||||
|
||||
static void ThreadFunc(void* aArg);
|
||||
|
||||
// Helper
|
||||
already_AddRefed<nsIThreadObserver> GetObserver()
|
||||
{
|
||||
nsIThreadObserver* obs;
|
||||
nsThread::GetObserver(&obs);
|
||||
return already_AddRefed<nsIThreadObserver>(obs);
|
||||
}
|
||||
|
||||
// Wrappers for event queue methods:
|
||||
nsresult PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget);
|
||||
nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent,
|
||||
nsNestedEventTarget* aTarget);
|
||||
|
||||
nsresult DispatchInternal(already_AddRefed<nsIRunnable> aEvent,
|
||||
uint32_t aFlags, nsNestedEventTarget* aTarget);
|
||||
|
||||
struct nsThreadShutdownContext* ShutdownInternal(bool aSync);
|
||||
|
||||
// Wrapper for nsEventQueue that supports chaining.
|
||||
class nsChainedEventQueue
|
||||
{
|
||||
public:
|
||||
explicit nsChainedEventQueue(mozilla::Mutex& aLock)
|
||||
: mNext(nullptr)
|
||||
, mEventsAvailable(aLock, "[nsChainedEventQueue.mEventsAvailable]")
|
||||
, mProcessSecondaryQueueRunnable(false)
|
||||
{
|
||||
mNormalQueue =
|
||||
mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
|
||||
nsEventQueue::eSharedCondVarQueue);
|
||||
// Both queues need to use the same CondVar!
|
||||
mSecondaryQueue =
|
||||
mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
|
||||
nsEventQueue::eSharedCondVarQueue);
|
||||
}
|
||||
|
||||
bool GetEvent(bool aMayWait, nsIRunnable** aEvent,
|
||||
mozilla::MutexAutoLock& aProofOfLock);
|
||||
|
||||
void PutEvent(nsIRunnable* aEvent, mozilla::MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
RefPtr<nsIRunnable> event(aEvent);
|
||||
PutEvent(event.forget(), aProofOfLock);
|
||||
}
|
||||
|
||||
void PutEvent(already_AddRefed<nsIRunnable> aEvent,
|
||||
mozilla::MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
RefPtr<nsIRunnable> event(aEvent);
|
||||
nsCOMPtr<nsIRunnablePriority> runnablePrio =
|
||||
do_QueryInterface(event);
|
||||
uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
|
||||
if (runnablePrio) {
|
||||
runnablePrio->GetPriority(&prio);
|
||||
}
|
||||
MOZ_ASSERT(prio == nsIRunnablePriority::PRIORITY_NORMAL ||
|
||||
prio == nsIRunnablePriority::PRIORITY_HIGH);
|
||||
if (prio == nsIRunnablePriority::PRIORITY_NORMAL) {
|
||||
mNormalQueue->PutEvent(event.forget(), aProofOfLock);
|
||||
} else {
|
||||
mSecondaryQueue->PutEvent(event.forget(), aProofOfLock);
|
||||
}
|
||||
}
|
||||
|
||||
bool HasPendingEvent(mozilla::MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
return mNormalQueue->HasPendingEvent(aProofOfLock) ||
|
||||
mSecondaryQueue->HasPendingEvent(aProofOfLock);
|
||||
}
|
||||
|
||||
nsChainedEventQueue* mNext;
|
||||
RefPtr<nsNestedEventTarget> mEventTarget;
|
||||
|
||||
private:
|
||||
mozilla::CondVar mEventsAvailable;
|
||||
mozilla::UniquePtr<nsEventQueue> mNormalQueue;
|
||||
mozilla::UniquePtr<nsEventQueue> mSecondaryQueue;
|
||||
|
||||
// Try to process one high priority runnable after each normal
|
||||
// priority runnable. This gives the processing model HTML spec has for
|
||||
// 'Update the rendering' in the case only vsync messages are in the
|
||||
// secondary queue and prevents starving the normal queue.
|
||||
bool mProcessSecondaryQueueRunnable;
|
||||
};
|
||||
|
||||
class nsNestedEventTarget final : public nsIEventTarget
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIEVENTTARGET
|
||||
|
||||
nsNestedEventTarget(NotNull<nsThread*> aThread,
|
||||
NotNull<nsChainedEventQueue*> aQueue)
|
||||
: mThread(aThread)
|
||||
, mQueue(aQueue)
|
||||
|
||||
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
NotNull<RefPtr<nsThread>> mThread;
|
||||
|
||||
// This is protected by mThread->mLock.
|
||||
nsChainedEventQueue* mQueue;
|
||||
|
||||
private:
|
||||
~nsNestedEventTarget()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// This lock protects access to mObserver, mEvents, mIdleEvents,
|
||||
// mIdlePeriod and mEventsAreDoomed. All of those fields are only
|
||||
// modified on the thread itself (never from another thread). This
|
||||
// means that we can avoid holding the lock while using mObserver
|
||||
// and mEvents on the thread itself. When calling PutEvent on
|
||||
// mEvents, we have to hold the lock to synchronize with
|
||||
// PopEventQueue.
|
||||
mozilla::Mutex mLock;
|
||||
|
||||
nsCOMPtr<nsIThreadObserver> mObserver;
|
||||
mozilla::CycleCollectedJSContext* mScriptObserver;
|
||||
|
||||
// Only accessed on the target thread.
|
||||
nsAutoTObserverArray<NotNull<nsCOMPtr<nsIThreadObserver>>, 2> mEventObservers;
|
||||
|
||||
NotNull<nsChainedEventQueue*> mEvents; // never null
|
||||
nsChainedEventQueue mEventsRoot;
|
||||
|
||||
// mIdlePeriod keeps track of the current idle period. If at any
|
||||
// time the main event queue is empty, calling
|
||||
// mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
|
||||
// the current idle period will end.
|
||||
nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
|
||||
mozilla::CondVar mIdleEventsAvailable;
|
||||
nsEventQueue mIdleEvents;
|
||||
|
||||
int32_t mPriority;
|
||||
PRThread* mThread;
|
||||
uint32_t mNestedEventLoopDepth;
|
||||
uint32_t mStackSize;
|
||||
|
||||
// The shutdown context for ourselves.
|
||||
struct nsThreadShutdownContext* mShutdownContext;
|
||||
// The shutdown contexts for any other threads we've asked to shut down.
|
||||
nsTArray<nsAutoPtr<struct nsThreadShutdownContext>> mRequestedShutdownContexts;
|
||||
|
||||
bool mShutdownRequired;
|
||||
// Set to true when events posted to this thread will never run.
|
||||
bool mEventsAreDoomed;
|
||||
MainThreadFlag mIsMainThread;
|
||||
|
||||
// Set to true if this thread creates a JSRuntime.
|
||||
bool mCanInvokeJS;
|
||||
};
|
||||
|
||||
#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM \
|
||||
&& defined(_GNU_SOURCE)
|
||||
# define MOZ_CANARY
|
||||
|
||||
extern int sCanaryOutputFD;
|
||||
#endif
|
||||
|
||||
#endif // nsThread_h__
|
||||
342
xpcom/threads/nsThreadManager.cpp
Normal file
342
xpcom/threads/nsThreadManager.cpp
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
/* -*- 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 "nsThreadManager.h"
|
||||
#include "nsThread.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsIClassInfoImpl.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#ifdef MOZ_CANARY
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "MainThreadIdlePeriod.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread;
|
||||
|
||||
bool
|
||||
NS_IsMainThread()
|
||||
{
|
||||
return sTLSIsMainThread.get();
|
||||
}
|
||||
|
||||
void
|
||||
NS_SetMainThread()
|
||||
{
|
||||
if (!sTLSIsMainThread.init()) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
sTLSIsMainThread.set(true);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
typedef nsTArray<NotNull<RefPtr<nsThread>>> nsThreadArray;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
ReleaseObject(void* aData)
|
||||
{
|
||||
static_cast<nsISupports*>(aData)->Release();
|
||||
}
|
||||
|
||||
// statically allocated instance
|
||||
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||
nsThreadManager::AddRef()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||
nsThreadManager::Release()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
NS_IMPL_CLASSINFO(nsThreadManager, nullptr,
|
||||
nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
|
||||
NS_THREADMANAGER_CID)
|
||||
NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager)
|
||||
NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
nsresult
|
||||
nsThreadManager::Init()
|
||||
{
|
||||
// Child processes need to initialize the thread manager before they
|
||||
// initialize XPCOM in order to set up the crash reporter. This leads to
|
||||
// situations where we get initialized twice.
|
||||
if (mInitialized) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseObject) == PR_FAILURE) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
#ifdef MOZ_CANARY
|
||||
const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK;
|
||||
const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
||||
char* env_var_flag = getenv("MOZ_KILL_CANARIES");
|
||||
sCanaryOutputFD =
|
||||
env_var_flag ? (env_var_flag[0] ? open(env_var_flag, flags, mode) :
|
||||
STDERR_FILENO) :
|
||||
0;
|
||||
#endif
|
||||
|
||||
// Setup "main" thread
|
||||
mMainThread = new nsThread(nsThread::MAIN_THREAD, 0);
|
||||
|
||||
nsresult rv = mMainThread->InitCurrentThread();
|
||||
if (NS_FAILED(rv)) {
|
||||
mMainThread = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
{
|
||||
nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
|
||||
mMainThread->RegisterIdlePeriod(idlePeriod.forget());
|
||||
}
|
||||
|
||||
// We need to keep a pointer to the current thread, so we can satisfy
|
||||
// GetIsMainThread calls that occur post-Shutdown.
|
||||
mMainThread->GetPRThread(&mMainPRThread);
|
||||
|
||||
mInitialized = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsThreadManager::Shutdown()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread");
|
||||
|
||||
// Prevent further access to the thread manager (no more new threads!)
|
||||
//
|
||||
// What happens if shutdown happens before NewThread completes?
|
||||
// We Shutdown() the new thread, and return error if we've started Shutdown
|
||||
// between when NewThread started, and when the thread finished initializing
|
||||
// and registering with ThreadManager.
|
||||
//
|
||||
mInitialized = false;
|
||||
|
||||
// Empty the main thread event queue before we begin shutting down threads.
|
||||
NS_ProcessPendingEvents(mMainThread);
|
||||
|
||||
// We gather the threads from the hashtable into a list, so that we avoid
|
||||
// holding the hashtable lock while calling nsIThread::Shutdown.
|
||||
nsThreadArray threads;
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(mLock);
|
||||
for (auto iter = mThreadsByPRThread.Iter(); !iter.Done(); iter.Next()) {
|
||||
RefPtr<nsThread>& thread = iter.Data();
|
||||
threads.AppendElement(WrapNotNull(thread));
|
||||
iter.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
// It's tempting to walk the list of threads here and tell them each to stop
|
||||
// accepting new events, but that could lead to badness if one of those
|
||||
// threads is stuck waiting for a response from another thread. To do it
|
||||
// right, we'd need some way to interrupt the threads.
|
||||
//
|
||||
// Instead, we process events on the current thread while waiting for threads
|
||||
// to shutdown. This means that we have to preserve a mostly functioning
|
||||
// world until such time as the threads exit.
|
||||
|
||||
// Shutdown all threads that require it (join with threads that we created).
|
||||
for (uint32_t i = 0; i < threads.Length(); ++i) {
|
||||
NotNull<nsThread*> thread = threads[i];
|
||||
if (thread->ShutdownRequired()) {
|
||||
thread->Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// NB: It's possible that there are events in the queue that want to *start*
|
||||
// an asynchronous shutdown. But we have already shutdown the threads above,
|
||||
// so there's no need to worry about them. We only have to wait for all
|
||||
// in-flight asynchronous thread shutdowns to complete.
|
||||
mMainThread->WaitForAllAsynchronousShutdowns();
|
||||
|
||||
// In case there are any more events somehow...
|
||||
NS_ProcessPendingEvents(mMainThread);
|
||||
|
||||
// There are no more background threads at this point.
|
||||
|
||||
// Clear the table of threads.
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(mLock);
|
||||
mThreadsByPRThread.Clear();
|
||||
}
|
||||
|
||||
// Normally thread shutdown clears the observer for the thread, but since the
|
||||
// main thread is special we do it manually here after we're sure all events
|
||||
// have been processed.
|
||||
mMainThread->SetObserver(nullptr);
|
||||
mMainThread->ClearObservers();
|
||||
|
||||
// Release main thread object.
|
||||
mMainThread = nullptr;
|
||||
|
||||
// Remove the TLS entry for the main thread.
|
||||
PR_SetThreadPrivate(mCurThreadIndex, nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
nsThreadManager::RegisterCurrentThread(nsThread& aThread)
|
||||
{
|
||||
MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
|
||||
|
||||
OffTheBooksMutexAutoLock lock(mLock);
|
||||
|
||||
++mCurrentNumberOfThreads;
|
||||
if (mCurrentNumberOfThreads > mHighestNumberOfThreads) {
|
||||
mHighestNumberOfThreads = mCurrentNumberOfThreads;
|
||||
}
|
||||
|
||||
mThreadsByPRThread.Put(aThread.GetPRThread(), &aThread); // XXX check OOM?
|
||||
|
||||
aThread.AddRef(); // for TLS entry
|
||||
PR_SetThreadPrivate(mCurThreadIndex, &aThread);
|
||||
}
|
||||
|
||||
void
|
||||
nsThreadManager::UnregisterCurrentThread(nsThread& aThread)
|
||||
{
|
||||
MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
|
||||
|
||||
OffTheBooksMutexAutoLock lock(mLock);
|
||||
|
||||
--mCurrentNumberOfThreads;
|
||||
mThreadsByPRThread.Remove(aThread.GetPRThread());
|
||||
|
||||
PR_SetThreadPrivate(mCurThreadIndex, nullptr);
|
||||
// Ref-count balanced via ReleaseObject
|
||||
}
|
||||
|
||||
nsThread*
|
||||
nsThreadManager::GetCurrentThread()
|
||||
{
|
||||
// read thread local storage
|
||||
void* data = PR_GetThreadPrivate(mCurThreadIndex);
|
||||
if (data) {
|
||||
return static_cast<nsThread*>(data);
|
||||
}
|
||||
|
||||
if (!mInitialized) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// OK, that's fine. We'll dynamically create one :-)
|
||||
RefPtr<nsThread> thread = new nsThread(nsThread::NOT_MAIN_THREAD, 0);
|
||||
if (!thread || NS_FAILED(thread->InitCurrentThread())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return thread.get(); // reference held in TLS
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadManager::NewThread(uint32_t aCreationFlags,
|
||||
uint32_t aStackSize,
|
||||
nsIThread** aResult)
|
||||
{
|
||||
// Note: can be called from arbitrary threads
|
||||
|
||||
// No new threads during Shutdown
|
||||
if (NS_WARN_IF(!mInitialized)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
RefPtr<nsThread> thr = new nsThread(nsThread::NOT_MAIN_THREAD, aStackSize);
|
||||
nsresult rv = thr->Init(); // Note: blocks until the new thread has been set up
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// At this point, we expect that the thread has been registered in mThreadByPRThread;
|
||||
// however, it is possible that it could have also been replaced by now, so
|
||||
// we cannot really assert that it was added. Instead, kill it if we entered
|
||||
// Shutdown() during/before Init()
|
||||
|
||||
if (NS_WARN_IF(!mInitialized)) {
|
||||
if (thr->ShutdownRequired()) {
|
||||
thr->Shutdown(); // ok if it happens multiple times
|
||||
}
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
thr.forget(aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadManager::GetThreadFromPRThread(PRThread* aThread, nsIThread** aResult)
|
||||
{
|
||||
// Keep this functioning during Shutdown
|
||||
if (NS_WARN_IF(!mMainThread)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (NS_WARN_IF(!aThread)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
RefPtr<nsThread> temp;
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(mLock);
|
||||
mThreadsByPRThread.Get(aThread, getter_AddRefs(temp));
|
||||
}
|
||||
|
||||
NS_IF_ADDREF(*aResult = temp);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadManager::GetMainThread(nsIThread** aResult)
|
||||
{
|
||||
// Keep this functioning during Shutdown
|
||||
if (NS_WARN_IF(!mMainThread)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
NS_ADDREF(*aResult = mMainThread);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadManager::GetCurrentThread(nsIThread** aResult)
|
||||
{
|
||||
// Keep this functioning during Shutdown
|
||||
if (NS_WARN_IF(!mMainThread)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
*aResult = GetCurrentThread();
|
||||
if (!*aResult) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
NS_ADDREF(*aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadManager::GetIsMainThread(bool* aResult)
|
||||
{
|
||||
// This method may be called post-Shutdown
|
||||
|
||||
*aResult = (PR_GetCurrentThread() == mMainPRThread);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
nsThreadManager::GetHighestNumberOfThreads()
|
||||
{
|
||||
OffTheBooksMutexAutoLock lock(mLock);
|
||||
return mHighestNumberOfThreads;
|
||||
}
|
||||
89
xpcom/threads/nsThreadManager.h
Normal file
89
xpcom/threads/nsThreadManager.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsThreadManager_h__
|
||||
#define nsThreadManager_h__
|
||||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsIThreadManager.h"
|
||||
#include "nsRefPtrHashtable.h"
|
||||
#include "nsThread.h"
|
||||
|
||||
class nsIRunnable;
|
||||
|
||||
class nsThreadManager : public nsIThreadManager
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSITHREADMANAGER
|
||||
|
||||
static nsThreadManager& get()
|
||||
{
|
||||
static nsThreadManager sInstance;
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
nsresult Init();
|
||||
|
||||
// Shutdown all threads. This function should only be called on the main
|
||||
// thread of the application process.
|
||||
void Shutdown();
|
||||
|
||||
// Called by nsThread to inform the ThreadManager it exists. This method
|
||||
// must be called when the given thread is the current thread.
|
||||
void RegisterCurrentThread(nsThread& aThread);
|
||||
|
||||
// Called by nsThread to inform the ThreadManager it is going away. This
|
||||
// method must be called when the given thread is the current thread.
|
||||
void UnregisterCurrentThread(nsThread& aThread);
|
||||
|
||||
// Returns the current thread. Returns null if OOM or if ThreadManager isn't
|
||||
// initialized.
|
||||
nsThread* GetCurrentThread();
|
||||
|
||||
// Returns the maximal number of threads that have been in existence
|
||||
// simultaneously during the execution of the thread manager.
|
||||
uint32_t GetHighestNumberOfThreads();
|
||||
|
||||
// This needs to be public in order to support static instantiation of this
|
||||
// class with older compilers (e.g., egcs-2.91.66).
|
||||
~nsThreadManager()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
nsThreadManager()
|
||||
: mCurThreadIndex(0)
|
||||
, mMainPRThread(nullptr)
|
||||
, mLock("nsThreadManager.mLock")
|
||||
, mInitialized(false)
|
||||
, mCurrentNumberOfThreads(1)
|
||||
, mHighestNumberOfThreads(1)
|
||||
{
|
||||
}
|
||||
|
||||
nsRefPtrHashtable<nsPtrHashKey<PRThread>, nsThread> mThreadsByPRThread;
|
||||
unsigned mCurThreadIndex; // thread-local-storage index
|
||||
RefPtr<nsThread> mMainThread;
|
||||
PRThread* mMainPRThread;
|
||||
mozilla::OffTheBooksMutex mLock; // protects tables
|
||||
mozilla::Atomic<bool> mInitialized;
|
||||
|
||||
// The current number of threads
|
||||
uint32_t mCurrentNumberOfThreads;
|
||||
// The highest number of threads encountered so far during the session
|
||||
uint32_t mHighestNumberOfThreads;
|
||||
};
|
||||
|
||||
#define NS_THREADMANAGER_CID \
|
||||
{ /* 7a4204c6-e45a-4c37-8ebb-6709a22c917c */ \
|
||||
0x7a4204c6, \
|
||||
0xe45a, \
|
||||
0x4c37, \
|
||||
{0x8e, 0xbb, 0x67, 0x09, 0xa2, 0x2c, 0x91, 0x7c} \
|
||||
}
|
||||
|
||||
#endif // nsThreadManager_h__
|
||||
449
xpcom/threads/nsThreadPool.cpp
Normal file
449
xpcom/threads/nsThreadPool.cpp
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
/* -*- 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 "nsIClassInfoImpl.h"
|
||||
#include "nsThreadPool.h"
|
||||
#include "nsThreadManager.h"
|
||||
#include "nsThread.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "prinrval.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "nsThreadSyncDispatch.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
static LazyLogModule sThreadPoolLog("nsThreadPool");
|
||||
#ifdef LOG
|
||||
#undef LOG
|
||||
#endif
|
||||
#define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
|
||||
|
||||
// DESIGN:
|
||||
// o Allocate anonymous threads.
|
||||
// o Use nsThreadPool::Run as the main routine for each thread.
|
||||
// o Each thread waits on the event queue's monitor, checking for
|
||||
// pending events and rescheduling itself as an idle thread.
|
||||
|
||||
#define DEFAULT_THREAD_LIMIT 4
|
||||
#define DEFAULT_IDLE_THREAD_LIMIT 1
|
||||
#define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
|
||||
|
||||
NS_IMPL_ADDREF(nsThreadPool)
|
||||
NS_IMPL_RELEASE(nsThreadPool)
|
||||
NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE,
|
||||
NS_THREADPOOL_CID)
|
||||
NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget,
|
||||
nsIRunnable)
|
||||
NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget)
|
||||
|
||||
nsThreadPool::nsThreadPool()
|
||||
: mMutex("[nsThreadPool.mMutex]")
|
||||
, mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]")
|
||||
, mEvents(mEventsAvailable, nsEventQueue::eNormalQueue)
|
||||
, mThreadLimit(DEFAULT_THREAD_LIMIT)
|
||||
, mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
|
||||
, mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
|
||||
, mIdleCount(0)
|
||||
, mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
|
||||
, mShutdown(false)
|
||||
{
|
||||
LOG(("THRD-P(%p) constructor!!!\n", this));
|
||||
}
|
||||
|
||||
nsThreadPool::~nsThreadPool()
|
||||
{
|
||||
// Threads keep a reference to the nsThreadPool until they return from Run()
|
||||
// after removing themselves from mThreads.
|
||||
MOZ_ASSERT(mThreads.IsEmpty());
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsThreadPool::PutEvent(nsIRunnable* aEvent)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event(aEvent);
|
||||
return PutEvent(event.forget(), 0);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
|
||||
{
|
||||
// Avoid spawning a new thread while holding the event queue lock...
|
||||
|
||||
bool spawnThread = false;
|
||||
uint32_t stackSize = 0;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (NS_WARN_IF(mShutdown)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(),
|
||||
mThreadLimit));
|
||||
MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops");
|
||||
|
||||
// Make sure we have a thread to service this event.
|
||||
if (mThreads.Count() < (int32_t)mThreadLimit &&
|
||||
!(aFlags & NS_DISPATCH_AT_END) &&
|
||||
// Spawn a new thread if we don't have enough idle threads to serve
|
||||
// pending events immediately.
|
||||
mEvents.Count(lock) >= mIdleCount) {
|
||||
spawnThread = true;
|
||||
}
|
||||
|
||||
mEvents.PutEvent(Move(aEvent), lock);
|
||||
stackSize = mStackSize;
|
||||
}
|
||||
|
||||
LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
|
||||
if (!spawnThread) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
nsThreadManager::get().NewThread(0, stackSize, getter_AddRefs(thread));
|
||||
if (NS_WARN_IF(!thread)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
bool killThread = false;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (mThreads.Count() < (int32_t)mThreadLimit) {
|
||||
mThreads.AppendObject(thread);
|
||||
} else {
|
||||
killThread = true; // okay, we don't need this thread anymore
|
||||
}
|
||||
}
|
||||
LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread));
|
||||
if (killThread) {
|
||||
// We never dispatched any events to the thread, so we can shut it down
|
||||
// asynchronously without worrying about anything.
|
||||
ShutdownThread(thread);
|
||||
} else {
|
||||
thread->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsThreadPool::ShutdownThread(nsIThread* aThread)
|
||||
{
|
||||
LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread));
|
||||
|
||||
// This is either called by a threadpool thread that is out of work, or
|
||||
// a thread that attempted to create a threadpool thread and raced in
|
||||
// such a way that the newly created thread is no longer necessary.
|
||||
// In the first case, we must go to another thread to shut aThread down
|
||||
// (because it is the current thread). In the second case, we cannot
|
||||
// synchronously shut down the current thread (because then Dispatch() would
|
||||
// spin the event loop, and that could blow up the world), and asynchronous
|
||||
// shutdown requires this thread have an event loop (and it may not, see bug
|
||||
// 10204784). The simplest way to cover all cases is to asynchronously
|
||||
// shutdown aThread from the main thread.
|
||||
NS_DispatchToMainThread(NewRunnableMethod(aThread,
|
||||
&nsIThread::AsyncShutdown));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::Run()
|
||||
{
|
||||
mThreadNaming.SetThreadPoolName(mName);
|
||||
|
||||
LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading()));
|
||||
|
||||
nsCOMPtr<nsIThread> current;
|
||||
nsThreadManager::get().GetCurrentThread(getter_AddRefs(current));
|
||||
|
||||
bool shutdownThreadOnExit = false;
|
||||
bool exitThread = false;
|
||||
bool wasIdle = false;
|
||||
PRIntervalTime idleSince;
|
||||
|
||||
nsCOMPtr<nsIThreadPoolListener> listener;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
listener = mListener;
|
||||
}
|
||||
|
||||
if (listener) {
|
||||
listener->OnThreadCreated();
|
||||
}
|
||||
|
||||
do {
|
||||
nsCOMPtr<nsIRunnable> event;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (!mEvents.GetPendingEvent(getter_AddRefs(event), lock)) {
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout);
|
||||
|
||||
// If we are shutting down, then don't keep any idle threads
|
||||
if (mShutdown) {
|
||||
exitThread = true;
|
||||
} else {
|
||||
if (wasIdle) {
|
||||
// if too many idle threads or idle for too long, then bail.
|
||||
if (mIdleCount > mIdleThreadLimit ||
|
||||
(mIdleThreadTimeout != UINT32_MAX && (now - idleSince) >= timeout)) {
|
||||
exitThread = true;
|
||||
}
|
||||
} else {
|
||||
// if would be too many idle threads...
|
||||
if (mIdleCount == mIdleThreadLimit) {
|
||||
exitThread = true;
|
||||
} else {
|
||||
++mIdleCount;
|
||||
idleSince = now;
|
||||
wasIdle = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exitThread) {
|
||||
if (wasIdle) {
|
||||
--mIdleCount;
|
||||
}
|
||||
shutdownThreadOnExit = mThreads.RemoveObject(current);
|
||||
} else {
|
||||
PRIntervalTime delta = timeout - (now - idleSince);
|
||||
LOG(("THRD-P(%p) %s waiting [%d]\n", this, mName.BeginReading(), delta));
|
||||
mEvents.Wait(delta);
|
||||
LOG(("THRD-P(%p) done waiting\n", this));
|
||||
}
|
||||
} else if (wasIdle) {
|
||||
wasIdle = false;
|
||||
--mIdleCount;
|
||||
}
|
||||
}
|
||||
if (event) {
|
||||
LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(), event.get()));
|
||||
event->Run();
|
||||
}
|
||||
} while (!exitThread);
|
||||
|
||||
if (listener) {
|
||||
listener->OnThreadShuttingDown();
|
||||
}
|
||||
|
||||
if (shutdownThreadOnExit) {
|
||||
ShutdownThread(current);
|
||||
}
|
||||
|
||||
LOG(("THRD-P(%p) leave\n", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event(aEvent);
|
||||
return Dispatch(event.forget(), aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
|
||||
{
|
||||
LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags));
|
||||
|
||||
if (NS_WARN_IF(mShutdown)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (aFlags & DISPATCH_SYNC) {
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
nsThreadManager::get().GetCurrentThread(getter_AddRefs(thread));
|
||||
if (NS_WARN_IF(!thread)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
RefPtr<nsThreadSyncDispatch> wrapper =
|
||||
new nsThreadSyncDispatch(thread, Move(aEvent));
|
||||
PutEvent(wrapper);
|
||||
|
||||
while (wrapper->IsPending()) {
|
||||
NS_ProcessNextEvent(thread);
|
||||
}
|
||||
} else {
|
||||
NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
|
||||
aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
|
||||
PutEvent(Move(aEvent), aFlags);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::IsOnCurrentThread(bool* aResult)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (NS_WARN_IF(mShutdown)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
nsIThread* thread = NS_GetCurrentThread();
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) {
|
||||
if (mThreads[i] == thread) {
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
*aResult = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::Shutdown()
|
||||
{
|
||||
nsCOMArray<nsIThread> threads;
|
||||
nsCOMPtr<nsIThreadPoolListener> listener;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mShutdown = true;
|
||||
mEvents.NotifyAll();
|
||||
|
||||
threads.AppendObjects(mThreads);
|
||||
mThreads.Clear();
|
||||
|
||||
// Swap in a null listener so that we release the listener at the end of
|
||||
// this method. The listener will be kept alive as long as the other threads
|
||||
// that were created when it was set.
|
||||
mListener.swap(listener);
|
||||
}
|
||||
|
||||
// It's important that we shutdown the threads while outside the event queue
|
||||
// monitor. Otherwise, we could end up dead-locking.
|
||||
|
||||
for (int32_t i = 0; i < threads.Count(); ++i) {
|
||||
threads[i]->Shutdown();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::GetThreadLimit(uint32_t* aValue)
|
||||
{
|
||||
*aValue = mThreadLimit;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::SetThreadLimit(uint32_t aValue)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue));
|
||||
mThreadLimit = aValue;
|
||||
if (mIdleThreadLimit > mThreadLimit) {
|
||||
mIdleThreadLimit = mThreadLimit;
|
||||
}
|
||||
|
||||
if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) {
|
||||
mEvents.NotifyAll(); // wake up threads so they observe this change
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::GetIdleThreadLimit(uint32_t* aValue)
|
||||
{
|
||||
*aValue = mIdleThreadLimit;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::SetIdleThreadLimit(uint32_t aValue)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue));
|
||||
mIdleThreadLimit = aValue;
|
||||
if (mIdleThreadLimit > mThreadLimit) {
|
||||
mIdleThreadLimit = mThreadLimit;
|
||||
}
|
||||
|
||||
// Do we need to kill some idle threads?
|
||||
if (mIdleCount > mIdleThreadLimit) {
|
||||
mEvents.NotifyAll(); // wake up threads so they observe this change
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue)
|
||||
{
|
||||
*aValue = mIdleThreadTimeout;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::SetIdleThreadTimeout(uint32_t aValue)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
uint32_t oldTimeout = mIdleThreadTimeout;
|
||||
mIdleThreadTimeout = aValue;
|
||||
|
||||
// Do we need to notify any idle threads that their sleep time has shortened?
|
||||
if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) {
|
||||
mEvents.NotifyAll(); // wake up threads so they observe this change
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::GetThreadStackSize(uint32_t* aValue)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
*aValue = mStackSize;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::SetThreadStackSize(uint32_t aValue)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mStackSize = aValue;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::GetListener(nsIThreadPoolListener** aListener)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
NS_IF_ADDREF(*aListener = mListener);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::SetListener(nsIThreadPoolListener* aListener)
|
||||
{
|
||||
nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mListener.swap(swappedListener);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThreadPool::SetName(const nsACString& aName)
|
||||
{
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (mThreads.Count()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
mName = aName;
|
||||
return NS_OK;
|
||||
}
|
||||
65
xpcom/threads/nsThreadPool.h
Normal file
65
xpcom/threads/nsThreadPool.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsThreadPool_h__
|
||||
#define nsThreadPool_h__
|
||||
|
||||
#include "nsIThreadPool.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsEventQueue.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
|
||||
class nsThreadPool final
|
||||
: public nsIThreadPool
|
||||
, public nsIRunnable
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIEVENTTARGET
|
||||
NS_DECL_NSITHREADPOOL
|
||||
NS_DECL_NSIRUNNABLE
|
||||
using nsIEventTarget::Dispatch;
|
||||
|
||||
nsThreadPool();
|
||||
|
||||
private:
|
||||
~nsThreadPool();
|
||||
|
||||
void ShutdownThread(nsIThread* aThread);
|
||||
nsresult PutEvent(nsIRunnable* aEvent);
|
||||
nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags);
|
||||
|
||||
nsCOMArray<nsIThread> mThreads;
|
||||
mozilla::Mutex mMutex;
|
||||
mozilla::CondVar mEventsAvailable;
|
||||
nsEventQueue mEvents;
|
||||
uint32_t mThreadLimit;
|
||||
uint32_t mIdleThreadLimit;
|
||||
uint32_t mIdleThreadTimeout;
|
||||
uint32_t mIdleCount;
|
||||
uint32_t mStackSize;
|
||||
nsCOMPtr<nsIThreadPoolListener> mListener;
|
||||
bool mShutdown;
|
||||
nsCString mName;
|
||||
nsThreadPoolNaming mThreadNaming;
|
||||
};
|
||||
|
||||
#define NS_THREADPOOL_CID \
|
||||
{ /* 547ec2a8-315e-4ec4-888e-6e4264fe90eb */ \
|
||||
0x547ec2a8, \
|
||||
0x315e, \
|
||||
0x4ec4, \
|
||||
{0x88, 0x8e, 0x6e, 0x42, 0x64, 0xfe, 0x90, 0xeb} \
|
||||
}
|
||||
|
||||
#endif // nsThreadPool_h__
|
||||
50
xpcom/threads/nsThreadSyncDispatch.h
Normal file
50
xpcom/threads/nsThreadSyncDispatch.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsThreadSyncDispatch_h_
|
||||
#define nsThreadSyncDispatch_h_
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
#include "LeakRefPtr.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
class nsThreadSyncDispatch : public mozilla::Runnable
|
||||
{
|
||||
public:
|
||||
nsThreadSyncDispatch(nsIThread* aOrigin, already_AddRefed<nsIRunnable>&& aTask)
|
||||
: mOrigin(aOrigin)
|
||||
, mSyncTask(mozilla::Move(aTask))
|
||||
{
|
||||
}
|
||||
|
||||
bool IsPending()
|
||||
{
|
||||
return !!mSyncTask;
|
||||
}
|
||||
|
||||
private:
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
if (nsIRunnable* task = mSyncTask.get()) {
|
||||
mozilla::DebugOnly<nsresult> result = task->Run();
|
||||
MOZ_ASSERT(NS_SUCCEEDED(result),
|
||||
"task in sync dispatch should not fail");
|
||||
// We must release the task here to ensure that when the original
|
||||
// thread is unblocked, this task has been released.
|
||||
mSyncTask.release();
|
||||
// unblock the origin thread
|
||||
mOrigin->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThread> mOrigin;
|
||||
// The task is leaked by default when Run() is not called, because
|
||||
// otherwise we may release it in an incorrect thread.
|
||||
mozilla::LeakRefPtr<nsIRunnable> mSyncTask;
|
||||
};
|
||||
|
||||
#endif // nsThreadSyncDispatch_h_
|
||||
658
xpcom/threads/nsTimerImpl.cpp
Normal file
658
xpcom/threads/nsTimerImpl.cpp
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
/* -*- 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 "nsTimerImpl.h"
|
||||
#include "TimerThread.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsThreadManager.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "pratom.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
#include "GeckoTaskTracerImpl.h"
|
||||
using namespace mozilla::tasktracer;
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <process.h>
|
||||
#ifndef getpid
|
||||
#define getpid _getpid
|
||||
#endif
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using mozilla::Atomic;
|
||||
using mozilla::LogLevel;
|
||||
using mozilla::TimeDuration;
|
||||
using mozilla::TimeStamp;
|
||||
|
||||
static TimerThread* gThread = nullptr;
|
||||
|
||||
// This module prints info about the precision of timers.
|
||||
static mozilla::LazyLogModule sTimerLog("nsTimerImpl");
|
||||
|
||||
mozilla::LogModule*
|
||||
GetTimerLog()
|
||||
{
|
||||
return sTimerLog;
|
||||
}
|
||||
|
||||
// This module prints info about which timers are firing, which is useful for
|
||||
// wakeups for the purposes of power profiling. Set the following environment
|
||||
// variable before starting the browser.
|
||||
//
|
||||
// MOZ_LOG=TimerFirings:4
|
||||
//
|
||||
// Then a line will be printed for every timer that fires. The name used for a
|
||||
// |Callback::Type::Function| timer depends on the circumstances.
|
||||
//
|
||||
// - If it was explicitly named (e.g. it was initialized with
|
||||
// InitWithNamedFuncCallback()) then that explicit name will be shown.
|
||||
//
|
||||
// - Otherwise, if we are on a platform that supports function name lookup
|
||||
// (Mac or Linux) then the looked-up name will be shown with a
|
||||
// "[from dladdr]" annotation. On Mac the looked-up name will be immediately
|
||||
// useful. On Linux it'll need post-processing with
|
||||
// tools/rb/fix_linux_stack.py.
|
||||
//
|
||||
// - Otherwise, no name will be printed. If many timers hit this case then
|
||||
// you'll need to re-run the workload on a Mac to find out which timers they
|
||||
// are, and then give them explicit names.
|
||||
//
|
||||
// If you redirect this output to a file called "out", you can then
|
||||
// post-process it with a command something like the following.
|
||||
//
|
||||
// cat out | grep timer | sort | uniq -c | sort -r -n
|
||||
//
|
||||
// This will show how often each unique line appears, with the most common ones
|
||||
// first.
|
||||
//
|
||||
// More detailed docs are here:
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Performance/TimerFirings_logging
|
||||
//
|
||||
static mozilla::LazyLogModule sTimerFiringsLog("TimerFirings");
|
||||
|
||||
mozilla::LogModule*
|
||||
GetTimerFiringsLog()
|
||||
{
|
||||
return sTimerFiringsLog;
|
||||
}
|
||||
|
||||
#include <math.h>
|
||||
|
||||
double nsTimerImpl::sDeltaSumSquared = 0;
|
||||
double nsTimerImpl::sDeltaSum = 0;
|
||||
double nsTimerImpl::sDeltaNum = 0;
|
||||
|
||||
static void
|
||||
myNS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues,
|
||||
double* meanResult, double* stdDevResult)
|
||||
{
|
||||
double mean = 0.0, var = 0.0, stdDev = 0.0;
|
||||
if (n > 0.0 && sumOfValues >= 0) {
|
||||
mean = sumOfValues / n;
|
||||
double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues);
|
||||
if (temp < 0.0 || n <= 1) {
|
||||
var = 0.0;
|
||||
} else {
|
||||
var = temp / (n * (n - 1));
|
||||
}
|
||||
// for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
|
||||
stdDev = var != 0.0 ? sqrt(var) : 0.0;
|
||||
}
|
||||
*meanResult = mean;
|
||||
*stdDevResult = stdDev;
|
||||
}
|
||||
|
||||
NS_IMPL_QUERY_INTERFACE(nsTimer, nsITimer)
|
||||
NS_IMPL_ADDREF(nsTimer)
|
||||
|
||||
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||
nsTimer::Release(void)
|
||||
{
|
||||
nsrefcnt count = --mRefCnt;
|
||||
NS_LOG_RELEASE(this, count, "nsTimer");
|
||||
|
||||
if (count == 1) {
|
||||
// Last ref, held by nsTimerImpl. Make sure the cycle is broken.
|
||||
// If there is a nsTimerEvent in a queue for this timer, the nsTimer will
|
||||
// live until that event pops, otherwise the nsTimerImpl will go away and
|
||||
// the nsTimer along with it.
|
||||
mImpl->Cancel();
|
||||
mImpl = nullptr;
|
||||
} else if (count == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
nsTimerImpl::nsTimerImpl(nsITimer* aTimer) :
|
||||
mGeneration(0),
|
||||
mDelay(0),
|
||||
mITimer(aTimer),
|
||||
mMutex("nsTimerImpl::mMutex")
|
||||
{
|
||||
// XXXbsmedberg: shouldn't this be in Init()?
|
||||
mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
|
||||
}
|
||||
|
||||
//static
|
||||
nsresult
|
||||
nsTimerImpl::Startup()
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
gThread = new TimerThread();
|
||||
|
||||
NS_ADDREF(gThread);
|
||||
rv = gThread->InitLocks();
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_RELEASE(gThread);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
nsTimerImpl::Shutdown()
|
||||
{
|
||||
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
|
||||
double mean = 0, stddev = 0;
|
||||
myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev);
|
||||
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n",
|
||||
sDeltaNum, sDeltaSum, sDeltaSumSquared));
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("mean: %fms, stddev: %fms\n", mean, stddev));
|
||||
}
|
||||
|
||||
if (!gThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
gThread->Shutdown();
|
||||
NS_RELEASE(gThread);
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
nsTimerImpl::InitCommon(uint32_t aDelay, uint32_t aType)
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
nsresult rv;
|
||||
|
||||
if (NS_WARN_IF(!gThread)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!mEventTarget) {
|
||||
NS_ERROR("mEventTarget is NULL");
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
rv = gThread->Init();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
gThread->RemoveTimer(this);
|
||||
++mGeneration;
|
||||
|
||||
mType = (uint8_t)aType;
|
||||
mDelay = aDelay;
|
||||
mTimeout = TimeStamp::Now() + TimeDuration::FromMilliseconds(mDelay);
|
||||
|
||||
return gThread->AddTimer(this);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsTimerImpl::InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc,
|
||||
void* aClosure,
|
||||
uint32_t aDelay,
|
||||
uint32_t aType,
|
||||
Callback::Name aName)
|
||||
{
|
||||
if (NS_WARN_IF(!aFunc)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
Callback cb; // Goes out of scope after the unlock, prevents deadlock
|
||||
cb.mType = Callback::Type::Function;
|
||||
cb.mCallback.c = aFunc;
|
||||
cb.mClosure = aClosure;
|
||||
cb.mName = aName;
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
cb.swap(mCallback);
|
||||
|
||||
return InitCommon(aDelay, aType);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc,
|
||||
void* aClosure,
|
||||
uint32_t aDelay,
|
||||
uint32_t aType)
|
||||
{
|
||||
Callback::Name name(Callback::Nothing);
|
||||
return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc,
|
||||
void* aClosure,
|
||||
uint32_t aDelay,
|
||||
uint32_t aType,
|
||||
const char* aNameString)
|
||||
{
|
||||
Callback::Name name(aNameString);
|
||||
return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::InitWithNameableFuncCallback(nsTimerCallbackFunc aFunc,
|
||||
void* aClosure,
|
||||
uint32_t aDelay,
|
||||
uint32_t aType,
|
||||
nsTimerNameCallbackFunc aNameFunc)
|
||||
{
|
||||
Callback::Name name(aNameFunc);
|
||||
return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback,
|
||||
uint32_t aDelay,
|
||||
uint32_t aType)
|
||||
{
|
||||
if (NS_WARN_IF(!aCallback)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
Callback cb; // Goes out of scope after the unlock, prevents deadlock
|
||||
cb.mType = Callback::Type::Interface;
|
||||
cb.mCallback.i = aCallback;
|
||||
NS_ADDREF(cb.mCallback.i);
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
cb.swap(mCallback);
|
||||
|
||||
return InitCommon(aDelay, aType);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelay, uint32_t aType)
|
||||
{
|
||||
if (NS_WARN_IF(!aObserver)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
Callback cb; // Goes out of scope after the unlock, prevents deadlock
|
||||
cb.mType = Callback::Type::Observer;
|
||||
cb.mCallback.o = aObserver;
|
||||
NS_ADDREF(cb.mCallback.o);
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
cb.swap(mCallback);
|
||||
|
||||
return InitCommon(aDelay, aType);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::Cancel()
|
||||
{
|
||||
Callback cb;
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (gThread) {
|
||||
gThread->RemoveTimer(this);
|
||||
}
|
||||
|
||||
cb.swap(mCallback);
|
||||
++mGeneration;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::SetDelay(uint32_t aDelay)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (GetCallback().mType == Callback::Type::Unknown && !IsRepeating()) {
|
||||
// This may happen if someone tries to re-use a one-shot timer
|
||||
// by re-setting delay instead of reinitializing the timer.
|
||||
NS_ERROR("nsITimer->SetDelay() called when the "
|
||||
"one-shot timer is not set up.");
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
bool reAdd = false;
|
||||
if (gThread) {
|
||||
reAdd = NS_SUCCEEDED(gThread->RemoveTimer(this));
|
||||
}
|
||||
|
||||
mDelay = aDelay;
|
||||
mTimeout = TimeStamp::Now() + TimeDuration::FromMilliseconds(mDelay);
|
||||
|
||||
if (reAdd) {
|
||||
gThread->AddTimer(this);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::GetDelay(uint32_t* aDelay)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
*aDelay = mDelay;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::SetType(uint32_t aType)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mType = (uint8_t)aType;
|
||||
// XXX if this is called, we should change the actual type.. this could effect
|
||||
// repeating timers. we need to ensure in Fire() that if mType has changed
|
||||
// during the callback that we don't end up with the timer in the queue twice.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::GetType(uint32_t* aType)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
*aType = mType;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::GetClosure(void** aClosure)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
*aClosure = GetCallback().mClosure;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::GetCallback(nsITimerCallback** aCallback)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (GetCallback().mType == Callback::Type::Interface) {
|
||||
NS_IF_ADDREF(*aCallback = GetCallback().mCallback.i);
|
||||
} else {
|
||||
*aCallback = nullptr;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::GetTarget(nsIEventTarget** aTarget)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
NS_IF_ADDREF(*aTarget = mEventTarget);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTimerImpl::SetTarget(nsIEventTarget* aTarget)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (NS_WARN_IF(mCallback.mType != Callback::Type::Unknown)) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
if (aTarget) {
|
||||
mEventTarget = aTarget;
|
||||
} else {
|
||||
mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
nsTimerImpl::Fire(int32_t aGeneration)
|
||||
{
|
||||
uint8_t oldType;
|
||||
uint32_t oldDelay;
|
||||
TimeStamp oldTimeout;
|
||||
|
||||
{
|
||||
// Don't fire callbacks or fiddle with refcounts when the mutex is locked.
|
||||
// If some other thread Cancels/Inits after this, they're just too late.
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (aGeneration != mGeneration) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbackDuringFire.swap(mCallback);
|
||||
oldType = mType;
|
||||
oldDelay = mDelay;
|
||||
oldTimeout = mTimeout;
|
||||
}
|
||||
|
||||
PROFILER_LABEL("Timer", "Fire",
|
||||
js::ProfileEntry::Category::OTHER);
|
||||
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
|
||||
TimeDuration delta = now - oldTimeout;
|
||||
int32_t d = delta.ToMilliseconds(); // delta in ms
|
||||
sDeltaSum += abs(d);
|
||||
sDeltaSumSquared += double(d) * double(d);
|
||||
sDeltaNum++;
|
||||
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("[this=%p] expected delay time %4ums\n", this, oldDelay));
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("[this=%p] actual delay time %4dms\n", this, oldDelay + d));
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("[this=%p] (mType is %d) -------\n", this, oldType));
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("[this=%p] delta %4dms\n", this, d));
|
||||
}
|
||||
|
||||
if (MOZ_LOG_TEST(GetTimerFiringsLog(), LogLevel::Debug)) {
|
||||
LogFiring(mCallbackDuringFire, oldType, oldDelay);
|
||||
}
|
||||
|
||||
switch (mCallbackDuringFire.mType) {
|
||||
case Callback::Type::Function:
|
||||
mCallbackDuringFire.mCallback.c(mITimer, mCallbackDuringFire.mClosure);
|
||||
break;
|
||||
case Callback::Type::Interface:
|
||||
mCallbackDuringFire.mCallback.i->Notify(mITimer);
|
||||
break;
|
||||
case Callback::Type::Observer:
|
||||
mCallbackDuringFire.mCallback.o->Observe(mITimer, NS_TIMER_CALLBACK_TOPIC,
|
||||
nullptr);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
Callback trash; // Swap into here to dispose of callback after the unlock
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (aGeneration == mGeneration && IsRepeating()) {
|
||||
// Repeating timer has not been re-init or canceled; reschedule
|
||||
mCallbackDuringFire.swap(mCallback);
|
||||
TimeDuration delay = TimeDuration::FromMilliseconds(mDelay);
|
||||
if (mType == nsITimer::TYPE_REPEATING_SLACK) {
|
||||
mTimeout = TimeStamp::Now() + delay;
|
||||
} else {
|
||||
mTimeout = mTimeout + delay;
|
||||
}
|
||||
if (gThread) {
|
||||
gThread->AddTimer(this);
|
||||
}
|
||||
}
|
||||
|
||||
mCallbackDuringFire.swap(trash);
|
||||
|
||||
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
|
||||
("[this=%p] Took %fms to fire timer callback\n",
|
||||
this, (TimeStamp::Now() - now).ToMilliseconds()));
|
||||
}
|
||||
|
||||
#if defined(HAVE_DLADDR) && defined(HAVE___CXA_DEMANGLE)
|
||||
#define USE_DLADDR 1
|
||||
#endif
|
||||
|
||||
#ifdef USE_DLADDR
|
||||
#include <cxxabi.h>
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
// See the big comment above GetTimerFiringsLog() to understand this code.
|
||||
void
|
||||
nsTimerImpl::LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay)
|
||||
{
|
||||
const char* typeStr;
|
||||
switch (aType) {
|
||||
case nsITimer::TYPE_ONE_SHOT: typeStr = "ONE_SHOT"; break;
|
||||
case nsITimer::TYPE_REPEATING_SLACK: typeStr = "SLACK "; break;
|
||||
case nsITimer::TYPE_REPEATING_PRECISE: /* fall through */
|
||||
case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP: typeStr = "PRECISE "; break;
|
||||
default: MOZ_CRASH("bad type");
|
||||
}
|
||||
|
||||
switch (aCallback.mType) {
|
||||
case Callback::Type::Function: {
|
||||
bool needToFreeName = false;
|
||||
const char* annotation = "";
|
||||
const char* name;
|
||||
static const size_t buflen = 1024;
|
||||
char buf[buflen];
|
||||
|
||||
if (aCallback.mName.is<Callback::NameString>()) {
|
||||
name = aCallback.mName.as<Callback::NameString>();
|
||||
|
||||
} else if (aCallback.mName.is<Callback::NameFunc>()) {
|
||||
aCallback.mName.as<Callback::NameFunc>()(
|
||||
mITimer, aCallback.mClosure, buf, buflen);
|
||||
name = buf;
|
||||
|
||||
} else {
|
||||
MOZ_ASSERT(aCallback.mName.is<Callback::NameNothing>());
|
||||
#ifdef USE_DLADDR
|
||||
annotation = "[from dladdr] ";
|
||||
|
||||
Dl_info info;
|
||||
void* addr = reinterpret_cast<void*>(aCallback.mCallback.c);
|
||||
if (dladdr(addr, &info) == 0) {
|
||||
name = "???[dladdr: failed]";
|
||||
|
||||
} else if (info.dli_sname) {
|
||||
int status;
|
||||
name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
|
||||
if (status == 0) {
|
||||
// Success. Because we didn't pass in a buffer to __cxa_demangle it
|
||||
// allocates its own one with malloc() which we must free() later.
|
||||
MOZ_ASSERT(name);
|
||||
needToFreeName = true;
|
||||
} else if (status == -1) {
|
||||
name = "???[__cxa_demangle: OOM]";
|
||||
} else if (status == -2) {
|
||||
name = "???[__cxa_demangle: invalid mangled name]";
|
||||
} else if (status == -3) {
|
||||
name = "???[__cxa_demangle: invalid argument]";
|
||||
} else {
|
||||
name = "???[__cxa_demangle: unexpected status value]";
|
||||
}
|
||||
|
||||
} else if (info.dli_fname) {
|
||||
// The "#0: " prefix is necessary for fix_linux_stack.py to interpret
|
||||
// this string as something to convert.
|
||||
snprintf(buf, buflen, "#0: ???[%s +0x%" PRIxPTR "]\n",
|
||||
info.dli_fname, uintptr_t(addr) - uintptr_t(info.dli_fbase));
|
||||
name = buf;
|
||||
|
||||
} else {
|
||||
name = "???[dladdr: no symbol or shared object obtained]";
|
||||
}
|
||||
#else
|
||||
name = "???[dladdr is unimplemented or doesn't work well on this OS]";
|
||||
#endif
|
||||
}
|
||||
|
||||
MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
|
||||
("[%d] fn timer (%s %5d ms): %s%s\n",
|
||||
getpid(), typeStr, aDelay, annotation, name));
|
||||
|
||||
if (needToFreeName) {
|
||||
free(const_cast<char*>(name));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Callback::Type::Interface: {
|
||||
MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
|
||||
("[%d] iface timer (%s %5d ms): %p\n",
|
||||
getpid(), typeStr, aDelay, aCallback.mCallback.i));
|
||||
break;
|
||||
}
|
||||
|
||||
case Callback::Type::Observer: {
|
||||
MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
|
||||
("[%d] obs timer (%s %5d ms): %p\n",
|
||||
getpid(), typeStr, aDelay, aCallback.mCallback.o));
|
||||
break;
|
||||
}
|
||||
|
||||
case Callback::Type::Unknown:
|
||||
default: {
|
||||
MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
|
||||
("[%d] ??? timer (%s, %5d ms)\n",
|
||||
getpid(), typeStr, aDelay));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsTimer::~nsTimer()
|
||||
{
|
||||
}
|
||||
|
||||
size_t
|
||||
nsTimer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
return aMallocSizeOf(this);
|
||||
}
|
||||
|
||||
/* static */
|
||||
const nsTimerImpl::Callback::NameNothing nsTimerImpl::Callback::Nothing = 0;
|
||||
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
void
|
||||
nsTimerImpl::GetTLSTraceInfo()
|
||||
{
|
||||
mTracedTask.GetTLSTraceInfo();
|
||||
}
|
||||
|
||||
TracedTaskCommon
|
||||
nsTimerImpl::GetTracedTask()
|
||||
{
|
||||
return mTracedTask;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
207
xpcom/threads/nsTimerImpl.h
Normal file
207
xpcom/threads/nsTimerImpl.h
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsTimerImpl_h___
|
||||
#define nsTimerImpl_h___
|
||||
|
||||
#include "nsITimer.h"
|
||||
#include "nsIEventTarget.h"
|
||||
#include "nsIObserver.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Variant.h"
|
||||
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
#include "TracedTaskCommon.h"
|
||||
#endif
|
||||
|
||||
extern mozilla::LogModule* GetTimerLog();
|
||||
|
||||
#define NS_TIMER_CID \
|
||||
{ /* 5ff24248-1dd2-11b2-8427-fbab44f29bc8 */ \
|
||||
0x5ff24248, \
|
||||
0x1dd2, \
|
||||
0x11b2, \
|
||||
{0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8} \
|
||||
}
|
||||
|
||||
// TimerThread, nsTimerEvent, and nsTimer have references to these. nsTimer has
|
||||
// a separate lifecycle so we can Cancel() the underlying timer when the user of
|
||||
// the nsTimer has let go of its last reference.
|
||||
class nsTimerImpl
|
||||
{
|
||||
~nsTimerImpl() {}
|
||||
public:
|
||||
typedef mozilla::TimeStamp TimeStamp;
|
||||
|
||||
explicit nsTimerImpl(nsITimer* aTimer);
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsTimerImpl)
|
||||
NS_DECL_NON_VIRTUAL_NSITIMER
|
||||
|
||||
static nsresult Startup();
|
||||
static void Shutdown();
|
||||
|
||||
void Fire(int32_t aGeneration);
|
||||
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
void GetTLSTraceInfo();
|
||||
mozilla::tasktracer::TracedTaskCommon GetTracedTask();
|
||||
#endif
|
||||
|
||||
int32_t GetGeneration()
|
||||
{
|
||||
return mGeneration;
|
||||
}
|
||||
|
||||
nsresult InitCommon(uint32_t aDelay, uint32_t aType);
|
||||
|
||||
struct Callback {
|
||||
Callback() :
|
||||
mType(Type::Unknown),
|
||||
mName(Nothing),
|
||||
mClosure(nullptr)
|
||||
{
|
||||
mCallback.c = nullptr;
|
||||
}
|
||||
|
||||
Callback(const Callback& other) = delete;
|
||||
Callback& operator=(const Callback& other) = delete;
|
||||
|
||||
~Callback()
|
||||
{
|
||||
if (mType == Type::Interface) {
|
||||
NS_RELEASE(mCallback.i);
|
||||
} else if (mType == Type::Observer) {
|
||||
NS_RELEASE(mCallback.o);
|
||||
}
|
||||
}
|
||||
|
||||
void swap(Callback& other)
|
||||
{
|
||||
std::swap(mType, other.mType);
|
||||
std::swap(mCallback, other.mCallback);
|
||||
std::swap(mName, other.mName);
|
||||
std::swap(mClosure, other.mClosure);
|
||||
}
|
||||
|
||||
enum class Type : uint8_t {
|
||||
Unknown = 0,
|
||||
Interface = 1,
|
||||
Function = 2,
|
||||
Observer = 3,
|
||||
};
|
||||
Type mType;
|
||||
|
||||
union CallbackUnion
|
||||
{
|
||||
nsTimerCallbackFunc c;
|
||||
// These refcounted references are managed manually, as they are in a union
|
||||
nsITimerCallback* MOZ_OWNING_REF i;
|
||||
nsIObserver* MOZ_OWNING_REF o;
|
||||
} mCallback;
|
||||
|
||||
// |Name| is a tagged union type representing one of (a) nothing, (b) a
|
||||
// string, or (c) a function. mozilla::Variant doesn't naturally handle the
|
||||
// "nothing" case, so we define a dummy type and value (which is unused and
|
||||
// so the exact value doesn't matter) for it.
|
||||
typedef const int NameNothing;
|
||||
typedef const char* NameString;
|
||||
typedef nsTimerNameCallbackFunc NameFunc;
|
||||
typedef mozilla::Variant<NameNothing, NameString, NameFunc> Name;
|
||||
static const NameNothing Nothing;
|
||||
Name mName;
|
||||
|
||||
void* mClosure;
|
||||
};
|
||||
|
||||
Callback& GetCallback()
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
if (mCallback.mType == Callback::Type::Unknown) {
|
||||
return mCallbackDuringFire;
|
||||
}
|
||||
|
||||
return mCallback;
|
||||
}
|
||||
|
||||
bool IsRepeating() const
|
||||
{
|
||||
static_assert(nsITimer::TYPE_ONE_SHOT < nsITimer::TYPE_REPEATING_SLACK,
|
||||
"invalid ordering of timer types!");
|
||||
static_assert(
|
||||
nsITimer::TYPE_REPEATING_SLACK < nsITimer::TYPE_REPEATING_PRECISE,
|
||||
"invalid ordering of timer types!");
|
||||
static_assert(
|
||||
nsITimer::TYPE_REPEATING_PRECISE <
|
||||
nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
|
||||
"invalid ordering of timer types!");
|
||||
return mType >= nsITimer::TYPE_REPEATING_SLACK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIEventTarget> mEventTarget;
|
||||
|
||||
void LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay);
|
||||
|
||||
nsresult InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc,
|
||||
void* aClosure,
|
||||
uint32_t aDelay,
|
||||
uint32_t aType,
|
||||
Callback::Name aName);
|
||||
|
||||
// These members are set by the initiating thread, when the timer's type is
|
||||
// changed and during the period where it fires on that thread.
|
||||
uint8_t mType;
|
||||
|
||||
// The generation number of this timer, re-generated each time the timer is
|
||||
// initialized so one-shot timers can be canceled and re-initialized by the
|
||||
// arming thread without any bad race conditions.
|
||||
// Updated only after this timer has been removed from the timer thread.
|
||||
int32_t mGeneration;
|
||||
|
||||
uint32_t mDelay;
|
||||
// Updated only after this timer has been removed from the timer thread.
|
||||
TimeStamp mTimeout;
|
||||
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
mozilla::tasktracer::TracedTaskCommon mTracedTask;
|
||||
#endif
|
||||
|
||||
static double sDeltaSum;
|
||||
static double sDeltaSumSquared;
|
||||
static double sDeltaNum;
|
||||
const RefPtr<nsITimer> mITimer;
|
||||
mozilla::Mutex mMutex;
|
||||
Callback mCallback;
|
||||
Callback mCallbackDuringFire;
|
||||
};
|
||||
|
||||
class nsTimer final : public nsITimer
|
||||
{
|
||||
virtual ~nsTimer();
|
||||
public:
|
||||
nsTimer() : mImpl(new nsTimerImpl(this)) {}
|
||||
|
||||
friend class TimerThread;
|
||||
friend class nsTimerEvent;
|
||||
friend struct TimerAdditionComparator;
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_FORWARD_SAFE_NSITIMER(mImpl);
|
||||
|
||||
virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
|
||||
|
||||
private:
|
||||
// nsTimerImpl holds a strong ref to us. When our refcount goes to 1, we will
|
||||
// null this to break the cycle.
|
||||
RefPtr<nsTimerImpl> mImpl;
|
||||
};
|
||||
|
||||
#endif /* nsTimerImpl_h___ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue