Bug 940737 - Monitor Compositor thread hangs using BackgroundHangMonitor; r=bsmedberg r=BenWa
authorJim Chen <nchen@mozilla.com>
Wed, 04 Dec 2013 21:24:28 -0500
changeset 158840 bed37a1c69a56149a313bddedb0648ee46095879
parent 158839 2e586efc61606321141c54d718bb96cd894642e3
child 158841 87468d6fc9361e5bce74ee4306c4538ddafd07dd
push id25763
push usercbook@mozilla.com
push dateThu, 05 Dec 2013 11:39:19 +0000
treeherdermozilla-central@056164bcce96 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, BenWa
bugs940737
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 940737 - Monitor Compositor thread hangs using BackgroundHangMonitor; r=bsmedberg r=BenWa
gfx/layers/ipc/CompositorParent.cpp
ipc/chromium/src/base/message_loop.cc
ipc/chromium/src/base/message_loop.h
ipc/chromium/src/base/message_pump_default.cc
ipc/chromium/src/base/thread.cc
ipc/chromium/src/base/thread.h
xpcom/threads/BackgroundHangMonitor.cpp
xpcom/threads/BackgroundHangMonitor.h
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -139,17 +139,27 @@ void CompositorParent::ShutDown()
 bool CompositorParent::CreateThread()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
   if (sCompositorThread || sCompositorLoop) {
     return true;
   }
   sCompositorThreadRefCount = 1;
   sCompositorThread = new Thread("Compositor");
-  if (!sCompositorThread->Start()) {
+
+  Thread::Options options;
+  /* Timeout values are powers-of-two to enable us get better data.
+     128ms is chosen for transient hangs because 8Hz should be the minimally
+     acceptable goal for Compositor responsiveness (normal goal is 60Hz). */
+  options.transient_hang_timeout = 128; // milliseconds
+  /* 8192ms is chosen for permanent hangs because it's several seconds longer
+     than the default hang timeout on major platforms (about 5 seconds). */
+  options.permanent_hang_timeout = 8192; // milliseconds
+
+  if (!sCompositorThread->StartWithOptions(options)) {
     delete sCompositorThread;
     sCompositorThread = nullptr;
     return false;
   }
   return true;
 }
 
 void CompositorParent::DestroyThread()
--- a/ipc/chromium/src/base/message_loop.cc
+++ b/ipc/chromium/src/base/message_loop.cc
@@ -94,16 +94,18 @@ MessageLoop::MessageLoop(Type type)
       id_(++message_loop_id_seq),
       nestable_tasks_allowed_(true),
       exception_restoration_(false),
       state_(NULL),
       run_depth_base_(1),
 #ifdef OS_WIN
       os_modal_loop_(false),
 #endif  // OS_WIN
