From f0a082ec84172f5b3f001bcc757bade06feb0457 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 25 Feb 2025 00:32:07 +0900 Subject: [PATCH] fortify: improve handling of RevertErr All this error wrapping is getting a bit ridiculous and I might want to do something about that somewhere down the line. Signed-off-by: Ophestra --- internal/app/process.go | 57 ++++++++++++++++++++++------------------- main.go | 43 ++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/internal/app/process.go b/internal/app/process.go index 1302d81..b0d6031 100644 --- a/internal/app/process.go +++ b/internal/app/process.go @@ -79,7 +79,8 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error { ec.Set(system.Process) if states, err := c.Load(); err != nil { // revert per-process state here to limit damage - return errors.Join(err, seal.sys.Revert(ec)) + storeErr.OpErr = err + return seal.sys.Revert(ec) } else { if l := len(states); l == 0 { fmsg.Verbose("no other launchers active, will clean up globals") @@ -115,14 +116,10 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error { } } - err := seal.sys.Revert(ec) - if err != nil { - err = err.(RevertCompoundError) - } - return err + return seal.sys.Revert(ec) }() }) - storeErr.Err = errors.Join(revertErr, store.Close()) + storeErr.save([]error{revertErr, store.Close()}) rs.RevertErr = storeErr.equiv("error returned during cleanup:") }() @@ -170,7 +167,9 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error { Time: *rs.Time, } var earlyStoreErr = new(StateStoreError) // returned after blocking on waitErr - earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) { earlyStoreErr.InnerErr = c.Save(&sd, seal.ct) }) + earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) { + earlyStoreErr.InnerErr = c.Save(&sd, seal.ct) + }) // destroy defunct state entry deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) } @@ -217,24 +216,26 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error { type StateStoreError struct { // whether inner function was called Inner bool - // returned by the Do method of [state.Store] - DoErr error // returned by the Save/Destroy method of [state.Cursor] InnerErr error - // stores an arbitrary error - Err error + // returned by the Do method of [state.Store] + DoErr error + // stores an arbitrary store operation error + OpErr error + // stores arbitrary errors + Err []error } -// save saves exactly one arbitrary error in [StateStoreError]. -func (e *StateStoreError) save(err error) { - if err == nil || e.Err != nil { +// save saves arbitrary errors in [StateStoreError] once. +func (e *StateStoreError) save(errs []error) { + if len(errs) == 0 || e.Err != nil { panic("invalid call to save") } - e.Err = err + e.Err = errs } func (e *StateStoreError) equiv(a ...any) error { - if e.Inner && e.DoErr == nil && e.InnerErr == nil && e.Err == nil { + if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Err...) == nil { return nil } else { return fmsg.WrapErrorSuffix(e, a...) @@ -245,13 +246,14 @@ func (e *StateStoreError) Error() string { if e.Inner && e.InnerErr != nil { return e.InnerErr.Error() } - if e.DoErr != nil { return e.DoErr.Error() } - - if e.Err != nil { - return e.Err.Error() + if e.OpErr != nil { + return e.OpErr.Error() + } + if err := errors.Join(e.Err...); err != nil { + return err.Error() } // equiv nullifies e for values where this is reached @@ -260,14 +262,17 @@ func (e *StateStoreError) Error() string { func (e *StateStoreError) Unwrap() (errs []error) { errs = make([]error, 0, 3) - if e.DoErr != nil { - errs = append(errs, e.DoErr) - } if e.InnerErr != nil { errs = append(errs, e.InnerErr) } - if e.Err != nil { - errs = append(errs, e.Err) + if e.DoErr != nil { + errs = append(errs, e.DoErr) + } + if e.OpErr != nil { + errs = append(errs, e.OpErr) + } + if err := errors.Join(e.Err...); err != nil { + errs = append(errs, err) } return } diff --git a/main.go b/main.go index be32f0b..dc738e5 100644 --- a/main.go +++ b/main.go @@ -304,7 +304,48 @@ func runApp(a fst.App, config *fst.Config) { } } if rs.RevertErr != nil { - fmsg.PrintBaseError(rs.RevertErr, "generic error returned during cleanup:") + var stateStoreError *app.StateStoreError + if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil { + fmsg.PrintBaseError(rs.RevertErr, "generic fault during cleanup:") + goto out + } + + if stateStoreError.Err != nil { + if len(stateStoreError.Err) == 2 { + if stateStoreError.Err[0] != nil { + if joinedErrs, ok := stateStoreError.Err[0].(interface{ Unwrap() []error }); !ok { + fmsg.PrintBaseError(stateStoreError.Err[0], "generic fault during revert:") + } else { + for _, err := range joinedErrs.Unwrap() { + if err != nil { + fmsg.PrintBaseError(err, "fault during revert:") + } + } + } + } + if stateStoreError.Err[1] != nil { + log.Printf("cannot close store: %v", stateStoreError.Err[1]) + } + } else { + log.Printf("fault during cleanup: %v", + errors.Join(stateStoreError.Err...)) + } + } + + if stateStoreError.OpErr != nil { + log.Printf("blind revert due to store fault: %v", + stateStoreError.OpErr) + } + + if stateStoreError.DoErr != nil { + fmsg.PrintBaseError(stateStoreError.DoErr, "state store operation unsuccessful:") + } + + if stateStoreError.Inner && stateStoreError.InnerErr != nil { + fmsg.PrintBaseError(stateStoreError.InnerErr, "cannot destroy state entry:") + } + + out: if rs.ExitCode == 0 { rs.ExitCode = 128 }