Skip to content

Commit

Permalink
cli: import multiError implementation from snapd
Browse files Browse the repository at this point in the history
  • Loading branch information
anpep committed Jun 26, 2023
1 parent f615dbf commit 73f1670
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 17 deletions.
8 changes: 4 additions & 4 deletions internals/overlord/overlord.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package overlord

import (
"errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -318,11 +319,10 @@ func (o *Overlord) settle(timeout time.Duration, beforeCleanups func()) error {
var errs []error
for !done {
if timeout > 0 && time.Since(t0) > timeout {
err := fmt.Errorf("Settle is not converging")
if len(errs) != 0 {
return &multiError{append(errs, err)}
return newMultiError("settle is not converging", errs)
}
return err
return errors.New("settle is not converging")
}
next := o.ensureTimerReset()
err := o.stateEng.Ensure()
Expand Down Expand Up @@ -355,7 +355,7 @@ func (o *Overlord) settle(timeout time.Duration, beforeCleanups func()) error {
}
}
if len(errs) != 0 {
return &multiError{errs}
return newMultiError("ensure error", errs)
}
return nil
}
Expand Down
52 changes: 39 additions & 13 deletions internals/overlord/stateengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@
package overlord

import (
"bytes"
"errors"
"fmt"
"strings"
"sync"

"github.com/canonical/pebble/internals/logger"
"github.com/canonical/pebble/internals/overlord/state"
)

var errStateEngineStopped = errors.New("state engine already stopped")

// StateManager is implemented by types responsible for observing
// the system and manipulating it to reflect the desired state.
type StateManager interface {
Expand Down Expand Up @@ -80,22 +80,48 @@ func (se *StateEngine) State() *state.State {
return se.state
}

// multiError collects multiple errors that affected an operation.
type multiError struct {
errs []error
header string
errs []error
}

func (e *multiError) Error() string {
if len(e.errs) == 1 {
return e.errs[0].Error()
}
// newMultiError returns a new multiError struct initialized with
// the given format string that explains what operation potentially
// went wrong. multiError can be nested and will render correctly
// in these cases.
func newMultiError(header string, errs []error) error {
return &multiError{header: header, errs: errs}
}

// Error formats the error string.
func (me *multiError) Error() string {
return me.nestedError(0)
}

errStrings := make([]string, len(e.errs))
for i, err := range e.errs {
errStrings[i] = err.Error()
// helper to ensure formating of nested multiErrors works.
func (me *multiError) nestedError(level int) string {
indent := strings.Repeat(" ", level)
buf := bytes.NewBufferString(fmt.Sprintf("%s:\n", me.header))
if level > 8 {
return "circular or too deep error nesting (max 8)?!"
}
return "multiple errors: " + strings.Join(errStrings, "; ")
for i, err := range me.errs {
switch v := err.(type) {
case *multiError:
fmt.Fprintf(buf, "%s- %v", indent, v.nestedError(level+1))
default:
fmt.Fprintf(buf, "%s- %v", indent, err)
}
if i < len(me.errs)-1 {
fmt.Fprintf(buf, "\n")
}
}
return buf.String()
}

var errStateEngineStopped = errors.New("state engine already stopped")

// DryStart ensures all managers are ready to run their activities
// before the first call to Ensure() is made.
func (se *StateEngine) DryStart() error {
Expand All @@ -112,7 +138,7 @@ func (se *StateEngine) DryStart() error {
}
}
if len(errs) != 0 {
return &multiError{errs}
return newMultiError("dry-start failed", errs)
}
se.dryStarted = true
return nil
Expand Down Expand Up @@ -144,7 +170,7 @@ func (se *StateEngine) Ensure() error {
}
}
if len(errs) != 0 {
return &multiError{errs}
return newMultiError("state ensure errors", errs)
}
return nil
}
Expand Down

0 comments on commit 73f1670

Please sign in to comment.