Bug 443874 - "Need a hook for thread creation and destruction in thread pool". r=bsmedberg.
authorBen Turner <bent.mozilla@gmail.com>
Thu, 24 Jul 2008 10:18:57 -0700
changeset 16176 c8ac37904c16d9a2d565c52b17825d1fdef913c8
parent 16175 9871082391f517dc30f1c21a8149641f4f99ec6a
child 16177 c7ca7c9ca7ba46de89f35cd81bd25c6dc0370aa2
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg
bugs443874
milestone1.9.1a2pre
Bug 443874 - "Need a hook for thread creation and destruction in thread pool". r=bsmedberg.
xpcom/tests/Makefile.in
xpcom/tests/TestThreadPoolListener.cpp
xpcom/threads/nsIThreadPool.idl
xpcom/threads/nsThreadPool.cpp
xpcom/threads/nsThreadPool.h
--- a/xpcom/tests/Makefile.in
+++ b/xpcom/tests/Makefile.in
@@ -74,16 +74,17 @@ CPPSRCS		= \
 		TestObserverService.cpp \
 		TestServMgr.cpp \
 		TestAutoPtr.cpp \
 		TestVersionComparator.cpp \
 		TestTextFormatter.cpp \
 		TestPipe.cpp \
 		TestRegistrationOrder.cpp \
 		TestProxies.cpp \
+		TestThreadPoolListener.cpp \
 		$(NULL)
 
 ifndef MOZ_ENABLE_LIBXUL
 CPPSRCS += \
 		TestArray.cpp \
 		TestTArray.cpp \
 		TestAtoms.cpp \
 		TestAutoLock.cpp \
@@ -135,16 +136,17 @@ CPP_UNIT_TESTS = \
   TestCOMPtrEq \
   TestFactory \
   TestHashtables \
   TestID \
   TestObserverService \
   TestPipe \
   TestServMgr \
   TestTextFormatter \
