Bug 1355315: Don't complain if nsTimerEvent is destroyed on the TimerThread. Allows some stuff to be simplified. r?froydnj draft
authorByron Campen [:bwc] <docfaraday@gmail.com>
Wed, 03 May 2017 13:05:19 -0500
changeset 576552 3301b104d45e1ef6676060739088d8640d28a9e5
parent 576334 3b96f277325747fe668ca8cd896d2f581238e4ee
child 628232 d19da425a345593bc39ca2d74258f88a6f0f2a21
push id58401
push userbcampen@mozilla.com
push dateThu, 11 May 2017 20:34:31 +0000
reviewersfroydnj
bugs1355315
milestone55.0a1
Bug 1355315: Don't complain if nsTimerEvent is destroyed on the TimerThread. Allows some stuff to be simplified. r?froydnj Ok. This is kinda messy. nsTimerImpl, as we know, was _way_ not threadsafe for a long time. nsTimerImpl was also something that the user of the timer held a ref to; it implemented nsITimer. I believe the concern was cases where a thread had stopped accepting new runnables, but was draining its queue, and possibly operating on its ref to the nsTimerImpl. But, since nsTimerImpl is threadsafe (apart from bugs), and since it isn't exposed to the user directly, we probably don't need to worry about this case. MozReview-Commit-ID: 8hZ9CB2mjNf
xpcom/threads/TimerThread.cpp
xpcom/threads/TimerThread.h
--- a/xpcom/threads/TimerThread.cpp
+++ b/xpcom/threads/TimerThread.cpp
@@ -140,19 +140,20 @@ public:
   nsresult Cancel() override
   {
     mTimer->Cancel();
     return NS_OK;
   }
 
   NS_IMETHOD GetName(nsACString& aName) override;
 
-  nsTimerEvent()
-    : mTimer()
-    , mGeneration(0)
+  explicit nsTimerEvent(already_AddRefed<nsTimerImpl>& aTimer)
+    : mTimer(aTimer)
+    , mGeneration(mTimer->GetGeneration())
+    , mTarget(mTimer->mEventTarget)
   {
     // Note: We override operator new for this class, and the override is
     // fallible!
     sAllocatorUsers++;
   }
 
   TimeStamp mInitTime;
 
@@ -165,41 +166,42 @@ public:
     return sAllocator->Alloc(aSize);
   }
   void operator delete(void* aPtr)
   {
     sAllocator->Free(aPtr);
     DeleteAllocatorIfNeeded();
   }
 
-  already_AddRefed<nsTimerImpl> ForgetTimer()
+  void DispatchToTarget()
   {
-    return mTimer.forget();
-  }
+    if (!mTarget) {
+      NS_ERROR("Attempt to post timer event to NULL event target");
+      return;
+    }
 
-  void SetTimer(already_AddRefed<nsTimerImpl> aTimer)
-  {
-    mTimer = aTimer;
-    mGeneration = mTimer->GetGeneration();
+    // If this fails, there isn't really anything we can do about it.
+    (void)mTarget->Dispatch(this, NS_DISPATCH_NORMAL);
   }
 
 private:
   nsTimerEvent(const nsTimerEvent&) = delete;
   nsTimerEvent& operator=(const nsTimerEvent&) = delete;
   nsTimerEvent& operator=(const nsTimerEvent&&) = delete;
 
   ~nsTimerEvent()
   {
     MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0,
                "This will result in us attempting to deallocate the nsTimerEvent allocator twice");
     sAllocatorUsers--;
   }
 
   RefPtr<nsTimerImpl> mTimer;
   int32_t      mGeneration;
+  nsCOMPtr<nsIEventTarget> mTarget;
 
   static TimerEventAllocator* sAllocator;
   static Atomic<int32_t> sAllocatorUsers;
   static bool sCanDeleteAllocator;
 };
 
 TimerEventAllocator* nsTimerEvent::sAllocator = nullptr;
 Atomic<int32_t> nsTimerEvent::sAllocatorUsers;
@@ -440,41 +442,17 @@ TimerThread::Run()
 
           RefPtr<nsTimerImpl> timerRef(mTimers[0]->Take());
           RemoveFirstTimerInternal();
 
           MOZ_LOG(GetTimerLog(), LogLevel::Debug,
                  ("Timer thread woke up %fms from when it was supposed to\n",
                   fabs((now - timerRef->mTimeout).ToMilliseconds())));
 
