Index: third_party/gtest-parallel/gtest-parallel |
diff --git a/third_party/gtest-parallel/gtest-parallel b/third_party/gtest-parallel/gtest-parallel |
index b847180939ab72feceb9fe30211d86d2c68ea453..b609ab93dc537eba7c1a97c9022d0ca9446a858c 100755 |
--- a/third_party/gtest-parallel/gtest-parallel |
+++ b/third_party/gtest-parallel/gtest-parallel |
@@ -18,13 +18,62 @@ import gzip |
import multiprocessing |
import optparse |
import os |
+import signal |
import subprocess |
import sys |
import tempfile |
+import thread |
import threading |
import time |
import zlib |
+# An object that catches SIGINT sent to the Python process and notices |
+# if processes passed to wait() die by SIGINT (we need to look for |
+# both of those cases, because pressing Ctrl+C can result in either |
+# the main process or one of the subprocesses getting the signal). |
+# |
+# Before a SIGINT is seen, wait(p) will simply call p.wait() and |
+# return the result. Once a SIGINT has been seen (in the main process |
+# or a subprocess, including the one the current call is waiting for), |
+# wait(p) will call p.terminate() and raise ProcessWasInterrupted. |
+class SigintHandler(object): |
+ class ProcessWasInterrupted(Exception): pass |
+ sigint_returncodes = {-signal.SIGINT, # Unix |
+ -1073741510, # Windows |
+ } |
+ def __init__(self): |
+ self.__lock = threading.Lock() |
+ self.__processes = set() |
+ self.__got_sigint = False |
+ signal.signal(signal.SIGINT, self.__sigint_handler) |
+ def __on_sigint(self): |
+ self.__got_sigint = True |
+ while self.__processes: |
+ try: |
+ self.__processes.pop().terminate() |
+ except OSError: |
+ pass |
+ def __sigint_handler(self, signal_num, frame): |
+ with self.__lock: |
+ self.__on_sigint() |
+ def got_sigint(self): |
+ with self.__lock: |
+ return self.__got_sigint |
+ def wait(self, p): |
+ with self.__lock: |
+ if self.__got_sigint: |
+ p.terminate() |
+ self.__processes.add(p) |
+ code = p.wait() |
+ with self.__lock: |
+ self.__processes.discard(p) |
+ if code in self.sigint_returncodes: |
+ self.__on_sigint() |
+ if self.__got_sigint: |
+ raise self.ProcessWasInterrupted |
+ return code |
+sigint_handler = SigintHandler() |
+ |
# Return the width of the terminal, or None if it couldn't be |
# determined (e.g. because we're not being run interactively). |
def term_width(out): |
@@ -301,7 +350,10 @@ def run_job((command, job_id, test)): |
['--gtest_color=' + options.gtest_color], |
stdout=log.file, |
stderr=log.file) |
- code = sub.wait() |
+ try: |
+ code = sigint_handler.wait(sub) |
+ except sigint_handler.ProcessWasInterrupted: |
+ thread.exit() |
runtime_ms = int(1000 * (time.time() - begin)) |
logger.logfile(job_id, log.name) |
@@ -344,4 +396,4 @@ if options.print_test_times: |
if times.get_test_time(test_binary, test) is not None) |
for (time_ms, test_binary, test) in ts: |
print "%8s %s" % ("%dms" % time_ms, test) |
-sys.exit(exit_code) |
+sys.exit(-signal.SIGINT if sigint_handler.got_sigint() else exit_code) |