@@ -24,6 +24,7 @@ import (
2424 "net/http"
2525 "sort"
2626 "strconv"
27+ "strings"
2728 "sync"
2829 "sync/atomic"
2930 "time"
@@ -175,6 +176,8 @@ type SubmitQueue struct {
175176 health submitQueueHealth
176177 healthHistory []healthRecord
177178
179+ emergencyMergeStopFlag int32
180+
178181 adminPort int
179182}
180183
@@ -194,10 +197,38 @@ func init() {
194197}
195198
196199// Name is the name usable in --pr-mungers
197- func (sq SubmitQueue ) Name () string { return "submit-queue" }
200+ func (sq * SubmitQueue ) Name () string { return "submit-queue" }
198201
199202// RequiredFeatures is a slice of 'features' that must be provided
200- func (sq SubmitQueue ) RequiredFeatures () []string { return []string {} }
203+ func (sq * SubmitQueue ) RequiredFeatures () []string { return []string {} }
204+
205+ func (sq * SubmitQueue ) emergencyMergeStop () bool {
206+ return atomic .LoadInt32 (& sq .emergencyMergeStopFlag ) != 0
207+ }
208+
209+ func (sq * SubmitQueue ) setEmergencyMergeStop (stopMerges bool ) {
210+ if stopMerges {
211+ atomic .StoreInt32 (& sq .emergencyMergeStopFlag , 1 )
212+ } else {
213+ atomic .StoreInt32 (& sq .emergencyMergeStopFlag , 0 )
214+ }
215+ }
216+
217+ // EmergencyStopHTTP sets the emergency stop flag. It expects the path of
218+ // req.URL to contain either "emergency/stop", "emergency/resume", or "emergency/status".
219+ func (sq * SubmitQueue ) EmergencyStopHTTP (res http.ResponseWriter , req * http.Request ) {
220+ switch {
221+ case strings .Contains (req .URL .Path , "emergency/stop" ):
222+ sq .setEmergencyMergeStop (true )
223+ case strings .Contains (req .URL .Path , "emergency/resume" ):
224+ sq .setEmergencyMergeStop (false )
225+ case strings .Contains (req .URL .Path , "emergency/status" ):
226+ default :
227+ http .NotFound (res , req )
228+ return
229+ }
230+ sq .serve (sq .marshal (struct { EmergencyInProgress bool }{sq .emergencyMergeStop ()}), res , req )
231+ }
201232
202233func round (num float64 ) int {
203234 return int (num + math .Copysign (0.5 , num ))
@@ -353,6 +384,10 @@ func (sq *SubmitQueue) internalInitialize(config *github.Config, features *featu
353384 go http .ListenAndServe (config .Address , nil )
354385 }
355386
387+ admin .Mux .HandleFunc ("/api/emergency/stop" , sq .EmergencyStopHTTP )
388+ admin .Mux .HandleFunc ("/api/emergency/resume" , sq .EmergencyStopHTTP )
389+ admin .Mux .HandleFunc ("/api/emergency/status" , sq .EmergencyStopHTTP )
390+
356391 if sq .githubE2EPollTime == 0 {
357392 sq .githubE2EPollTime = githubE2EPollTime
358393 }
@@ -431,21 +466,26 @@ func (sq *SubmitQueue) updateHealth() {
431466 }
432467 // Make the current record
433468 stable , _ := sq .e2e .GCSBasedStable ()
469+ emergencyStop := sq .emergencyMergeStop ()
434470 newEntry := healthRecord {
435471 Time : time .Now (),
436- Overall : stable ,
472+ Overall : stable && ! emergencyStop ,
437473 Jobs : map [string ]bool {},
438474 }
439475 for job , status := range sq .e2e .GetBuildStatus () {
440476 // Ignore flakes.
441477 newEntry .Jobs [job ] = status .Status != "Not Stable"
442478 }
479+ if emergencyStop {
480+ // invent an "emergency stop" job that's failing.
481+ newEntry .Jobs ["Emergency Stop" ] = false
482+ }
443483 sq .healthHistory = append (sq .healthHistory , newEntry )
444484 // Now compute the health structure so we don't have to do it on page load
445485 sq .health .TotalLoops = len (sq .healthHistory )
446486 sq .health .NumStable = 0
447487 sq .health .NumStablePerJob = map [string ]int {}
448- sq .health .MergePossibleNow = stable
488+ sq .health .MergePossibleNow = stable && ! emergencyStop
449489 for _ , record := range sq .healthHistory {
450490 if record .Overall {
451491 sq .health .NumStable += 1
@@ -466,6 +506,9 @@ func (sq *SubmitQueue) e2eStable(aboutToMerge bool) bool {
466506 wentUnstable := false
467507
468508 stable , ignorableFlakes := sq .e2e .GCSBasedStable ()
509+ if stable && sq .emergencyMergeStop () {
510+ stable = false
511+ }
469512
470513 weakStable := sq .e2e .GCSWeakStable ()
471514 if ! weakStable {
0 commit comments