//go:build linux || darwin package proc import ( "os" "os/signal" "sync" "syscall" "time" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/threading" ) type ProcConf struct { WrapUpTime time.Duration `json:",default=1s"` WaitTime time.Duration `json:",default=5.5s"` } var ( wrapUpListeners = new(listenerManager) shutdownListeners = new(listenerManager) wrapUpTime = time.Second // why we use 5500 milliseconds is because most of our queue are blocking mode with 5 seconds delayTimeBeforeForceQuit = 5500 * time.Millisecond ) // AddShutdownListener adds fn as a shutdown listener. // The returned func can be used to wait for fn getting called. func AddShutdownListener(fn func()) (waitForCalled func()) { return shutdownListeners.addListener(fn) } // AddWrapUpListener adds fn as a wrap up listener. // The returned func can be used to wait for fn getting called. func AddWrapUpListener(fn func()) (waitForCalled func()) { return wrapUpListeners.addListener(fn) } // SetTimeToForceQuit sets the waiting time before force quitting. func SetTimeToForceQuit(duration time.Duration) { delayTimeBeforeForceQuit = duration } func Setup(conf ProcConf) { wrapUpTime = conf.WrapUpTime delayTimeBeforeForceQuit = conf.WaitTime } // Shutdown calls the registered shutdown listeners, only for test purpose. func Shutdown() { shutdownListeners.notifyListeners() } // WrapUp wraps up the process, only for test purpose. func WrapUp() { wrapUpListeners.notifyListeners() } func gracefulStop(signals chan os.Signal, sig syscall.Signal) { signal.Stop(signals) logx.Infof("Got signal %d, shutting down...", sig) go wrapUpListeners.notifyListeners() time.Sleep(wrapUpTime) go shutdownListeners.notifyListeners() time.Sleep(delayTimeBeforeForceQuit - wrapUpTime) logx.Infof("Still alive after %v, going to force kill the process...", delayTimeBeforeForceQuit) _ = syscall.Kill(syscall.Getpid(), sig) } type listenerManager struct { lock sync.Mutex waitGroup sync.WaitGroup listeners []func() } func (lm *listenerManager) addListener(fn func()) (waitForCalled func()) { lm.waitGroup.Add(1) lm.lock.Lock() lm.listeners = append(lm.listeners, func() { defer lm.waitGroup.Done() fn() }) lm.lock.Unlock() // we can return lm.waitGroup.Wait directly, // but we want to make the returned func more readable. // creating an extra closure would be negligible in practice. return func() { lm.waitGroup.Wait() } } func (lm *listenerManager) notifyListeners() { lm.lock.Lock() defer lm.lock.Unlock() group := threading.NewRoutineGroup() for _, listener := range lm.listeners { group.RunSafe(listener) } group.Wait() lm.listeners = nil }