Bug 1799222 - Prevent accidental dispatches to threadpool and timer threads, r=xpcom-reviewers,necko-reviewers,geckoview-reviewers,media-playback-reviewers,jesup,m_kato,padenot,kmag

Differential Revision: https://phabricator.services.mozilla.com/D161349
This commit is contained in:
Nika Layzell 2022-12-06 20:44:15 +00:00
Родитель 391c21b2f8
Коммит 40a3a64aee
21 изменённых файлов: 92 добавлений и 49 удалений

Просмотреть файл

@ -17,7 +17,7 @@ namespace mozilla {
nsCOMPtr<nsIThread> mThread;
nsresult rv =
NS_NewNamedThread("DDMediaLogs", getter_AddRefs(mThread), nullptr,
nsIThreadManager::kThreadPoolStackSize);
{.stackSize = nsIThreadManager::kThreadPoolStackSize});
if (NS_WARN_IF(NS_FAILED(rv))) {
return {rv, nullptr};
}

Просмотреть файл

@ -76,7 +76,7 @@ class WorkerThread::Observer final : public nsIThreadObserver {
WorkerThread::WorkerThread(ConstructorKey)
: nsThread(
MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
nsThread::NOT_MAIN_THREAD, kWorkerStackSize),
nsThread::NOT_MAIN_THREAD, {.stackSize = kWorkerStackSize}),
mLock("WorkerThread::mLock"),
mWorkerPrivateCondVar(mLock, "WorkerThread::mWorkerPrivateCondVar"),
mWorkerPrivate(nullptr),

Просмотреть файл

@ -257,7 +257,7 @@ class WorkletThread::TerminateRunnable final : public Runnable {
WorkletThread::WorkletThread(WorkletImpl* aWorkletImpl)
: nsThread(
MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
nsThread::NOT_MAIN_THREAD, kWorkletStackSize),
nsThread::NOT_MAIN_THREAD, {.stackSize = kWorkletStackSize}),
mWorkletImpl(aWorkletImpl),
mExitLoop(false),
mIsTerminating(false) {

Просмотреть файл

@ -69,7 +69,7 @@ void CanvasRenderThread::Start() {
nsthread->SetUseHangMonitor(true);
nsthread->SetPriority(nsISupportsPriority::PRIORITY_HIGH);
}),
stackSize);
{.stackSize = stackSize});
if (NS_FAILED(rv)) {
return;

Просмотреть файл

@ -83,7 +83,7 @@ CompositorThreadHolder::CreateCompositorThread() {
nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
static_cast<nsThread*>(thread.get())->SetUseHangMonitor(true);
}),
stackSize);
{.stackSize = stackSize});
if (NS_FAILED(rv)) {
return nullptr;

Просмотреть файл

@ -788,7 +788,7 @@ nsSocketTransportService::Init() {
if (!XRE_IsContentProcess() ||
StaticPrefs::network_allow_raw_sockets_in_content_processes_AtStartup()) {
nsresult rv = NS_NewNamedThread("Socket Thread", getter_AddRefs(thread),
this, GetThreadStackSize());
this, {.stackSize = GetThreadStackSize()});
NS_ENSURE_SUCCESS(rv, rv);
} else {
// In the child process, we just want a regular nsThread with no socket

Просмотреть файл

@ -93,7 +93,7 @@ NS_IMETHODIMP nsWifiMonitor::StartWatching(nsIWifiListener* aListener) {
if (!mThread) {
rv = NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread), this,
GetMonitorThreadStackSize());
{.stackSize = GetMonitorThreadStackSize()});
if (NS_FAILED(rv)) {
return rv;
}

Просмотреть файл

@ -60,7 +60,7 @@ class AndroidUiThread : public nsThread {
AndroidUiThread()
: nsThread(
MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
nsThread::NOT_MAIN_THREAD, 0) {}
nsThread::NOT_MAIN_THREAD, {.stackSize = 0}) {}
nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent,
uint32_t aFlags) override;

Просмотреть файл

