|
7 | 7 | // Package chann provides a unified representation of buffered, |
8 | 8 | // unbuffered, and unbounded channels in Go. |
9 | 9 | // |
10 | | -// The package is compatible with existing buffered and unbuffered channels. |
11 | | -// For example, in Go, to create a buffered or unbuffered channel, one |
12 | | -// uses built-in function `make` to create a channel: |
| 10 | +// The package is compatible with existing buffered and unbuffered |
| 11 | +// channels. For example, in Go, to create a buffered or unbuffered |
| 12 | +// channel, one uses built-in function `make` to create a channel: |
13 | 13 | // |
14 | | -// ch := make(chan int) // unbuffered channel |
15 | | -// // or |
16 | | -// ch := make(chan int, 42) // buffered channel |
| 14 | +// ch := make(chan int) // unbuffered channel |
| 15 | +// ch := make(chan int, 42) // or buffered channel |
17 | 16 | // |
18 | 17 | // However, all these channels have a finite capacity for caching, and |
19 | 18 | // it is impossible to create a channel with unlimited capacity, namely, |
|
22 | 21 | // This package provides the ability to create all possible types of |
23 | 22 | // channels. To create an unbuffered or a buffered channel: |
24 | 23 | // |
25 | | -// ch := chann.New[int](chann.Cap(0)) // unbuffered channel |
26 | | -// // or |
27 | | -// ch := chann.New[int](chann.Cap(42)) // buffered channel |
| 24 | +// ch := chann.New[int](chann.Cap(0)) // unbuffered channel |
| 25 | +// ch := chann.New[int](chann.Cap(42)) // or buffered channel |
28 | 26 | // |
29 | 27 | // More importantly, when the capacity of the channel is unspecified, |
30 | 28 | // or provided as negative values, the created channel is an unbounded |
31 | 29 | // channel: |
32 | 30 | // |
33 | | -// ch := chann.New[int]() // unbounded channel |
34 | | -// // or |
35 | | -// ch := chann.New[int](chann.Cap(-42)) // unbounded channel |
| 31 | +// ch := chann.New[int]() // unbounded channel |
| 32 | +// ch := chann.New[int](chann.Cap(-42)) // or unbounded channel |
36 | 33 | // |
37 | 34 | // Furthermore, all channels provides methods to send (In()), |
38 | 35 | // receive (Out()), and close (Close()). |
| 36 | +// |
| 37 | +// Note that to close a channel, must use Close() method instead of the |
| 38 | +// language built-in method |
39 | 39 | // Two additional methods: ApproxLen and Cap returns the current status |
40 | 40 | // of the channel: an approximation of the current length of the channel, |
41 | 41 | // as well as the current capacity of the channel. |
|
45 | 45 | // with this package. |
46 | 46 | package chann |
47 | 47 |
|
48 | | -import "sync/atomic" |
| 48 | +import ( |
| 49 | +"sync/atomic" |
| 50 | +) |
49 | 51 |
|
50 | 52 | // Opt represents an option to configure the created channel. The current possible |
51 | 53 | // option is Cap. |
@@ -78,6 +80,7 @@ func Cap(n int) Opt { |
78 | 80 | // one, and use Cap to configure the capacity of the channel. |
79 | 81 | type Chann[T any] struct { |
80 | 82 | in, out chan T |
| 83 | +close chan struct{} |
81 | 84 | cfg *config |
82 | 85 | } |
83 | 86 |
|
@@ -121,58 +124,85 @@ func New[T any](opts ...Opt) *Chann[T] { |
121 | 124 | case unbounded: |
122 | 125 | ch.in = make(chan T, 16) |
123 | 126 | ch.out = make(chan T, 16) |
| 127 | +ch.close = make(chan struct{}) |
124 | 128 | ready := make(chan struct{}) |
| 129 | +var nilT T |
| 130 | + |
125 | 131 | go func() { |
126 | 132 | q := make([]T, 0, 1<<10) |
127 | 133 | ready <- struct{}{} |
128 | 134 | for { |
129 | | -e, ok := <-ch.in |
130 | | -if !ok { |
131 | | -close(ch.out) |
132 | | -return |
| 135 | +select { |
| 136 | +case e, ok := <-ch.in: |
| 137 | +if !ok { |
| 138 | +panic("chann: send-only channel ch.In() closed unexpectedly") |
| 139 | +} |
| 140 | +atomic.AddInt64(&ch.cfg.len, 1) |
| 141 | +q = append(q, e) |
| 142 | +case <-ch.close: |
| 143 | +goto closed |
133 | 144 | } |
134 | | -atomic.AddInt64(&ch.cfg.len, 1) |
135 | | -q = append(q, e) |
136 | 145 |
|
137 | 146 | for len(q) > 0 { |
138 | 147 | select { |
139 | 148 | case ch.out <- q[0]: |
140 | 149 | atomic.AddInt64(&ch.cfg.len, -1) |
| 150 | +q[0] = nilT |
141 | 151 | q = q[1:] |
142 | 152 | case e, ok := <-ch.in: |
143 | | -if ok { |
144 | | -atomic.AddInt64(&ch.cfg.len, 1) |
145 | | -q = append(q, e) |
146 | | -break |
| 153 | +if !ok { |
| 154 | +panic("chann: send-only channel ch.In() closed unexpectedly") |
147 | 155 | } |
148 | | -for _, e := range q { |
149 | | -atomic.AddInt64(&ch.cfg.len, -1) |
150 | | -ch.out <- e |
151 | | -} |
152 | | -close(ch.out) |
153 | | -return |
| 156 | +atomic.AddInt64(&ch.cfg.len, 1) |
| 157 | +q = append(q, e) |
| 158 | +case <-ch.close: |
| 159 | +goto closed |
154 | 160 | } |
155 | 161 | } |
156 | 162 | if cap(q) < 1<<5 { |
157 | 163 | q = make([]T, 0, 1<<10) |
158 | 164 | } |
159 | 165 | } |
| 166 | + |
| 167 | +closed: |
| 168 | +close(ch.in) |
| 169 | +for e := range ch.in { |
| 170 | +q = append(q, e) |
| 171 | +} |
| 172 | +for len(q) > 0 { |
| 173 | +select { |
| 174 | +case ch.out <- q[0]: |
| 175 | +q[0] = nilT // de-reference earlier to help GC |
| 176 | +q = q[1:] |
| 177 | +default: |
| 178 | +} |
| 179 | +} |
| 180 | +close(ch.out) |
| 181 | +close(ch.close) |
160 | 182 | }() |
161 | 183 | <-ready |
162 | 184 | } |
163 | 185 | return ch |
164 | 186 | } |
165 | 187 |
|
166 | 188 | // In returns the send channel of the given Chann, which can be used to |
167 | | -// send values to the channel. |
| 189 | +// send values to the channel. If one closes the channel using close(), |
| 190 | +// it will result in a runtime panic. Instead, use Close() method. |
168 | 191 | func (ch *Chann[T]) In() chan<- T { return ch.in } |
169 | 192 |
|
170 | 193 | // Out returns the receive channel of the given Chann, which can be used |
171 | 194 | // to receive values from the channel. |
172 | 195 | func (ch *Chann[T]) Out() <-chan T { return ch.out } |
173 | 196 |
|
174 | | -// Close closesa the channel. |
175 | | -func (ch *Chann[T]) Close() { close(ch.in) } |
| 197 | +// Close closes the channel gracefully. |
| 198 | +func (ch *Chann[T]) Close() { |
| 199 | +switch ch.cfg.typ { |
| 200 | +case buffered, unbuffered: |
| 201 | +close(ch.in) |
| 202 | +default: |
| 203 | +ch.close <- struct{}{} |
| 204 | +} |
| 205 | +} |
176 | 206 |
|
177 | 207 | // ApproxLen returns an approximation of the length of the channel. |
178 | 208 | // |
|
0 commit comments