+  TestThreadPoolListener \
   $(NULL)
 
 ifndef MOZ_ENABLE_LIBXUL
 CPP_UNIT_TESTS += \
   TestArray \
   TestAutoLock \
   TestCRT \
   TestEncoding \
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestThreadPoolListener.cpp
@@ -0,0 +1,235 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Proxy Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Ben Turner <bent.mozilla@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "TestHarness.h"
+
+#include "nsIThread.h"
+#include "nsIThreadPool.h"
+
+#include "nsAutoLock.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.h"
+#include "pratom.h"
+#include "prinrval.h"
+#include "prmon.h"
+#include "prthread.h"
+
+#define NUMBER_OF_THREADS 4
+
+// One hour... because test boxes can be slow!
+#define IDLE_THREAD_TIMEOUT 3600000
+
+static nsIThread** gCreatedThreadList = nsnull;
+static nsIThread** gShutDownThreadList = nsnull;
+
+static PRMonitor* gMonitor = nsnull;
+
+static PRBool gAllRunnablesPosted = PR_FALSE;
+static PRBool gAllThreadsCreated = PR_FALSE;
+static PRBool gAllThreadsShutDown = PR_FALSE;
+
+class Listener : public nsIThreadPoolListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITHREADPOOLLISTENER
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(Listener, nsIThreadPoolListener)
+
+NS_IMETHODIMP
+Listener::OnThreadCreated()
+{
+  nsIThread* current = NS_GetCurrentThread();
+  NS_ASSERTION(current, "Couldn't get current thread!");
+
+  nsAutoMonitor mon(gMonitor);
+
+  while (!gAllRunnablesPosted) {
+    mon.Wait();
+  }
+
+  for (PRUint32 i = 0; i < NUMBER_OF_THREADS; i++) {
+    nsIThread* thread = gCreatedThreadList[i];
+    NS_ASSERTION(thread != current, "Saw the same thread twice!");
+
+    if (!thread) {
+      gCreatedThreadList[i] = current;
+      if (i == (NUMBER_OF_THREADS - 1)) {
+        gAllThreadsCreated = PR_TRUE;
+        mon.NotifyAll();
+      }
+      return NS_OK;
+    }
+  }
+
+  NS_NOTREACHED("Too many threads!");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Listener::OnThreadShuttingDown()
+{
+  nsIThread* current = NS_GetCurrentThread();
+  NS_ASSERTION(current, "Couldn't get current thread!");
+
+  nsAutoMonitor mon(gMonitor);
+
+  for (PRUint32 i = 0; i < NUMBER_OF_THREADS; i++) {
+    nsIThread* thread = gShutDownThreadList[i];
+    NS_ASSERTION(thread != current, "Saw the same thread twice!");
+
+    if (!thread) {
+      gShutDownThreadList[i] = current;
+      if (i == (NUMBER_OF_THREADS - 1)) {
+        gAllThreadsShutDown = PR_TRUE;
+        mon.NotifyAll();
+      }
+      return NS_OK;
+    }
+  }
+
+  NS_NOTREACHED("Too many threads!");
+  return NS_ERROR_FAILURE;
+}
+
+class AutoCreateAndDestroyMonitor
+{
+public:
+  AutoCreateAndDestroyMonitor(PRMonitor** aMonitorPtr)
+  : mMonitorPtr(aMonitorPtr) {
+    *aMonitorPtr = nsAutoMonitor::NewMonitor("TestThreadPoolListener::AutoMon");
+    NS_ASSERTION(*aMonitorPtr, "Out of memory!");
+  }
+
+  ~AutoCreateAndDestroyMonitor() {
+    if (*mMonitorPtr) {
+      nsAutoMonitor::DestroyMonitor(*mMonitorPtr);
+      *mMonitorPtr = nsnull;
+    }
+  }
+
+private:
+  PRMonitor** mMonitorPtr;
+};
+
+int main(int argc, char** argv)
+{
+  ScopedXPCOM xpcom("ThreadPoolListener");
+  NS_ENSURE_FALSE(xpcom.failed(), 1);
+
+  nsIThread* createdThreadList[NUMBER_OF_THREADS] = { nsnull };
+  gCreatedThreadList = createdThreadList;
+
+  nsIThread* shutDownThreadList[NUMBER_OF_THREADS] = { nsnull };
+  gShutDownThreadList = shutDownThreadList;
+
+  AutoCreateAndDestroyMonitor newMon(&gMonitor);
+  NS_ENSURE_TRUE(gMonitor, 1);
+
+  nsresult rv;
+  nsCOMPtr<nsIThreadPool> pool =
+    do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  rv = pool->SetThreadLimit(NUMBER_OF_THREADS);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  rv = pool->SetIdleThreadLimit(NUMBER_OF_THREADS);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  rv = pool->SetIdleThreadTimeout(IDLE_THREAD_TIMEOUT);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  nsCOMPtr<nsIThreadPoolListener> listener = new Listener();
+  NS_ENSURE_TRUE(listener, 1);
+
+  rv = pool->SetListener(listener);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  {
+    nsAutoMonitor mon(gMonitor);
+
+    for (PRUint32 i = 0; i < NUMBER_OF_THREADS; i++) {
+      nsCOMPtr<nsIRunnable> runnable = new nsRunnable();
+      NS_ENSURE_TRUE(runnable, 1);
+
+      rv = pool->Dispatch(runnable, NS_DISPATCH_NORMAL);
+      NS_ENSURE_SUCCESS(rv, 1);
+    }
+
+    gAllRunnablesPosted = PR_TRUE;
+    mon.NotifyAll();
+  }
+
+  {
+    nsAutoMonitor mon(gMonitor);
+    while (!gAllThreadsCreated) {
+      mon.Wait();
+    }
+  }
+
+  rv = pool->Shutdown();
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  {
+    nsAutoMonitor mon(gMonitor);
+    while (!gAllThreadsShutDown) {
+      mon.Wait();
+    }
+  }
+
+  for (PRUint32 i = 0; i < NUMBER_OF_THREADS; i++) {
+    nsIThread* created = gCreatedThreadList[i];
+    NS_ENSURE_TRUE(created, 1);
+
+    PRBool match = PR_FALSE;
+    for (PRUint32 j = 0; j < NUMBER_OF_THREADS; j++) {
+      nsIThread* destroyed = gShutDownThreadList[j];
+      NS_ENSURE_TRUE(destroyed, 1);
+
+      if (destroyed == created) {
+        match = PR_TRUE;
+        break;
+      }
+    }
+
+    NS_ENSURE_TRUE(match, 1);
+  }
+
+  return 0;
+}
--- a/xpcom/threads/nsIThreadPool.idl
+++ b/xpcom/threads/nsIThreadPool.idl
@@ -33,16 +33,32 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIEventTarget.idl"
 
+[scriptable, uuid(ef194cab-3f86-4b61-b132-e5e96a79e5d1)]
+interface nsIThreadPoolListener : nsISupports
+{
+  /**
+   * Called when a new thread is created by the thread pool. The notification
+   * happens on the newly-created thread.
+   */
+  void onThreadCreated();
+
+  /**
+   * Called when a thread is about to be destroyed by the thread pool. The
+   * notification happens on the thread that is about to be destroyed.
+   */
+  void onThreadShuttingDown();
+};
+
 /**
  * An interface to a thread pool.  A thread pool creates a limited number of
  * anonymous (unnamed) worker threads.  An event dispatched to the thread pool
  * will be run on the next available worker thread.
  */
 [scriptable, uuid(394c29f0-225f-487f-86d3-4c259da76cab)]
 interface nsIThreadPool : nsIEventTarget
 {
@@ -65,9 +81,26 @@ interface nsIThreadPool : nsIEventTarget
    */
   attribute unsigned long idleThreadLimit;
 
   /**
    * Get/set the amount of time in milliseconds before an idle thread is
    * destroyed.
    */
   attribute unsigned long idleThreadTimeout;
+
+  /**
+   * An optional listener that will be notified when a thread is created or
+   * destroyed in the course of the thread pool's operation.
+   *
+   * A listener will only receive notifications about threads created after the
+   * listener is set so it is recommended that the consumer set the listener
+   * before dispatching the first event. A listener that receives an
+   * onThreadCreated() notification is guaranteed to always receive the
+   * corresponding onThreadShuttingDown() notification.
+   *
+   * The thread pool takes ownership of the listener and releases it when the
+   * shutdown() method is called. Threads created after the listener is set will
+   * also take ownership of the listener so that the listener will be kept alive
+   * long enough to receive the guaranteed onThreadShuttingDown() notification.
+   */
+  attribute nsIThreadPoolListener listener;
 };
--- a/xpcom/threads/nsThreadPool.cpp
+++ b/xpcom/threads/nsThreadPool.cpp
@@ -157,16 +157,26 @@ nsThreadPool::Run()
   nsCOMPtr<nsIThread> current;
   nsThreadManager::get()->GetCurrentThread(getter_AddRefs(current));
 
   PRBool shutdownThreadOnExit = PR_FALSE;
   PRBool exitThread = PR_FALSE;
   PRBool wasIdle = PR_FALSE;
   PRIntervalTime idleSince;
 
+  nsCOMPtr<nsIThreadPoolListener> listener;
+  {
+    nsAutoMonitor mon(mEvents.Monitor());
+    listener = mListener;
+  }
+
+  if (listener) {
+    listener->OnThreadCreated();
+  }
+
   do {
     nsCOMPtr<nsIRunnable> event;
     {
       nsAutoMonitor mon(mEvents.Monitor());
       if (!mEvents.GetPendingEvent(getter_AddRefs(event))) {
         PRIntervalTime now     = PR_IntervalNow();
         PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout);
 
@@ -205,18 +215,22 @@ nsThreadPool::Run()
       }
     }
     if (event) {
       LOG(("THRD-P(%p) running [%p]\n", this, event.get()));
       event->Run();
     }
   } while (!exitThread);
 
