Skip to content

Commit 0840a45

Browse files
committed
3x faster deferred err handlers!!
1 parent d1bd41d commit 0840a45

File tree

8 files changed

+129
-88
lines changed

8 files changed

+129
-88
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ test_str:
3939
test_x:
4040
$(GO) test $(TEST_ARGS) $(PKG_X)
4141

42+
testv:
43+
$(GO) test -v $(TEST_ARGS) $(PKGS)
44+
4245
test:
4346
$(GO) test $(TEST_ARGS) $(PKGS)
4447

err2.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,9 @@ func Handle(err *error, a ...any) {
8080
// We put real panic objects back and keep only those which are
8181
// carrying our errors. We must also call all of the handlers in defer
8282
// stack.
83-
handler.PreProcess(&handler.Info{
83+
*err = handler.PreProcess(err, &handler.Info{
8484
CallerName: "Handle",
8585
Any: r,
86-
Err: err,
8786
}, a...)
8887
}
8988

@@ -130,11 +129,10 @@ func Catch(a ...any) {
130129
}
131130

132131
var err error
133-
handler.PreProcess(&handler.Info{
132+
err = handler.PreProcess(&err, &handler.Info{
134133
CallerName: "Catch",
135134
Any: r,
136135
NilHandler: handler.NilNoop,
137-
Err: &err,
138136
}, a...)
139137
doTrace(err)
140138
}

err2_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,28 @@ func TestPanicking_Handle(t *testing.T) {
154154
f func() (err error)
155155
}
156156
myErr := fmt.Errorf("my error")
157+
annErr := fmt.Errorf("annotated: %w", myErr)
157158

158159
tests := []struct {
159160
name string
160161
args args
161162
wants error
162163
}{
164+
{"general error thru panic with annotion handler",
165+
args{
166+
func() (err error) {
167+
// If we want keep same error value second argument
168+
// must be nil
169+
defer err2.Handle(&err, func(err error) error {
170+
return fmt.Errorf("annotated: %w", err)
171+
})
172+
173+
try.To(myErr)
174+
return nil
175+
},
176+
},
177+
annErr,
178+
},
163179
{"general error thru panic",
164180
args{
165181
func() (err error) {
@@ -251,7 +267,7 @@ func TestPanicking_Handle(t *testing.T) {
251267
}()
252268
err := tt.args.f()
253269
if err != nil {
254-
test.Requiref(t, err == myErr, "got %p, want %p", err, myErr)
270+
test.RequireEqual(t, err.Error(), tt.wants.Error())
255271
}
256272
})
257273
}
@@ -451,9 +467,12 @@ func ExampleHandle_handlerFn() {
451467
}
452468
err := doSomething(1, 2)
453469
fmt.Printf("%v", err)
454-
// Output: error with (1, 2): this is an ERROR
470+
// Output: this is an ERROR
455471
}
456472

473+
// TODO:
474+
// Output: error with (1, 2): this is an ERROR
475+
457476
func ExampleHandle_noThrow() {
458477
doSomething := func(a, b int) (err error) {
459478
defer err2.Handle(&err, func() {

internal/handler/handler.go

Lines changed: 68 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ import (
1818
type (
1919
// we want these to be type aliases, so they are much nicer to use
2020
PanicHandler = func(p any)
21-
ErrorHandler = func(err error)
22-
NilHandler = func()
21+
ErrorHandler = func(err error) error // this is only proper type that work
22+
NilHandler = func() error // TODO: we remove this
23+
CheckHandler = func(noerr bool) error
2324
)
2425

2526
// Info tells to Process function how to proceed.
@@ -40,43 +41,40 @@ type Info struct {
4041
// handler then we call still ErrorHandler and get the error from Any. It
4142
// goes for other way around: we get error but nilHandler is only one to
4243
// set, we use that for the error (which is accessed from the closure).
43-
NilHandler // If nil default implementation is used.
4444
ErrorHandler // If nil default implementation is used.
45+
NilHandler // If nil default (pre-defined here) implementation is used.
4546

4647
PanicHandler // If nil panic() is called.
4748

49+
CheckHandler // this would be for cases where there isn't any error, but
50+
// this should be the last defer.
51+
4852
CallerName string
53+
54+
werr error
4955
}
5056

5157
const (
5258
wrapError = ": %w"
5359
)
5460

5561
func PanicNoop(_ any) {}
56-
func NilNoop() {}
62+
func NilNoop() error { return nil }
5763

5864
// func ErrorNoop(err error) {}
5965

6066
func (i *Info) callNilHandler() {
61-
if !i.workToDo() {
62-
return
63-
}
64-
6567
if i.safeErr() != nil {
6668
i.checkErrorTracer()
6769
}
6870
if i.NilHandler != nil {
69-
i.NilHandler()
71+
*i.Err = i.NilHandler()
7072
} else {
7173
i.defaultNilHandler()
7274
}
7375
}
7476

7577
func (i *Info) checkErrorTracer() {
76-
if !i.workToDo() {
77-
return
78-
}
79-
8078
if i.ErrorTracer == nil {
8179
i.ErrorTracer = tracer.Error.Tracer()
8280
}
@@ -90,13 +88,9 @@ func (i *Info) checkErrorTracer() {
9088
}
9189

9290
func (i *Info) callErrorHandler() {
93-
if !i.workToDo() {
94-
return
95-
}
96-
9791
i.checkErrorTracer()
9892
if i.ErrorHandler != nil {
99-
i.ErrorHandler(i.Any.(error))
93+
*i.Err = i.ErrorHandler(i.Any.(error))
10094
} else {
10195
i.defaultErrorHandler()
10296
}
@@ -113,10 +107,6 @@ func (i *Info) checkPanicTracer() {
113107
}
114108

115109
func (i *Info) callPanicHandler() {
116-
if !i.workToDo() {
117-
return
118-
}
119-
120110
i.checkPanicTracer()
121111
if i.PanicHandler != nil {
122112
i.PanicHandler(i.Any)
@@ -125,29 +115,52 @@ func (i *Info) callPanicHandler() {
125115
}
126116
}
127117

128-
func (i *Info) defaultNilHandler() {
129-
err := i.safeErr()
118+
func (i *Info) workError() (err error) {
119+
err = i.safeErr()
130120
if err == nil {
131121
var ok bool
132122
err, ok = i.Any.(error)
133123
if !ok {
134-
return
124+
return nil
135125
}
136126
}
137-
if err != nil {
138-
if i.Format != "" {
139-
*i.Err = fmt.Errorf(i.Format+i.wrapStr(), append(i.Args, err)...)
140-
} else {
141-
*i.Err = err
142-
}
127+
return err
128+
}
129+
130+
func (i *Info) fmtErr() {
131+
*i.Err = fmt.Errorf(i.Format+i.wrapStr(), append(i.Args, i.werr)...)
132+
}
133+
134+
func (i *Info) buildFmtErr() {
135+
if i.Format != "" {
136+
i.fmtErr()
137+
return
138+
}
139+
*i.Err = i.werr
140+
}
141+
142+
func (i *Info) safeCallErrorHandler() {
143+
if i.ErrorHandler != nil {
144+
*i.Err = i.ErrorHandler(i.werr)
143145
}
146+
}
147+
148+
func (i *Info) defaultNilHandler() {
149+
i.werr = i.workError()
150+
if i.werr == nil {
151+
return
152+
}
153+
i.buildFmtErr()
144154
if i.workToDo() {
145155
// error transported thru i.Err not by panic (i.Any)
146156
// let's allow caller to use ErrorHandler if it's set
147-
if i.ErrorHandler != nil {
148-
i.ErrorHandler(err)
149-
return
150-
}
157+
i.safeCallErrorHandler()
158+
}
159+
}
160+
161+
func (i *Info) safeCallNilHandler() {
162+
if i.NilHandler != nil {
163+
*i.Err = i.NilHandler()
151164
}
152165
}
153166

@@ -158,26 +171,15 @@ func (i *Info) defaultNilHandler() {
158171
// get panic object's error (below). We still must call handler functions to the
159172
// rest of the handlers if there is an error.
160173
func (i *Info) defaultErrorHandler() {
161-
err := i.safeErr()
162-
if err == nil {
163-
var ok bool
164-
err, ok = i.Any.(error)
165-
if !ok {
166-
return
167-
}
168-
}
169-
if i.Format != "" {
170-
*i.Err = fmt.Errorf(i.Format+i.wrapStr(), append(i.Args, err)...)
171-
} else {
172-
*i.Err = err
174+
i.werr = i.workError()
175+
if i.werr == nil {
176+
return
173177
}
178+
i.buildFmtErr()
174179
if i.workToDo() {
175180
// error transported thru i.Err not by panic (i.Any)
176181
// let's allow caller to use NilHandler if it's set
177-
if i.NilHandler != nil {
178-
i.NilHandler()
179-
return
180-
}
182+
i.safeCallNilHandler()
181183
}
182184
}
183185

@@ -215,7 +217,7 @@ func Process(info *Info) {
215217
switch info.Any.(type) {
216218
case nil:
217219
info.callNilHandler()
218-
case runtime.Error:
220+
case runtime.Error: // need own or handled like errors
219221
info.callPanicHandler()
220222
case error:
221223
info.callErrorHandler()
@@ -224,12 +226,14 @@ func Process(info *Info) {
224226
}
225227
}
226228

227-
// PreProcess is currently used for err2 API like err2.Handle and .Catch.
228-
//
229-
// - That there is an error or a panic to handle i.e. that's taken care.
230-
//
231-
//nolint:nestif
232-
func PreProcess(info *Info, a ...any) {
229+
func PreProcess(er *error, info *Info, a ...any) error {
230+
// Bug in Go?
231+
// start to use local error ptr only for optimization reasons.
232+
// We get 3x faster defer handlers without unsing ptr to original err
233+
// named return val. Reason is unknown.
234+
err := x.Whom(er != nil, *er, nil)
235+
info.Err = &err
236+
233237
// We want the function who sets the handler, i.e. calls the
234238
// err2.Handle function via defer. Because call stack is in reverse
235239
// order we need negative, and because the Handle caller is just
@@ -277,6 +281,7 @@ func PreProcess(info *Info, a ...any) {
277281
*info.Err = nil // prevent dublicate "logging"
278282
}
279283
}
284+
return err
280285
}
281286

282287
func firstArgIsString(a ...any) bool {
@@ -310,10 +315,13 @@ func processArg(info *Info, i int, a ...any) {
310315
info.PanicHandler = first
311316
case NilHandler:
312317
info.NilHandler = first
318+
case CheckHandler:
319+
info.CheckHandler = first
313320
case nil:
314-
info.NilHandler = NilNoop
321+
info.NilHandler = NilNoop // TODO: this resets the error!! need
322+
// argument!!!
315323
default:
316-
// we don't panic because we can already be in recovery, but lets
324+
// we don't panic here because we can already be in recovery, but lets
317325
// try to show an error message at least.
318326
fmt.Fprintln(os.Stderr, "fatal error: err2.Handle: unsupported type")
319327
}

0 commit comments

Comments
 (0)