Merge "Cancel jobs behind a failed change."
diff --git a/zuul/model.py b/zuul/model.py
index 86fdc5c..f95195d 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -489,6 +489,14 @@
                 return False
         return True
 
+    def didAnyJobFail(self):
+        tree = self.project.getJobTreeForQueue(self.queue_name)
+        for job in self._filterJobs(tree.getJobs()):
+            build = self.current_build_set.getBuild(job.name)
+            if build and build.result == 'FAILURE':
+                return True
+        return False
+
     def delete(self):
         if self.change_behind:
             self.change_behind.change_ahead = None
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 232804d..7b28dec 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -632,10 +632,11 @@
                     change.change_behind, change))
             self.launchJobs(change.change_behind)
 
-    def cancelJobs(self, change):
+    def cancelJobs(self, change, prime=True):
         self.log.debug("Cancel jobs for change %s" % change)
         to_remove = []
-        change.resetAllBuilds()
+        if prime:
+            change.resetAllBuilds()
         for build, build_change in self.building_jobs.items():
             if build_change == change:
                 self.log.debug("Found build %s for change %s to cancel" % (
@@ -653,7 +654,20 @@
         if change.change_behind:
             self.log.debug("Canceling jobs for change %s, \
 behind change %s" % (change.change_behind, change))
-            self.cancelJobs(change.change_behind)
+            self.cancelJobs(change.change_behind, prime=prime)
+
+    def onBuildCompleted(self, build):
+        change = self.building_jobs.get(build)
+        if not super(DependentQueueManager, self).onBuildCompleted(build):
+            return False
+        if change and change.didAnyJobFail():
+            # This or some other build failed. All changes behind this change
+            # will need to be retested. To free up resources cancel the builds
+            # behind this one as they will be rerun anyways.
+            self.cancelJobs(change.change_behind, prime=False)
+            self.log.debug("Canceling builds behind change: %s due to"
+                        " failure." % change)
+        return True
 
     def possiblyReportChange(self, change):
         self.log.debug("Possibly reporting change %s" % change)