github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/internal/interrupt_handler/interrupt_handler.go (about) 1 package interrupt_handler 2 3 import ( 4 "fmt" 5 "os" 6 "os/signal" 7 "runtime" 8 "sync" 9 "syscall" 10 "time" 11 12 "github.com/onsi/ginkgo/formatter" 13 "github.com/onsi/ginkgo/internal/parallel_support" 14 ) 15 16 const TIMEOUT_REPEAT_INTERRUPT_MAXIMUM_DURATION = 30 * time.Second 17 const TIMEOUT_REPEAT_INTERRUPT_FRACTION_OF_TIMEOUT = 10 18 const ABORT_POLLING_INTERVAL = 500 * time.Millisecond 19 const ABORT_REPEAT_INTERRUPT_DURATION = 30 * time.Second 20 21 type InterruptCause uint 22 23 const ( 24 InterruptCauseInvalid InterruptCause = iota 25 26 InterruptCauseSignal 27 InterruptCauseTimeout 28 InterruptCauseAbortByOtherProcess 29 ) 30 31 func (ic InterruptCause) String() string { 32 switch ic { 33 case InterruptCauseSignal: 34 return "Interrupted by User" 35 case InterruptCauseTimeout: 36 return "Interrupted by Timeout" 37 case InterruptCauseAbortByOtherProcess: 38 return "Interrupted by Other Ginkgo Process" 39 } 40 return "INVALID_INTERRUPT_CAUSE" 41 } 42 43 type InterruptStatus struct { 44 Interrupted bool 45 Channel chan interface{} 46 Cause InterruptCause 47 } 48 49 type InterruptHandlerInterface interface { 50 Status() InterruptStatus 51 SetInterruptPlaceholderMessage(string) 52 ClearInterruptPlaceholderMessage() 53 InterruptMessageWithStackTraces() string 54 } 55 56 type InterruptHandler struct { 57 c chan interface{} 58 lock *sync.Mutex 59 interrupted bool 60 interruptPlaceholderMessage string 61 interruptCause InterruptCause 62 client parallel_support.Client 63 stop chan interface{} 64 } 65 66 func NewInterruptHandler(timeout time.Duration, client parallel_support.Client) *InterruptHandler { 67 handler := &InterruptHandler{ 68 c: make(chan interface{}), 69 lock: &sync.Mutex{}, 70 interrupted: false, 71 stop: make(chan interface{}), 72 client: client, 73 } 74 handler.registerForInterrupts(timeout) 75 return handler 76 } 77 78 func (handler *InterruptHandler) Stop() { 79 close(handler.stop) 80 } 81 82 func (handler *InterruptHandler) registerForInterrupts(timeout time.Duration) { 83 // os signal handling 84 signalChannel := make(chan os.Signal, 1) 85 signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) 86 87 // timeout handling 88 var timeoutChannel <-chan time.Time 89 var timeoutTimer *time.Timer 90 if timeout > 0 { 91 timeoutTimer = time.NewTimer(timeout) 92 timeoutChannel = timeoutTimer.C 93 } 94 95 // cross-process abort handling 96 var abortChannel chan bool 97 if handler.client != nil { 98 abortChannel = make(chan bool) 99 go func() { 100 pollTicker := time.NewTicker(ABORT_POLLING_INTERVAL) 101 for { 102 select { 103 case <-pollTicker.C: 104 if handler.client.ShouldAbort() { 105 abortChannel <- true 106 pollTicker.Stop() 107 return 108 } 109 case <-handler.stop: 110 pollTicker.Stop() 111 return 112 } 113 } 114 }() 115 } 116 117 // listen for any interrupt signals 118 // note that some (timeouts, cross-process aborts) will only trigger once 119 // for these we set up a ticker to keep interrupting the suite until it ends 120 // this ensures any `AfterEach` or `AfterSuite`s that get stuck cleaning up 121 // get interrupted eventually 122 go func() { 123 var interruptCause InterruptCause 124 var repeatChannel <-chan time.Time 125 var repeatTicker *time.Ticker 126 for { 127 select { 128 case <-signalChannel: 129 interruptCause = InterruptCauseSignal 130 case <-timeoutChannel: 131 interruptCause = InterruptCauseTimeout 132 repeatInterruptTimeout := timeout / time.Duration(TIMEOUT_REPEAT_INTERRUPT_FRACTION_OF_TIMEOUT) 133 if repeatInterruptTimeout > TIMEOUT_REPEAT_INTERRUPT_MAXIMUM_DURATION { 134 repeatInterruptTimeout = TIMEOUT_REPEAT_INTERRUPT_MAXIMUM_DURATION 135 } 136 timeoutTimer.Stop() 137 repeatTicker = time.NewTicker(repeatInterruptTimeout) 138 repeatChannel = repeatTicker.C 139 case <-abortChannel: 140 interruptCause = InterruptCauseAbortByOtherProcess 141 repeatTicker = time.NewTicker(ABORT_REPEAT_INTERRUPT_DURATION) 142 repeatChannel = repeatTicker.C 143 case <-repeatChannel: 144 //do nothing, just interrupt again using the same interruptCause 145 case <-handler.stop: 146 if timeoutTimer != nil { 147 timeoutTimer.Stop() 148 } 149 if repeatTicker != nil { 150 repeatTicker.Stop() 151 } 152 signal.Stop(signalChannel) 153 return 154 } 155 handler.lock.Lock() 156 handler.interruptCause = interruptCause 157 if handler.interruptPlaceholderMessage != "" { 158 fmt.Println(handler.interruptPlaceholderMessage) 159 } 160 handler.interrupted = true 161 close(handler.c) 162 handler.c = make(chan interface{}) 163 handler.lock.Unlock() 164 } 165 }() 166 } 167 168 func (handler *InterruptHandler) Status() InterruptStatus { 169 handler.lock.Lock() 170 defer handler.lock.Unlock() 171 172 return InterruptStatus{ 173 Interrupted: handler.interrupted, 174 Channel: handler.c, 175 Cause: handler.interruptCause, 176 } 177 } 178 179 func (handler *InterruptHandler) SetInterruptPlaceholderMessage(message string) { 180 handler.lock.Lock() 181 defer handler.lock.Unlock() 182 183 handler.interruptPlaceholderMessage = message 184 } 185 186 func (handler *InterruptHandler) ClearInterruptPlaceholderMessage() { 187 handler.lock.Lock() 188 defer handler.lock.Unlock() 189 190 handler.interruptPlaceholderMessage = "" 191 } 192 193 func (handler *InterruptHandler) InterruptMessageWithStackTraces() string { 194 handler.lock.Lock() 195 out := fmt.Sprintf("%s\n\n", handler.interruptCause.String()) 196 defer handler.lock.Unlock() 197 if handler.interruptCause == InterruptCauseAbortByOtherProcess { 198 return out 199 } 200 out += "Here's a stack trace of all running goroutines:\n" 201 buf := make([]byte, 8192) 202 for { 203 n := runtime.Stack(buf, true) 204 if n < len(buf) { 205 buf = buf[:n] 206 break 207 } 208 buf = make([]byte, 2*len(buf)) 209 } 210 out += formatter.Fi(1, "%s", string(buf)) 211 return out 212 }