Bug 443877 - "Need a way to point timers at a different event target". r=bsmedberg.
authorBen Turner <bent.mozilla@gmail.com>
Thu, 24 Jul 2008 10:20:33 -0700
changeset 16177 c7ca7c9ca7ba46de89f35cd81bd25c6dc0370aa2
parent 16176 c8ac37904c16d9a2d565c52b17825d1fdef913c8
child 16178 2c6f51bf3bed832cbfad13655ae403faa0943b5f
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg
bugs443877
milestone1.9.1a2pre
Bug 443877 - "Need a way to point timers at a different event target". r=bsmedberg.
xpcom/tests/Makefile.in
xpcom/tests/TestTimers.cpp
xpcom/threads/nsITimer.idl
xpcom/threads/nsTimerImpl.cpp
xpcom/threads/nsTimerImpl.h
--- a/xpcom/tests/Makefile.in
+++ b/xpcom/tests/Makefile.in
@@ -75,16 +75,17 @@ CPPSRCS		= \
 		TestServMgr.cpp \
 		TestAutoPtr.cpp \
 		TestVersionComparator.cpp \
 		TestTextFormatter.cpp \
 		TestPipe.cpp \
 		TestRegistrationOrder.cpp \
 		TestProxies.cpp \
 		TestThreadPoolListener.cpp \
+		TestTimers.cpp \
 		$(NULL)
 
 ifndef MOZ_ENABLE_LIBXUL
 CPPSRCS += \
 		TestArray.cpp \
 		TestTArray.cpp \
 		TestAtoms.cpp \
 		TestAutoLock.cpp \
@@ -137,16 +138,17 @@ CPP_UNIT_TESTS = \
   TestFactory \
   TestHashtables \
   TestID \
   TestObserverService \
   TestPipe \
   TestServMgr \
   TestTextFormatter \
   TestThreadPoolListener \