-          // We are going to let the call to PostTimerEvent here handle the
-          // release of the timer so that we don't end up releasing the timer
-          // on the TimerThread instead of on the thread it targets.
-          timerRef = PostTimerEvent(timerRef.forget());
-
-          if (timerRef) {
-            // We got our reference back due to an error.
-            // Unhook the nsRefPtr, and release manually so we can get the
-            // refcount.
-            nsrefcnt rc = timerRef.forget().take()->Release();
-            (void)rc;
-
-            // The nsITimer interface requires that its users keep a reference
-            // to the timers they use while those timers are initialized but
-            // have not yet fired.  If this ever happens, it is a bug in the
-            // code that created and used the timer.
-            //
-            // Further, note that this should never happen even with a
-            // misbehaving user, because nsTimerImpl::Release checks for a
-            // refcount of 1 with an armed timer (a timer whose only reference
-            // is from the timer thread) and when it hits this will remove the
-            // timer from the timer thread and thus destroy the last reference,
-            // preventing this situation from occurring.
-            MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!");
-          }
+          PostTimerEvent(timerRef.forget());
 
           if (mShutdown) {
             break;
           }
 
           // Update now, as PostTimerEvent plus the locking may have taken a
           // tick or two, and we may goto next below.
           now = TimeStamp::Now();
@@ -660,70 +638,50 @@ void
 TimerThread::RemoveFirstTimerInternal()
 {
   mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(!mTimers.IsEmpty());
   std::pop_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan);
   mTimers.RemoveElementAt(mTimers.Length() - 1);
 }
 
-already_AddRefed<nsTimerImpl>
+void
 TimerThread::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef)
 {
   mMonitor.AssertCurrentThreadOwns();
 
-  RefPtr<nsTimerImpl> timer(aTimerRef);
-  if (!timer->mEventTarget) {
-    NS_ERROR("Attempt to post timer event to NULL event target");
-    return timer.forget();
-  }
-
   // XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
 
   // Since we already addref'd 'timer', we don't need to addref here.
   // We will release either in ~nsTimerEvent(), or pass the reference back to
   // the caller. We need to copy the generation number from this timer into the
   // event, so we can avoid firing a timer that was re-initialized after being
   // canceled.
 
-  RefPtr<nsTimerEvent> event = new nsTimerEvent;
+  RefPtr<nsTimerEvent> event = new nsTimerEvent(aTimerRef);
   if (!event) {
-    return timer.forget();
+    return;
   }
 
   if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
     event->mInitTime = TimeStamp::Now();
   }
 
 #ifdef MOZ_TASK_TRACER
   // During the dispatch of TimerEvent, we overwrite the current TraceInfo
   // partially with the info saved in timer earlier, and restore it back by
   // AutoSaveCurTraceInfo.
   AutoSaveCurTraceInfo saveCurTraceInfo;
   (timer->GetTracedTask()).SetTLSTraceInfo();
 #endif
 
-  nsCOMPtr<nsIEventTarget> target = timer->mEventTarget;
-  event->SetTimer(timer.forget());
-
-  nsresult rv;
-  {
-    // We release mMonitor around the Dispatch because if this timer is targeted
-    // at the TimerThread we'll deadlock.
-    MonitorAutoUnlock unlock(mMonitor);
-    rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
-  }
-
-  if (NS_FAILED(rv)) {
-    timer = event->ForgetTimer();
-    RemoveTimerInternal(timer);
-    return timer.forget();
-  }
-
-  return nullptr;
+  // We release mMonitor around the Dispatch because if this timer is targeted
+  // at the TimerThread we'll deadlock.
+  MonitorAutoUnlock unlock(mMonitor);
+  event->DispatchToTarget();
 }
 
 void
 TimerThread::DoBeforeSleep()
 {
   // Mainthread
   MonitorAutoLock lock(mMonitor);
   mSleeping = true;
--- a/xpcom/threads/TimerThread.h
+++ b/xpcom/threads/TimerThread.h
@@ -63,17 +63,17 @@ private:
   // These internal helper methods must be called while mMonitor is held.
   // AddTimerInternal returns false if the insertion failed.
   bool    AddTimerInternal(nsTimerImpl* aTimer);
   bool    RemoveTimerInternal(nsTimerImpl* aTimer);
   void    RemoveLeadingCanceledTimersInternal();
   void    RemoveFirstTimerInternal();
   nsresult Init();
 
-  already_AddRefed<nsTimerImpl> PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef);
+  void PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef);
 
   nsCOMPtr<nsIThread> mThread;
   Monitor mMonitor;
 
   bool mShutdown;
   bool mWaiting;
   bool mNotified;
   bool mSleeping;