diff --git a/src/internal/operations/backup.go b/src/internal/operations/backup.go index 8e40d820f..c4f547f47 100644 --- a/src/internal/operations/backup.go +++ b/src/internal/operations/backup.go @@ -2,6 +2,7 @@ package operations import ( "context" + "runtime/debug" "time" "github.com/alcionai/clues" @@ -106,7 +107,13 @@ type detailsWriter interface { // --------------------------------------------------------------------------- // Run begins a synchronous backup operation. -func (op *BackupOperation) Run(ctx context.Context) error { +func (op *BackupOperation) Run(ctx context.Context) (err error) { + defer func() { + if r := recover(); r != nil { + err = clues.Wrap(r.(error), "panic recovery").WithClues(ctx).With("stacktrace", debug.Stack()) + } + }() + ctx, end := D.Span(ctx, "operations:backup:run") defer func() { end() @@ -589,6 +596,11 @@ func (op *BackupOperation) persistResults( opStats.writeErr) } + if opStats.gc == nil { + op.Status = Failed + return errors.New("data population never completed") + } + if opStats.readErr == nil && opStats.writeErr == nil && opStats.gc.Successful == 0 { op.Status = NoData } diff --git a/src/internal/operations/restore.go b/src/internal/operations/restore.go index 30bc303a9..c94c69a4c 100644 --- a/src/internal/operations/restore.go +++ b/src/internal/operations/restore.go @@ -3,6 +3,7 @@ package operations import ( "context" "fmt" + "runtime/debug" "time" "github.com/alcionai/clues" @@ -106,6 +107,12 @@ type restorer interface { // Run begins a synchronous restore operation. func (op *RestoreOperation) Run(ctx context.Context) (restoreDetails *details.Details, err error) { + defer func() { + if r := recover(); r != nil { + err = clues.Wrap(r.(error), "panic recovery").WithClues(ctx).With("stacktrace", debug.Stack()) + } + }() + ctx, end := D.Span(ctx, "operations:restore:run") defer func() { end() @@ -250,6 +257,11 @@ func (op *RestoreOperation) persistResults( opStats.writeErr) } + if opStats.gc == nil { + op.Status = Failed + return errors.New("data restoration never completed") + } + if opStats.readErr == nil && opStats.writeErr == nil && opStats.gc.Successful == 0 { op.Status = NoData } diff --git a/src/pkg/fault/fault.go b/src/pkg/fault/fault.go index 69017029c..b8f57108b 100644 --- a/src/pkg/fault/fault.go +++ b/src/pkg/fault/fault.go @@ -87,11 +87,12 @@ func (e *Errors) Fail(err error) *Errors { // setErr handles setting errors.err. Sync locking gets // handled upstream of this call. func (e *Errors) setErr(err error) *Errors { - if e.err != nil { - return e.addErr(err) + if e.err == nil { + e.err = err + return e } - e.err = err + e.errs = append(e.errs, err) return e } diff --git a/src/pkg/fault/fault_test.go b/src/pkg/fault/fault_test.go index 3f5ad127c..8fb2981d7 100644 --- a/src/pkg/fault/fault_test.go +++ b/src/pkg/fault/fault_test.go @@ -73,6 +73,8 @@ func (suite *FaultErrorsUnitSuite) TestErr() { suite.T().Run(test.name, func(t *testing.T) { n := fault.New(test.failFast) require.NotNil(t, n) + require.NoError(t, n.Err()) + require.Empty(t, n.Errs()) e := n.Fail(test.fail) require.NotNil(t, e) @@ -90,6 +92,8 @@ func (suite *FaultErrorsUnitSuite) TestFail() { n := fault.New(false) require.NotNil(t, n) + require.NoError(t, n.Err()) + require.Empty(t, n.Errs()) n.Fail(assert.AnError) assert.Error(t, n.Err())