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

     1  package modules
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  
     8  	"github.com/tevino/abool"
     9  
    10  	"github.com/safing/portbase/log"
    11  )
    12  
    13  var (
    14  	initialStartCompleted = abool.NewBool(false)
    15  	globalPrepFn          func() error
    16  )
    17  
    18  // SetGlobalPrepFn sets a global prep function that is run before all modules.
    19  // This can be used to pre-initialize modules, such as setting the data root
    20  // or database path.
    21  func SetGlobalPrepFn(fn func() error) {
    22  	if globalPrepFn == nil {
    23  		globalPrepFn = fn
    24  	}
    25  }
    26  
    27  // IsStarting returns whether the initial global start is still in progress.
    28  func IsStarting() bool {
    29  	return !initialStartCompleted.IsSet()
    30  }
    31  
    32  // Start starts all modules in the correct order. In case of an error, it will automatically shutdown again.
    33  func Start() error {
    34  	if !modulesLocked.SetToIf(false, true) {
    35  		return errors.New("module system already started")
    36  	}
    37  
    38  	// lock mgmt
    39  	mgmtLock.Lock()
    40  	defer mgmtLock.Unlock()
    41  
    42  	// start microtask scheduler
    43  	go microTaskScheduler()
    44  
    45  	// inter-link modules
    46  	err := initDependencies()
    47  	if err != nil {
    48  		fmt.Fprintf(os.Stderr, "CRITICAL ERROR: failed to initialize modules: %s\n", err)
    49  		return err
    50  	}
    51  
    52  	// parse flags
    53  	err = parseFlags()
    54  	if err != nil {
    55  		if !errors.Is(err, ErrCleanExit) {
    56  			fmt.Fprintf(os.Stderr, "CRITICAL ERROR: failed to parse flags: %s\n", err)
    57  		}
    58  		return err
    59  	}
    60  
    61  	// execute global prep fn
    62  	if globalPrepFn != nil {
    63  		err = globalPrepFn()
    64  		if err != nil {
    65  			if !errors.Is(err, ErrCleanExit) {
    66  				fmt.Fprintf(os.Stderr, "CRITICAL ERROR: %s\n", err)
    67  			}
    68  			return err
    69  		}
    70  	}
    71  
    72  	// prep modules
    73  	err = prepareModules()
    74  	if err != nil {
    75  		if !errors.Is(err, ErrCleanExit) {
    76  			fmt.Fprintf(os.Stderr, "CRITICAL ERROR: %s\n", err)
    77  		}
    78  		return err
    79  	}
    80  
    81  	// execute command if available
    82  	if cmdLineOperation != nil {
    83  		err := cmdLineOperation()
    84  		if err != nil {
    85  			SetExitStatusCode(1)
    86  			fmt.Fprintf(os.Stderr, "cmdline operation failed: %s\n", err)
    87  		}
    88  		return ErrCleanExit
    89  	}
    90  
    91  	// start logging
    92  	log.EnableScheduling()
    93  	err = log.Start()
    94  	if err != nil {
    95  		fmt.Fprintf(os.Stderr, "CRITICAL ERROR: failed to start logging: %s\n", err)
    96  		return err
    97  	}
    98  
    99  	// build dependency tree
   100  	buildEnabledTree()
   101  
   102  	// start modules
   103  	log.Info("modules: initiating...")
   104  	err = startModules()
   105  	if err != nil {
   106  		log.Critical(err.Error())
   107  		return err
   108  	}
   109  
   110  	// complete startup
   111  	if moduleMgmtEnabled.IsSet() {
   112  		log.Info("modules: started enabled modules")
   113  	} else {
   114  		log.Infof("modules: started all %d modules", len(modules))
   115  	}
   116  
   117  	go taskQueueHandler()
   118  	go taskScheduleHandler()
   119  
   120  	initialStartCompleted.Set()
   121  	return nil
   122  }
   123  
   124  type report struct {
   125  	module *Module
   126  	err    error
   127  }
   128  
   129  func prepareModules() error {
   130  	var rep *report
   131  	reports := make(chan *report)
   132  	execCnt := 0
   133  	reportCnt := 0
   134  
   135  	for {
   136  		waiting := 0
   137  
   138  		// find modules to exec
   139  		for _, m := range modules {
   140  			switch m.readyToPrep() {
   141  			case statusNothingToDo:
   142  			case statusWaiting:
   143  				waiting++
   144  			case statusReady:
   145  				execCnt++
   146  				m.prep(reports)
   147  			}
   148  		}
   149  
   150  		if reportCnt < execCnt {
   151  			// wait for reports
   152  			rep = <-reports
   153  			if rep.err != nil {
   154  				if errors.Is(rep.err, ErrCleanExit) {
   155  					return rep.err
   156  				}
   157  				rep.module.NewErrorMessage("prep module", rep.err).Report()
   158  				return fmt.Errorf("failed to prep module %s: %w", rep.module.Name, rep.err)
   159  			}
   160  			reportCnt++
   161  		} else {
   162  			// finished
   163  			if waiting > 0 {
   164  				// check for dep loop
   165  				return fmt.Errorf("modules: dependency loop detected, cannot continue")
   166  			}
   167  			return nil
   168  		}
   169  
   170  	}
   171  }
   172  
   173  func startModules() error {
   174  	var rep *report
   175  	reports := make(chan *report)
   176  	execCnt := 0
   177  	reportCnt := 0
   178  
   179  	for {
   180  		waiting := 0
   181  
   182  		// find modules to exec
   183  		for _, m := range modules {
   184  			switch m.readyToStart() {
   185  			case statusNothingToDo:
   186  			case statusWaiting:
   187  				waiting++
   188  			case statusReady:
   189  				execCnt++
   190  				m.start(reports)
   191  				// DEBUG SNIPPET
   192  				// Slow-start for non-attributable performance issues.
   193  				// If you use subsystems, you'll need the snippet there too.
   194  				// log.Errorf("modules: starting %s", m.Name)
   195  				// time.Sleep(10 * time.Second)
   196  				// break
   197  				// END DEBUG SNIPPET
   198  			}
   199  		}
   200  
   201  		if reportCnt < execCnt {
   202  			// wait for reports
   203  			rep = <-reports
   204  			if rep.err != nil {
   205  				rep.module.NewErrorMessage("start module", rep.err).Report()
   206  				return fmt.Errorf("modules: could not start module %s: %w", rep.module.Name, rep.err)
   207  			}
   208  			reportCnt++
   209  			log.Infof("modules: started %s", rep.module.Name)
   210  		} else {
   211  			// finished
   212  			if waiting > 0 {
   213  				// check for dep loop
   214  				return fmt.Errorf("modules: dependency loop detected, cannot continue")
   215  			}
   216  			// return last error
   217  			return nil
   218  		}
   219  	}
   220  }