Bug 1542802 - Replace LazyIdleThread with a thin wrapper around nsThreadPool, r=jesup,gstoll

This does not reduce the number of threads we end up using in these
cases, but simplifies the complex implementation substantially by
relying on the idle thread lifetime management logic from nsThreadPool.

The nsISerialEventTarget implementation is maintained by wrapping the
nsThreadPool with a TaskQueue.

As a result of using these more reliable backing implementations, the
type is now also safe to use from any thread, rather than requiring
dispatches to all occur from the creator thread.

Unfortunately, this required some changes to the
UntrustedModulesProcessor, which was relying on the ability to fetch the
PRThread* (and from that the HANDLE) from the lazy thread in order to
perform priority changes on it. This was fixed by using a
nsIThreadPoolListener to watch the thread being created and destroyed,
and manually record a HANDLE to the thread which can be used for these
priority changes.

Differential Revision: https://phabricator.services.mozilla.com/D168017
This commit is contained in:
Nika Layzell 2023-02-02 19:43:31 +00:00
Родитель a9aee9ebad
Коммит 373d136fe4
17 изменённых файлов: 184 добавлений и 799 удалений

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

@ -391,7 +391,7 @@ GpsdLocationProvider::Startup() {
// ... or create a new one.
if (!pollThread) {
pollThread = MakeAndAddRef<LazyIdleThread>(GPSD_POLL_THREAD_TIMEOUT_MS,
"Gpsd poll thread"_ns,
"Gpsd poll thread",
LazyIdleThread::ManualShutdown);
}

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

@ -883,7 +883,7 @@ Result<Ok, nsresult> ExtensionProtocolHandler::NewFD(
if (!mFileOpenerThread) {
mFileOpenerThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
"ExtensionProtocolHandler"_ns);
"ExtensionProtocolHandler");
}
RefPtr<ExtensionJARFileOpener> fileOpener =

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

@ -128,7 +128,7 @@ Classifier::Classifier()
mIsClosed(false) {
// Make a lazy thread for any IO
mUpdateThread =
new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Classifier Update"_ns,
new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Classifier Update",
LazyIdleThread::ShutdownMethod::ManualShutdown);
}
@ -1779,7 +1779,7 @@ nsresult Classifier::LoadMetadata(nsIFile* aDirectory, nsACString& aResult,
bool Classifier::ShouldAbort() const {
return mIsClosed || nsUrlClassifierDBService::ShutdownHasStarted() ||
(mUpdateInterrupted && (NS_GetCurrentThread() == mUpdateThread));
(mUpdateInterrupted && mUpdateThread->IsOnCurrentThread());
}
} // namespace safebrowsing

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