@ -209,7 +209,7 @@ already_AddRefed<nsISerialEventTarget> ThreadEventQueue::PushEventQueue() {
auto queue = MakeUnique<EventQueue>();
RefPtr<NestedSink> sink = new NestedSink(queue.get(), this);
RefPtr<ThreadEventTarget> eventTarget =
new ThreadEventTarget(sink, NS_IsMainThread());
new ThreadEventTarget(sink, NS_IsMainThread(), false);
MutexAutoLock lock(mLock);

Просмотреть файл

@ -31,13 +31,12 @@ static mozilla::Atomic<bool, mozilla::SequentiallyConsistent>
#endif
ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink,
bool aIsMainThread)
: mSink(aSink)
bool aIsMainThread, bool aBlockDispatch)
: mSink(aSink),
#ifdef DEBUG
,
mIsMainThread(aIsMainThread)
mIsMainThread(aIsMainThread),
#endif
{
mBlockDispatch(aBlockDispatch) {
mThread = PR_GetCurrentThread();
}
@ -77,6 +76,14 @@ ThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent,
PR_GetCurrentThread() == mThread,
"Dispatch to non-main thread after xpcom-shutdown-threads");
if (mBlockDispatch && !(aFlags & NS_DISPATCH_IGNORE_BLOCK_DISPATCH)) {
MOZ_DIAGNOSTIC_ASSERT(
false,
"Attempt to dispatch to thread which does not usually process "
"dispatched runnables until shutdown");
return NS_ERROR_NOT_IMPLEMENTED;
}
LogRunnable::LogDispatch(event.get());
if (aFlags & DISPATCH_SYNC) {
@ -109,7 +116,8 @@ ThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent,
return NS_OK;
}
NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END,
NS_ASSERTION((aFlags & (NS_DISPATCH_AT_END |
NS_DISPATCH_IGNORE_BLOCK_DISPATCH)) == aFlags,
"unexpected dispatch flags");
if (!mSink->PutEvent(event.take(), EventQueuePriority::Normal)) {
return NS_ERROR_UNEXPECTED;

Просмотреть файл

@ -19,7 +19,8 @@ class DelayedRunnable;
// be used with any ThreadTargetSink implementation.
class ThreadEventTarget final : public nsISerialEventTarget {
public:
ThreadEventTarget(ThreadTargetSink* aSink, bool aIsMainThread);
ThreadEventTarget(ThreadTargetSink* aSink, bool aIsMainThread,
bool aBlockDispatch);
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIEVENTTARGET_FULL
@ -52,8 +53,9 @@ class ThreadEventTarget final : public nsISerialEventTarget {
RefPtr<ThreadTargetSink> mSink;
#ifdef DEBUG
bool mIsMainThread;
const bool mIsMainThread;
#endif
const bool mBlockDispatch;
};
} // namespace mozilla

Просмотреть файл

@ -25,7 +25,7 @@
using namespace mozilla;
// Uncomment the following line to enable runtime stats during development.
//#define TIMERS_RUNTIME_STATS
// #define TIMERS_RUNTIME_STATS
#ifdef TIMERS_RUNTIME_STATS
// This class gathers durations and displays some basic stats when destroyed.
@ -376,7 +376,8 @@ nsresult TimerThread::Init() {
nsTimerEvent::Init();
// We hold on to mThread to keep the thread alive.
nsresult rv = NS_NewNamedThread("Timer", getter_AddRefs(mThread), this);
nsresult rv = NS_NewNamedThread("Timer", getter_AddRefs(mThread), this,
{.blockDispatch = true});
if (NS_FAILED(rv)) {
mThread = nullptr;
} else {

Просмотреть файл

@ -65,6 +65,15 @@ interface nsIEventTarget : nsISupports
*/
const unsigned long DISPATCH_EVENT_MAY_BLOCK = 4;
/**
* This flag specifies that the dispatched event should be delivered to the
* target thread even if the thread has been configured to block dispatching
* of runnables. This is generally done for threads which have their own
* internal event loop, such as thread pools or the timer thread, and will not
* service runnables dispatched to them until shutdown.
*/
const unsigned long DISPATCH_IGNORE_BLOCK_DISPATCH = 8;
/**
* IsOnCurrentThread() should return true if events dispatched to this target
* can possibly run on the current thread, and false otherwise. In the case
@ -216,6 +225,7 @@ public:
#define NS_DISPATCH_SYNC nsIEventTarget::DISPATCH_SYNC
#define NS_DISPATCH_AT_END nsIEventTarget::DISPATCH_AT_END
#define NS_DISPATCH_EVENT_MAY_BLOCK nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK
#define NS_DISPATCH_IGNORE_BLOCK_DISPATCH nsIEventTarget::DISPATCH_IGNORE_BLOCK_DISPATCH
// Convenient NS_DECL variant that includes some C++-only methods.
#define NS_DECL_NSIEVENTTARGET_FULL \

Просмотреть файл

@ -7,6 +7,7 @@
#include "nsISupports.idl"
[ptr] native PRThread(PRThread);
native ThreadCreationOptions(nsIThreadManager::ThreadCreationOptions);
interface nsIEventTarget;
interface nsIRunnable;
@ -53,6 +54,21 @@ interface nsIThreadManager : nsISupports
#endif
static const uint32_t kThreadPoolStackSize = DEFAULT_STACK_SIZE;
struct ThreadCreationOptions {
// The size in bytes to reserve for the thread's stack. A value of `0` means
// to use the platform default.
uint32_t stackSize = nsIThreadManager::DEFAULT_STACK_SIZE;
// If set to `true`, any attempts to dispatch runnables to this thread
// without `DISPATCH_IGNORE_BLOCK_DISPATCH` will fail.
//
// This is intended to be used for threads which are expected to generally
// only service a single runnable (other than thread lifecycle runnables),
// and perform their own event dispatching internaly, such as thread pool
// threads or the timer thread.
bool blockDispatch = false;
};
%}
/**
@ -60,14 +76,13 @@ interface nsIThreadManager : nsISupports
*
* @param name
* The name of the thread. If it is empty the thread will not be named.
* @param stackSize
* Number of bytes to reserve for the thread's stack. 0 means use platform
* default.
* @param options
* Configuration options for the newly created thread.
*
* @returns
* The newly created nsIThread object.
*/
[noscript] nsIThread newNamedThread(in ACString name, [optional] in unsigned long stackSize);
[noscript] nsIThread newNamedThread(in ACString name, in ThreadCreationOptions options);
/**
* Get the main thread.

Просмотреть файл

@ -544,15 +544,16 @@ int sCanaryOutputFD = -1;
#endif
nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue,
MainThreadFlag aMainThread, uint32_t aStackSize)
MainThreadFlag aMainThread,
nsIThreadManager::ThreadCreationOptions aOptions)
: mEvents(aQueue.get()),
mEventTarget(
new ThreadEventTarget(mEvents.get(), aMainThread == MAIN_THREAD)),
mEventTarget(new ThreadEventTarget(
mEvents.get(), aMainThread == MAIN_THREAD, aOptions.blockDispatch)),
mOutstandingShutdownContexts(0),
mShutdownContext(nullptr),
mScriptObserver(nullptr),
mThreadName("<uninitialized>"),
mStackSize(aStackSize),
mStackSize(aOptions.stackSize),
mNestedEventLoopDepth(0),
mShutdownRequired(false),
mPriority(PRIORITY_NORMAL),

Просмотреть файл

@ -171,7 +171,8 @@ class nsThread : public nsIThreadInternal,
enum MainThreadFlag { MAIN_THREAD, NOT_MAIN_THREAD };
nsThread(NotNull<mozilla::SynchronizedEventQueue*> aQueue,
MainThreadFlag aMainThread, uint32_t aStackSize);
MainThreadFlag aMainThread,
nsIThreadManager::ThreadCreationOptions aOptions);
private:
nsThread();

Просмотреть файл

@ -298,8 +298,8 @@ nsresult nsThreadManager::Init() {
RefPtr<ThreadEventQueue> synchronizedQueue =
new ThreadEventQueue(std::move(queue), true);
mMainThread =
new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD, 0);
mMainThread = new nsThread(WrapNotNull(synchronizedQueue),
nsThread::MAIN_THREAD, {.stackSize = 0});
nsresult rv = mMainThread->InitCurrentThread();
if (NS_FAILED(rv)) {
@ -452,7 +452,8 @@ nsThread* nsThreadManager::CreateCurrentThread(
return nullptr;
}
RefPtr<nsThread> thread = new nsThread(WrapNotNull(aQueue), aMainThread, 0);
RefPtr<nsThread> thread =
new nsThread(WrapNotNull(aQueue), aMainThread, {.stackSize = 0});
if (!thread || NS_FAILED(thread->InitCurrentThread())) {
return nullptr;
}
@ -514,8 +515,9 @@ bool nsThreadManager::IsNSThread() const {
}
NS_IMETHODIMP
nsThreadManager::NewNamedThread(const nsACString& aName, uint32_t aStackSize,
nsIThread** aResult) {
nsThreadManager::NewNamedThread(
const nsACString& aName, nsIThreadManager::ThreadCreationOptions aOptions,
nsIThread** aResult) {
// Note: can be called from arbitrary threads
// No new threads during Shutdown
@ -528,7 +530,7 @@ nsThreadManager::NewNamedThread(const nsACString& aName, uint32_t aStackSize,
RefPtr<ThreadEventQueue> queue =
new ThreadEventQueue(MakeUnique<EventQueue>());
RefPtr<nsThread> thr =
new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aStackSize);
new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aOptions);
nsresult rv =
thr->Init(aName); // Note: blocks until the new thread has been set up
if (NS_FAILED(rv)) {

Просмотреть файл

@ -119,8 +119,9 @@ nsresult nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent,
}
nsCOMPtr<nsIThread> thread;
nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName(name),
getter_AddRefs(thread), nullptr, stackSize);
nsresult rv = NS_NewNamedThread(
mThreadNaming.GetNextThreadName(name), getter_AddRefs(thread), nullptr,
{.stackSize = stackSize, .blockDispatch = true});
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_UNEXPECTED;
}
@ -146,7 +147,7 @@ nsresult nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent,
// asynchronously without worrying about anything.
ShutdownThread(thread);
} else {
thread->Dispatch(this, NS_DISPATCH_NORMAL);
thread->Dispatch(this, NS_DISPATCH_IGNORE_BLOCK_DISPATCH);
}
return NS_OK;

Просмотреть файл

@ -43,7 +43,7 @@ class nsThreadSyncDispatch : public mozilla::Runnable {
mIsPending = false;
// unblock the origin thread
mOrigin->Dispatch(this, NS_DISPATCH_NORMAL);
mOrigin->Dispatch(this, NS_DISPATCH_IGNORE_BLOCK_DISPATCH);
}
return NS_OK;

Просмотреть файл

@ -13,6 +13,7 @@
#include "mozilla/TimeStamp.h"
#include "nsComponentManagerUtils.h"
#include "nsExceptionHandler.h"
#include "nsIEventTarget.h"
#include "nsITimer.h"
#include "nsTimerImpl.h"
#include "prsystem.h"
@ -157,24 +158,25 @@ PrioritizableCancelableRunnable::GetPriority(uint32_t* aPriority) {
//-----------------------------------------------------------------------------
nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult,
nsIRunnable* aInitialEvent, uint32_t aStackSize) {
nsIRunnable* aInitialEvent,
nsIThreadManager::ThreadCreationOptions aOptions) {
nsCOMPtr<nsIRunnable> event = aInitialEvent;
return NS_NewNamedThread(aName, aResult, event.forget(), aStackSize);
return NS_NewNamedThread(aName, aResult, event.forget(), aOptions);
}
nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult,
already_AddRefed<nsIRunnable> aInitialEvent,
uint32_t aStackSize) {
nsIThreadManager::ThreadCreationOptions aOptions) {
nsCOMPtr<nsIRunnable> event = std::move(aInitialEvent);
nsCOMPtr<nsIThread> thread;
nsresult rv = nsThreadManager::get().nsThreadManager::NewNamedThread(
aName, aStackSize, getter_AddRefs(thread));
aName, aOptions, getter_AddRefs(thread));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (event) {
rv = thread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
rv = thread->Dispatch(event.forget(), NS_DISPATCH_IGNORE_BLOCK_DISPATCH);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

Просмотреть файл

@ -58,32 +58,32 @@ class nsIThread;
extern nsresult NS_NewNamedThread(
const nsACString& aName, nsIThread** aResult,
nsIRunnable* aInitialEvent = nullptr,
uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE);
nsIThreadManager::ThreadCreationOptions aOptions = {});
extern nsresult NS_NewNamedThread(
const nsACString& aName, nsIThread** aResult,
already_AddRefed<nsIRunnable> aInitialEvent,
uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE);
nsIThreadManager::ThreadCreationOptions aOptions = {});
template <size_t LEN>
inline nsresult NS_NewNamedThread(
const char (&aName)[LEN], nsIThread** aResult,
already_AddRefed<nsIRunnable> aInitialEvent,
uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE) {
nsIThreadManager::ThreadCreationOptions aOptions = {}) {
static_assert(LEN <= 16, "Thread name must be no more than 16 characters");
return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult,
std::move(aInitialEvent), aStackSize);
std::move(aInitialEvent), aOptions);
}
template <size_t LEN>
inline nsresult NS_NewNamedThread(
const char (&aName)[LEN], nsIThread** aResult,
nsIRunnable* aInitialEvent = nullptr,
uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE) {
nsIThreadManager::ThreadCreationOptions aOptions = {}) {
nsCOMPtr<nsIRunnable> event = aInitialEvent;
static_assert(LEN <= 16, "Thread name must be no more than 16 characters");
return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult,
event.forget(), aStackSize);
event.forget(), aOptions);
}
/**