Bug 1016441 - Switch to using real-time signal in ThreadStackHelper; r=snorp
authorJim Chen <nchen@mozilla.com>
Tue, 08 Jul 2014 14:53:17 -0400
changeset 214884 cea3a5af46287dea938e18052305059134263b2d
parent 214883 20479ebf9be80a9d98cdbc4f527b390f59717d20
child 214885 67001e68b078c85111a876e48f3049540de424a6
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1016441
milestone33.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 1016441 - Switch to using real-time signal in ThreadStackHelper; r=snorp
toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js
toolkit/components/telemetry/tests/unit/xpcshell.ini
xpcom/threads/ThreadStackHelper.cpp
xpcom/threads/ThreadStackHelper.h
--- a/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js
+++ b/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js
@@ -15,16 +15,27 @@ function run_test() {
   // We disable hang reporting in several situations (e.g. debug builds,
   // official releases). In those cases, we don't have hang stats available
   // and should exit the test early.
   if (!startHangs) {
     ok("Hang reporting not enabled.");
     return;
   }
 
+  if (Services.appinfo.OS === 'Linux' || Services.appinfo.OS === 'Android') {
+    // We use the rt_tgsigqueueinfo syscall on Linux which requires a
+    // certain kernel version. It's not an error if the system running
+    // the test is older than that.
+    let kernel = Services.sysinfo.kernel_version || Services.sysinfo.version;
+    if (Services.vc.compare(kernel, '2.6.31') < 0) {
+      ok("Hang reporting not supported for old kernel.");
+      return;
+    }
+  }
+
   // Run two events in the event loop:
   // the first event causes a hang;
   // the second event checks results from the first event.
 
   do_execute_soon(() => {
     // Cause a hang lasting 1 second.
     let startTime = Date.now();
     while ((Date.now() - startTime) < 1000) {
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -11,8 +11,10 @@ tail =
 # fail-if = os == "android"
 [test_TelemetryPing_idle.js]
 [test_TelemetryStopwatch.js]
 [test_TelemetryPingBuildID.js]
 [test_ThirdPartyCookieProbe.js]
 [test_TelemetrySendOldPings.js]
 skip-if = debug == true || os == "android" # Disabled due to intermittent orange on Android
 [test_ThreadHangStats.js]
+# Bug 1032996: test fails consistently in Android x86 emulator
+skip-if = os == "android" && processor == "x86"
--- a/xpcom/threads/ThreadStackHelper.cpp
+++ b/xpcom/threads/ThreadStackHelper.cpp
@@ -22,139 +22,125 @@
 #include <unistd.h>
 #include <sys/syscall.h>
 #endif
 
 #ifdef ANDROID
 #ifndef SYS_gettid
 #define SYS_gettid __NR_gettid
 #endif
-#ifndef SYS_tgkill
-#define SYS_tgkill __NR_tgkill
+#if defined(__arm__) && !defined(__NR_rt_tgsigqueueinfo)
+// Some NDKs don't define this constant even though the kernel supports it.
+#define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363)
+#endif
+#ifndef SYS_rt_tgsigqueueinfo
+#define SYS_rt_tgsigqueueinfo __NR_rt_tgsigqueueinfo
 #endif
 #endif
 
 namespace mozilla {
 
 void
 ThreadStackHelper::Startup()
 {
 #if defined(XP_LINUX)
   MOZ_ASSERT(NS_IsMainThread());
   if (!sInitialized) {
-    MOZ_ALWAYS_TRUE(!::sem_init(&sSem, 0, 0));
+    // TODO: centralize signal number allocation
+    sFillStackSignum = SIGRTMIN + 4;
+    if (sFillStackSignum > SIGRTMAX) {
+      // Leave uninitialized
+      MOZ_ASSERT(false);
+      return;
+    }
+    struct sigaction sigact = {};
+    sigact.sa_sigaction = FillStackHandler;
+    sigemptyset(&sigact.sa_mask);
+    sigact.sa_flags = SA_SIGINFO | SA_RESTART;
+    MOZ_ALWAYS_TRUE(!::sigaction(sFillStackSignum, &sigact, nullptr));
   }
   sInitialized++;
 #endif
 }
 
 void
 ThreadStackHelper::Shutdown()
 {
 #if defined(XP_LINUX)
   MOZ_ASSERT(NS_IsMainThread());
   if (sInitialized == 1) {
-    MOZ_ALWAYS_TRUE(!::sem_destroy(&sSem));
+    struct sigaction sigact = {};
+    sigact.sa_handler = SIG_DFL;
+    MOZ_ALWAYS_TRUE(!::sigaction(sFillStackSignum, &sigact, nullptr));
   }
   sInitialized--;
 #endif
 }
 
 ThreadStackHelper::ThreadStackHelper()
   :
 #ifdef MOZ_ENABLE_PROFILER_SPS
     mPseudoStack(mozilla_get_pseudo_stack()),
 #endif
     mStackToFill(nullptr)
   , mMaxStackSize(Stack::sMaxInlineStorage)
   , mMaxBufferSize(0)
 {
 #if defined(XP_LINUX)
+  MOZ_ALWAYS_TRUE(!::sem_init(&mSem, 0, 0));
   mThreadID = ::syscall(SYS_gettid);
 #elif defined(XP_WIN)
   mInitialized = !!::DuplicateHandle(
     ::GetCurrentProcess(), ::GetCurrentThread(),
     ::GetCurrentProcess(), &mThreadID,
     THREAD_SUSPEND_RESUME, FALSE, 0);
   MOZ_ASSERT(mInitialized);
 #elif defined(XP_MACOSX)
   mThreadID = mach_thread_self();
 #endif
 }
 
 ThreadStackHelper::~ThreadStackHelper()
 {
-#if defined(XP_WIN)
+#if defined(XP_LINUX)
+  MOZ_ALWAYS_TRUE(!::sem_destroy(&mSem));
+#elif defined(XP_WIN)
   if (mInitialized) {
     MOZ_ALWAYS_TRUE(!!::CloseHandle(mThreadID));
   }
 #endif
 }
 
-#if defined(XP_LINUX) && defined(__arm__)
-// Some (old) Linux kernels on ARM have a bug where a signal handler
-// can be called without clearing the IT bits in CPSR first. The result
-// is that the first few instructions of the handler could be skipped,
-// ultimately resulting in crashes. To workaround this bug, the handler
-// on ARM is a trampoline that starts with enough NOP instructions, so
-// that even if the IT bits are not cleared, only the NOP instructions
-// will be skipped over.
-
-template <void (*H)(int, siginfo_t*, void*)>
-__attribute__((naked)) void
-SignalTrampoline(int aSignal, siginfo_t* aInfo, void* aContext)
-{
-  asm volatile (
-    "nop; nop; nop; nop"
-    : : : "memory");
-
-  // Because the assembler may generate additional insturctions below, we
-  // need to ensure NOPs are inserted first by separating them out above.
-
-  asm volatile (
-    "bx %0"
-    :
-    : "r"(H), "l"(aSignal), "l"(aInfo), "l"(aContext)
-    : "memory");
-}
-#endif // XP_LINUX && __arm__
-
 void
 ThreadStackHelper::GetStack(Stack& aStack)
 {
   // Always run PrepareStackBuffer first to clear aStack
   if (!PrepareStackBuffer(aStack)) {
     // Skip and return empty aStack
     return;
   }
 
 #if defined(XP_LINUX)
-  if (profiler_is_active()) {
-    // Profiler can interfere with our Linux signal handling
-    return;
-  }
   if (!sInitialized) {
     MOZ_ASSERT(false);
     return;
   }
-  sCurrent = this;
-  struct sigaction sigact = {};
-#ifdef __arm__
-  sigact.sa_sigaction = SignalTrampoline<SigAction>;
-#else
-  sigact.sa_sigaction = SigAction;
-#endif
-  sigemptyset(&sigact.sa_mask);
-  sigact.sa_flags = SA_SIGINFO | SA_RESTART;
-  if (::sigaction(SIGPROF, &sigact, &sOldSigAction)) {
-    MOZ_ASSERT(false);
+  siginfo_t uinfo = {};
+  uinfo.si_signo = sFillStackSignum;
+  uinfo.si_code = SI_QUEUE;
+  uinfo.si_pid = getpid();
+  uinfo.si_uid = getuid();
+  uinfo.si_value.sival_ptr = this;
+  if (::syscall(SYS_rt_tgsigqueueinfo, uinfo.si_pid,
+                mThreadID, sFillStackSignum, &uinfo)) {
+    // rt_tgsigqueueinfo was added in Linux 2.6.31.
+    // Could have failed because the syscall did not exist.
     return;
   }
-  MOZ_ALWAYS_TRUE(!::syscall(SYS_tgkill, getpid(), mThreadID, SIGPROF));
-  MOZ_ALWAYS_TRUE(!::sem_wait(&sSem));
+  MOZ_ALWAYS_TRUE(!::sem_wait(&mSem));
 
 #elif defined(XP_WIN)
   if (!mInitialized) {
     MOZ_ASSERT(false);
     return;
   }
   if (::SuspendThread(mThreadID) == DWORD(-1)) {
     MOZ_ASSERT(false);
@@ -172,27 +158,26 @@ ThreadStackHelper::GetStack(Stack& aStac
   MOZ_ALWAYS_TRUE(::thread_resume(mThreadID) == KERN_SUCCESS);
 
 #endif
 }
 
 #ifdef XP_LINUX
 
 int ThreadStackHelper::sInitialized;
-sem_t ThreadStackHelper::sSem;
-struct sigaction ThreadStackHelper::sOldSigAction;
-ThreadStackHelper* ThreadStackHelper::sCurrent;
+int ThreadStackHelper::sFillStackSignum;
 
 void
-ThreadStackHelper::SigAction(int aSignal, siginfo_t* aInfo, void* aContext)
+ThreadStackHelper::FillStackHandler(int aSignal, siginfo_t* aInfo,
+                                    void* aContext)
 {
-  ::sigaction(SIGPROF, &sOldSigAction, nullptr);
-  sCurrent->FillStackBuffer();
-  sCurrent = nullptr;
-  ::sem_post(&sSem);
+  ThreadStackHelper* const helper =
+    reinterpret_cast<ThreadStackHelper*>(aInfo->si_value.sival_ptr);
+  helper->FillStackBuffer();
+  ::sem_post(&helper->mSem);
 }
 
 #endif // XP_LINUX
 
 bool
 ThreadStackHelper::PrepareStackBuffer(Stack& aStack)
 {
   // Return false to skip getting the stack and return an empty stack
--- a/xpcom/threads/ThreadStackHelper.h
+++ b/xpcom/threads/ThreadStackHelper.h
@@ -79,22 +79,21 @@ public:
    *
    * @param aStack Stack instance to be filled.
    */
   void GetStack(Stack& aStack);
 
 #if defined(XP_LINUX)
 private:
   static int sInitialized;
-  static sem_t sSem;
-  static struct sigaction sOldSigAction;
-  static ThreadStackHelper* sCurrent;
+  static int sFillStackSignum;
 
-  static void SigAction(int aSignal, siginfo_t* aInfo, void* aContext);
+  static void FillStackHandler(int aSignal, siginfo_t* aInfo, void* aContext);
 
+  sem_t mSem;
   pid_t mThreadID;
 
 #elif defined(XP_WIN)
 private:
   bool mInitialized;
   HANDLE mThreadID;
 
 #elif defined(XP_MACOSX)