+  TestTimers \
   $(NULL)
 
 ifndef MOZ_ENABLE_LIBXUL
 CPP_UNIT_TESTS += \
   TestArray \
   TestAutoLock \
   TestCRT \
   TestEncoding \
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestTimers.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 Timer 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 "nsITimer.h"
+
+#include "nsAutoLock.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "prinrval.h"
+#include "prmon.h"
+
+typedef nsresult(*TestFuncPtr)();
+
+class AutoTestThread
+{
+public:
+  AutoTestThread() {
+    nsCOMPtr<nsIThread> newThread;
+    nsresult rv = NS_NewThread(getter_AddRefs(newThread));
+    NS_ENSURE_SUCCESS(rv,);
+
+    newThread.swap(mThread);
+  }
+
+  ~AutoTestThread() {
+    mThread->Shutdown();
+  }
+
+  operator nsDerivedSafe<nsIThread>*() const {
+    return mThread;
+  }
+
+  nsDerivedSafe<nsIThread>* operator->() const {
+    return mThread;
+  }
+
+private:
+  nsCOMPtr<nsIThread> mThread;
+};
+
+class AutoCreateAndDestroyMonitor
+{
+public:
+  AutoCreateAndDestroyMonitor() {
+    mMonitor = nsAutoMonitor::NewMonitor("TestTimers::AutoMon");
+    NS_ASSERTION(mMonitor, "Out of memory!");
+  }
+
+  ~AutoCreateAndDestroyMonitor() {
+    if (mMonitor) {
+      nsAutoMonitor::DestroyMonitor(mMonitor);
+    }
+  }
+
+  operator PRMonitor*() {
+    return mMonitor;
+  }
+
+private:
+  PRMonitor* mMonitor;
+};
+
+class TimerCallback : public nsITimerCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  TimerCallback(nsIThread** aThreadPtr, PRMonitor* aMonitor)
+  : mThreadPtr(aThreadPtr), mMonitor(aMonitor) { }
+
+  NS_IMETHOD Notify(nsITimer* aTimer) {
+    nsCOMPtr<nsIThread> current(do_GetCurrentThread());
+
+    nsAutoMonitor mon(mMonitor);
+
+    NS_ASSERTION(!*mThreadPtr, "Timer called back more than once!");
+    *mThreadPtr = current;
+
+    mon.Notify();
+
+    return NS_OK;
+  }
+private:
+  nsIThread** mThreadPtr;
+  PRMonitor* mMonitor;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(TimerCallback, nsITimerCallback)
+
+nsresult
+TestTargetedTimers()
+{
+  AutoCreateAndDestroyMonitor newMon;
+  NS_ENSURE_TRUE(newMon, NS_ERROR_OUT_OF_MEMORY);
+
+  AutoTestThread testThread;
+  NS_ENSURE_TRUE(testThread, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv;
+  nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsIEventTarget* target = static_cast<nsIEventTarget*>(testThread);
+
+  rv = timer->SetTarget(target);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsIThread* notifiedThread = nsnull;
+
+  nsCOMPtr<nsITimerCallback> callback =
+    new TimerCallback(&notifiedThread, newMon);
+  NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
+
+  rv = timer->InitWithCallback(callback, PR_MillisecondsToInterval(2000),
+                               nsITimer::TYPE_ONE_SHOT);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoMonitor mon(newMon);
+  while (!notifiedThread) {
+    mon.Wait();
+  }
+  NS_ENSURE_TRUE(notifiedThread == testThread, NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+int main(int argc, char** argv)
+{
+  ScopedXPCOM xpcom("TestTimers");
+  NS_ENSURE_FALSE(xpcom.failed(), 1);
+
+  static TestFuncPtr testsToRun[] = {
+    TestTargetedTimers
+  };
+  static PRUint32 testCount = sizeof(testsToRun) / sizeof(testsToRun[0]);
+
+  for (PRUint32 i = 0; i < testCount; i++) {
+    nsresult rv = testsToRun[i]();
+    NS_ENSURE_SUCCESS(rv, 1);
+  }
+
+  return 0;
+}
--- a/xpcom/threads/nsITimer.idl
+++ b/xpcom/threads/nsITimer.idl
@@ -35,16 +35,17 @@
  * 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 "nsISupports.idl"
 
 interface nsIObserver;
+interface nsIEventTarget;
 
 %{C++
 /**
  * The signature of the timer callback function passed to initWithFuncCallback.
  * This is the function that will get called when the timer expires if the
  * timer is initialized via initWithFuncCallback.
  *
  * @param aTimer the timer which has expired
@@ -77,17 +78,17 @@ interface nsITimerCallback : nsISupports
 
 
 /**
  * nsITimer instances must be initialized by calling one of the "init" methods
  * documented below.  You may also re-initialize an existing instance with new
  * delay to avoid the overhead of destroying and creating a timer.  It is not
  * necessary to cancel the timer in that case.
  */
-[scriptable, uuid(436a83fa-b396-11d9-bcfa-00112478d626)]
+[scriptable, uuid(193fc37a-8aa4-4d29-aa57-1acd87c26b66)]
 interface nsITimer : nsISupports
 {
   /* Timer types */
 
   /**
    * Type of a timer that fires once only.
    */
   const short TYPE_ONE_SHOT           = 0;
@@ -182,15 +183,21 @@ interface nsITimer : nsISupports
    * The opaque pointer pass to initWithFuncCallback.
    */  
   [noscript] readonly attribute voidPtr closure;
 
   /**
    * The nsITimerCallback object passed to initWithCallback.
    */
   readonly attribute nsITimerCallback callback;
+
+  /**
+   * The nsIEventTarget where the callback will be dispatched. Note that this
+   * target may only be set before the call to one of the init methods above.
+   */
+  attribute nsIEventTarget target;
 };
 
 %{C++
 #define NS_TIMER_CONTRACTID "@mozilla.org/timer;1"
 #define NS_TIMER_CALLBACK_TOPIC "timer-callback"
 %}
 
--- a/xpcom/threads/nsTimerImpl.cpp
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -142,17 +142,17 @@ nsTimerImpl::nsTimerImpl() :
   mFiring(PR_FALSE),
   mArmed(PR_FALSE),
   mCanceled(PR_FALSE),
   mGeneration(0),
   mDelay(0),
   mTimeout(0)
 {
   // XXXbsmedberg: shouldn't this be in Init()?
-  mCallingThread = do_GetCurrentThread();
+  mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
 
   mCallback.c = nsnull;
 
 #ifdef DEBUG_TIMERS
   mStart = 0;
   mStart2 = 0;
 #endif
 }
@@ -342,16 +342,36 @@ NS_IMETHODIMP nsTimerImpl::GetCallback(n
     NS_ADDREF(*aCallback = mTimerCallbackWhileFiring);
   else
     *aCallback = nsnull;
 
   return NS_OK;
 }
 
 
+NS_IMETHODIMP nsTimerImpl::GetTarget(nsIEventTarget** aTarget)
+{
+  NS_IF_ADDREF(*aTarget = mEventTarget);
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP nsTimerImpl::SetTarget(nsIEventTarget* aTarget)
+{
+  NS_ENSURE_TRUE(mCallbackType == CALLBACK_TYPE_UNKNOWN,
+                 NS_ERROR_ALREADY_INITIALIZED);
+
+  if (aTarget)
+    mEventTarget = aTarget;
+  else
+    mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+  return NS_OK;
+}
+
+
 void nsTimerImpl::Fire()
 {
   if (mCanceled)
     return;
 
   PRIntervalTime now = PR_IntervalNow();
 #ifdef DEBUG_TIMERS
   if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
@@ -517,17 +537,17 @@ nsresult nsTimerImpl::PostTimerEvent()
     SetDelayInternal(mDelay);
     if (gThread) {
       nsresult rv = gThread->AddTimer(this);
       if (NS_FAILED(rv))
         return rv;
     }
   }
 
-  nsresult rv = mCallingThread->Dispatch(event, NS_DISPATCH_NORMAL);
+  nsresult rv = mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
   if (NS_FAILED(rv) && gThread)
     gThread->RemoveTimer(this);
   return rv;
 }
 
 void nsTimerImpl::SetDelayInternal(PRUint32 aDelay)
 {
   PRIntervalTime delayInterval = PR_MillisecondsToInterval(aDelay);
--- a/xpcom/threads/nsTimerImpl.h
+++ b/xpcom/threads/nsTimerImpl.h
@@ -39,17 +39,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsTimerImpl_h___
 #define nsTimerImpl_h___
 
 //#define FORCE_PR_LOG /* Allow logging in the release build */
 
 #include "nsITimer.h"
-#include "nsIThread.h"
+#include "nsIEventTarget.h"
 #include "nsIObserver.h"
 
 #include "nsCOMPtr.h"
 
 #include "prlog.h"
 
 #if defined(PR_LOGGING)
 static PRLogModuleInfo *gTimerLog = PR_NewLogModule("nsTimerImpl");
@@ -116,17 +116,17 @@ private:
     mCallbackType = CALLBACK_TYPE_UNKNOWN; 
 
     if (cbType == CALLBACK_TYPE_INTERFACE)
       NS_RELEASE(mCallback.i);
     else if (cbType == CALLBACK_TYPE_OBSERVER)
       NS_RELEASE(mCallback.o);
   }
 
-  nsCOMPtr<nsIThread>   mCallingThread;
+  nsCOMPtr<nsIEventTarget> mEventTarget;
 
   void *                mClosure;
 
   union CallbackUnion {
     nsTimerCallbackFunc c;
     nsITimerCallback *  i;
     nsIObserver *       o;
   } mCallback;