// backendLoop runs the backend loop of the scene. func (s *scene) backendLoop(l loop.Loop) (err error) { // Defer cleanup. defer func() { cerr := s.cleanupAllProps() if err == nil { err = cerr } }() // Init timers. var watchdog <-chan time.Time var clapperboard <-chan time.Time if s.absolute > 0 { clapperboard = time.After(s.absolute) } // Run loop. for { if s.inactivity > 0 { watchdog = time.After(s.inactivity) } select { case <-l.ShallStop(): return nil case timeout := <-watchdog: return errors.New(ErrTimeout, errorMessages, "inactivity", timeout) case timeout := <-clapperboard: return errors.New(ErrTimeout, errorMessages, "absolute", timeout) case command := <-s.commandChan: s.processCommand(command) } } }
// WaitFlagLimited is specified on the Scene interface. func (s *scene) WaitFlagLimited(topic string, timeout time.Duration) error { // Add signal channel. command := &envelope{ kind: wait, signaling: &signaling{ topic: topic, signalChan: make(chan struct{}, 1), }, respChan: make(chan *envelope, 1), } _, err := s.command(command) if err != nil { return err } // Wait for signal. var timeoutChan <-chan time.Time if timeout > 0 { timeoutChan = time.After(timeout) } select { case <-s.backend.IsStopping(): err = s.Wait() if err == nil { err = errors.New(ErrSceneEnded, errorMessages) } return err case <-command.signaling.signalChan: return nil case <-timeoutChan: return errors.New(ErrWaitedTooLong, errorMessages, topic) } }
// checkRecovering checks if the backend can be recovered. func (m *systemMonitor) checkRecovering(rs loop.Recoverings) (loop.Recoverings, error) { if rs.Frequency(12, time.Minute) { logger.Errorf("monitor cannot be recovered: %v", rs.Last().Reason) return nil, errors.New(ErrMonitorCannotBeRecovered, errorMessages, rs.Last().Reason) } logger.Warningf("monitor recovered: %v", rs.Last().Reason) return rs.Trim(12), nil }
// checkRecovering checks if the backend can be recovered. func (c *Crontab) checkRecovering(rs loop.Recoverings) (loop.Recoverings, error) { if rs.Frequency(12, time.Minute) { logger.Errorf("crontab cannot be recovered: %v", rs.Last().Reason) return nil, errors.New(ErrCrontabCannotBeRecovered, errorMessages, rs.Last().Reason) } logger.Warningf("crontab recovered: %v", rs.Last().Reason) return rs.Trim(12), nil }
// command sends a command to the system monitor and waits for a response. func (m *systemMonitor) command(opCode int, args interface{}) (interface{}, error) { cmd := &command{opCode, args, make(chan interface{})} m.commandChan <- cmd resp, ok := <-cmd.respChan if !ok { return nil, errors.New(ErrMonitorPanicked, errorMessages) } if err, ok := resp.(error); ok { return nil, err } return resp, nil }
// NewUUIDByHex creates a UUID based on the passed hex string which has to // have the length of 32 bytes. func NewUUIDByHex(source string) (UUID, error) { uuid := UUID{} if len([]byte(source)) != 32 { return uuid, errors.New(ErrInvalidHexLength, errorMessages) } raw, err := hex.DecodeString(source) if err != nil { return uuid, errors.Annotate(err, ErrInvalidHexValue, errorMessages) } copy(uuid[:], raw) return uuid, nil }
// Test the validation. func TestValidation(t *testing.T) { assert := asserts.NewTestingAssertion(t, true) ec := 1 messages := errors.Messages{ec: "valid"} err := errors.New(ec, messages) packageName, fileName, line, lerr := errors.Location(err) assert.True(errors.Valid(err)) assert.Nil(lerr) assert.Equal(packageName, "github.com/pellaeon/goas/v3/errors_test") assert.Equal(fileName, "errors_test.go") assert.Equal(line, 31) }
// command sends a command envelope to the backend and // waits for the response. func (s *scene) command(command *envelope) (*envelope, error) { select { case s.commandChan <- command: case <-s.backend.IsStopping(): err := s.Wait() if err == nil { err = errors.New(ErrSceneEnded, errorMessages) } return nil, err } select { case <-s.backend.IsStopping(): err := s.Wait() if err == nil { err = errors.New(ErrSceneEnded, errorMessages) } return nil, err case resp := <-command.respChan: if resp.err != nil { return nil, resp.err } return resp, nil } }
// Test creation and checking. func TestIsError(t *testing.T) { assert := asserts.NewTestingAssertion(t, true) ec := 42 messages := errors.Messages{ec: "test error %d"} err := errors.New(ec, messages, 1) assert.ErrorMatch(err, `\[ERRORS_TEST:042\] test error 1`) assert.True(errors.IsError(err, ec)) assert.False(errors.IsError(err, 0)) err = testError("test error 2") assert.ErrorMatch(err, "test error 2") assert.False(errors.IsError(err, ec)) assert.False(errors.IsError(err, 0)) }
// processCommand handles the received commands of the monitor. func (m *systemMonitor) processCommand(cmd *command) { defer cmd.close() switch cmd.opCode { case cmdReset: // Reset monitoring. m.init() cmd.ok() case cmdMeasuringPointRead: // Read just one measuring point. id := cmd.args.(string) if mp, ok := m.etmData[id]; ok { // Measuring point found. clone := *mp cmd.respond(&clone) } else { // Measuring point does not exist. cmd.respond(errors.New(ErrMeasuringPointNotExists, errorMessages, id)) } case cmdMeasuringPointsReadAll: // Read all measuring points. resp := MeasuringPoints{} for _, mp := range m.etmData { clone := *mp resp = append(resp, &clone) } sort.Sort(resp) cmd.respond(resp) case cmdStaySetVariableRead: // Read just one stay-set variable. id := cmd.args.(string) if ssv, ok := m.ssvData[id]; ok { // Variable found. clone := *ssv cmd.respond(&clone) } else { // Variable does not exist. cmd.respond(errors.New(ErrStaySetVariableNotExists, errorMessages, id)) } case cmdStaySetVariablesReadAll: // Read all stay-set variables. resp := StaySetVariables{} for _, mp := range m.ssvData { clone := *mp resp = append(resp, &clone) } sort.Sort(resp) cmd.respond(resp) case cmdDynamicStatusRetrieverRead: // Read just one dynamic status value. id := cmd.args.(string) if dsr, ok := m.dsrData[id]; ok { // Dynamic status found. v, err := dsr() if err != nil { cmd.respond(err) } else { cmd.respond(v) } } else { // Dynamic status does not exist. cmd.respond(errors.New(ErrDynamicStatusNotExists, errorMessages, id)) } case cmdDynamicStatusRetrieversReadAll: // Read all dynamic status values. resp := DynamicStatusValues{} for id, dsr := range m.dsrData { v, err := dsr() if err != nil { cmd.respond(err) } dsv := &DynamicStatusValue{id, v} resp = append(resp, dsv) } sort.Sort(resp) cmd.respond(resp) } }
// processCommand processes the sent commands. func (s *scene) processCommand(command *envelope) { switch command.kind { case storeProp: // Add a new prop. _, ok := s.props[command.box.key] if ok { command.err = errors.New(ErrPropAlreadyExist, errorMessages, command.box.key) } else { s.props[command.box.key] = command.box } case fetchProp: // Retrieve a prop. box, ok := s.props[command.box.key] if !ok { command.err = errors.New(ErrPropNotFound, errorMessages, command.box.key) } else { command.box = box } case disposeProp: // Remove a prop. box, ok := s.props[command.box.key] if !ok { command.err = errors.New(ErrPropNotFound, errorMessages, command.box.key) } else { delete(s.props, command.box.key) command.box = box if box.cleanup != nil { cerr := box.cleanup(box.key, box.prop) if cerr != nil { command.err = errors.Annotate(cerr, ErrCleanupFailed, errorMessages, box.key) } } } case flag: // Signal a topic. s.flags[command.signaling.topic] = true // Notify subscribers. subscribers, ok := s.signalings[command.signaling.topic] if ok { delete(s.signalings, command.signaling.topic) for _, subscriber := range subscribers { subscriber <- struct{}{} } } case unflag: // Drop a topic. delete(s.flags, command.signaling.topic) case wait: // Add a waiter for a topic. active := s.flags[command.signaling.topic] if active { command.signaling.signalChan <- struct{}{} } else { waiters := s.signalings[command.signaling.topic] s.signalings[command.signaling.topic] = append(waiters, command.signaling.signalChan) } default: panic("illegal command") } // Return the changed command as response. command.respChan <- command }