-
Notifications
You must be signed in to change notification settings - Fork 1
/
closable-chan.go
129 lines (106 loc) · 3.06 KB
/
closable-chan.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
/*
© 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
ISC License
*/
package parl
import "sync"
// ClosableChan wraps a channel with thread-safe idempotent panic-free observable close.
// - ClosableChan is initialization-free
// - Close is deferrable
// - IsClosed provides wether the channel is closed
//
// Usage:
//
// var errCh parl.ClosableChan[error]
// go thread(&errCh)
// err, ok := <-errCh.Ch()
// if errCh.IsClosed() { // can be inspected
// …
//
// func thread(errCh *parl.ClosableChan[error]) {
// var err error
// …
// defer errCh.Close(&err) // will not terminate the process
// errCh.Ch() <- err
type ClosableChan[T any] struct {
hasChannel AtomicBool
chLock sync.Mutex
ch chan T // behind lock
closeOnce Once
}
// NewClosableChan returns a channel with thread-safe idempotent panic-free observable close
func NewClosableChan[T any](ch ...chan T) (cl *ClosableChan[T]) {
c := ClosableChan[T]{}
c.getCh(ch...) // ch... or make provides the channel
return &c
}
// Ch retrieves the channel
// - nil is never returned
// - the channel may already be closed
// - do not close the channel other than using the Close method
// - as or all channel close, if one thread is blocked in channel send
// while another thread closes the channel, a data race occurs
func (cl *ClosableChan[T]) Ch() (ch chan T) {
return cl.getCh()
}
// Close ensures the channel is closed
// - Close does not return until the channel is closed.
// - panic-free thread-safe deferrable observable
// - all invocations have close result in err
// - didClose indicates whether this invocation closed the channel
func (cl *ClosableChan[T]) Close(errp ...*error) (didClose bool, err error) {
// first thread closes the channel
// all threads provide the result
didClose, err = cl.close()
if len(errp) > 0 {
if errp0 := errp[0]; errp0 != nil {
*errp0 = err
}
}
return
}
// IsClosed indicates whether the Close method has been invoked
func (cl *ClosableChan[T]) IsClosed() (isClosed bool) {
return cl.closeOnce.IsDone()
}
func (cl *ClosableChan[T]) getCh(ch0 ...chan T) (ch chan T) {
// wrap lock in performance-friendly atomic
// channel is still provided when closed
if cl.hasChannel.IsTrue() {
return cl.ch
}
// ensure a channel is present
cl.chLock.Lock()
defer cl.chLock.Unlock()
if cl.closeOnce.IsDone() || cl.hasChannel.IsTrue() {
ch = cl.ch
return // already closed or already present return
}
if ch = cl.ch; ch == nil {
if len(ch0) > 0 {
ch = ch0[0]
} else {
ch = make(chan T)
}
cl.ch = ch
}
cl.hasChannel.Set()
return
}
func (cl *ClosableChan[T]) close() (didClose bool, err error) {
// provide result with atomic performance
var hasResult bool
if _, hasResult, err = cl.closeOnce.Result(); hasResult {
return // already closed return
}
didClose, _, err = cl.closeOnce.DoErr(cl.doClose)
return
}
func (cl *ClosableChan[T]) doClose() (err error) {
// ensure a channel exists
cl.getCh()
cl.chLock.Lock()
defer cl.chLock.Unlock()
Closer(cl.ch, &err)
return
}