+      transient_hang_timeout_(0),
+      permanent_hang_timeout_(0),
       next_sequence_num_(0) {
   DCHECK(!current()) << "should only have one message loop per thread";
   lazy_tls_ptr.Pointer()->Set(this);
   if (type_ == TYPE_MOZILLA_UI) {
     pump_ = new mozilla::ipc::MessagePump();
     return;
   }
   if (type_ == TYPE_MOZILLA_CHILD) {
--- a/ipc/chromium/src/base/message_loop.h
+++ b/ipc/chromium/src/base/message_loop.h
@@ -264,16 +264,30 @@ public:
     os_modal_loop_ = os_modal_loop;
   }
 
   bool & os_modal_loop() {
     return os_modal_loop_;
   }
 #endif  // OS_WIN
 
+  // Set the timeouts for background hang monitoring.
+  // A value of 0 indicates there is no timeout.
+  void set_hang_timeouts(uint32_t transient_timeout_ms,
+                         uint32_t permanent_timeout_ms) {
+    transient_hang_timeout_ = transient_timeout_ms;
+    permanent_hang_timeout_ = permanent_timeout_ms;
+  }
+  uint32_t transient_hang_timeout() const {
+    return transient_hang_timeout_;
+  }
+  uint32_t permanent_hang_timeout() const {
+    return permanent_hang_timeout_;
+  }
+
   //----------------------------------------------------------------------------
  protected:
   struct RunState {
     // Used to count how many Run() invocations are on the stack.
     int run_depth;
 
     // Used to record that Quit() was called, or that we should quit the pump
     // once it becomes idle.
@@ -415,16 +429,20 @@ public:
   int run_depth_base_;
 
 #if defined(OS_WIN)
   // Should be set to true before calling Windows APIs like TrackPopupMenu, etc
   // which enter a modal message loop.
   bool os_modal_loop_;
 #endif
 
+  // Timeout values for hang monitoring
+  uint32_t transient_hang_timeout_;
+  uint32_t permanent_hang_timeout_;
+
   // The next sequence number to use for delayed tasks.
   int next_sequence_num_;
 
   DISALLOW_COPY_AND_ASSIGN(MessageLoop);
 };
 
 //-----------------------------------------------------------------------------
 // MessageLoopForUI extends MessageLoop with methods that are particular to a
--- a/ipc/chromium/src/base/message_pump_default.cc
+++ b/ipc/chromium/src/base/message_pump_default.cc
@@ -1,55 +1,69 @@
 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "base/message_pump_default.h"
 
 #include "base/logging.h"
+#include "base/message_loop.h"
 #include "base/scoped_nsautorelease_pool.h"
 #include "GeckoProfiler.h"
 
+#include "mozilla/BackgroundHangMonitor.h"
+
 namespace base {
 
 MessagePumpDefault::MessagePumpDefault()
     : keep_running_(true),
       event_(false, false) {
 }
 
 void MessagePumpDefault::Run(Delegate* delegate) {
   DCHECK(keep_running_) << "Quit must have been called outside of Run!";
 
+  const MessageLoop* const loop = MessageLoop::current();
+  mozilla::BackgroundHangMonitor hangMonitor(
+    loop->thread_name().c_str(),
+    loop->transient_hang_timeout(),
+    loop->permanent_hang_timeout());
+
   for (;;) {
     ScopedNSAutoreleasePool autorelease_pool;
 
+    hangMonitor.NotifyActivity();
     bool did_work = delegate->DoWork();
     if (!keep_running_)
       break;
 
+    hangMonitor.NotifyActivity();
     did_work |= delegate->DoDelayedWork(&delayed_work_time_);
     if (!keep_running_)
       break;
 
     if (did_work)
       continue;
 
+    hangMonitor.NotifyActivity();
     did_work = delegate->DoIdleWork();
     if (!keep_running_)
       break;
 
     if (did_work)
       continue;
 
     if (delayed_work_time_.is_null()) {
+      hangMonitor.NotifyWait();
       PROFILER_LABEL("MessagePump", "Wait");
       event_.Wait();
     } else {
       TimeDelta delay = delayed_work_time_ - TimeTicks::Now();
       if (delay > TimeDelta()) {
+        hangMonitor.NotifyWait();
         PROFILER_LABEL("MessagePump", "Wait");
         event_.TimedWait(delay);
       } else {
         // It looks like delayed_work_time_ indicates a time in the past, so we
         // need to call DoDelayedWork now.
         delayed_work_time_ = TimeTicks();
       }
     }
--- a/ipc/chromium/src/base/thread.cc
+++ b/ipc/chromium/src/base/thread.cc
@@ -142,16 +142,18 @@ void Thread::ThreadMain() {
 
   // The message loop for this thread.
   MessageLoop message_loop(startup_data_->options.message_loop_type);
 
   // Complete the initialization of our Thread object.
   thread_id_ = PlatformThread::CurrentId();
   PlatformThread::SetName(name_.c_str());
   message_loop.set_thread_name(name_);
+  message_loop.set_hang_timeouts(startup_data_->options.transient_hang_timeout,
+                                 startup_data_->options.permanent_hang_timeout);
   message_loop_ = &message_loop;
 
   // Let the thread do extra initialization.
   // Let's do this before signaling we are started.
   Init();
 
   startup_data_->event.Signal();
   // startup_data_ can't be touched anymore since the starting thread is now
--- a/ipc/chromium/src/base/thread.h
+++ b/ipc/chromium/src/base/thread.h
@@ -1,15 +1,16 @@
 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #ifndef BASE_THREAD_H_
 #define BASE_THREAD_H_
 
+#include <stdint.h>
 #include <string>
 
 #include "base/message_loop.h"
 #include "base/platform_thread.h"
 
 namespace base {
 
 // A simple thread abstraction that establishes a MessageLoop on a new thread.
@@ -23,19 +24,31 @@ class Thread : PlatformThread::Delegate 
     // Specifies the type of message loop that will be allocated on the thread.
     MessageLoop::Type message_loop_type;
 
     // Specifies the maximum stack size that the thread is allowed to use.
     // This does not necessarily correspond to the thread's initial stack size.
     // A value of 0 indicates that the default maximum should be used.
     size_t stack_size;
 
-    Options() : message_loop_type(MessageLoop::TYPE_DEFAULT), stack_size(0) {}
+    // Specifies the transient and permanent hang timeouts for background hang
+    // monitoring. A value of 0 indicates there is no timeout.
+    uint32_t transient_hang_timeout;
+    uint32_t permanent_hang_timeout;
+
+    Options()
+        : message_loop_type(MessageLoop::TYPE_DEFAULT)
+        , stack_size(0)
+        , transient_hang_timeout(0)
+        , permanent_hang_timeout(0) {}
     Options(MessageLoop::Type type, size_t size)
-        : message_loop_type(type), stack_size(size) {}
+        : message_loop_type(type)
+        , stack_size(size)
+        , transient_hang_timeout(0)
+        , permanent_hang_timeout(0) {}
   };
 
   // Constructor.
   // name is a display string to identify the thread.
   explicit Thread(const char *name);
 
   // Destroys the thread, stopping it if necessary.
   //
--- a/xpcom/threads/BackgroundHangMonitor.cpp
+++ b/xpcom/threads/BackgroundHangMonitor.cpp
@@ -296,18 +296,22 @@ BackgroundHangManager::RunMonitorThread(
 }
 
 
 BackgroundHangThread::BackgroundHangThread(const char* aName,
                                            uint32_t aTimeoutMs,
                                            uint32_t aMaxTimeoutMs)
   : mManager(BackgroundHangManager::sInstance)
   , mThreadID(PR_GetCurrentThread())
-  , mTimeout(PR_MillisecondsToInterval(aTimeoutMs))
-  , mMaxTimeout(PR_MillisecondsToInterval(aMaxTimeoutMs))
+  , mTimeout(aTimeoutMs == BackgroundHangMonitor::kNoTimeout
+             ? PR_INTERVAL_NO_TIMEOUT
+             : PR_MillisecondsToInterval(aTimeoutMs))
+  , mMaxTimeout(aMaxTimeoutMs == BackgroundHangMonitor::kNoTimeout
+                ? PR_INTERVAL_NO_TIMEOUT
+                : PR_MillisecondsToInterval(aMaxTimeoutMs))
   , mInterval(mManager->mIntervalNow)
   , mHangStart(mInterval)
   , mHanging(false)
   , mWaiting(true)
   , mStats(aName)
 {
   if (sTlsKey.initialized()) {
     sTlsKey.set(this);
--- a/xpcom/threads/BackgroundHangMonitor.h
+++ b/xpcom/threads/BackgroundHangMonitor.h
@@ -101,16 +101,18 @@ class BackgroundHangThread;
  *
  */
 class BackgroundHangMonitor
 {
 private:
   RefPtr<BackgroundHangThread> mThread;
 
 public:
+  static const uint32_t kNoTimeout = 0;
+
   /**
    * ThreadHangStatsIterator is used to iterate through the ThreadHangStats
    * associated with each active monitored thread. Because of an internal
    * lock while this object is alive, a thread must use only one instance
    * of this class at a time and must iterate through the list as fast as
    * possible. The following example shows using the iterator:
    *
    * {