github.com/safing/portbase@v0.19.5/modules/stop.go (about)

     1  package modules
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/tevino/abool"
     8  
     9  	"github.com/safing/portbase/log"
    10  )
    11  
    12  var (
    13  	shutdownSignal = make(chan struct{})
    14  	shutdownFlag   = abool.NewBool(false)
    15  
    16  	shutdownCompleteSignal = make(chan struct{})
    17  
    18  	globalShutdownFn func()
    19  )
    20  
    21  // SetGlobalShutdownFn sets a global shutdown function that is called first when shutting down.
    22  func SetGlobalShutdownFn(fn func()) {
    23  	if globalShutdownFn == nil {
    24  		globalShutdownFn = fn
    25  	}
    26  }
    27  
    28  // IsShuttingDown returns whether the global shutdown is in progress.
    29  func IsShuttingDown() bool {
    30  	return shutdownFlag.IsSet()
    31  }
    32  
    33  // ShuttingDown returns a channel read on the global shutdown signal.
    34  func ShuttingDown() <-chan struct{} {
    35  	return shutdownSignal
    36  }
    37  
    38  // Shutdown stops all modules in the correct order.
    39  func Shutdown() error {
    40  	// lock mgmt
    41  	mgmtLock.Lock()
    42  	defer mgmtLock.Unlock()
    43  
    44  	if shutdownFlag.SetToIf(false, true) {
    45  		close(shutdownSignal)
    46  	} else {
    47  		// shutdown was already issued
    48  		return errors.New("shutdown already initiated")
    49  	}
    50  
    51  	// Execute global shutdown function.
    52  	if globalShutdownFn != nil {
    53  		globalShutdownFn()
    54  	}
    55  
    56  	if initialStartCompleted.IsSet() {
    57  		log.Warning("modules: starting shutdown...")
    58  	} else {
    59  		log.Warning("modules: aborting, shutting down...")
    60  	}
    61  
    62  	err := stopModules()
    63  	if err != nil {
    64  		log.Errorf("modules: shutdown completed with error: %s", err)
    65  	} else {
    66  		log.Info("modules: shutdown completed")
    67  	}
    68  
    69  	log.Shutdown()
    70  	close(shutdownCompleteSignal)
    71  	return err
    72  }
    73  
    74  func stopModules() error {
    75  	var rep *report
    76  	var lastErr error
    77  	reports := make(chan *report)
    78  	execCnt := 0
    79  	reportCnt := 0
    80  
    81  	// get number of started modules
    82  	startedCnt := 0
    83  	for _, m := range modules {
    84  		if m.Status() >= StatusStarting {
    85  			startedCnt++
    86  		}
    87  	}
    88  
    89  	for {
    90  		waiting := 0
    91  
    92  		// find modules to exec
    93  		for _, m := range modules {
    94  			switch m.readyToStop() {
    95  			case statusNothingToDo:
    96  			case statusWaiting:
    97  				waiting++
    98  			case statusReady:
    99  				execCnt++
   100  				m.stop(reports)
   101  			}
   102  		}
   103  
   104  		if reportCnt < execCnt {
   105  			// wait for reports
   106  			rep = <-reports
   107  			if rep.err != nil {
   108  				lastErr = rep.err
   109  				rep.module.NewErrorMessage("stop module", rep.err).Report()
   110  				log.Warningf("modules: could not stop module %s: %s", rep.module.Name, rep.err)
   111  			}
   112  			reportCnt++
   113  			log.Infof("modules: stopped %s", rep.module.Name)
   114  		} else {
   115  			// finished
   116  			if waiting > 0 {
   117  				// check for dep loop
   118  				return fmt.Errorf("modules: dependency loop detected, cannot continue")
   119  			}
   120  			// return last error
   121  			return lastErr
   122  		}
   123  	}
   124  }