ART: Dump all threads on test timeout

Use SIGRTMIN+2 as a special signal to dump all threads similar to
SIGQUIT. Use nested timeouts to enforce a test timeout and dump
the threads in a deadlock.

Bug: 18933933
Change-Id: I4209047eeca07ff360d7c19922d5b5da64fd69a5
diff --git a/runtime/runtime_linux.cc b/runtime/runtime_linux.cc
index a5a0204..a0adcd1 100644
--- a/runtime/runtime_linux.cc
+++ b/runtime/runtime_linux.cc
@@ -28,6 +28,7 @@
 #include "base/mutex.h"
 #include "base/stringprintf.h"
 #include "thread-inl.h"
+#include "thread_list.h"
 #include "utils.h"
 
 namespace art {
@@ -283,10 +284,22 @@
   mcontext_t& context;
 };
 
+static int GetTimeoutSignal() {
+  return SIGRTMIN + 2;
+}
+
+static bool IsTimeoutSignal(int signal_number) {
+  return signal_number == GetTimeoutSignal();
+}
+
 void HandleUnexpectedSignal(int signal_number, siginfo_t* info, void* raw_context) {
   static bool handlingUnexpectedSignal = false;
   if (handlingUnexpectedSignal) {
     LogMessage::LogLine(__FILE__, __LINE__, INTERNAL_FATAL, "HandleUnexpectedSignal reentered\n");
+    if (IsTimeoutSignal(signal_number)) {
+      // Ignore a recursive timeout.
+      return;
+    }
     _exit(1);
   }
   handlingUnexpectedSignal = true;
@@ -320,6 +333,10 @@
                       << "Backtrace:\n" << Dumpable<Backtrace>(thread_backtrace);
   Runtime* runtime = Runtime::Current();
   if (runtime != nullptr) {
+    if (IsTimeoutSignal(signal_number)) {
+      // Special timeout signal. Try to dump all threads.
+      runtime->GetThreadList()->DumpForSigQuit(LOG(INTERNAL_FATAL));
+    }
     gc::Heap* heap = runtime->GetHeap();
     LOG(INTERNAL_FATAL) << "Fault message: " << runtime->GetFaultMessage();
     if (kDumpHeapObjectOnSigsevg && heap != nullptr && info != nullptr) {
@@ -374,6 +391,8 @@
   rc += sigaction(SIGSTKFLT, &action, NULL);
 #endif
   rc += sigaction(SIGTRAP, &action, NULL);
+  // Special dump-all timeout.
+  rc += sigaction(GetTimeoutSignal(), &action, NULL);
   CHECK_EQ(rc, 0);
 }
 
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 5c0f83f..2710df8 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -37,7 +37,8 @@
 RELOCATE="y"
 SECONDARY_DEX=""
 TIME_OUT="y"
-TIME_OUT_VALUE=5m
+# Value in minutes.
+TIME_OUT_VALUE=5
 USE_GDB="n"
 USE_JVM="n"
 VERIFY="y"
@@ -377,7 +378,13 @@
 
     if [ "$TIME_OUT" = "y" ]; then
       # Add timeout command if time out is desired.
-      cmdline="timeout $TIME_OUT_VALUE $cmdline"
+      #
+      # Note: We use nested timeouts. The inner timeout sends SIGRTMIN+2 (usually 36) to ART, which
+      #       will induce a full thread dump before abort. However, dumping threads might deadlock,
+      #       so the outer timeout sends the regular SIGTERM after an additional minute to ensure
+      #       termination (without dumping all threads).
+      TIME_PLUS_ONE=$(($TIME_OUT_VALUE + 1))
+      cmdline="timeout ${TIME_PLUS_ONE}m timeout -s SIGRTMIN+2 ${TIME_OUT_VALUE}m $cmdline"
     fi
 
     if [ "$DEV_MODE" = "y" ]; then