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  }