-  if (shutdownThreadOnExit)
+  if (shutdownThreadOnExit) {
+    if (listener) {
+      listener->OnThreadShuttingDown();
+    }
     ShutdownThread(current);
+  }
 
   LOG(("THRD-P(%p) leave\n", this));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThreadPool::Dispatch(nsIRunnable *event, PRUint32 flags)
 {
@@ -252,22 +266,28 @@ nsThreadPool::IsOnCurrentThread(PRBool *
   *result = PR_FALSE;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThreadPool::Shutdown()
 {
   nsCOMArray<nsIThread> threads;
+  nsCOMPtr<nsIThreadPoolListener> listener;
   {
     nsAutoMonitor mon(mEvents.Monitor());
     mShutdown = PR_TRUE;
     mon.NotifyAll();
 
     threads.AppendObjects(mThreads);
+
+    // Swap in a null listener so that we release the listener at the end of
+    // this method. The listener will be kept alive as long as the other threads
+    // that were created when it was set.
+    mListener.swap(listener);
   }
 
   // It's important that we shutdown the threads while outside the event queue
   // monitor.  Otherwise, we could end up dead-locking.  The threads will take
   // care of removing themselves from mThreads as they exit.
 
   for (PRInt32 i = 0; i < threads.Count(); ++i)
     threads[i]->Shutdown();
@@ -321,8 +341,27 @@ nsThreadPool::GetIdleThreadTimeout(PRUin
 NS_IMETHODIMP
 nsThreadPool::SetIdleThreadTimeout(PRUint32 value)
 {
   nsAutoMonitor mon(mEvents.Monitor());
   mIdleThreadTimeout = value;
   mon.NotifyAll();  // wake up threads so they observe this change
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsThreadPool::GetListener(nsIThreadPoolListener** aListener)
+{
+  nsAutoMonitor mon(mEvents.Monitor());
+  NS_IF_ADDREF(*aListener = mListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::SetListener(nsIThreadPoolListener* aListener)
+{
+  nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
+  {
+    nsAutoMonitor mon(mEvents.Monitor());
+    mListener.swap(swappedListener);
+  }
+  return NS_OK;
+}
--- a/xpcom/threads/nsThreadPool.h
+++ b/xpcom/threads/nsThreadPool.h
@@ -39,16 +39,17 @@
 #ifndef nsThreadPool_h__
 #define nsThreadPool_h__
 
 #include "nsIThreadPool.h"
 #include "nsIThread.h"
 #include "nsIRunnable.h"
 #include "nsEventQueue.h"
 #include "nsCOMArray.h"
+#include "nsCOMPtr.h"
 
 class nsThreadPool : public nsIThreadPool, public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIEVENTTARGET
   NS_DECL_NSITHREADPOOL
   NS_DECL_NSIRUNNABLE
@@ -62,16 +63,17 @@ private:
   nsresult PutEvent(nsIRunnable *event);
 
   nsCOMArray<nsIThread> mThreads;
   nsEventQueue          mEvents;
   PRUint32              mThreadLimit;
   PRUint32              mIdleThreadLimit;
   PRUint32              mIdleThreadTimeout;
   PRUint32              mIdleCount;
+  nsCOMPtr<nsIThreadPoolListener> mListener;
   PRBool                mShutdown;
 };
 
 #define NS_THREADPOOL_CLASSNAME "nsThreadPool"
 #define NS_THREADPOOL_CID                          \
 { /* 547ec2a8-315e-4ec4-888e-6e4264fe90eb */       \
   0x547ec2a8,                                      \
   0x315e,                                          \