package modules import ( "errors" "fmt" "github.com/tevino/abool" "github.com/safing/portbase/log" ) var ( shutdownSignal = make(chan struct{}) shutdownFlag = abool.NewBool(false) shutdownCompleteSignal = make(chan struct{}) globalShutdownFn func() ) // SetGlobalShutdownFn sets a global shutdown function that is called first when shutting down. func SetGlobalShutdownFn(fn func()) { if globalShutdownFn == nil { globalShutdownFn = fn } } // IsShuttingDown returns whether the global shutdown is in progress. func IsShuttingDown() bool { return shutdownFlag.IsSet() } // ShuttingDown returns a channel read on the global shutdown signal. func ShuttingDown() <-chan struct{} { return shutdownSignal } // Shutdown stops all modules in the correct order. func Shutdown() error { // lock mgmt mgmtLock.Lock() defer mgmtLock.Unlock() if shutdownFlag.SetToIf(false, true) { close(shutdownSignal) } else { // shutdown was already issued return errors.New("shutdown already initiated") } // Execute global shutdown function. if globalShutdownFn != nil { globalShutdownFn() } if initialStartCompleted.IsSet() { log.Warning("modules: starting shutdown...") } else { log.Warning("modules: aborting, shutting down...") } err := stopModules() if err != nil { log.Errorf("modules: shutdown completed with error: %s", err) } else { log.Info("modules: shutdown completed") } log.Shutdown() close(shutdownCompleteSignal) return err } func stopModules() error { var rep *report var lastErr error reports := make(chan *report) execCnt := 0 reportCnt := 0 // get number of started modules startedCnt := 0 for _, m := range modules { if m.Status() >= StatusStarting { startedCnt++ } } for { waiting := 0 // find modules to exec for _, m := range modules { switch m.readyToStop() { case statusNothingToDo: case statusWaiting: waiting++ case statusReady: execCnt++ m.stop(reports) } } if reportCnt < execCnt { // wait for reports rep = <-reports if rep.err != nil { lastErr = rep.err rep.module.NewErrorMessage("stop module", rep.err).Report() log.Warningf("modules: could not stop module %s: %s", rep.module.Name, rep.err) } reportCnt++ log.Infof("modules: stopped %s", rep.module.Name) } else { // finished if waiting > 0 { // check for dep loop return fmt.Errorf("modules: dependency loop detected, cannot continue") } // return last error return lastErr } } }