diff --git a/tools/run_tests/jobset.py b/tools/run_tests/jobset.py
index 985b7a7f165063cc366108eab5a4612e80fbae9c..058a30d1ce28b050bae70d07b910674dbf4c949f 100755
--- a/tools/run_tests/jobset.py
+++ b/tools/run_tests/jobset.py
@@ -223,6 +223,7 @@ class Jobset(object):
     self._travis = travis
     self._cache = cache
     self._stop_on_failure = stop_on_failure
+    self._hashes = {}
 
   def start(self, spec):
     """Start a job. Return True on success, False on failure."""
@@ -231,11 +232,15 @@ class Jobset(object):
       self.reap()
     if self.cancelled(): return False
     if spec.hash_targets:
-      bin_hash = hashlib.sha1()
-      for fn in spec.hash_targets:
-        with open(which(fn)) as f:
-          bin_hash.update(f.read())
-      bin_hash = bin_hash.hexdigest()
+      if spec.identity() in self._hashes:
+        bin_hash = self._hashes[spec.identity()]
+      else:
+        bin_hash = hashlib.sha1()
+        for fn in spec.hash_targets:
+          with open(which(fn)) as f:
+            bin_hash.update(f.read())
+        bin_hash = bin_hash.hexdigest()
+        self._hashes[spec.identity()] = bin_hash
       should_run = self._cache.should_run(spec.identity(), bin_hash)
     else:
       bin_hash = None
@@ -266,6 +271,7 @@ class Jobset(object):
             for job in self._running:
               job.kill()
         dead.add(job)
+        break
       for job in dead:
         self._completed += 1
         self._running.remove(job)
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 32405675b6ba0d8b60e3d315ee650fc024223d57..ea40d7e990cc37a1203babc70afadd18683b720c 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -313,7 +313,7 @@ _CONFIGS = {
     'dbg': SimpleConfig('dbg'),
     'opt': SimpleConfig('opt'),
     'tsan': SimpleConfig('tsan', environ={
-        'TSAN_OPTIONS': 'suppressions=tools/tsan_suppressions.txt'}),
+        'TSAN_OPTIONS': 'suppressions=tools/tsan_suppressions.txt:halt_on_error=1'}),
     'msan': SimpleConfig('msan'),
     'ubsan': SimpleConfig('ubsan'),
     'asan': SimpleConfig('asan', environ={
@@ -449,6 +449,7 @@ class TestCache(object):
   def __init__(self, use_cache_results):
     self._last_successful_run = {}
     self._use_cache_results = use_cache_results
+    self._last_save = time.time()
 
   def should_run(self, cmdline, bin_hash):
     if cmdline not in self._last_successful_run:
@@ -461,7 +462,8 @@ class TestCache(object):
 
   def finished(self, cmdline, bin_hash):
     self._last_successful_run[cmdline] = bin_hash
-    self.save()
+    if time.time() - self._last_save > 1:
+      self.save()
 
   def dump(self):
     return [{'cmdline': k, 'hash': v}
@@ -473,6 +475,7 @@ class TestCache(object):
   def save(self):
     with open('.run_tests_cache', 'w') as f:
       f.write(json.dumps(self.dump()))
+    self._last_save = time.time()
 
   def maybe_load(self):
     if os.path.exists('.run_tests_cache'):
@@ -515,6 +518,8 @@ def _build_and_run(check_cancelled, newline_on_success, travis, cache):
     for antagonist in antagonists:
       antagonist.kill()
 
+  if cache: cache.save()
+
   return 0