Replaced legacy `*.orly` module imports with `next.orly.dev/pkg` paths across the codebase for consistency. Removed legacy `go.mod` files from sub-packages, consolidating dependency management. Added Dockerfiles and configurations for benchmarking environments.
154 lines
3.7 KiB
Go
154 lines
3.7 KiB
Go
// Package interrupt is a library for providing handling for Ctrl-C/Interrupt
|
|
// handling and triggering callbacks for such things as closing files, flushing
|
|
// buffers, and other elements of graceful shutdowns.
|
|
package interrupt
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
|
|
"lol.mleku.dev/log"
|
|
"next.orly.dev/pkg/utils/atomic"
|
|
"next.orly.dev/pkg/utils/qu"
|
|
)
|
|
|
|
// HandlerWithSource is an interrupt handling closure and the source location
|
|
// that it was sent from.
|
|
type HandlerWithSource struct {
|
|
Source string
|
|
Fn func()
|
|
}
|
|
|
|
var (
|
|
// RestartRequested is set true after restart is requested.
|
|
RestartRequested bool // = true
|
|
requested atomic.Bool
|
|
|
|
// ch is used to receive SIGINT (Ctrl+C) signals.
|
|
ch chan os.Signal
|
|
|
|
// signals is the list of signals that cause the interrupt
|
|
signals = []os.Signal{os.Interrupt}
|
|
|
|
// ShutdownRequestChan is a channel that can receive shutdown requests
|
|
ShutdownRequestChan = qu.T()
|
|
|
|
// addHandlerChan is used to add an interrupt handler to the list of
|
|
// handlers to be invoked on SIGINT (Ctrl+C) signals.
|
|
addHandlerChan = make(chan HandlerWithSource)
|
|
|
|
// HandlersDone is closed after all interrupt handlers run the first time an
|
|
// interrupt is signaled.
|
|
HandlersDone = make(qu.C)
|
|
|
|
interruptCallbacks []func()
|
|
interruptCallbackSources []string
|
|
)
|
|
|
|
// Listener listens for interrupt signals, registers interrupt callbacks, and
|
|
// responds to custom shutdown signals as required
|
|
func Listener() {
|
|
invokeCallbacks := func() {
|
|
// run handlers in LIFO order.
|
|
for i := range interruptCallbacks {
|
|
idx := len(interruptCallbacks) - 1 - i
|
|
log.T.F(
|
|
"running callback %d from %s", idx,
|
|
interruptCallbackSources[idx],
|
|
)
|
|
interruptCallbacks[idx]()
|
|
}
|
|
log.D.Ln("interrupt handlers finished")
|
|
HandlersDone.Q()
|
|
if RestartRequested {
|
|
Restart()
|
|
} else {
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
out:
|
|
for {
|
|
select {
|
|
case _ = <-ch:
|
|
fmt.Fprintf(os.Stderr, "\r")
|
|
requested.Store(true)
|
|
invokeCallbacks()
|
|
break out
|
|
|
|
case <-ShutdownRequestChan.Wait():
|
|
log.W.Ln("received shutdown request - shutting down...")
|
|
requested.Store(true)
|
|
invokeCallbacks()
|
|
break out
|
|
|
|
case handler := <-addHandlerChan:
|
|
interruptCallbacks = append(interruptCallbacks, handler.Fn)
|
|
interruptCallbackSources = append(
|
|
interruptCallbackSources,
|
|
handler.Source,
|
|
)
|
|
|
|
case <-HandlersDone.Wait():
|
|
break out
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddHandler adds a handler to call when a SIGINT (Ctrl+C) is received.
|
|
func AddHandler(handler func()) {
|
|
// Create the channel and start the main interrupt handler which invokes all
|
|
// other callbacks and exits if not already done.
|
|
_, loc, line, _ := runtime.Caller(1)
|
|
msg := fmt.Sprintf("%s:%d", loc, line)
|
|
if ch == nil {
|
|
ch = make(chan os.Signal)
|
|
signal.Notify(ch, signals...)
|
|
go Listener()
|
|
}
|
|
addHandlerChan <- HandlerWithSource{
|
|
msg, handler,
|
|
}
|
|
}
|
|
|
|
// Request programmatically requests a shutdown
|
|
func Request() {
|
|
_, f, l, _ := runtime.Caller(1)
|
|
log.D.Ln("interrupt requested", f, l, requested.Load())
|
|
if requested.Load() {
|
|
log.D.Ln("requested again")
|
|
return
|
|
}
|
|
requested.Store(true)
|
|
ShutdownRequestChan.Q()
|
|
var ok bool
|
|
select {
|
|
case _, ok = <-ShutdownRequestChan:
|
|
default:
|
|
}
|
|
if ok {
|
|
close(ShutdownRequestChan)
|
|
}
|
|
}
|
|
|
|
// GoroutineDump returns a string with the current goroutine dump in order to
|
|
// show what's going on in case of timeout.
|
|
func GoroutineDump() string {
|
|
buf := make([]byte, 1<<18)
|
|
n := runtime.Stack(buf, true)
|
|
return string(buf[:n])
|
|
}
|
|
|
|
// RequestRestart sets the reset flag and requests a restart
|
|
func RequestRestart() {
|
|
RestartRequested = true
|
|
log.D.Ln("requesting restart")
|
|
Request()
|
|
}
|
|
|
|
// Requested returns true if an interrupt has been requested
|
|
func Requested() bool {
|
|
return requested.Load()
|
|
}
|