-
Notifications
You must be signed in to change notification settings - Fork 1
/
atomic-error.go
99 lines (84 loc) · 3.13 KB
/
atomic-error.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
/*
© 2024–present Harald Rudell <[email protected]> (https://haraldrudell.github.io/haraldrudell/)
ISC License
*/
package parl
import (
"sync/atomic"
"github.com/haraldrudell/parl/perrors"
)
// AtomicError is a thread-safe container for a single error value
// - [AtomicError.AddError] updates the errorvalue
// - [AtomicError.AddErrorSwap] conditionally updates the error value
// - [AtomicError.Error] returns the current error value
// - AtomicError is not closable and holds only one updatable value
// - AtomicError is not awaitable or readable to empty
// - consecutive Get returns the same error value
// - initialization-free, not awaitable
type AtomicError struct{ err atomic.Pointer[error] }
// AtomicError is an [ErrorSink1] for one error at a time
// - AtomicError is not closable and holds only one updatable value
var _ ErrorSink1 = &AtomicError{}
// AtomicError is an [ErrorSource1] for one error at a time
// - AtomicError is not awaitable or readable to empty
// - consecutive Get returns the same error value
var _ ErrorSource1 = &AtomicError{}
// AddError is a function to submit non-fatal errors
// - if the container is not empty, err is appended to the current error value
// - values are received by [ErrorSource1.Error]
func (a *AtomicError) AddError(err error) {
// try to store new value
if a.err.Load() == nil && a.err.CompareAndSwap(nil, &err) {
return // stored new error value
}
// err.Load() is not nil
// append err to error container’s error
for {
var ep = a.err.Load()
var newErrrorValue = perrors.AppendError(*ep, err)
if a.err.CompareAndSwap(ep, &newErrrorValue) {
return // appended to error value
}
}
}
// AddErrorSwap is thread-safe atomic swap of error values
// - oldErr: nil or an error returned by [AtomicError.AddErrorSwap] or
// this methods’s otherErr
// - newErr: the error to store
// - didSwap true: newErr was stored either because of empty or matching oldErr
// - didSwap false: the error held by the container that is differerent from oldErr and
// is returned in otherErr
// - AddErrorSwap writes newErr if:
// - — oldErr is nil and the errror container is empty or
// - — oldErr matches the errror held by the error container
func (a *AtomicError) AddErrorSwap(oldErr, newErr error) (didSwap bool, otherErr error) {
// if empty, write new value
if a.err.Load() == nil {
if didSwap = a.err.CompareAndSwap(nil, &newErr); didSwap {
return // wrote new error return: didSwap true, otherErr nil
}
}
// err.Load() is not nil, didSwap is false
for {
// check if error value is oldErr
var ep = a.err.Load()
if *ep != oldErr {
otherErr = *ep
return // swap fail return: didSwap false, otherErr valid
}
// try replacing oldErr
if didSwap = a.err.CompareAndSwap(ep, &newErr); didSwap {
return // updated error return: didSwap true, otherErr nil
}
}
}
// Error returns the error value
// - hasValue true: err is non-nil
// - hasValue false: the error source is currently empty
func (a *AtomicError) Error() (err error, hasValue bool) {
if ep := a.err.Load(); ep != nil {
err = *ep
hasValue = err != nil
}
return
}