-
Notifications
You must be signed in to change notification settings - Fork 1
/
recover.go
167 lines (138 loc) · 4.51 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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
© 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
recEnsureErrorFrames = 2
// Recover() and Recover2() are deferred functions invoked on panic
// Because the functions are directly invoked by runtime panic code,
// there are no intermediate stack frames between Recover*() and runtime.panic*.
// therefore, the Recover stack frame must be included in the error stack frames
// recover2() + processRecover() + ensureError() == 3
recProcessRecoverFrames = 3
)
// 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, recoverValue 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, recoverValue); 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)
}
}
// NoOnError is used with Recover to silence the default error logging
func NoOnError(err error) {}
// Annotation provides a default annotation [base package].[function]: "mypackage.MyFunc"
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, recProcessRecoverFrames); err == nil {
return // panicValue nil return: no error
}
// annotate
if annotation != "" {
err = perrors.Errorf("%s \x27%w\x27", annotation, err)
}
return
}
// AddToPanic ensures that a recover() value is an error or nil.
func EnsureError(panicValue interface{}) (err error) {
return ensureError(panicValue, recEnsureErrorFrames)
}
func ensureError(panicValue interface{}, frames int) (err error) {
if panicValue == nil {
return // no panic return
}
// ensure value to be error
var ok bool
if err, ok = panicValue.(error); !ok {
err = fmt.Errorf("non-error value: %T %+[1]v", panicValue)
}
// ensure stack trace
if !perrors.HasStack(err) {
err = perrors.Stackn(err, frames)
}
return
}
// AddToPanic takes a recover() value and adds it to additionalErr.
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 panic in fn returning error.
func HandlePanic(fn func()) (err error) {
defer Recover(Annotation(), &err, nil)
fn()
return
}
// HandleErrp recovers from a panic in fn storing at *errp.
// HandleErrp is deferable.
func HandleErrp(fn func(), errp *error) {
defer Recover(Annotation(), errp, nil)
fn()
}
// HandleParlError recovers from panic in fn invoking an error callback.
// HandleParlError is deferable
// storeError can be the thread-safe perrors.ParlError.AddErrorProc()
func HandleParlError(fn func(), storeError func(err error)) {
defer Recover(Annotation(), nil, storeError)
fn()
}
// perrors.ParlError.AddErrorProc can be used with HandleParlError
var _ func(err error) = (&perrors.ParlError{}).AddErrorProc