@ -15,9 +15,10 @@
#include "nsString.h"
#include "nsIFile.h"
class nsIThread;
namespace mozilla {
class LazyIdleThread;
namespace safebrowsing {
/**
@ -242,7 +243,7 @@ class Classifier {
// True once CLose() has been called
Atomic<bool> mIsClosed;
nsCOMPtr<nsIThread> mUpdateThread; // For async update.
RefPtr<LazyIdleThread> mUpdateThread; // For async update.
// Identical to mRootStoreDirectory but for update only because
// nsIFile is not thread safe and mRootStoreDirectory needs to

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

@ -31,21 +31,6 @@
#include "nsXULAppAPI.h"
#include "private/prpriv.h" // For PR_GetThreadID
static DWORD ToWin32ThreadId(nsIThread* aThread) {
if (!aThread) {
return 0UL;
}
PRThread* prThread;
nsresult rv = aThread->GetPRThread(&prThread);
if (NS_FAILED(rv)) {
// Possible when a LazyInitThread's underlying nsThread is not present
return 0UL;
}
return DWORD(::PR_GetThreadID(prThread));
}
namespace mozilla {
class MOZ_RAII BackgroundPriorityRegion final {
@ -62,19 +47,12 @@ class MOZ_RAII BackgroundPriorityRegion final {
Clear(::GetCurrentThread());
}
static void Clear(nsIThread* aThread) {
DWORD tid = ToWin32ThreadId(aThread);
if (!tid) {
static void Clear(const nsAutoHandle& aThread) {
if (!aThread) {
return;
}
nsAutoHandle thread(
::OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, tid));
if (!thread) {
return;
}
Clear(thread);
Clear(aThread.get());
}
BackgroundPriorityRegion(const BackgroundPriorityRegion&) = delete;
@ -121,14 +99,16 @@ RefPtr<UntrustedModulesProcessor> UntrustedModulesProcessor::Create(
return result.forget();
}
NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver)
NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver, nsIThreadPoolListener)
static const uint32_t kThreadTimeoutMS = 120000; // 2 minutes
UntrustedModulesProcessor::UntrustedModulesProcessor(
bool aIsReadyForBackgroundProcessing)
: mThread(new LazyIdleThread(kThreadTimeoutMS, "Untrusted Modules"_ns,
: mThread(new LazyIdleThread(kThreadTimeoutMS, "Untrusted Modules",
LazyIdleThread::ManualShutdown)),
mThreadHandleMutex(
"mozilla::UntrustedModulesProcessor::mThreadHandleMutex"),
mUnprocessedMutex(
"mozilla::UntrustedModulesProcessor::mUnprocessedMutex"),
mModuleCacheMutex(
@ -146,6 +126,7 @@ void UntrustedModulesProcessor::AddObservers() {
if (XRE_IsContentProcess()) {
obsServ->AddObserver(this, "content-child-will-shutdown", false);
}
mThread->SetListener(this);
}
bool UntrustedModulesProcessor::IsReadyForBackgroundProcessing() const {
@ -154,7 +135,10 @@ bool UntrustedModulesProcessor::IsReadyForBackgroundProcessing() const {
void UntrustedModulesProcessor::Disable() {
// Ensure that mThread cannot run at low priority anymore
BackgroundPriorityRegion::Clear(mThread);
{
MutexAutoLock lock(mThreadHandleMutex);
BackgroundPriorityRegion::Clear(mThreadHandle);
}
// No more background processing allowed beyond this point
if (mStatus.exchange(Status::ShuttingDown) != Status::Allowed) {
@ -220,6 +204,32 @@ NS_IMETHODIMP UntrustedModulesProcessor::Observe(nsISupports* aSubject,
return NS_OK;
}
NS_IMETHODIMP UntrustedModulesProcessor::OnThreadCreated() {
// Whenever a backing lazy thread is created, record a thread handle to it.
HANDLE threadHandle;
if (!::DuplicateHandle(
::GetCurrentProcess(), ::GetCurrentThread(), ::GetCurrentProcess(),
&threadHandle,
THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION,
FALSE, 0)) {
MOZ_ASSERT_UNREACHABLE("DuplicateHandle failed on GetCurrentThread()?");
threadHandle = nullptr;
}
MutexAutoLock lock(mThreadHandleMutex);
mThreadHandle.own(threadHandle);
return NS_OK;
}
NS_IMETHODIMP UntrustedModulesProcessor::OnThreadShuttingDown() {
// When a lazy thread shuts down, clean up the thread handle reference unless
// it's already been replaced.
MutexAutoLock lock(mThreadHandleMutex);
if (mThreadHandle && ::GetCurrentThreadId() == ::GetThreadId(mThreadHandle)) {
mThreadHandle.reset();
}
return NS_OK;
}
void UntrustedModulesProcessor::RemoveObservers() {
nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
obsServ->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
@ -228,6 +238,7 @@ void UntrustedModulesProcessor::RemoveObservers() {
if (XRE_IsContentProcess()) {
obsServ->RemoveObserver(this, "content-child-will-shutdown");
}
mThread->SetListener(nullptr);
}
void UntrustedModulesProcessor::ScheduleNonEmptyQueueProcessing(
@ -303,10 +314,13 @@ void UntrustedModulesProcessor::Enqueue(
return;
}
DWORD bgThreadId = ToWin32ThreadId(mThread);
if (aModLoadInfo.mNtLoadInfo.mThreadId == bgThreadId) {
// Exclude loads that were caused by our own background thread
return;
{
MutexAutoLock lock(mThreadHandleMutex);
DWORD bgThreadId = ::GetThreadId(mThreadHandle);
if (aModLoadInfo.mNtLoadInfo.mThreadId == bgThreadId) {
// Exclude loads that were caused by our own background thread
return;
}
}
MutexAutoLock lock(mUnprocessedMutex);
@ -337,12 +351,7 @@ void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) {
void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() {
#if defined(DEBUG)
PRThread* curThread;
PRThread* lazyIdleThread;
MOZ_ASSERT(NS_SUCCEEDED(NS_GetCurrentThread()->GetPRThread(&curThread)) &&
NS_SUCCEEDED(mThread->GetPRThread(&lazyIdleThread)) &&
curThread == lazyIdleThread);
MOZ_ASSERT(mThread->IsOnCurrentThread());
#endif // defined(DEBUG)
}
@ -350,7 +359,10 @@ RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetProcessedData() {
MOZ_ASSERT(NS_IsMainThread());
// Clear any background priority in case background processing is running.
BackgroundPriorityRegion::Clear(mThread);
{
MutexAutoLock lock(mThreadHandleMutex);
BackgroundPriorityRegion::Clear(mThreadHandle);
}
RefPtr<UntrustedModulesProcessor> self(this);
return InvokeAsync(mThread, __func__, [self = std::move(self)]() {
@ -375,7 +387,10 @@ RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrust(
if (aRunAtNormalPriority) {
// Clear any background priority in case background processing is running.
BackgroundPriorityRegion::Clear(mThread);
{
MutexAutoLock lock(mThreadHandleMutex);
BackgroundPriorityRegion::Clear(mThreadHandle);
}
return InvokeAsync(mThread, __func__, std::move(run));
}

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

@ -51,13 +51,15 @@ struct UnprocessedModuleLoadInfoContainer final
using UnprocessedModuleLoads =
AutoCleanLinkedList<UnprocessedModuleLoadInfoContainer>;
class UntrustedModulesProcessor final : public nsIObserver {
class UntrustedModulesProcessor final : public nsIObserver,
public nsIThreadPoolListener {
public:
static RefPtr<UntrustedModulesProcessor> Create(
bool aIsReadyForBackgroundProcessing);
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
NS_DECL_NSITHREADPOOLLISTENER
// Called to check if the parent process is ready when a child process
// is spanwed
@ -93,8 +95,10 @@ class UntrustedModulesProcessor final : public nsIObserver {
void AddObservers();
void RemoveObservers();
void ScheduleNonEmptyQueueProcessing(const MutexAutoLock& aProofOfLock);
void CancelScheduledProcessing(const MutexAutoLock& aProofOfLock);
void ScheduleNonEmptyQueueProcessing(const MutexAutoLock& aProofOfLock)
MOZ_REQUIRES(mUnprocessedMutex);
void CancelScheduledProcessing(const MutexAutoLock& aProofOfLock)
MOZ_REQUIRES(mUnprocessedMutex);
void DispatchBackgroundProcessing();
void BackgroundProcessModuleLoadQueue();
@ -150,12 +154,17 @@ class UntrustedModulesProcessor final : public nsIObserver {
private:
RefPtr<LazyIdleThread> mThread;
Mutex mUnprocessedMutex MOZ_UNANNOTATED;
Mutex mModuleCacheMutex MOZ_UNANNOTATED;
Mutex mThreadHandleMutex;
Mutex mUnprocessedMutex;
Mutex mModuleCacheMutex;
// Windows HANDLE for the currently active mThread, if active.
nsAutoHandle mThreadHandle MOZ_GUARDED_BY(mThreadHandleMutex);
// The members in this group are protected by mUnprocessedMutex
UnprocessedModuleLoads mUnprocessedModuleLoads;
nsCOMPtr<nsIRunnable> mIdleRunnable;
UnprocessedModuleLoads mUnprocessedModuleLoads
MOZ_GUARDED_BY(mUnprocessedMutex);
nsCOMPtr<nsIRunnable> mIdleRunnable MOZ_GUARDED_BY(mUnprocessedMutex);
// This member must only be touched on mThread
UntrustedModulesData mProcessedModuleLoads;
@ -168,7 +177,7 @@ class UntrustedModulesProcessor final : public nsIObserver {
// Cache all module records, including ones trusted and ones loaded in
// child processes, in the browser process to avoid evaluating the same
// module multiple times
ModulesMap mGlobalModuleCache;
ModulesMap mGlobalModuleCache MOZ_GUARDED_BY(mModuleCacheMutex);
};
} // namespace mozilla

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

@ -118,7 +118,7 @@ JumpListBuilder::JumpListBuilder()
}
// Make a lazy thread for any IO
mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List"_ns,
mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List",
LazyIdleThread::ManualShutdown);
Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);

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

@ -22,6 +22,7 @@
#include "nsIObserver.h"
#include "nsTArray.h"
#include "mozilla/Attributes.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/mscom/AgileReference.h"
#include "mozilla/ReentrantMonitor.h"
@ -49,7 +50,7 @@ class JumpListBuilder : public nsIJumpListBuilder, public nsIObserver {
mscom::AgileReference mJumpListMgr MOZ_GUARDED_BY(mMonitor);
uint32_t mMaxItems MOZ_GUARDED_BY(mMonitor);
bool mHasCommit;
nsCOMPtr<nsIThread> mIOThread;
RefPtr<LazyIdleThread> mIOThread;
ReentrantMonitor mMonitor;
nsString mAppUserModelId;

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

@ -273,7 +273,7 @@ nsresult JumpListSeparator::GetSeparator(RefPtr<IShellLinkW>& aShellLink) {
// (static) Creates a ShellLink that encapsulate a shortcut to local apps.
nsresult JumpListShortcut::GetShellLink(nsCOMPtr<nsIJumpListItem>& item,
RefPtr<IShellLinkW>& aShellLink,
nsCOMPtr<nsIThread>& aIOThread) {
RefPtr<LazyIdleThread>& aIOThread) {
HRESULT hr;
IShellLinkW* psl;
nsresult rv;

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

@ -11,6 +11,7 @@
#undef LogSeverity // SetupAPI.h #defines this as DWORD
#include "mozilla/RefPtr.h"
#include "mozilla/LazyIdleThread.h"
#include "nsIJumpListItem.h" // defines nsIJumpListItem
#include "nsIMIMEInfo.h" // defines nsILocalHandlerApp
#include "nsTArray.h"
@ -105,7 +106,7 @@ class JumpListShortcut : public JumpListItemBase, public nsIJumpListShortcut {
static nsresult GetShellLink(nsCOMPtr<nsIJumpListItem>& item,
RefPtr<IShellLinkW>& aShellLink,
nsCOMPtr<nsIThread>& aIOThread);
RefPtr<LazyIdleThread>& aIOThread);
static nsresult GetJumpListShortcut(IShellLinkW* pLink,
nsCOMPtr<nsIJumpListShortcut>& aShortcut);
static nsresult GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
@ -117,12 +118,6 @@ class JumpListShortcut : public JumpListItemBase, public nsIJumpListShortcut {
nsCOMPtr<nsILocalHandlerApp> mHandlerApp;
bool ExecutableExists(nsCOMPtr<nsILocalHandlerApp>& handlerApp);
static nsresult ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI,
nsString& aICOFilePath,
nsCOMPtr<nsIThread>& aIOThread);
static nsresult CacheIconFileFromFaviconURIAsync(
nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
nsCOMPtr<nsIThread>& aIOThread);
};
} // namespace widget

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

@ -840,7 +840,7 @@ void WinUtils::InvalidatePluginAsWorkaround(nsIWidget* aWidget,
************************************************************************/
AsyncFaviconDataReady::AsyncFaviconDataReady(
nsIURI* aNewURI, nsCOMPtr<nsIThread>& aIOThread, const bool aURLShortcut,
nsIURI* aNewURI, RefPtr<LazyIdleThread>& aIOThread, const bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable)
: mNewURI(aNewURI),
mIOThread(aIOThread),
@ -1129,7 +1129,7 @@ AsyncDeleteAllFaviconsFromDisk::~AsyncDeleteAllFaviconsFromDisk() {}
*/
nsresult FaviconHelper::ObtainCachedIconFile(
nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath,
nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut,
RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable) {
nsCOMPtr<nsIRunnable> runnable = aRunnable;
// Obtain the ICO file path
@ -1234,7 +1234,7 @@ nsresult FaviconHelper::GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
// page aFaviconPageURI and stores it to disk at the path of aICOFile.
nsresult FaviconHelper::CacheIconFileFromFaviconURIAsync(
nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut,
RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable) {
nsCOMPtr<nsIRunnable> runnable = aRunnable;
#ifdef MOZ_PLACES

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

@ -39,6 +39,7 @@
#include "mozilla/EventForwards.h"
#include "mozilla/HalScreenConfiguration.h"
#include "mozilla/HashTable.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "mozilla/WindowsDpiAwareness.h"
@ -647,7 +648,7 @@ class AsyncFaviconDataReady final : public nsIFaviconDataCallback {
NS_DECL_ISUPPORTS
NS_DECL_NSIFAVICONDATACALLBACK
AsyncFaviconDataReady(nsIURI* aNewURI, nsCOMPtr<nsIThread>& aIOThread,
AsyncFaviconDataReady(nsIURI* aNewURI, RefPtr<LazyIdleThread>& aIOThread,
const bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable);
nsresult OnFaviconDataNotAvailable(void);
@ -656,7 +657,7 @@ class AsyncFaviconDataReady final : public nsIFaviconDataCallback {
~AsyncFaviconDataReady() {}
nsCOMPtr<nsIURI> mNewURI;
nsCOMPtr<nsIThread> mIOThread;
RefPtr<LazyIdleThread> mIOThread;
nsCOMPtr<nsIRunnable> mRunnable;
const bool mURLShortcut;
};
@ -709,7 +710,7 @@ class FaviconHelper {
static const char kShortcutCacheDir[];
static nsresult ObtainCachedIconFile(
nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath,
nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut,
RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable = nullptr);
static nsresult HashURI(nsCOMPtr<nsICryptoHash>& aCryptoHash, nsIURI* aUri,
@ -721,7 +722,7 @@ class FaviconHelper {
static nsresult CacheIconFileFromFaviconURIAsync(
nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut,
RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable);
static int32_t GetICOCacheSecondsTimeout();

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

@ -481,7 +481,7 @@ nsDataObj::nsDataObj(nsIURI* uri)
mTransferable(nullptr),
mIsAsyncMode(FALSE),
mIsInOperation(FALSE) {
mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "nsDataObj"_ns,
mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "nsDataObj",
LazyIdleThread::ManualShutdown);
m_enumFE = new CEnumFormatEtc();
m_enumFE->AddRef();

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

@ -10,6 +10,7 @@
#include <shldisp.h>
#include "mozilla/glue/WinUtils.h"
#include "mozilla/LazyIdleThread.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIFile.h"
@ -34,7 +35,7 @@ class nsITransferable;
* associated with instances via SetDragDrop().
*/
class nsDataObj : public IDataObject, public IDataObjectAsyncCapability {
nsCOMPtr<nsIThread> mIOThread;
RefPtr<mozilla::LazyIdleThread> mIOThread;
public: // construction, destruction
explicit nsDataObj(nsIURI* uri = nullptr);

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

@ -17,12 +17,13 @@
using namespace mozilla;
// Cast the pointer to nsISupports* before doing the QI in order to avoid
// a static assert intended to prevent trivial QIs.
// Cast the pointer to nsISupports* through nsIEventTarget* before doing the QI
// in order to avoid a static assert intended to prevent trivial QIs, while also
// avoiding ambiguous base errors.
template <typename TargetInterface, typename SourcePtr>
bool TestQITo(SourcePtr& aPtr1) {
nsCOMPtr<TargetInterface> aPtr2 =
do_QueryInterface(static_cast<nsISupports*>(aPtr1.get()));
nsCOMPtr<TargetInterface> aPtr2 = do_QueryInterface(
static_cast<nsISupports*>(static_cast<nsIEventTarget*>(aPtr1.get())));
return (bool)aPtr2;
}
@ -71,7 +72,7 @@ TEST(TestEventTargetQI, ThrottledEventQueue)
TEST(TestEventTargetQI, LazyIdleThread)
{
nsCOMPtr<nsIThread> thing = new LazyIdleThread(0, "TestThread"_ns);
RefPtr<LazyIdleThread> thing = new LazyIdleThread(0, "TestThread");
EXPECT_TRUE(thing);
EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing));

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

@ -7,12 +7,8 @@
#include "LazyIdleThread.h"
#include "nsIObserverService.h"
#include "GeckoProfiler.h"
#include "nsComponentManagerUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "mozilla/Services.h"
#ifdef DEBUG
# define ASSERT_OWNING_THREAD() \
@ -25,335 +21,60 @@
namespace mozilla {
LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const nsACString& aName,
ShutdownMethod aShutdownMethod,
nsIObserver* aIdleObserver)
: mMutex("LazyIdleThread::mMutex"),
mOwningEventTarget(GetCurrentSerialEventTarget()),
mIdleObserver(aIdleObserver),
mQueuedRunnables(nullptr),
mIdleTimeoutMS(aIdleTimeoutMS),
mPendingEventCount(0),
mIdleNotificationCount(0),
mShutdownMethod(aShutdownMethod),
mShutdown(false),
mThreadIsShuttingDown(false),
mIdleTimeoutEnabled(true),
mName(aName) {
MOZ_ASSERT(mOwningEventTarget, "Need owning thread!");
LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const char* aName,
ShutdownMethod aShutdownMethod)
: mOwningEventTarget(GetCurrentSerialEventTarget()),
mThreadPool(new nsThreadPool()),
mTaskQueue(TaskQueue::Create(do_AddRef(mThreadPool), aName)) {
// Configure the threadpool to host a single thread. It will be responsible
// for managing the thread's lifetime.
MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetThreadLimit(1));
MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadLimit(1));
MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadTimeout(aIdleTimeoutMS));
MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetName(nsDependentCString(aName)));
if (aShutdownMethod == ShutdownMethod::AutomaticShutdown &&
NS_IsMainThread()) {
if (nsCOMPtr<nsIObserverService> obs =
do_GetService(NS_OBSERVERSERVICE_CONTRACTID)) {
MOZ_ALWAYS_SUCCEEDS(
obs->AddObserver(this, "xpcom-shutdown-threads", false));
}
}
}
static void LazyIdleThreadShutdown(nsThreadPool* aThreadPool,
TaskQueue* aTaskQueue) {
aTaskQueue->BeginShutdown();
aTaskQueue->AwaitShutdownAndIdle();
aThreadPool->Shutdown();
}
LazyIdleThread::~LazyIdleThread() {
if (!mShutdown) {
mOwningEventTarget->Dispatch(NS_NewRunnableFunction(
"LazyIdleThread::~LazyIdleThread",
[threadPool = mThreadPool, taskQueue = mTaskQueue] {
LazyIdleThreadShutdown(threadPool, taskQueue);
}));
}
}
void LazyIdleThread::Shutdown() {
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("LazyIdleThreadDummyRunnable"));
if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch!");
}
if (!mShutdown) {
mShutdown = true;
LazyIdleThreadShutdown(mThreadPool, mTaskQueue);
}
}
void LazyIdleThread::PreDispatch() {
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
mPendingEventCount++;
nsresult LazyIdleThread::SetListener(nsIThreadPoolListener* aListener) {
return mThreadPool->SetListener(aListener);
}
nsresult LazyIdleThread::EnsureThread() {
ASSERT_OWNING_THREAD();
if (mShutdown) {
return NS_ERROR_UNEXPECTED;
}
if (mThread) {
return NS_OK;
}
#ifdef DEBUG
{
MutexAutoLock lock(mMutex);
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!");
}
#endif
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 = NS_NewTimer();
if (NS_WARN_IF(!mIdleTimer)) {
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
"LazyIdleThread::InitThread", this, &LazyIdleThread::InitThread);
if (NS_WARN_IF(!runnable)) {
return NS_ERROR_UNEXPECTED;
}
rv = NS_NewNamedThread(mName, getter_AddRefs(mThread), runnable);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void LazyIdleThread::InitThread() {
// 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;
}
}
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(
"LazyIdleThread::CleanupThread", 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(
"LazyIdleThread::SelfDestruct", 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,
nsISerialEventTarget, nsITimerCallback,
nsIThreadObserver, nsIObserver, nsINamed)
NS_IMPL_ISUPPORTS(LazyIdleThread, nsIEventTarget, nsISerialEventTarget,
nsIObserver)
NS_IMETHODIMP
LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
@ -364,33 +85,7 @@ LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t 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);
return mTaskQueue->Dispatch(std::move(aEvent), aFlags);
}
NS_IMETHODIMP
@ -408,227 +103,20 @@ LazyIdleThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
LazyIdleThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) {
if (mThread) {
return mThread->GetRunningEventDelay(aDelay, aStart);
}
*aDelay = TimeDuration();
*aStart = TimeStamp();
return NS_OK;
}
NS_IMETHODIMP
LazyIdleThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) {
if (mThread) {
return mThread->SetRunningEventDelay(aDelay, aStart);
}
return NS_OK;
}
NS_IMETHODIMP
LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) {
if (mThread) {
return mThread->IsOnCurrentThread(aIsOnCurrentThread);
}
*aIsOnCurrentThread = false;
return NS_OK;
return mTaskQueue->IsOnCurrentThread(aIsOnCurrentThread);
}
NS_IMETHODIMP_(bool)
LazyIdleThread::IsOnCurrentThreadInfallible() {
if (mThread) {
return mThread->IsOnCurrentThread();
}
return false;
}
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::GetLastLongTaskEnd(TimeStamp* _retval) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
LazyIdleThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
LazyIdleThread::SetNameForWakeupTelemetry(const nsACString& aName) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
LazyIdleThread::AsyncShutdown() {
ASSERT_OWNING_THREAD();
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
LazyIdleThread::BeginShutdown(nsIThreadShutdown** aShutdown) {
ASSERT_OWNING_THREAD();
*aShutdown = nullptr;
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.
MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
LazyIdleThread::HasPendingHighPriorityEvents(bool* aHasPendingEvents) {
// This is only supposed to be called from the thread itself so it's not
// implemented here.
MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
LazyIdleThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
EventQueuePriority aQueue) {
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.
MOZ_ASSERT_UNREACHABLE("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::GetName(nsACString& aName) {
aName.Assign(mName);
return NS_OK;
}
NS_IMETHODIMP
LazyIdleThread::OnDispatchedEvent() {
MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
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(
"LazyIdleThread::ScheduleTimer", this, &LazyIdleThread::ScheduleTimer);
if (NS_WARN_IF(!runnable)) {
return NS_ERROR_UNEXPECTED;
}
nsresult rv =
mOwningEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
return NS_OK;
return mTaskQueue->IsOnCurrentThreadInfallible();
}
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();

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

@ -11,208 +11,81 @@
# error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)."
#endif
#include "nsINamed.h"
#include "mozilla/TaskQueue.h"
#include "nsIObserver.h"
#include "nsIThreadInternal.h"
#include "nsITimer.h"
#include "mozilla/Mutex.h"
#include "nsCOMPtr.h"
#include "nsTArrayForwardDeclare.h"
#include "nsString.h"
#include "mozilla/Attributes.h"
#define IDLE_THREAD_TOPIC "thread-shutting-down"
#include "nsThreadPool.h"
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.
* destroys its thread after a period of inactivity. It may be created and used
* on any thread but it may only be shut down from the thread on which it is
* created.
*/
class LazyIdleThread final : public nsIThread,
public nsITimerCallback,
public nsIThreadObserver,
public nsIObserver,
public nsINamed {
class LazyIdleThread final : public nsISerialEventTarget, public nsIObserver {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIEVENTTARGET_FULL
NS_DECL_NSITHREAD
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSITHREADOBSERVER
NS_DECL_NSIOBSERVER
NS_DECL_NSINAMED
/**
* If AutomaticShutdown is specified, and the LazyIdleThread is created on the
* main thread, Shutdown() will automatically be called during
* xpcom-shutdown-threads.
*/
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 nsACString& aName,
ShutdownMethod aShutdownMethod = AutomaticShutdown,
nsIObserver* aIdleObserver = nullptr);
LazyIdleThread(uint32_t aIdleTimeoutMS, const char* aName,
ShutdownMethod aShutdownMethod = AutomaticShutdown);
/**
* 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.
* Shuts down the LazyIdleThread, waiting for any pending work to complete.
* Must be called from mOwningEventTarget.
*/
void SetWeakIdleObserver(nsIObserver* aObserver);
void Shutdown();
/**
* Disable the idle timeout for this thread. No effect if the timeout is
* already disabled.
* Register a nsIThreadPoolListener on the underlying threadpool to track the
* thread as it is created/destroyed.
*/
void DisableIdleTimeout();
/**
* Enable the idle timeout. No effect if the timeout is already enabled.
*/
void EnableIdleTimeout();
nsresult SetListener(nsIThreadPoolListener* aListener);
private:
/**
* Calls Shutdown().
* Asynchronously shuts down the LazyIdleThread on mOwningEventTarget.
*/
~LazyIdleThread();
/**
* Called just before dispatching to mThread.
* The thread which created this LazyIdleThread and is responsible for
* shutting it down.
*/
void PreDispatch();
const nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
/**
* Makes sure a valid thread lives in mThread.
* The single-thread backing threadpool which provides the actual threads used
* by LazyIdleThread, and implements the timeout.
*/
nsresult EnsureThread();
const RefPtr<nsThreadPool> mThreadPool;
/**
* Called on mThread to set up the thread observer.
* The serial event target providing a `nsISerialEventTarget` implementation
* when on the LazyIdleThread.
*/
void InitThread();
const RefPtr<TaskQueue> mTaskQueue;
/**
* Called on mThread to clean up the thread observer.
* Only safe to access on the owning thread or in the destructor (as no other
* threads have access then). If `true`, means the LazyIdleThread has already
* been shut down, so it does not need to be shut down asynchronously from the
* destructor.
*/
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<nsISerialEventTarget> mOwningEventTarget;
/**
* Only accessed on the owning thread. Set by EnsureThread().
*/
nsCOMPtr<nsIThread> mThread;
/**
* 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.
* Only accessed from 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 MOZ_GUARDED_BY(mMutex);
/**
* 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 MOZ_GUARDED_BY(mMutex);
/**
* 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 MOZ_GUARDED_BY(mMutex);
/**
* 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;
bool mShutdown = false;
};
} // namespace mozilla