-
Notifications
You must be signed in to change notification settings - Fork 1
/
recover.go
141 lines (119 loc) · 3.42 KB
/
recover.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
© 2020–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
ISC License
*/
package parl
import (
"fmt"
"github.com/haraldrudell/parl/perrors"
"github.com/haraldrudell/parl/pruntime"
)
const (
recAnnStackFrames = 1
recRecStackFrames = 2
)
// Recover recovers from a panic invoking a function no more than once.
// If there is *errp does not hold an error and there is no panic, onError is not invoked.
// Otherwise, onError is invoked exactly once.
// *errp is updated with a possible panic.
func Recover(annotation string, errp *error, onError func(error)) {
recover2(annotation, errp, onError, false, recover())
}
// Recover2 recovers from a panic and may invoke onError multiple times.
// onError is invoked if there is an error at *errp and on a possible panic.
// *errp is updated with a possible panic.
func Recover2(annotation string, errp *error, onError func(error)) {
recover2(annotation, errp, onError, true, recover())
}
func recover2(annotation string, errp *error, onError func(error), multiple bool, v interface{}) {
// ensure non-empty annotation
if annotation == "" {
annotation = pruntime.NewCodeLocation(recRecStackFrames).PackFunc() + ": panic:"
}
// consume *errp
var err error
if errp != nil {
if err = *errp; err != nil && multiple {
invokeOnError(onError, err)
}
}
// consume recover()
if e := processRecover(annotation, v); e != nil {
if multiple {
invokeOnError(onError, e)
} else {
err = perrors.AppendError(err, e)
}
}
// write back to *errp, do non-multiple invocation
if err != nil {
if errp != nil && *errp != err {
*errp = err
}
if !multiple {
invokeOnError(onError, err)
}
}
}
func invokeOnError(onError func(error), err error) {
if onError != nil {
onError(err)
} else {
Log("Recover: %+v\n", err)
}
}
func Annotation() (annotation string) {
return fmt.Sprintf("Recover from panic in %s:", pruntime.NewCodeLocation(recAnnStackFrames).PackFunc())
}
// processRecover ensures non-nil result to be error with Stack
func processRecover(annotation string, panicValue interface{}) (err error) {
if err = EnsureError(panicValue); err == nil {
return
}
// annotate
if annotation != "" {
err = Errorf("%s '%w'", annotation, err)
}
return
}
func EnsureError(panicValue interface{}) (err error) {
if panicValue == nil {
return
}
// ensure value to be error
var ok bool
if err, ok = panicValue.(error); !ok {
err = Errorf("non-error value: %T %+[1]v", panicValue)
}
return
}
func AddToPanic(panicValue interface{}, additionalErr error) (err error) {
if err = EnsureError(panicValue); err == nil {
return additionalErr
}
if additionalErr == nil {
return
}
return perrors.AppendError(err, additionalErr)
}
// HandlePanic recovers from panics when executing fn.
// A panic is returned in err
func HandlePanic(fn func()) (err error) {
defer Recover(Annotation(), &err, nil)
fn()
return
}
// HandleErrp recovers from panics when executing fn.
// A panic is stored at errp using error116.AppendError()
func HandleErrp(fn func(), errp *error) {
defer Recover(Annotation(), errp, nil)
fn()
}
// HandleErrp recovers from panics when executing fn.
// A panic is provided to the storeError function.
// storeError can be the thread-safe error116.ParlError.AddErrorProc()
func HandleParlError(fn func(), storeError func(error)) {
defer Recover(Annotation(), nil, storeError)
fn()
}
var _ = (&perrors.ParlError{}).AddErrorProc