diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..73f69e0
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/interrupt.iml b/.idea/interrupt.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/interrupt.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..cc2f1ed
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/ctrlctest.go b/cmd/ctrlctest.go
new file mode 100644
index 0000000..32fc33b
--- /dev/null
+++ b/cmd/ctrlctest.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/p9c/pod/pkg/util/interrupt"
+)
+
+func main() {
+ interrupt.AddHandler(func() {
+ fmt.Println("IT'S THE END OF THE WORLD!")
+ })
+ <-interrupt.HandlersDone
+}
diff --git a/log.go b/log.go
new file mode 100644
index 0000000..713b190
--- /dev/null
+++ b/log.go
@@ -0,0 +1,42 @@
+package interrupt
+
+import (
+ "github.com/p9c/pod/pkg/logg"
+)
+
+var subsystem = logg.AddLoggerSubsystem()
+var F, E, W, I, D, T logg.LevelPrinter = logg.GetLogPrinterSet(subsystem)
+
+func init() {
+ // to filter out this package, uncomment the following
+ // var _ = logg.AddFilteredSubsystem(subsystem)
+
+ // to highlight this package, uncomment the following
+ // var _ = logg.AddHighlightedSubsystem(subsystem)
+
+ // these are here to test whether they are working
+ // F.Ln("F.Ln")
+ // E.Ln("E.Ln")
+ // W.Ln("W.Ln")
+ // I.Ln("I.Ln")
+ // D.Ln("D.Ln")
+ // F.Ln("T.Ln")
+ // F.F("%s", "F.F")
+ // E.F("%s", "E.F")
+ // W.F("%s", "W.F")
+ // I.F("%s", "I.F")
+ // D.F("%s", "D.F")
+ // T.F("%s", "T.F")
+ // F.C(func() string { return "F.C" })
+ // E.C(func() string { return "E.C" })
+ // W.C(func() string { return "W.C" })
+ // I.C(func() string { return "I.C" })
+ // D.C(func() string { return "D.C" })
+ // T.C(func() string { return "T.C" })
+ // F.C(func() string { return "F.C" })
+ // E.Chk(errors.New("E.Chk"))
+ // W.Chk(errors.New("W.Chk"))
+ // I.Chk(errors.New("I.Chk"))
+ // D.Chk(errors.New("D.Chk"))
+ // T.Chk(errors.New("T.Chk"))
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..7f01ad0
--- /dev/null
+++ b/main.go
@@ -0,0 +1,187 @@
+package interrupt
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "os/signal"
+ "runtime"
+ "strings"
+ "syscall"
+
+ uberatomic "go.uber.org/atomic"
+
+ "github.com/p9c/pod/pkg/util/qu"
+
+ "github.com/kardianos/osext"
+)
+
+type HandlerWithSource struct {
+ Source string
+ Fn func()
+}
+
+var (
+ Restart bool // = true
+ requested uberatomic.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)
+)
+
+var interruptCallbacks []func()
+var interruptCallbackSources []string
+
+// Listener listens for interrupt signals, registers interrupt callbacks,
+// and responds to custom shutdown signals as required
+func Listener() {
+ invokeCallbacks := func() {
+ D.Ln(
+ "running interrupt callbacks",
+ len(interruptCallbacks),
+ strings.Repeat(" ", 48),
+ interruptCallbackSources,
+ )
+ // run handlers in LIFO order.
+ for i := range interruptCallbacks {
+ idx := len(interruptCallbacks) - 1 - i
+ D.Ln("running callback", idx, interruptCallbackSources[idx])
+ interruptCallbacks[idx]()
+ }
+ D.Ln("interrupt handlers finished")
+ HandlersDone.Q()
+ if Restart {
+ var file string
+ var e error
+ file, e = osext.Executable()
+ if e != nil {
+ E.Ln(e)
+ return
+ }
+ D.Ln("restarting")
+ if runtime.GOOS != "windows" {
+ e = syscall.Exec(file, os.Args, os.Environ())
+ if e != nil {
+ F.Ln(e)
+ }
+ } else {
+ 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:]...)
+ D.Ln("windows restart done")
+ if e = cmd.Start(); E.Chk(e) {
+ }
+ // // select{}
+ // os.Exit(0)
+ }
+ }
+ // time.Sleep(time.Second * 3)
+ // os.Exit(1)
+ // close(HandlersDone)
+ }
+out:
+ for {
+ select {
+ case sig := <-ch:
+ // if !requested {
+ // L.Printf("\r>>> received signal (%s)\n", sig)
+ D.Ln("received interrupt signal", sig)
+ requested.Store(true)
+ invokeCallbacks()
+ // pprof.Lookup("goroutine").WriteTo(os.Stderr, 2)
+ // }
+ break out
+ case <-ShutdownRequestChan.Wait():
+ // if !requested {
+ W.Ln("received shutdown request - shutting down...")
+ requested.Store(true)
+ invokeCallbacks()
+ break out
+ // }
+ case handler := <-addHandlerChan:
+ // if !requested {
+ // D.Ln("adding handler")
+ 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)
+ D.Ln("handler added by:", msg)
+ 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)
+ D.Ln("interrupt requested", f, l, requested.Load())
+ if requested.Load() {
+ D.Ln("requested again")
+ return
+ }
+ requested.Store(true)
+ ShutdownRequestChan.Q()
+ // qu.PrintChanState()
+ var ok bool
+ select {
+ case _, ok = <-ShutdownRequestChan:
+ default:
+ }
+ D.Ln("shutdownrequestchan", ok)
+ 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() {
+ Restart = true
+ D.Ln("requesting restart")
+ Request()
+}
+
+// Requested returns true if an interrupt has been requested
+func Requested() bool {
+ return requested.Load()
+}
diff --git a/sigterm.go b/sigterm.go
new file mode 100644
index 0000000..4e547cc
--- /dev/null
+++ b/sigterm.go
@@ -0,0 +1,13 @@
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package interrupt
+
+import (
+ "os"
+ "syscall"
+)
+
+func init() {
+
+ signals = []os.Signal{os.Interrupt, syscall.SIGTERM}
+}