github.com/safing/portbase@v0.19.5/modules/status.go (about) 1 package modules 2 3 import ( 4 "context" 5 6 "github.com/tevino/abool" 7 ) 8 9 // Module Status Values. 10 const ( 11 StatusDead uint8 = 0 // not prepared, not started 12 StatusPreparing uint8 = 1 13 StatusOffline uint8 = 2 // prepared, not started 14 StatusStopping uint8 = 3 15 StatusStarting uint8 = 4 16 StatusOnline uint8 = 5 // online and running 17 ) 18 19 // Module Failure Status Values. 20 const ( 21 FailureNone uint8 = 0 22 FailureHint uint8 = 1 23 FailureWarning uint8 = 2 24 FailureError uint8 = 3 25 ) 26 27 // Ready Stati. 28 const ( 29 statusWaiting uint8 = iota 30 statusReady 31 statusNothingToDo 32 ) 33 34 var ( 35 failureUpdateNotifyFunc func(moduleFailure uint8, id, title, msg string) 36 failureUpdateNotifyFuncEnabled = abool.NewBool(false) 37 failureUpdateNotifyFuncReady = abool.NewBool(false) 38 ) 39 40 // SetFailureUpdateNotifyFunc sets a function that is called on every change 41 // of a module's failure status. 42 func SetFailureUpdateNotifyFunc(fn func(moduleFailure uint8, id, title, msg string)) bool { 43 if failureUpdateNotifyFuncEnabled.SetToIf(false, true) { 44 failureUpdateNotifyFunc = fn 45 failureUpdateNotifyFuncReady.Set() 46 return true 47 } 48 return false 49 } 50 51 // Online returns whether the module is online. 52 func (m *Module) Online() bool { 53 return m.Status() == StatusOnline 54 } 55 56 // OnlineSoon returns whether the module is or is about to be online. 57 func (m *Module) OnlineSoon() bool { 58 if moduleMgmtEnabled.IsSet() && 59 !m.enabled.IsSet() && 60 !m.enabledAsDependency.IsSet() { 61 return false 62 } 63 return !m.stopFlag.IsSet() 64 } 65 66 // Status returns the current module status. 67 func (m *Module) Status() uint8 { 68 m.RLock() 69 defer m.RUnlock() 70 71 return m.status 72 } 73 74 // FailureStatus returns the current failure status, ID and message. 75 func (m *Module) FailureStatus() (failureStatus uint8, failureID, failureMsg string) { 76 m.RLock() 77 defer m.RUnlock() 78 79 return m.failureStatus, m.failureID, m.failureMsg 80 } 81 82 // Hint sets failure status to hint. This is a somewhat special failure status, 83 // as the module is believed to be working correctly, but there is an important 84 // module specific information to convey. The supplied failureID is for 85 // improved automatic handling within connected systems, the failureMsg is for 86 // humans. 87 // The given ID must be unique for the given title and message. A call to 88 // Hint(), Warning() or Error() with the same ID as the existing one will be 89 // ignored. 90 func (m *Module) Hint(id, title, msg string) { 91 m.setFailure(FailureHint, id, title, msg, true) 92 } 93 94 // Warning sets failure status to warning. The supplied failureID is for 95 // improved automatic handling within connected systems, the failureMsg is for 96 // humans. 97 // The given ID must be unique for the given title and message. A call to 98 // Hint(), Warning() or Error() with the same ID as the existing one will be 99 // ignored. 100 func (m *Module) Warning(id, title, msg string) { 101 m.setFailure(FailureWarning, id, title, msg, true) 102 } 103 104 // Error sets failure status to error. The supplied failureID is for improved 105 // automatic handling within connected systems, the failureMsg is for humans. 106 // The given ID must be unique for the given title and message. A call to 107 // Hint(), Warning() or Error() with the same ID as the existing one will be 108 // ignored. 109 func (m *Module) Error(id, title, msg string) { 110 m.setFailure(FailureError, id, title, msg, true) 111 } 112 113 func (m *Module) setFailure(status uint8, id, title, msg string, lockModule bool) { 114 var resolveFailureID string 115 func() { 116 if lockModule { 117 m.Lock() 118 defer m.Unlock() 119 } 120 121 // Ignore calls with the same ID. 122 if id == m.failureID { 123 return 124 } 125 126 // Copy data for failure status update worker. 127 resolveFailureID = m.failureID 128 129 // Set new failure status. 130 m.failureStatus = status 131 m.failureID = id 132 m.failureTitle = title 133 m.failureMsg = msg 134 }() 135 136 // Notify of module change. 137 m.notifyOfChange() 138 139 // Propagate failure status. 140 if failureUpdateNotifyFuncReady.IsSet() { 141 _ = m.RunWorker("failure status updater", func(context.Context) error { 142 // Only use data in worker that won't change anymore. 143 144 // Resolve previous failure state if available. 145 if resolveFailureID != "" { 146 failureUpdateNotifyFunc(FailureNone, resolveFailureID, "", "") 147 } 148 149 // Notify of new failure state. 150 failureUpdateNotifyFunc(status, id, title, msg) 151 152 return nil 153 }) 154 } 155 } 156 157 // Resolve removes the failure state from the module if the given failureID matches the current failure ID. If the given failureID is an empty string, Resolve removes any failure state. 158 func (m *Module) Resolve(failureID string) { 159 m.Lock() 160 defer m.Unlock() 161 162 // Check if resolving is necessary. 163 if failureID != "" && failureID != m.failureID { 164 // Return immediately if not resolving any (`""`) or if the failure ID 165 // does not match. 166 return 167 } 168 169 // Copy data for failure status update worker. 170 resolveFailureID := m.failureID 171 172 // Set failure status on module. 173 m.failureStatus = FailureNone 174 m.failureID = "" 175 m.failureTitle = "" 176 m.failureMsg = "" 177 178 // Notify of module change. 179 m.notifyOfChange() 180 181 // Propagate failure status. 182 if failureUpdateNotifyFuncReady.IsSet() { 183 m.StartWorker("failure status updater", func(context.Context) error { 184 // Only use data in worker that won't change anymore. 185 failureUpdateNotifyFunc(FailureNone, resolveFailureID, "", "") 186 return nil 187 }) 188 } 189 } 190 191 // readyToPrep returns whether all dependencies are ready for this module to prep. 192 func (m *Module) readyToPrep() uint8 { 193 // check if valid state for prepping 194 if m.Status() != StatusDead { 195 return statusNothingToDo 196 } 197 198 for _, dep := range m.depModules { 199 if dep.Status() < StatusOffline { 200 return statusWaiting 201 } 202 } 203 204 return statusReady 205 } 206 207 // readyToStart returns whether all dependencies are ready for this module to start. 208 func (m *Module) readyToStart() uint8 { 209 // check if start is wanted 210 if moduleMgmtEnabled.IsSet() { 211 if !m.enabled.IsSet() && !m.enabledAsDependency.IsSet() { 212 return statusNothingToDo 213 } 214 } 215 216 // check if valid state for starting 217 if m.Status() != StatusOffline { 218 return statusNothingToDo 219 } 220 221 // check if all dependencies are ready 222 for _, dep := range m.depModules { 223 if dep.Status() < StatusOnline { 224 return statusWaiting 225 } 226 } 227 228 return statusReady 229 } 230 231 // readyToStop returns whether all dependencies are ready for this module to stop. 232 func (m *Module) readyToStop() uint8 { 233 // check if stop is wanted 234 if moduleMgmtEnabled.IsSet() && !shutdownFlag.IsSet() { 235 if m.enabled.IsSet() || m.enabledAsDependency.IsSet() { 236 return statusNothingToDo 237 } 238 } 239 240 // check if valid state for stopping 241 if m.Status() != StatusOnline { 242 return statusNothingToDo 243 } 244 245 for _, revDep := range m.depReverse { 246 // not ready if a reverse dependency was started, but not yet stopped 247 if revDep.Status() > StatusOffline { 248 return statusWaiting 249 } 250 } 251 252 return statusReady 253 }