implemented event and req
This commit is contained in:
2
pkg/utils/interrupt/README.md
Normal file
2
pkg/utils/interrupt/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# interrupt
|
||||
Handle shutdowns cleanly and enable hot reload
|
||||
153
pkg/utils/interrupt/main.go
Normal file
153
pkg/utils/interrupt/main.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// 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"
|
||||
"utils.orly/atomic"
|
||||
"utils.orly/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()
|
||||
}
|
||||
26
pkg/utils/interrupt/restart.go
Normal file
26
pkg/utils/interrupt/restart.go
Normal file
@@ -0,0 +1,26 @@
|
||||
//go:build linux
|
||||
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"lol.mleku.dev/log"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
// Restart uses syscall.Exec to restart the process. macOS and Windows are not
|
||||
// implemented, currently.
|
||||
func Restart() {
|
||||
log.D.Ln("restarting")
|
||||
file, e := osext.Executable()
|
||||
if e != nil {
|
||||
log.E.Ln(e)
|
||||
return
|
||||
}
|
||||
e = syscall.Exec(file, os.Args, os.Environ())
|
||||
if e != nil {
|
||||
log.F.Ln(e)
|
||||
}
|
||||
}
|
||||
20
pkg/utils/interrupt/restart_darwin.go
Normal file
20
pkg/utils/interrupt/restart_darwin.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package interrupt
|
||||
|
||||
func Restart() {
|
||||
// TODO: test this thing actually works!
|
||||
// log.D.Ln("doing windows restart")
|
||||
// // procAttr := new(os.ProcAttr)
|
||||
// // procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||
// // os.StartProcess(os.Args[0], os.Args[1:], procAttr)
|
||||
// var s []string
|
||||
// // s = []string{"cmd.exe", "/C", "start"}
|
||||
// s = append(s, os.Args[0])
|
||||
// // s = append(s, "--delaystart")
|
||||
// s = append(s, os.Args[1:]...)
|
||||
// cmd := exec.Command(s[0], s[1:]...)
|
||||
// log.D.Ln("windows restart done")
|
||||
// if err := cmd.Start(); log.Fail(err) {
|
||||
// }
|
||||
// // select{}
|
||||
// os.Exit(0)
|
||||
}
|
||||
20
pkg/utils/interrupt/restart_windows.go
Normal file
20
pkg/utils/interrupt/restart_windows.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package interrupt
|
||||
|
||||
func Restart() {
|
||||
// TODO: test this thing actually works!
|
||||
// log.D.Ln("doing windows restart")
|
||||
// // procAttr := new(os.ProcAttr)
|
||||
// // procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||
// // os.StartProcess(os.Args[0], os.Args[1:], procAttr)
|
||||
// var s []string
|
||||
// // s = []string{"cmd.exe", "/C", "start"}
|
||||
// s = append(s, os.Args[0])
|
||||
// // s = append(s, "--delaystart")
|
||||
// s = append(s, os.Args[1:]...)
|
||||
// cmd := exec.Command(s[0], s[1:]...)
|
||||
// log.D.Ln("windows restart done")
|
||||
// if err := cmd.Start(); log.Fail(err) {
|
||||
// }
|
||||
// // select{}
|
||||
// os.Exit(0)
|
||||
}
|
||||
12
pkg/utils/interrupt/sigterm.go
Normal file
12
pkg/utils/interrupt/sigterm.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
signals = []os.Signal{os.Interrupt, syscall.SIGTERM}
|
||||
}
|
||||
Reference in New Issue
Block a user