michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsIClassInfoImpl.h" michael@0: #include "nsThreadPool.h" michael@0: #include "nsThreadManager.h" michael@0: #include "nsThread.h" michael@0: #include "nsMemory.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "prinrval.h" michael@0: #include "prlog.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo * michael@0: GetThreadPoolLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("nsThreadPool"); michael@0: return sLog; michael@0: } michael@0: #endif michael@0: #ifdef LOG michael@0: #undef LOG michael@0: #endif michael@0: #define LOG(args) PR_LOG(GetThreadPoolLog(), PR_LOG_DEBUG, args) michael@0: michael@0: // DESIGN: michael@0: // o Allocate anonymous threads. michael@0: // o Use nsThreadPool::Run as the main routine for each thread. michael@0: // o Each thread waits on the event queue's monitor, checking for michael@0: // pending events and rescheduling itself as an idle thread. michael@0: michael@0: #define DEFAULT_THREAD_LIMIT 4 michael@0: #define DEFAULT_IDLE_THREAD_LIMIT 1 michael@0: #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60) michael@0: michael@0: NS_IMPL_ADDREF(nsThreadPool) michael@0: NS_IMPL_RELEASE(nsThreadPool) michael@0: NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE, michael@0: NS_THREADPOOL_CID) michael@0: NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget, michael@0: nsIRunnable) michael@0: NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget) michael@0: michael@0: nsThreadPool::nsThreadPool() michael@0: : mThreadLimit(DEFAULT_THREAD_LIMIT) michael@0: , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT) michael@0: , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT) michael@0: , mIdleCount(0) michael@0: , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE) michael@0: , mShutdown(false) michael@0: { michael@0: } michael@0: michael@0: nsThreadPool::~nsThreadPool() michael@0: { michael@0: // Threads keep a reference to the nsThreadPool until they return from Run() michael@0: // after removing themselves from mThreads. michael@0: MOZ_ASSERT(mThreads.IsEmpty()); michael@0: } michael@0: michael@0: nsresult michael@0: nsThreadPool::PutEvent(nsIRunnable *event) michael@0: { michael@0: // Avoid spawning a new thread while holding the event queue lock... michael@0: michael@0: bool spawnThread = false; michael@0: uint32_t stackSize = 0; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: michael@0: LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(), michael@0: mThreadLimit)); michael@0: MOZ_ASSERT(mIdleCount <= (uint32_t) mThreads.Count(), "oops"); michael@0: michael@0: // Make sure we have a thread to service this event. michael@0: if (mIdleCount == 0 && mThreads.Count() < (int32_t) mThreadLimit) michael@0: spawnThread = true; michael@0: michael@0: mEvents.PutEvent(event); michael@0: stackSize = mStackSize; michael@0: } michael@0: michael@0: LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread)); michael@0: if (!spawnThread) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr thread; michael@0: nsThreadManager::get()->NewThread(0, michael@0: stackSize, michael@0: getter_AddRefs(thread)); michael@0: if (NS_WARN_IF(!thread)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: bool killThread = false; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: if (mThreads.Count() < (int32_t) mThreadLimit) { michael@0: mThreads.AppendObject(thread); michael@0: } else { michael@0: killThread = true; // okay, we don't need this thread anymore michael@0: } michael@0: } michael@0: LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread)); michael@0: if (killThread) { michael@0: // Pending events are processed on the current thread during michael@0: // nsIThread::Shutdown() execution, so if nsThreadPool::Dispatch() is called michael@0: // under caller's lock then deadlock could occur. This happens e.g. in case michael@0: // of nsStreamCopier. To prevent this situation, dispatch a shutdown event michael@0: // to the current thread instead of calling nsIThread::Shutdown() directly. michael@0: michael@0: nsRefPtr r = NS_NewRunnableMethod(thread, michael@0: &nsIThread::Shutdown); michael@0: NS_DispatchToCurrentThread(r); michael@0: } else { michael@0: thread->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsThreadPool::ShutdownThread(nsIThread *thread) michael@0: { michael@0: LOG(("THRD-P(%p) shutdown async [%p]\n", this, thread)); michael@0: michael@0: // This method is responsible for calling Shutdown on |thread|. This must be michael@0: // done from some other thread, so we use the main thread of the application. michael@0: michael@0: MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); michael@0: michael@0: nsRefPtr r = NS_NewRunnableMethod(thread, &nsIThread::Shutdown); michael@0: NS_DispatchToMainThread(r); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::Run() michael@0: { michael@0: LOG(("THRD-P(%p) enter\n", this)); michael@0: michael@0: mThreadNaming.SetThreadPoolName(mName); michael@0: michael@0: nsCOMPtr current; michael@0: nsThreadManager::get()->GetCurrentThread(getter_AddRefs(current)); michael@0: michael@0: bool shutdownThreadOnExit = false; michael@0: bool exitThread = false; michael@0: bool wasIdle = false; michael@0: PRIntervalTime idleSince; michael@0: michael@0: nsCOMPtr listener; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: listener = mListener; michael@0: } michael@0: michael@0: if (listener) { michael@0: listener->OnThreadCreated(); michael@0: } michael@0: michael@0: do { michael@0: nsCOMPtr event; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: if (!mEvents.GetPendingEvent(getter_AddRefs(event))) { michael@0: PRIntervalTime now = PR_IntervalNow(); michael@0: PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout); michael@0: michael@0: // If we are shutting down, then don't keep any idle threads michael@0: if (mShutdown) { michael@0: exitThread = true; michael@0: } else { michael@0: if (wasIdle) { michael@0: // if too many idle threads or idle for too long, then bail. michael@0: if (mIdleCount > mIdleThreadLimit || (now - idleSince) >= timeout) michael@0: exitThread = true; michael@0: } else { michael@0: // if would be too many idle threads... michael@0: if (mIdleCount == mIdleThreadLimit) { michael@0: exitThread = true; michael@0: } else { michael@0: ++mIdleCount; michael@0: idleSince = now; michael@0: wasIdle = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (exitThread) { michael@0: if (wasIdle) michael@0: --mIdleCount; michael@0: shutdownThreadOnExit = mThreads.RemoveObject(current); michael@0: } else { michael@0: PRIntervalTime delta = timeout - (now - idleSince); michael@0: LOG(("THRD-P(%p) waiting [%d]\n", this, delta)); michael@0: mon.Wait(delta); michael@0: } michael@0: } else if (wasIdle) { michael@0: wasIdle = false; michael@0: --mIdleCount; michael@0: } michael@0: } michael@0: if (event) { michael@0: LOG(("THRD-P(%p) running [%p]\n", this, event.get())); michael@0: event->Run(); michael@0: } michael@0: } while (!exitThread); michael@0: michael@0: if (listener) { michael@0: listener->OnThreadShuttingDown(); michael@0: } michael@0: michael@0: if (shutdownThreadOnExit) { michael@0: ShutdownThread(current); michael@0: } michael@0: michael@0: LOG(("THRD-P(%p) leave\n", this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::Dispatch(nsIRunnable *event, uint32_t flags) michael@0: { michael@0: LOG(("THRD-P(%p) dispatch [%p %x]\n", this, event, flags)); michael@0: michael@0: if (NS_WARN_IF(mShutdown)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (flags & DISPATCH_SYNC) { michael@0: nsCOMPtr thread; michael@0: nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thread)); michael@0: if (NS_WARN_IF(!thread)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsRefPtr wrapper = michael@0: new nsThreadSyncDispatch(thread, event); michael@0: PutEvent(wrapper); michael@0: michael@0: while (wrapper->IsPending()) michael@0: NS_ProcessNextEvent(thread); michael@0: } else { michael@0: NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); michael@0: PutEvent(event); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::IsOnCurrentThread(bool *result) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: nsIThread* thread = NS_GetCurrentThread(); michael@0: for (uint32_t i = 0; i < static_cast(mThreads.Count()); ++i) { michael@0: if (mThreads[i] == thread) { michael@0: *result = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: *result = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::Shutdown() michael@0: { michael@0: nsCOMArray threads; michael@0: nsCOMPtr listener; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: mShutdown = true; michael@0: mon.NotifyAll(); michael@0: michael@0: threads.AppendObjects(mThreads); michael@0: mThreads.Clear(); michael@0: michael@0: // Swap in a null listener so that we release the listener at the end of michael@0: // this method. The listener will be kept alive as long as the other threads michael@0: // that were created when it was set. michael@0: mListener.swap(listener); michael@0: } michael@0: michael@0: // It's important that we shutdown the threads while outside the event queue michael@0: // monitor. Otherwise, we could end up dead-locking. michael@0: michael@0: for (int32_t i = 0; i < threads.Count(); ++i) michael@0: threads[i]->Shutdown(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::GetThreadLimit(uint32_t *value) michael@0: { michael@0: *value = mThreadLimit; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::SetThreadLimit(uint32_t value) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: mThreadLimit = value; michael@0: if (mIdleThreadLimit > mThreadLimit) michael@0: mIdleThreadLimit = mThreadLimit; michael@0: michael@0: if (static_cast(mThreads.Count()) > mThreadLimit) { michael@0: mon.NotifyAll(); // wake up threads so they observe this change michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::GetIdleThreadLimit(uint32_t *value) michael@0: { michael@0: *value = mIdleThreadLimit; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::SetIdleThreadLimit(uint32_t value) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: mIdleThreadLimit = value; michael@0: if (mIdleThreadLimit > mThreadLimit) michael@0: mIdleThreadLimit = mThreadLimit; michael@0: michael@0: // Do we need to kill some idle threads? michael@0: if (mIdleCount > mIdleThreadLimit) { michael@0: mon.NotifyAll(); // wake up threads so they observe this change michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::GetIdleThreadTimeout(uint32_t *value) michael@0: { michael@0: *value = mIdleThreadTimeout; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::SetIdleThreadTimeout(uint32_t value) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: uint32_t oldTimeout = mIdleThreadTimeout; michael@0: mIdleThreadTimeout = value; michael@0: michael@0: // Do we need to notify any idle threads that their sleep time has shortened? michael@0: if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) { michael@0: mon.NotifyAll(); // wake up threads so they observe this change michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::GetThreadStackSize(uint32_t* value) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: *value = mStackSize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::SetThreadStackSize(uint32_t value) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: mStackSize = value; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::GetListener(nsIThreadPoolListener** aListener) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: NS_IF_ADDREF(*aListener = mListener); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::SetListener(nsIThreadPoolListener* aListener) michael@0: { michael@0: nsCOMPtr swappedListener(aListener); michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: mListener.swap(swappedListener); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadPool::SetName(const nsACString& aName) michael@0: { michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); michael@0: if (mThreads.Count()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: mName = aName; michael@0: return NS_OK; michael@0: }