@@ -24,6 +24,7 @@ import (
2424 "net/http"
2525 "sort"
2626 "strconv"
27+ "strings"
2728 "sync"
2829 "sync/atomic"
2930 "time"
@@ -181,6 +182,8 @@ type SubmitQueue struct {
181182 health submitQueueHealth
182183 healthHistory []healthRecord
183184
185+ emergencyMergeStopFlag int32
186+
184187 adminPort int
185188}
186189
@@ -200,10 +203,38 @@ func init() {
200203}
201204
202205// Name is the name usable in --pr-mungers
203- func (sq SubmitQueue ) Name () string { return "submit-queue" }
206+ func (sq * SubmitQueue ) Name () string { return "submit-queue" }
204207
205208// RequiredFeatures is a slice of 'features' that must be provided
206- func (sq SubmitQueue ) RequiredFeatures () []string { return []string {} }
209+ func (sq * SubmitQueue ) RequiredFeatures () []string { return []string {} }
210+
211+ func (sq * SubmitQueue ) emergencyMergeStop () bool {
212+ return atomic .LoadInt32 (& sq .emergencyMergeStopFlag ) != 0
213+ }
214+
215+ func (sq * SubmitQueue ) setEmergencyMergeStop (stopMerges bool ) {
216+ if stopMerges {
217+ atomic .StoreInt32 (& sq .emergencyMergeStopFlag , 1 )
218+ } else {
219+ atomic .StoreInt32 (& sq .emergencyMergeStopFlag , 0 )
220+ }
221+ }
222+
223+ // EmergencyStopHTTP sets the emergency stop flag. It expects the path of
224+ // req.URL to contain either "emergency/stop", "emergency/resume", or "emergency/status".
225+ func (sq * SubmitQueue ) EmergencyStopHTTP (res http.ResponseWriter , req * http.Request ) {
226+ switch {
227+ case strings .Contains (req .URL .Path , "emergency/stop" ):
228+ sq .setEmergencyMergeStop (true )
229+ case strings .Contains (req .URL .Path , "emergency/resume" ):
230+ sq .setEmergencyMergeStop (false )
231+ case strings .Contains (req .URL .Path , "emergency/status" ):
232+ default :
233+ http .NotFound (res , req )
234+ return
235+ }
236+ sq .serve (sq .marshal (struct { EmergencyInProgress bool }{sq .emergencyMergeStop ()}), res , req )
237+ }
207238
208239func round (num float64 ) int {
209240 return int (num + math .Copysign (0.5 , num ))
@@ -361,6 +392,10 @@ func (sq *SubmitQueue) internalInitialize(config *github.Config, features *featu
361392 go http .ListenAndServe (config .Address , nil )
362393 }
363394
395+ admin .Mux .HandleFunc ("/api/emergency/stop" , sq .EmergencyStopHTTP )
396+ admin .Mux .HandleFunc ("/api/emergency/resume" , sq .EmergencyStopHTTP )
397+ admin .Mux .HandleFunc ("/api/emergency/status" , sq .EmergencyStopHTTP )
398+
364399 if sq .githubE2EPollTime == 0 {
365400 sq .githubE2EPollTime = githubE2EPollTime
366401 }
@@ -440,21 +475,26 @@ func (sq *SubmitQueue) updateHealth() {
440475 }
441476 // Make the current record
442477 stable , _ := sq .e2e .GCSBasedStable ()
478+ emergencyStop := sq .emergencyMergeStop ()
443479 newEntry := healthRecord {
444480 Time : time .Now (),
445- Overall : stable ,
481+ Overall : stable && ! emergencyStop ,
446482 Jobs : map [string ]bool {},
447483 }
448484 for job , status := range sq .e2e .GetBuildStatus () {
449485 // Ignore flakes.
450486 newEntry .Jobs [job ] = status .Status != "Not Stable"
451487 }
488+ if emergencyStop {
489+ // invent an "emergency stop" job that's failing.
490+ newEntry .Jobs ["Emergency Stop" ] = false
491+ }
452492 sq .healthHistory = append (sq .healthHistory , newEntry )
453493 // Now compute the health structure so we don't have to do it on page load
454494 sq .health .TotalLoops = len (sq .healthHistory )
455495 sq .health .NumStable = 0
456496 sq .health .NumStablePerJob = map [string ]int {}
457- sq .health .MergePossibleNow = stable
497+ sq .health .MergePossibleNow = stable && ! emergencyStop
458498 for _ , record := range sq .healthHistory {
459499 if record .Overall {
460500 sq .health .NumStable += 1
@@ -475,6 +515,9 @@ func (sq *SubmitQueue) e2eStable(aboutToMerge bool) bool {
475515 wentUnstable := false
476516
477517 stable , ignorableFlakes := sq .e2e .GCSBasedStable ()
518+ if stable && sq .emergencyMergeStop () {
519+ stable = false
520+ }
478521
479522 weakStable := sq .e2e .GCSWeakStable ()
480523 if ! weakStable {
0 commit comments