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 }