Files
palemoon27/xpcom/threads/nsThread.cpp
T
roytam1 8a0a002cf2 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1148708: Add missing 'override' annotations in DocAccessibleChild.h. rs=ehsan (d606358545)
- Bug 1210408 - make nsMaiInterfaceAction work with proxies, r=tbsaunde (f7c819c6ae)
- Bug 1210407 - teach nsMaiInterfaceTable to use proxies, r=tbsaunde (4ca4f10b5f)
- bug 1185157 make sure we don't send an event to a destroyed ipc document r=billm (23acf53f75)
- bug 1214864 - make SetCarretOffset() async r=davidb (e3079e9b2d)
- missing of Bug 1139972 - IPC Proxy for charAt, r=tbsaunde (e9593ed752)
- bug 1191598 - Pass MOZ_CURRENT_PROJECT in environment when running post-build automation steps for universal mac builds. r=gps (fc342c6ced)
- Bug 1164596 - Add mach android-emulator command; r=ahal (afeb9b27d1)
- Bug 1223149 - Add basic usage documentation for mach build; r=glandium (bfb802d175)
- Bug 1182301 - Improve 'mach build' notifications. r=gps (2c65a122d1)
- Bug 1184696 - Add clobber targets to |mach clobber|; Ability to clobber compiled python files, r=gps (35d8be292e)
- Bug 1117958 - Allow any debugging options to the run or gtest mach subcommands to automatically enable debugging. r=gps (32f986af4b)
- Bug 1180081 - Properly rebuild gtest/libxul before running gtests. r=gps (80db9a3d49)
- Bug 1171647, part 1 - Define a new function to convert the mode to a string. r=njn (61ad16f5ba)
- Bug 1171647, part 2 - Remove redundant assertion for dark matter mode. r=njn (b5ac9519f3)
- Bug 1058178, part 1 - Implement DMD heap scanning mode. r=njn (60e1079536)
- Bug 1058178, part 2 - Implement address clamping analysis for DMD scan logs. r=njn (45c0326b93)
- Bug 1102388 - Fix DMD static constructor ordering dependency. r=mccr8 (59b87897a1)
- Bug 1128705 - Don't redefine PAGE_SIZE in DMD if it's already defined. r=erahm (49216348ee)
- Bug 1179042 - Add a script for analyzing memory blocks using a heap scan DMD log. r=njn DONTBUILD (1c08d2d66e)
- Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat (1c999d139b)
- Bug 1158772 - fix non-idiomatic memset call in nsDeque.cpp; r=erahm (de6b555245)
- Bug 1199400 - Part 1: Use CheckedInt when growing nsDeque capacity. r=froydnj (dfdf6814a3)
- Bug 1199400 - Part 0: Remove unused nsDequeIterator. r=froydnj (38d69d7f47)
- Bug 1199400 - Part 2: Add tests for possible nsDeque corner cases. r=froydnj (931baff195)
- Bug 1201997 - Part 1 - Converted compiled test to gtest for nsDeque class. Added tests to test untested methods. r=froydn (e893916651)
- Bug 1201997 - Part 2 - Removing unused methods from the nsDeque class. r=froydn (41595a90ac)
- Bug 1201997 - Part 3 - Make internally used methods private. r=froydn (8cd3afd96f)
- Bug 1201997 - Part 4 - Change size and offset variables to size_t.r=froydn (73eabc8d60)
- Bug 1215140 P1 Add an nsIConsoleReportCollector interface to support navigation channel logging. r=bz (8a41535e2b)
- Bug 1215140 P2 Make HttpBaseChannel implement nsIConsoleReportCollector. r=bz (75fca301f2)
- Bug 1197679 - If nsUnknownDecoder is involved in e10s DivertToParent can break. r=jduell (5d94a12504)
- Bug 1178991 - smartptr for http converter r=hurley (8e7fbc8443)
- bug 366559 - patch 2, fix nsHTTPCompressConv indentation r=bagder (ba762da587)
- bug 366559 - patch 3, fix nsHTTPCompressConv bracing style r=bagder (54195ab451)
- bits of  bug 366559 - patch 7, content-encoding brotli for http (f0b4051022)
- Bug 1205112 - Make PushEvent.data nullable. r=mt,smaug (775db32856)
- Bug 1193414 - SharedWorkers thread should be kept alive also when the SharedWorker object is CCed, r=khuey (b77ea8125c)
- Bug 1206520: Add about:config prefs to enable throwing on asm.js validation failures; r=bz (c42126665d)
- Bug 1193414 - Telemetry for SharedWorker spawning. r=bkelly (77984b7bcc)
- Bug 1205676 - Enable WPT service-worker/unregister-then-register-new-script.https.html in e10s, r=nsm (ec24939cf6)
- Bug 1193133 - Throw when calling postMessage from a Service Worker dom object with no global. r=bkelly (526dcacfab)
- Bug 1181871 P1 Only enforce Cache Context shared data destruction on target thread after init. r=ehsan (cdbf3ed3a8)
- Bug 1181871 P2 Fix ServiceWorkerManager usage of stack-based ErrorResult. r=ehsan (c449195d90)
- minor cleanup and missing bit of 1198230 (02f459db05)
- Bug 1143717 - Implement the ServiceWorkerMessageEvent interface. r=baku (027b3465f2)
- fix misspatch (708eee4e84)
- Bug 1188545 - Disentangle service workers from shared workers and refactor event dispatching code into a separate class. r=nsm,mrbkap (fb5b5341c9)
- Bug 1205228 - Change PackagedAppVerifier to notify the verification result asynchronously. r=valentin. (9edda0fa00)
- Bug 1178518 - Packaged App Utils. r=valentin (f60f3b7a93)
- Bug 1213150 - Part 1: Add a nsContentUtils::IsNonSubresourceRequest helper; r=jdm (b509cc3cc9)
- Bug 1213150 - Part 2: Rework ShouldPrepareForIntercept() in terms of subresource requests; r=jdm (2e92fe8780)
- Bug 1213150 - Part 3: Remove nsIInterceptedChannel.isNavigation; r=jdm (becf1cc12f)
- Bug 1213150 follow-up: fix build bustage (8d73d6ca73)
- Bug 1198394 - Part 1: Allow interception of HSTS upgraded connections in non-e10s mode; r=mcmanus (f504c5be08)
- Bug 1198394 - Part 2: Add a test for interception of HSTS upgraded connections; r=jdm (054e984eef)
- Bug 1187011 - Don't allow response body with null body status. r=bkelly (b1860741d1)
- missing bit of 1140788 (29d319712e)
- Bug 1213436 - Reject core dumps with node IDs that don't fit in an IEEE 754 double; r=sfink (3c1f6fdda0)
- Bug 1211006 - Add Debugger.Source.prototype.canonicalId; r=ejpbruel (eef7b79fce)
- Bug 1199218 - Implement JS::ubi::Node::size for js::LazyScript referents; r=sfink (098a48d240)
- Bug 1220031 - Add JS::ubi::Node::scriptFilename; r=sfink (6b824ae680)
- Bug 1143575. Remove unused MediaQueue::Empty. r=cpearce (de737f3433)
- Bug 1209933 - Make sure all parent runtime pointers are the topmost parent, r=billm. (fe824d967d)
- Bug 1197012 - Fix ThrowTypeError in Notification. r=mccr8 (0b1a097526)
- Bug 1197893 - Check the number of arguments for ThrowTypeError() and ThrowRangeError() at compile time. r=peterv (d98c7d78a0)
- Bug 1142083 - Add test for IDN Unicode domain redirect. r=mcmanus (0c8961fe17)
- Bug 1187159 - Add mochitest for loading packaged apps (iframe+fetch+mozapp) r=jduell (ce90ea561b)
- Bug 1186290 - Notify TabParent to switch process when loading a signed package. r=honzab, r=kanru. (c58a14554a)
- fix (15e2df75eb)
- Bug 1206124 P1 Fix "same-origin" CORS credentials in FetchDriver. r=ehsan (fae1bb6ab3)
- Bug 1206124 P2 Test fetch() with credentials and redirects. r=ehsan (ffc6254112)
- Bug 1211751: Remove nsIChannelEventSink-forwarding from EventSource and FetchDriver. It's never needed. r=smaug (adafe5737a)
- Bug 1212433 Fail fetch() calls that require preflight and also redirect. r=sicking a=abillings (c0d6742b9e)
- Bug 1193128 - Fix base64 decoding when fetching data URIs. r=baku (80bafa291a)
- Bug 1195167 part 1: Let necko handle all protocols. r=bkelly (bb932b0ada)
- Bug 1195167 part 2: Remove redundant aCORSFlag argument and instead use mCORSFlagEverSet. r=bkelly (beadafcad0)
- Bug 1195167 part 3: Remove more scheme-specific handling from FetchDriver. r=bkelly (d00b38db9e)
- Bug 1195167 part 4: Remove FetchDriver::BasicFetch since it is empty. r=bkelly (c5ed097267)
- Bug 1210413 P2 Test CORS credentials on cross-origin redirects. r=sicking a=dveditz (b4eeb8aac0)
- Bug 1210413 P1 Propagate new channel load flags from child to parent on redirect. r=jduell a=dveditz (8b329af4fa)
- Bug 1195167 part 5: Make FetchDriver use AsyncOpen2. r=bkelly (cc217c4cc1)
- Bug 1195167 part 6: Some code simplification since necko handles fetch recursion. r=bkelly (f3b6da2262)
- Bug 1195167: Followup to fix test which I forgot to change (81e7439a2e)
- Bug 1215746: Remove RequestMode::Cors_with_forced_preflight. r=bkelly (0336e812b6)
- Bug 1211000: Move CORS preflight logic from nsCORSListenerProxy to nsCORSPreflightListener. r=ehsan (bf2f71cf22)
- missing bit of Bug 1211443 - Drop scheduled update if decoder initialization isn't done yet. r=jya (f6bc074e33)
- Bug 1182571: Fix nsILoadInfo->GetContentPolicyType API to be less ambigious. Audit and fix all users of it. r=ckerschb (5af6fa7442)
- fix (e40c8e7625)
- Bug 1173811 - Part 1: Propagate the response URL to intercepted channels when necessary (non-e10s). r=mayhemer,bkelly (26f4f13c28)
- Bug 1173811 - Part 2: Propagate the response URL to intercepted channels when necessary (e10s). r=mayhemer,bkelly (a603fe1df2)
- Bug 1154309 - Add New Resource Timing Fields r=bz,hurley (1d14eb6bef)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (a5d18bb637)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (fb07d2c8aa)
- Bug 1212904 P1 Add a LoadTainting enumeration. r=jduell (a1db8a3e99)
- Bug 1212904 P2 Add LoadTainting information to nsILoadInfo. r=jduell (2482e5e334)
- Bug 1221151 - use [infallible] in nsILoadInfo.idl instead of manual %{C++ blocks; r=jduell (aae73129b6)
- Bug 1045891 - CSP 2 child-src implementation r=ckerschb (792920aeb9)
- Bug 1219931 - CSP: Don't allow removing a policy (r=sicking) (9daaab4186)
- Bug 1208661 - Dump client-side layer textures. r=BenWa (1f2d17d515)
2022-10-17 11:08:37 +08:00

1183 lines
32 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsThread.h"
#if !defined(MOZILLA_XPCOMRT_API)
#include "base/message_loop.h"
#endif // !defined(MOZILLA_XPCOMRT_API)
// Chromium's logging can sometimes leak through...
#ifdef LOG
#undef LOG
#endif
#include "mozilla/ReentrantMonitor.h"
#include "nsMemoryPressure.h"
#include "nsThreadManager.h"
#include "nsIClassInfoImpl.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsQueryObject.h"
#include "pratom.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/Logging.h"
#include "nsIObserverService.h"
#if !defined(MOZILLA_XPCOMRT_API)
#include "mozilla/HangMonitor.h"
#include "mozilla/IOInterposer.h"
#include "mozilla/ipc/MessageChannel.h"
#include "mozilla/ipc/BackgroundChild.h"
#endif // defined(MOZILLA_XPCOMRT_API)
#include "mozilla/Services.h"
#include "nsXPCOMPrivate.h"
#include "mozilla/ChaosMode.h"
#include "mozilla/TimeStamp.h"
#ifdef MOZ_CRASHREPORTER
#include "nsServiceManagerUtils.h"
#include "nsICrashReporter.h"
#endif
#ifdef MOZ_NUWA_PROCESS
#include "private/pprthred.h"
#endif
#ifdef XP_LINUX
#include <sys/time.h>
#include <sys/resource.h>
#include <sched.h>
#endif
#define HAVE_UALARM _BSD_SOURCE || (_XOPEN_SOURCE >= 500 || \
_XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \
!(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
#if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE)
#define HAVE_SCHED_SETAFFINITY
#endif
#ifdef MOZ_CANARY
# include <unistd.h>
# include <execinfo.h>
# include <signal.h>
# include <fcntl.h>
# include "nsXULAppAPI.h"
#endif
#if defined(NS_FUNCTION_TIMER) && defined(_MSC_VER)
#include "nsTimerImpl.h"
#include "mozilla/StackWalk.h"
#endif
#ifdef NS_FUNCTION_TIMER
#include "nsCRT.h"
#endif
#ifdef MOZ_TASK_TRACER
#include "GeckoTaskTracer.h"
#include "TracedTaskCommon.h"
using namespace mozilla::tasktracer;
#endif
using namespace mozilla;
static PRLogModuleInfo*
GetThreadLog()
{
static PRLogModuleInfo* sLog;
if (!sLog) {
sLog = PR_NewLogModule("nsThread");
}
return sLog;
}
#ifdef LOG
#undef LOG
#endif
#define LOG(args) MOZ_LOG(GetThreadLog(), mozilla::LogLevel::Debug, args)
NS_DECL_CI_INTERFACE_GETTER(nsThread)
//-----------------------------------------------------------------------------
// Because we do not have our own nsIFactory, we have to implement nsIClassInfo
// somewhat manually.
class nsThreadClassInfo : public nsIClassInfo
{
public:
NS_DECL_ISUPPORTS_INHERITED // no mRefCnt
NS_DECL_NSICLASSINFO
nsThreadClassInfo()
{
}
};
NS_IMETHODIMP_(MozExternalRefCountType)
nsThreadClassInfo::AddRef()
{
return 2;
}
NS_IMETHODIMP_(MozExternalRefCountType)
nsThreadClassInfo::Release()
{
return 1;
}
NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo)
NS_IMETHODIMP
nsThreadClassInfo::GetInterfaces(uint32_t* aCount, nsIID*** aArray)
{
return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aCount, aArray);
}
NS_IMETHODIMP
nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult)
{
*aResult = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsThreadClassInfo::GetContractID(char** aResult)
{
*aResult = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsThreadClassInfo::GetClassDescription(char** aResult)
{
*aResult = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsThreadClassInfo::GetClassID(nsCID** aResult)
{
*aResult = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsThreadClassInfo::GetFlags(uint32_t* aResult)
{
*aResult = THREADSAFE;
return NS_OK;
}
NS_IMETHODIMP
nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult)
{
return NS_ERROR_NOT_AVAILABLE;
}
//-----------------------------------------------------------------------------
NS_IMPL_ADDREF(nsThread)
NS_IMPL_RELEASE(nsThread)
NS_INTERFACE_MAP_BEGIN(nsThread)
NS_INTERFACE_MAP_ENTRY(nsIThread)
NS_INTERFACE_MAP_ENTRY(nsIThreadInternal)
NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread)
if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
static nsThreadClassInfo sThreadClassInfo;
foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo);
} else
NS_INTERFACE_MAP_END
NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal,
nsIEventTarget, nsISupportsPriority)
//-----------------------------------------------------------------------------
class nsThreadStartupEvent : public nsRunnable
{
public:
nsThreadStartupEvent()
: mMon("nsThreadStartupEvent.mMon")
, mInitialized(false)
{
}
// This method does not return until the thread startup object is in the
// completion state.
void Wait()
{
if (mInitialized) {
// Maybe avoid locking...
return;
}
ReentrantMonitorAutoEnter mon(mMon);
while (!mInitialized) {
mon.Wait();
}
}
// This method needs to be public to support older compilers (xlC_r on AIX).
// It should be called directly as this class type is reference counted.
virtual ~nsThreadStartupEvent() {}
private:
NS_IMETHOD Run()
{
ReentrantMonitorAutoEnter mon(mMon);
mInitialized = true;
mon.Notify();
return NS_OK;
}
ReentrantMonitor mMon;
bool mInitialized;
};
//-----------------------------------------------------------------------------
struct nsThreadShutdownContext
{
// NB: This will be the last reference.
RefPtr<nsThread> terminatingThread;
nsThread* joiningThread;
bool awaitingShutdownAck;
};
// This event is responsible for notifying nsThread::Shutdown that it is time
// to call PR_JoinThread. It implements nsICancelableRunnable so that it can
// run on a DOM Worker thread (where all events must implement
// nsICancelableRunnable.)
class nsThreadShutdownAckEvent : public nsRunnable,
public nsICancelableRunnable
{
public:
explicit nsThreadShutdownAckEvent(nsThreadShutdownContext* aCtx)
: mShutdownContext(aCtx)
{
}
NS_DECL_ISUPPORTS_INHERITED
NS_IMETHOD Run() override
{
mShutdownContext->terminatingThread->ShutdownComplete(mShutdownContext);
return NS_OK;
}
NS_IMETHOD Cancel() override
{
return Run();
}
private:
virtual ~nsThreadShutdownAckEvent() { }
nsThreadShutdownContext* mShutdownContext;
};
NS_IMPL_ISUPPORTS_INHERITED(nsThreadShutdownAckEvent, nsRunnable,
nsICancelableRunnable)
// This event is responsible for setting mShutdownContext
class nsThreadShutdownEvent : public nsRunnable
{
public:
nsThreadShutdownEvent(nsThread* aThr, nsThreadShutdownContext* aCtx)
: mThread(aThr)
, mShutdownContext(aCtx)
{
}
NS_IMETHOD Run()
{
mThread->mShutdownContext = mShutdownContext;
#if !defined(MOZILLA_XPCOMRT_API)
MessageLoop::current()->Quit();
#endif // !defined(MOZILLA_XPCOMRT_API)
return NS_OK;
}
private:
RefPtr<nsThread> mThread;
nsThreadShutdownContext* mShutdownContext;
};
//-----------------------------------------------------------------------------
static void
SetupCurrentThreadForChaosMode()
{
if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
return;
}
#ifdef XP_LINUX
// PR_SetThreadPriority doesn't really work since priorities >
// PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use
// setpriority(2) to set random 'nice values'. In regular Linux this is only
// a dynamic adjustment so it still doesn't really do what we want, but tools
// like 'rr' can be more aggressive about honoring these values.
// Some of these calls may fail due to trying to lower the priority
// (e.g. something may have already called setpriority() for this thread).
// This makes it hard to have non-main threads with higher priority than the
// main thread, but that's hard to fix. Tools like rr can choose to honor the
// requested values anyway.
// Use just 4 priorities so there's a reasonable chance of any two threads
// having equal priority.
setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4));
#else
// We should set the affinity here but NSPR doesn't provide a way to expose it.
uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1);
PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority));
#endif
#ifdef HAVE_SCHED_SETAFFINITY
// Force half the threads to CPU 0 so they compete for CPU
if (ChaosMode::randomUint32LessThan(2)) {
cpu_set_t cpus;
CPU_ZERO(&cpus);
CPU_SET(0, &cpus);
sched_setaffinity(0, sizeof(cpus), &cpus);
}
#endif
}
/*static*/ void
nsThread::ThreadFunc(void* aArg)
{
#if !defined(MOZILLA_XPCOMRT_API)
using mozilla::ipc::BackgroundChild;
#endif // !defined(MOZILLA_XPCOMRT_API)
nsThread* self = static_cast<nsThread*>(aArg); // strong reference
self->mThread = PR_GetCurrentThread();
SetupCurrentThreadForChaosMode();
// Inform the ThreadManager
nsThreadManager::get()->RegisterCurrentThread(self);
#if !defined(MOZILLA_XPCOMRT_API)
mozilla::IOInterposer::RegisterCurrentThread();
#endif // !defined(MOZILLA_XPCOMRT_API)
// Wait for and process startup event
nsCOMPtr<nsIRunnable> event;
{
MutexAutoLock lock(self->mLock);
if (!self->mEvents->GetEvent(true, getter_AddRefs(event), lock)) {
NS_WARNING("failed waiting for thread startup event");
return;
}
}
event->Run(); // unblocks nsThread::Init
event = nullptr;
{
#if defined(MOZILLA_XPCOMRT_API)
while(!self->mShutdownContext) {
NS_ProcessNextEvent();
}
#else
// Scope for MessageLoop.
nsAutoPtr<MessageLoop> loop(
new MessageLoop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD));
// Now, process incoming events...
loop->Run();
BackgroundChild::CloseForCurrentThread();
#endif // defined(MOZILLA_XPCOMRT_API)
// Do NS_ProcessPendingEvents but with special handling to set
// mEventsAreDoomed atomically with the removal of the last event. The key
// invariant here is that we will never permit PutEvent to succeed if the
// event would be left in the queue after our final call to
// NS_ProcessPendingEvents. We also have to keep processing events as long
// as we have outstanding mRequestedShutdownContexts.
while (true) {
// Check and see if we're waiting on any threads.
while (self->mRequestedShutdownContexts.Length()) {
// We can't stop accepting events just yet. Block and check again.
NS_ProcessNextEvent(self, true);
}
{
MutexAutoLock lock(self->mLock);
if (!self->mEvents->HasPendingEvent(lock)) {
// No events in the queue, so we will stop now. Don't let any more
// events be added, since they won't be processed. It is critical
// that no PutEvent can occur between testing that the event queue is
// empty and setting mEventsAreDoomed!
self->mEventsAreDoomed = true;
break;
}
}
NS_ProcessPendingEvents(self);
}
}
#if !defined(MOZILLA_XPCOMRT_API)
mozilla::IOInterposer::UnregisterCurrentThread();
#endif // !defined(MOZILLA_XPCOMRT_API)
// Inform the threadmanager that this thread is going away
nsThreadManager::get()->UnregisterCurrentThread(self);
// Dispatch shutdown ACK
MOZ_ASSERT(self->mShutdownContext->terminatingThread == self);
event = do_QueryObject(new nsThreadShutdownAckEvent(self->mShutdownContext));
self->mShutdownContext->joiningThread->Dispatch(event, NS_DISPATCH_NORMAL);
// Release any observer of the thread here.
self->SetObserver(nullptr);
#ifdef MOZ_TASK_TRACER
FreeTraceInfo();
#endif
NS_RELEASE(self);
}
//-----------------------------------------------------------------------------
#ifdef MOZ_CANARY
int sCanaryOutputFD = -1;
#endif
nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
: mLock("nsThread.mLock")
, mScriptObserver(nullptr)
, mEvents(&mEventsRoot)
, mEventsRoot(mLock)
, mPriority(PRIORITY_NORMAL)
, mThread(nullptr)
, mNestedEventLoopDepth(0)
, mStackSize(aStackSize)
, mShutdownContext(nullptr)
, mShutdownRequired(false)
, mEventsAreDoomed(false)
, mIsMainThread(aMainThread)
{
}
nsThread::~nsThread()
{
}
nsresult
nsThread::Init()
{
// spawn thread and wait until it is fully setup
RefPtr<nsThreadStartupEvent> startup = new nsThreadStartupEvent();
NS_ADDREF_THIS();
mShutdownRequired = true;
// ThreadFunc is responsible for setting mThread
PRThread* thr = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this,
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
PR_JOINABLE_THREAD, mStackSize);
if (!thr) {
NS_RELEASE_THIS();
return NS_ERROR_OUT_OF_MEMORY;
}
// ThreadFunc will wait for this event to be run before it tries to access
// mThread. By delaying insertion of this event into the queue, we ensure
// that mThread is set properly.
{
MutexAutoLock lock(mLock);
mEventsRoot.PutEvent(startup, lock); // retain a reference
}
// Wait for thread to call ThreadManager::SetupCurrentThread, which completes
// initialization of ThreadFunc.
startup->Wait();
return NS_OK;
}
nsresult
nsThread::InitCurrentThread()
{
mThread = PR_GetCurrentThread();
SetupCurrentThreadForChaosMode();
nsThreadManager::get()->RegisterCurrentThread(this);
return NS_OK;
}
nsresult
nsThread::PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget)
{
nsCOMPtr<nsIRunnable> event(aEvent);
return PutEvent(event.forget(), aTarget);
}
nsresult
nsThread::PutEvent(already_AddRefed<nsIRunnable>&& aEvent, nsNestedEventTarget* aTarget)
{
nsCOMPtr<nsIThreadObserver> obs;
#ifdef MOZ_NUWA_PROCESS
// On debug build or when tests are enabled, assert that we are not about to
// create a deadlock in the Nuwa process.
NuwaAssertNotFrozen(PR_GetThreadID(mThread), PR_GetThreadName(mThread));
#endif
{
MutexAutoLock lock(mLock);
nsChainedEventQueue* queue = aTarget ? aTarget->mQueue : &mEventsRoot;
if (!queue || (queue == &mEventsRoot && mEventsAreDoomed)) {
NS_WARNING("An event was posted to a thread that will never run it (rejected)");
nsCOMPtr<nsIRunnable> temp(aEvent);
nsIRunnable* temp2 = temp.forget().take(); // can't use unused << aEvent here due to Windows (boo)
return temp2 ? NS_ERROR_UNEXPECTED : NS_ERROR_UNEXPECTED; // to make compiler not bletch on us
}
queue->PutEvent(Move(aEvent), lock);
// Make sure to grab the observer before dropping the lock, otherwise the
// event that we just placed into the queue could run and eventually delete
// this nsThread before the calling thread is scheduled again. We would then
// crash while trying to access a dead nsThread.
obs = mObserver;
}
if (obs) {
obs->OnDispatchedEvent(this);
}
return NS_OK;
}
nsresult
nsThread::DispatchInternal(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aFlags,
nsNestedEventTarget* aTarget)
{
nsCOMPtr<nsIRunnable> event(aEvent);
if (NS_WARN_IF(!event)) {
return NS_ERROR_INVALID_ARG;
}
if (gXPCOMThreadsShutDown && MAIN_THREAD != mIsMainThread && !aTarget) {
NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads");
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
#ifdef MOZ_TASK_TRACER
nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event); // adds a ref
(static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask();
event = tracedRunnable.forget();
#endif
if (aFlags & DISPATCH_SYNC) {
nsThread* thread = nsThreadManager::get()->GetCurrentThread();
if (NS_WARN_IF(!thread)) {
return NS_ERROR_NOT_AVAILABLE;
}
// XXX we should be able to do something better here... we should
// be able to monitor the slot occupied by this event and use
// that to tell us when the event has been processed.
RefPtr<nsThreadSyncDispatch> wrapper =
new nsThreadSyncDispatch(thread, event.forget());
nsresult rv = PutEvent(wrapper, aTarget); // hold a ref
// Don't wait for the event to finish if we didn't dispatch it...
if (NS_FAILED(rv)) {
return rv;
}
// Allows waiting; ensure no locks are held that would deadlock us!
while (wrapper->IsPending()) {
NS_ProcessNextEvent(thread, true);
}
return wrapper->Result();
}
NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "unexpected dispatch flags");
return PutEvent(event.forget(), aTarget);
}
//-----------------------------------------------------------------------------
// nsIEventTarget
NS_IMETHODIMP
nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
{
nsCOMPtr<nsIRunnable> event(aEvent);
return Dispatch(event.forget(), aFlags);
}
NS_IMETHODIMP
nsThread::Dispatch(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aFlags)
{
LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */nullptr, aFlags));
return DispatchInternal(Move(aEvent), aFlags, nullptr);
}
NS_IMETHODIMP
nsThread::IsOnCurrentThread(bool* aResult)
{
*aResult = (PR_GetCurrentThread() == mThread);
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsIThread
NS_IMETHODIMP
nsThread::GetPRThread(PRThread** aResult)
{
*aResult = mThread;
return NS_OK;
}
NS_IMETHODIMP
nsThread::AsyncShutdown()
{
LOG(("THRD(%p) async shutdown\n", this));
// XXX If we make this warn, then we hit that warning at xpcom shutdown while
// shutting down a thread in a thread pool. That happens b/c the thread
// in the thread pool is already shutdown by the thread manager.
if (!mThread) {
return NS_OK;
}
return !!ShutdownInternal(/* aSync = */ false) ? NS_OK : NS_ERROR_UNEXPECTED;
}
nsThreadShutdownContext*
nsThread::ShutdownInternal(bool aSync)
{
MOZ_ASSERT(mThread);
if (NS_WARN_IF(mThread == PR_GetCurrentThread())) {
return nullptr;
}
// Prevent multiple calls to this method
{
MutexAutoLock lock(mLock);
if (!mShutdownRequired) {
return nullptr;
}
mShutdownRequired = false;
}
nsThread* currentThread = nsThreadManager::get()->GetCurrentThread();
MOZ_ASSERT(currentThread);
nsAutoPtr<nsThreadShutdownContext>& context =
*currentThread->mRequestedShutdownContexts.AppendElement();
context = new nsThreadShutdownContext();
context->terminatingThread = this;
context->joiningThread = currentThread;
context->awaitingShutdownAck = aSync;
// Set mShutdownContext and wake up the thread in case it is waiting for
// events to process.
nsCOMPtr<nsIRunnable> event = new nsThreadShutdownEvent(this, context);
// XXXroc What if posting the event fails due to OOM?
PutEvent(event.forget(), nullptr);
// We could still end up with other events being added after the shutdown
// task, but that's okay because we process pending events in ThreadFunc
// after setting mShutdownContext just before exiting.
return context;
}
void
nsThread::ShutdownComplete(nsThreadShutdownContext* aContext)
{
MOZ_ASSERT(mThread);
MOZ_ASSERT(aContext->terminatingThread == this);
if (aContext->awaitingShutdownAck) {
// We're in a synchronous shutdown, so tell whatever is up the stack that
// we're done and unwind the stack so it can call us again.
aContext->awaitingShutdownAck = false;
return;
}
// Now, it should be safe to join without fear of dead-locking.
PR_JoinThread(mThread);
mThread = nullptr;
// We hold strong references to our event observers, and once the thread is
// shut down the observers can't easily unregister themselves. Do it here
// to avoid leaking.
ClearObservers();
#ifdef DEBUG
{
MutexAutoLock lock(mLock);
MOZ_ASSERT(!mObserver, "Should have been cleared at shutdown!");
}
#endif
// Delete aContext.
MOZ_ALWAYS_TRUE(
aContext->joiningThread->mRequestedShutdownContexts.RemoveElement(aContext));
}
NS_IMETHODIMP
nsThread::Shutdown()
{
LOG(("THRD(%p) sync shutdown\n", this));
// XXX If we make this warn, then we hit that warning at xpcom shutdown while
// shutting down a thread in a thread pool. That happens b/c the thread
// in the thread pool is already shutdown by the thread manager.
if (!mThread) {
return NS_OK;
}
nsThreadShutdownContext* context = ShutdownInternal(/* aSync = */ true);
NS_ENSURE_TRUE(context, NS_ERROR_UNEXPECTED);
// Process events on the current thread until we receive a shutdown ACK.
// Allows waiting; ensure no locks are held that would deadlock us!
while (context->awaitingShutdownAck) {
NS_ProcessNextEvent(context->joiningThread, true);
}
ShutdownComplete(context);
return NS_OK;
}
NS_IMETHODIMP
nsThread::HasPendingEvents(bool* aResult)
{
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
{
MutexAutoLock lock(mLock);
*aResult = mEvents->HasPendingEvent(lock);
}
return NS_OK;
}
#ifdef MOZ_CANARY
void canary_alarm_handler(int signum);
class Canary
{
//XXX ToDo: support nested loops
public:
Canary()
{
if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) {
signal(SIGALRM, canary_alarm_handler);
ualarm(15000, 0);
}
}
~Canary()
{
if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) {
ualarm(0, 0);
}
}
static bool EventLatencyIsImportant()
{
return NS_IsMainThread() && XRE_IsParentProcess();
}
};
void canary_alarm_handler(int signum)
{
void* array[30];
const char msg[29] = "event took too long to run:\n";
// use write to be safe in the signal handler
write(sCanaryOutputFD, msg, sizeof(msg));
backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD);
}
#endif
#define NOTIFY_EVENT_OBSERVERS(func_, params_) \
PR_BEGIN_MACRO \
if (!mEventObservers.IsEmpty()) { \
nsAutoTObserverArray<nsCOMPtr<nsIThreadObserver>, 2>::ForwardIterator \
iter_(mEventObservers); \
nsCOMPtr<nsIThreadObserver> obs_; \
while (iter_.HasMore()) { \
obs_ = iter_.GetNext(); \
obs_ -> func_ params_ ; \
} \
} \
PR_END_MACRO
NS_IMETHODIMP
nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
{
LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
mNestedEventLoopDepth));
#if !defined(MOZILLA_XPCOMRT_API)
// If we're on the main thread, we shouldn't be dispatching CPOWs.
if (mIsMainThread == MAIN_THREAD) {
ipc::CancelCPOWs();
}
#endif // !defined(MOZILLA_XPCOMRT_API)
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
// The toplevel event loop normally blocks waiting for the next event, but
// if we're trying to shut this thread down, we must exit the event loop when
// the event queue is empty.
// This only applys to the toplevel event loop! Nested event loops (e.g.
// during sync dispatch) are waiting for some state change and must be able
// to block even if something has requested shutdown of the thread. Otherwise
// we'll just busywait as we endlessly look for an event, fail to find one,
// and repeat the nested event loop since its state change hasn't happened yet.
bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
#if !defined(MOZILLA_XPCOMRT_API)
if (MAIN_THREAD == mIsMainThread && reallyWait) {
HangMonitor::Suspend();
}
#endif // !defined(MOZILLA_XPCOMRT_API)
// Fire a memory pressure notification, if we're the main thread and one is
// pending.
if (MAIN_THREAD == mIsMainThread && !ShuttingDown()) {
MemoryPressureState mpPending = NS_GetPendingMemoryPressure();
if (mpPending != MemPressure_None) {
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
// Use no-forward to prevent the notifications from being transferred to
// the children of this process.
NS_NAMED_LITERAL_STRING(lowMem, "low-memory-no-forward");
NS_NAMED_LITERAL_STRING(lowMemOngoing, "low-memory-ongoing-no-forward");
if (os) {
os->NotifyObservers(nullptr, "memory-pressure",
mpPending == MemPressure_New ? lowMem.get() :
lowMemOngoing.get());
} else {
NS_WARNING("Can't get observer service!");
}
}
}
#ifdef MOZ_CRASHREPORTER
if (MAIN_THREAD == mIsMainThread && !ShuttingDown()) {
// Keep an eye on memory usage (cheap, ~7ms) somewhat frequently,
// but save memory reports (expensive, ~75ms) less frequently.
const size_t LOW_MEMORY_CHECK_SECONDS = 30;
const size_t LOW_MEMORY_SAVE_SECONDS = 3 * 60;
static TimeStamp nextCheck = TimeStamp::NowLoRes()
+ TimeDuration::FromSeconds(LOW_MEMORY_CHECK_SECONDS);
TimeStamp now = TimeStamp::NowLoRes();
if (now >= nextCheck) {
if (SaveMemoryReportNearOOM()) {
nextCheck = now + TimeDuration::FromSeconds(LOW_MEMORY_SAVE_SECONDS);
} else {
nextCheck = now + TimeDuration::FromSeconds(LOW_MEMORY_CHECK_SECONDS);
}
}
}
#endif
++mNestedEventLoopDepth;
bool callScriptObserver = !!mScriptObserver;
if (callScriptObserver) {
mScriptObserver->BeforeProcessTask(reallyWait);
}
nsCOMPtr<nsIThreadObserver> obs = mObserver;
if (obs) {
obs->OnProcessNextEvent(this, reallyWait);
}
NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, (this, reallyWait));
#ifdef MOZ_CANARY
Canary canary;
#endif
nsresult rv = NS_OK;
{
// Scope for |event| to make sure that its destructor fires while
// mNestedEventLoopDepth has been incremented, since that destructor can
// also do work.
// If we are shutting down, then do not wait for new events.
nsCOMPtr<nsIRunnable> event;
{
MutexAutoLock lock(mLock);
mEvents->GetEvent(reallyWait, getter_AddRefs(event), lock);
}
*aResult = (event.get() != nullptr);
if (event) {
LOG(("THRD(%p) running [%p]\n", this, event.get()));
#if !defined(MOZILLA_XPCOMRT_API)
if (MAIN_THREAD == mIsMainThread) {
HangMonitor::NotifyActivity();
}
#endif // !defined(MOZILLA_XPCOMRT_API)
event->Run();
} else if (aMayWait) {
MOZ_ASSERT(ShuttingDown(),
"This should only happen when shutting down");
rv = NS_ERROR_UNEXPECTED;
}
}
NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, (this, *aResult));
if (obs) {
obs->AfterProcessNextEvent(this, *aResult);
}
if (callScriptObserver && mScriptObserver) {
mScriptObserver->AfterProcessTask(mNestedEventLoopDepth);
}
--mNestedEventLoopDepth;
return rv;
}
//-----------------------------------------------------------------------------
// nsISupportsPriority
NS_IMETHODIMP
nsThread::GetPriority(int32_t* aPriority)
{
*aPriority = mPriority;
return NS_OK;
}
NS_IMETHODIMP
nsThread::SetPriority(int32_t aPriority)
{
if (NS_WARN_IF(!mThread)) {
return NS_ERROR_NOT_INITIALIZED;
}
// NSPR defines the following four thread priorities:
// PR_PRIORITY_LOW
// PR_PRIORITY_NORMAL
// PR_PRIORITY_HIGH
// PR_PRIORITY_URGENT
// We map the priority values defined on nsISupportsPriority to these values.
mPriority = aPriority;
PRThreadPriority pri;
if (mPriority <= PRIORITY_HIGHEST) {
pri = PR_PRIORITY_URGENT;
} else if (mPriority < PRIORITY_NORMAL) {
pri = PR_PRIORITY_HIGH;
} else if (mPriority > PRIORITY_NORMAL) {
pri = PR_PRIORITY_LOW;
} else {
pri = PR_PRIORITY_NORMAL;
}
// If chaos mode is active, retain the randomly chosen priority
if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
PR_SetThreadPriority(mThread, pri);
}
return NS_OK;
}
NS_IMETHODIMP
nsThread::AdjustPriority(int32_t aDelta)
{
return SetPriority(mPriority + aDelta);
}
//-----------------------------------------------------------------------------
// nsIThreadInternal
NS_IMETHODIMP
nsThread::GetObserver(nsIThreadObserver** aObs)
{
MutexAutoLock lock(mLock);
NS_IF_ADDREF(*aObs = mObserver);
return NS_OK;
}
NS_IMETHODIMP
nsThread::SetObserver(nsIThreadObserver* aObs)
{
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
MutexAutoLock lock(mLock);
mObserver = aObs;
return NS_OK;
}
uint32_t
nsThread::RecursionDepth() const
{
MOZ_ASSERT(PR_GetCurrentThread() == mThread);
return mNestedEventLoopDepth;
}
NS_IMETHODIMP
nsThread::AddObserver(nsIThreadObserver* aObserver)
{
if (NS_WARN_IF(!aObserver)) {
return NS_ERROR_INVALID_ARG;
}
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
NS_WARN_IF_FALSE(!mEventObservers.Contains(aObserver),
"Adding an observer twice!");
if (!mEventObservers.AppendElement(aObserver)) {
NS_WARNING("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
NS_IMETHODIMP
nsThread::RemoveObserver(nsIThreadObserver* aObserver)
{
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
if (aObserver && !mEventObservers.RemoveElement(aObserver)) {
NS_WARNING("Removing an observer that was never added!");
}
return NS_OK;
}
NS_IMETHODIMP
nsThread::PushEventQueue(nsIEventTarget** aResult)
{
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
nsChainedEventQueue* queue = new nsChainedEventQueue(mLock);
queue->mEventTarget = new nsNestedEventTarget(this, queue);
{
MutexAutoLock lock(mLock);
queue->mNext = mEvents;
mEvents = queue;
}
NS_ADDREF(*aResult = queue->mEventTarget);
return NS_OK;
}
NS_IMETHODIMP
nsThread::PopEventQueue(nsIEventTarget* aInnermostTarget)
{
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
if (NS_WARN_IF(!aInnermostTarget)) {
return NS_ERROR_NULL_POINTER;
}
// Don't delete or release anything while holding the lock.
nsAutoPtr<nsChainedEventQueue> queue;
RefPtr<nsNestedEventTarget> target;
{
MutexAutoLock lock(mLock);
// Make sure we're popping the innermost event target.
if (NS_WARN_IF(mEvents->mEventTarget != aInnermostTarget)) {
return NS_ERROR_UNEXPECTED;
}
MOZ_ASSERT(mEvents != &mEventsRoot);
queue = mEvents;
mEvents = mEvents->mNext;
nsCOMPtr<nsIRunnable> event;
while (queue->GetEvent(false, getter_AddRefs(event), lock)) {
mEvents->PutEvent(event.forget(), lock);
}
// Don't let the event target post any more events.
queue->mEventTarget.swap(target);
target->mQueue = nullptr;
}
return NS_OK;
}
void
nsThread::SetScriptObserver(mozilla::CycleCollectedJSRuntime* aScriptObserver)
{
if (!aScriptObserver) {
mScriptObserver = nullptr;
return;
}
MOZ_ASSERT(!mScriptObserver);
mScriptObserver = aScriptObserver;
}
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsThreadSyncDispatch::Run()
{
if (mSyncTask) {
mResult = mSyncTask->Run();
mSyncTask = nullptr;
// unblock the origin thread
mOrigin->Dispatch(this, NS_DISPATCH_NORMAL);
}
return NS_OK;
}
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS(nsThread::nsNestedEventTarget, nsIEventTarget)
NS_IMETHODIMP
nsThread::nsNestedEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
{
nsCOMPtr<nsIRunnable> event(aEvent);
return Dispatch(event.forget(), aFlags);
}
NS_IMETHODIMP
nsThread::nsNestedEventTarget::Dispatch(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aFlags)
{
LOG(("THRD(%p) Dispatch [%p %x] to nested loop %p\n", mThread.get(), /*XXX aEvent*/ nullptr,
aFlags, this));
return mThread->DispatchInternal(Move(aEvent), aFlags, this);
}
NS_IMETHODIMP
nsThread::nsNestedEventTarget::IsOnCurrentThread(bool* aResult)
{
return mThread->IsOnCurrentThread(aResult);
}