├── .github └── workflows │ └── chann.yml ├── .gitignore ├── LICENSE ├── README.md ├── chann.go ├── chann_test.go ├── example_test.go ├── export_test.go ├── go.mod ├── lb.go ├── lb_test.go ├── utils.go └── utils_test.go /.github/workflows/chann.yml: -------------------------------------------------------------------------------- 1 | name: chann 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - uses: actions/checkout@v2 17 | - uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.18 20 | 21 | - name: Test 22 | run: go test -v ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 The golang.design Initiative 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chann ![example workflow](https://github.com/golang-design/chann/actions/workflows/chann.yml/badge.svg) ![](https://changkun.de/urlstat?mode=github&repo=golang-design/chann) 2 | 3 | a unified channel package in Go 4 | 5 | ```go 6 | import "golang.design/x/chann" 7 | ``` 8 | 9 | This package requires Go 1.18. 10 | 11 | ## Basic Usage 12 | 13 | Different types of channels: 14 | 15 | ```go 16 | ch := chann.New[int]() // unbounded, capacity unlimited 17 | ch := chann.New[func()](chann.Cap(0)) // unbufferd, capacity 0 18 | ch := chann.New[string](chann.Cap(100)) // buffered, capacity 100 19 | ``` 20 | 21 | Send and receive operations: 22 | 23 | ```go 24 | ch.In() <- 42 25 | println(<-ch.Out()) // 42 26 | ``` 27 | 28 | Close operation: 29 | 30 | ```go 31 | ch.Close() 32 | ``` 33 | 34 | Channel properties: 35 | 36 | ```go 37 | ch.Len() // the length of the channel 38 | ch.Cap() // the capacity of the channel 39 | ``` 40 | 41 | See https://golang.design/research/ultimate-channel for more details of 42 | the motivation of this abstraction. 43 | 44 | ## License 45 | 46 | 47 | MIT | © 2021 The [golang.design](https://golang.design) Initiative Authors, written by [Changkun Ou](https://changkun.de). -------------------------------------------------------------------------------- /chann.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The golang.design Initiative Authors. 2 | // All rights reserved. Use of this source code is governed 3 | // by a MIT license that can be found in the LICENSE file. 4 | // 5 | // Written by Changkun Ou 6 | 7 | // Package chann providesa a unified channel package. 8 | // 9 | // The package is compatible with existing buffered and unbuffered 10 | // channels. For example, in Go, to create a buffered or unbuffered 11 | // channel, one uses built-in function `make` to create a channel: 12 | // 13 | // ch := make(chan int) // unbuffered channel 14 | // ch := make(chan int, 42) // or buffered channel 15 | // 16 | // However, all these channels have a finite capacity for caching, and 17 | // it is impossible to create a channel with unlimited capacity, namely, 18 | // an unbounded channel. 19 | // 20 | // This package provides the ability to create all possible types of 21 | // channels. To create an unbuffered or a buffered channel: 22 | // 23 | // ch := chann.New[int](chann.Cap(0)) // unbuffered channel 24 | // ch := chann.New[int](chann.Cap(42)) // or buffered channel 25 | // 26 | // More importantly, when the capacity of the channel is unspecified, 27 | // or provided as negative values, the created channel is an unbounded 28 | // channel: 29 | // 30 | // ch := chann.New[int]() // unbounded channel 31 | // ch := chann.New[int](chann.Cap(-42)) // or unbounded channel 32 | // 33 | // Furthermore, all channels provides methods to send (In()), 34 | // receive (Out()), and close (Close()). 35 | // 36 | // Note that to close a channel, must use Close() method instead of the 37 | // language built-in method 38 | // Two additional methods: ApproxLen and Cap returns the current status 39 | // of the channel: an approximation of the current length of the channel, 40 | // as well as the current capacity of the channel. 41 | // 42 | // See https://golang.design/research/ultimate-channel to understand 43 | // the motivation of providing this package and the possible use cases 44 | // with this package. 45 | package chann // import "golang.design/x/chann" 46 | 47 | import ( 48 | "sync/atomic" 49 | ) 50 | 51 | // Opt represents an option to configure the created channel. The current possible 52 | // option is Cap. 53 | type Opt func(*config) 54 | 55 | // Cap is the option to configure the capacity of a creating buffer. 56 | // if the provided number is 0, Cap configures the creating buffer to a 57 | // unbuffered channel; if the provided number is a positive integer, then 58 | // Cap configures the creating buffer to a buffered channel with the given 59 | // number of capacity for caching. If n is a negative integer, then it 60 | // configures the creating channel to become an unbounded channel. 61 | func Cap(n int) Opt { 62 | return func(s *config) { 63 | switch { 64 | case n == 0: 65 | s.cap = int64(0) 66 | s.typ = unbuffered 67 | case n > 0: 68 | s.cap = int64(n) 69 | s.typ = buffered 70 | default: 71 | s.cap = int64(-1) 72 | s.typ = unbounded 73 | } 74 | } 75 | } 76 | 77 | // Chann is a generic channel abstraction that can be either buffered, 78 | // unbuffered, or unbounded. To create a new channel, use New to allocate 79 | // one, and use Cap to configure the capacity of the channel. 80 | type Chann[T any] struct { 81 | in, out chan T 82 | close chan struct{} 83 | cfg *config 84 | q []T 85 | } 86 | 87 | // New returns a Chann that may be a buffered, an unbuffered or an 88 | // unbounded channel. To configure the type of the channel, use Cap. 89 | // 90 | // By default, or without specification, the function returns an unbounded 91 | // channel with unlimited capacity. 92 | // 93 | // ch := chann.New[float64]() 94 | // // or 95 | // ch := chann.New[float64](chann.Cap(-1)) 96 | // 97 | // If the chann.Cap specified a non-negative integer, the returned channel 98 | // is either unbuffered (0) or buffered (positive). 99 | // 100 | // An unbounded channel is not a buffered channel with infinite capacity, 101 | // and they have different memory model semantics in terms of receiving 102 | // a value: The recipient of a buffered channel is immediately available 103 | // after a send is complete. However, the recipient of an unbounded channel 104 | // may be available within a bounded time frame after a send is complete. 105 | // 106 | // Note that although the input arguments are specified as variadic parameter 107 | // list, however, the function panics if there is more than one option is 108 | // provided. 109 | func New[T any](opts ...Opt) *Chann[T] { 110 | cfg := &config{ 111 | cap: -1, len: 0, 112 | typ: unbounded, 113 | } 114 | 115 | if len(opts) > 1 { 116 | panic("chann: too many arguments") 117 | } 118 | for _, o := range opts { 119 | o(cfg) 120 | } 121 | ch := &Chann[T]{cfg: cfg, close: make(chan struct{})} 122 | switch ch.cfg.typ { 123 | case unbuffered: 124 | ch.in = make(chan T) 125 | ch.out = ch.in 126 | case buffered: 127 | ch.in = make(chan T, ch.cfg.cap) 128 | ch.out = ch.in 129 | case unbounded: 130 | ch.in = make(chan T, 16) 131 | ch.out = make(chan T, 16) 132 | go ch.unboundedProcessing() 133 | } 134 | return ch 135 | } 136 | 137 | // In returns the send channel of the given Chann, which can be used to 138 | // send values to the channel. If one closes the channel using close(), 139 | // it will result in a runtime panic. Instead, use Close() method. 140 | func (ch *Chann[T]) In() chan<- T { return ch.in } 141 | 142 | // Out returns the receive channel of the given Chann, which can be used 143 | // to receive values from the channel. 144 | func (ch *Chann[T]) Out() <-chan T { return ch.out } 145 | 146 | // Close closes the channel gracefully. 147 | func (ch *Chann[T]) Close() { 148 | switch ch.cfg.typ { 149 | case buffered, unbuffered: 150 | close(ch.in) 151 | close(ch.close) 152 | default: 153 | ch.close <- struct{}{} 154 | } 155 | } 156 | 157 | // unboundedProcessing is a processing loop that implements unbounded 158 | // channel semantics. 159 | func (ch *Chann[T]) unboundedProcessing() { 160 | var nilT T 161 | 162 | ch.q = make([]T, 0, 1<<10) 163 | for { 164 | select { 165 | case e, ok := <-ch.in: 166 | if !ok { 167 | panic("chann: send-only channel ch.In() closed unexpectedly") 168 | } 169 | atomic.AddInt64(&ch.cfg.len, 1) 170 | ch.q = append(ch.q, e) 171 | case <-ch.close: 172 | ch.unboundedTerminate() 173 | return 174 | } 175 | 176 | for len(ch.q) > 0 { 177 | select { 178 | case ch.out <- ch.q[0]: 179 | atomic.AddInt64(&ch.cfg.len, -1) 180 | ch.q[0] = nilT 181 | ch.q = ch.q[1:] 182 | case e, ok := <-ch.in: 183 | if !ok { 184 | panic("chann: send-only channel ch.In() closed unexpectedly") 185 | } 186 | atomic.AddInt64(&ch.cfg.len, 1) 187 | ch.q = append(ch.q, e) 188 | case <-ch.close: 189 | ch.unboundedTerminate() 190 | return 191 | } 192 | } 193 | if cap(ch.q) < 1<<5 { 194 | ch.q = make([]T, 0, 1<<10) 195 | } 196 | } 197 | } 198 | 199 | // unboundedTerminate terminates the unbounde channel's processing loop 200 | // and make sure all unprocessed elements either be consumed if there is 201 | // a pending receiver. 202 | func (ch *Chann[T]) unboundedTerminate() { 203 | var nilT T 204 | 205 | close(ch.in) 206 | for e := range ch.in { 207 | ch.q = append(ch.q, e) 208 | } 209 | for len(ch.q) > 0 { 210 | select { 211 | case ch.out <- ch.q[0]: 212 | // The default branch exists because we need guarantee 213 | // the loop can terminate. If there is a receiver, the 214 | // first case will ways be selected. See #3. 215 | default: 216 | } 217 | ch.q[0] = nilT // de-reference earlier to help GC 218 | ch.q = ch.q[1:] 219 | } 220 | close(ch.out) 221 | close(ch.close) 222 | } 223 | 224 | // isClose reports the close status of a channel. 225 | func (ch *Chann[T]) isClosed() bool { 226 | select { 227 | case <-ch.close: 228 | return true 229 | default: 230 | return false 231 | } 232 | } 233 | 234 | // Len returns an approximation of the length of the channel. 235 | // 236 | // Note that in a concurrent scenario, the returned length of a channel 237 | // may never be accurate. Hence the function is named with an Approx prefix. 238 | func (ch *Chann[T]) Len() int { 239 | switch ch.cfg.typ { 240 | case buffered, unbuffered: 241 | return len(ch.in) 242 | default: 243 | return int(atomic.LoadInt64(&ch.cfg.len)) + len(ch.in) + len(ch.out) 244 | } 245 | } 246 | 247 | // Cap returns the capacity of the channel. 248 | func (ch *Chann[T]) Cap() int { 249 | switch ch.cfg.typ { 250 | case buffered, unbuffered: 251 | return cap(ch.in) 252 | default: 253 | return int(atomic.LoadInt64(&ch.cfg.cap)) + cap(ch.in) + cap(ch.out) 254 | } 255 | } 256 | 257 | type chanType int 258 | 259 | const ( 260 | unbuffered chanType = iota 261 | buffered 262 | unbounded 263 | ) 264 | 265 | type config struct { 266 | typ chanType 267 | len, cap int64 268 | } 269 | -------------------------------------------------------------------------------- /chann_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Changkun Ou . All rights reserved. 2 | // Use of this source code is governed by a MIT license that 3 | // can be found in the LICENSE file. 4 | 5 | package chann_test 6 | 7 | import ( 8 | "runtime" 9 | "sync" 10 | "sync/atomic" 11 | "testing" 12 | "time" 13 | 14 | "golang.design/x/chann" 15 | ) 16 | 17 | func TestChan(t *testing.T) { 18 | defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) 19 | N := 200 20 | if testing.Short() { 21 | N = 20 22 | } 23 | for chanCap := 0; chanCap < N; chanCap++ { 24 | { 25 | // Ensure that receive from empty chan blocks. 26 | c := chann.New[int](chann.Cap(chanCap)) 27 | recv1 := false 28 | go func() { 29 | _ = <-c.Out() 30 | recv1 = true 31 | }() 32 | recv2 := false 33 | go func() { 34 | _, _ = <-c.Out() 35 | recv2 = true 36 | }() 37 | time.Sleep(time.Millisecond) 38 | if recv1 || recv2 { 39 | t.Fatalf("chan[%d]: receive from empty chan", chanCap) 40 | } 41 | // Ensure that non-blocking receive does not block. 42 | select { 43 | case _ = <-c.Out(): 44 | t.Fatalf("chan[%d]: receive from empty chan", chanCap) 45 | default: 46 | } 47 | select { 48 | case _, _ = <-c.Out(): 49 | t.Fatalf("chan[%d]: receive from empty chan", chanCap) 50 | default: 51 | } 52 | c.In() <- 0 53 | c.In() <- 0 54 | } 55 | 56 | { 57 | // Ensure that send to full chan blocks. 58 | c := chann.New[int](chann.Cap(chanCap)) 59 | for i := 0; i < chanCap; i++ { 60 | c.In() <- i 61 | } 62 | sent := uint32(0) 63 | go func() { 64 | c.In() <- 0 65 | atomic.StoreUint32(&sent, 1) 66 | }() 67 | time.Sleep(time.Millisecond) 68 | if atomic.LoadUint32(&sent) != 0 { 69 | t.Fatalf("chan[%d]: send to full chan", chanCap) 70 | } 71 | // Ensure that non-blocking send does not block. 72 | select { 73 | case c.In() <- 0: 74 | t.Fatalf("chan[%d]: send to full chan", chanCap) 75 | default: 76 | } 77 | <-c.Out() 78 | } 79 | 80 | { 81 | // Ensure that we receive 0 from closed chan. 82 | c := chann.New[int](chann.Cap(chanCap)) 83 | for i := 0; i < chanCap; i++ { 84 | c.In() <- i 85 | } 86 | c.Close() 87 | for i := 0; i < chanCap; i++ { 88 | v := <-c.Out() 89 | if v != i { 90 | t.Fatalf("chan[%d]: received %v, expected %v", chanCap, v, i) 91 | } 92 | } 93 | if v := <-c.Out(); v != 0 { 94 | t.Fatalf("chan[%d]: received %v, expected %v", chanCap, v, 0) 95 | } 96 | if v, ok := <-c.Out(); v != 0 || ok { 97 | t.Fatalf("chan[%d]: received %v/%v, expected %v/%v", chanCap, v, ok, 0, false) 98 | } 99 | } 100 | 101 | { 102 | // Ensure that close unblocks receive. 103 | c := chann.New[int](chann.Cap(chanCap)) 104 | done := make(chan bool) 105 | go func() { 106 | v, ok := <-c.Out() 107 | done <- v == 0 && ok == false 108 | }() 109 | time.Sleep(time.Millisecond) 110 | c.Close() 111 | if !<-done { 112 | t.Fatalf("chan[%d]: received non zero from closed chan", chanCap) 113 | } 114 | } 115 | 116 | { 117 | // Send 100 integers, 118 | // ensure that we receive them non-corrupted in FIFO order. 119 | c := chann.New[int](chann.Cap(chanCap)) 120 | go func() { 121 | for i := 0; i < 100; i++ { 122 | c.In() <- i 123 | } 124 | }() 125 | for i := 0; i < 100; i++ { 126 | v := <-c.Out() 127 | if v != i { 128 | t.Fatalf("chan[%d]: received %v, expected %v", chanCap, v, i) 129 | } 130 | } 131 | 132 | // Same, but using recv2. 133 | go func() { 134 | for i := 0; i < 100; i++ { 135 | c.In() <- i 136 | } 137 | }() 138 | for i := 0; i < 100; i++ { 139 | v, ok := <-c.Out() 140 | if !ok { 141 | t.Fatalf("chan[%d]: receive failed, expected %v", chanCap, i) 142 | } 143 | if v != i { 144 | t.Fatalf("chan[%d]: received %v, expected %v", chanCap, v, i) 145 | } 146 | } 147 | 148 | // Send 1000 integers in 4 goroutines, 149 | // ensure that we receive what we send. 150 | const P = 4 151 | const L = 1000 152 | for p := 0; p < P; p++ { 153 | go func() { 154 | for i := 0; i < L; i++ { 155 | c.In() <- i 156 | } 157 | }() 158 | } 159 | done := chann.New[map[int]int](chann.Cap(0)) 160 | for p := 0; p < P; p++ { 161 | go func() { 162 | recv := make(map[int]int) 163 | for i := 0; i < L; i++ { 164 | v := <-c.Out() 165 | recv[v] = recv[v] + 1 166 | } 167 | done.In() <- recv 168 | }() 169 | } 170 | recv := make(map[int]int) 171 | for p := 0; p < P; p++ { 172 | for k, v := range <-done.Out() { 173 | recv[k] = recv[k] + v 174 | } 175 | } 176 | if len(recv) != L { 177 | t.Fatalf("chan[%d]: received %v values, expected %v", chanCap, len(recv), L) 178 | } 179 | for _, v := range recv { 180 | if v != P { 181 | t.Fatalf("chan[%d]: received %v values, expected %v", chanCap, v, P) 182 | } 183 | } 184 | } 185 | 186 | { 187 | // Test len/cap. 188 | c := chann.New[int](chann.Cap(chanCap)) 189 | if c.Len() != 0 || c.Cap() != chanCap { 190 | t.Fatalf("chan[%d]: bad len/cap, expect %v/%v, got %v/%v", chanCap, 0, chanCap, c.Len(), c.Cap()) 191 | } 192 | for i := 0; i < chanCap; i++ { 193 | c.In() <- i 194 | } 195 | if c.Len() != chanCap || c.Cap() != chanCap { 196 | t.Fatalf("chan[%d]: bad len/cap, expect %v/%v, got %v/%v", chanCap, chanCap, chanCap, c.Len(), c.Cap()) 197 | } 198 | } 199 | } 200 | } 201 | 202 | func TestNonblockRecvRace(t *testing.T) { 203 | n := 10000 204 | if testing.Short() { 205 | n = 100 206 | } 207 | for i := 0; i < n; i++ { 208 | c := chann.New[int](chann.Cap(1)) 209 | c.In() <- 1 210 | t.Log(i) 211 | go func() { 212 | select { 213 | case <-c.Out(): 214 | default: 215 | t.Error("chan is not ready") 216 | } 217 | }() 218 | c.Close() 219 | <-c.Out() 220 | if t.Failed() { 221 | return 222 | } 223 | } 224 | } 225 | 226 | const internalCacheSize = 16 + 1<<10 227 | 228 | // This test checks that select acts on the state of the channels at one 229 | // moment in the execution, not over a smeared time window. 230 | // In the test, one goroutine does: 231 | // create c1, c2 232 | // make c1 ready for receiving 233 | // create second goroutine 234 | // make c2 ready for receiving 235 | // make c1 no longer ready for receiving (if possible) 236 | // The second goroutine does a non-blocking select receiving from c1 and c2. 237 | // From the time the second goroutine is created, at least one of c1 and c2 238 | // is always ready for receiving, so the select in the second goroutine must 239 | // always receive from one or the other. It must never execute the default case. 240 | func TestNonblockSelectRace(t *testing.T) { 241 | n := 1000 242 | done := chann.New[bool](chann.Cap(1)) 243 | for i := 0; i < n; i++ { 244 | c1 := chann.New[int]() 245 | c2 := chann.New[int]() 246 | 247 | // The input channel of an unbounded buffer have an internal 248 | // cache queue. When the input channel and the internal cache 249 | // queue both gets full, we are certain that once the next send 250 | // is complete, the out will be available for sure hence the 251 | // waiting time of a receive is bounded. 252 | for i := 0; i < internalCacheSize; i++ { 253 | c1.In() <- 1 254 | } 255 | c1.In() <- 1 256 | go func() { 257 | select { 258 | case <-c1.Out(): 259 | case <-c2.Out(): 260 | default: 261 | done.In() <- false 262 | return 263 | } 264 | done.In() <- true 265 | }() 266 | // Same for c2 267 | for i := 0; i < internalCacheSize; i++ { 268 | c2.In() <- 1 269 | } 270 | c2.In() <- 1 271 | select { 272 | case <-c1.Out(): 273 | default: 274 | } 275 | if !<-done.Out() { 276 | t.Fatal("no chan is ready") 277 | } 278 | } 279 | } 280 | 281 | // Same as TestNonblockSelectRace, but close(c2) replaces c2 <- 1. 282 | func TestNonblockSelectRace2(t *testing.T) { 283 | n := 1000 284 | done := make(chan bool, 1) 285 | for i := 0; i < n; i++ { 286 | c1 := chann.New[int]() 287 | c2 := chann.New[int]() 288 | 289 | // See TestNonblockSelectRace. 290 | for i := 0; i < internalCacheSize; i++ { 291 | c1.In() <- 1 292 | } 293 | c1.In() <- 1 294 | go func() { 295 | select { 296 | case <-c1.Out(): 297 | case <-c2.Out(): 298 | default: 299 | done <- false 300 | return 301 | } 302 | done <- true 303 | }() 304 | c2.Close() 305 | select { 306 | case <-c1.Out(): 307 | default: 308 | } 309 | if !<-done { 310 | t.Fatal("no chan is ready") 311 | } 312 | } 313 | } 314 | 315 | func TestUnboundedChann(t *testing.T) { 316 | N := 200 317 | if testing.Short() { 318 | N = 20 319 | } 320 | 321 | wg := sync.WaitGroup{} 322 | for i := 0; i < N; i++ { 323 | t.Run("interface{}", func(t *testing.T) { 324 | t.Run("send", func(t *testing.T) { 325 | // Ensure send to an unbounded channel does not block. 326 | c := chann.New[interface{}]() 327 | blocked := false 328 | wg.Add(1) 329 | go func() { 330 | defer wg.Done() 331 | select { 332 | case c.In() <- true: 333 | default: 334 | blocked = true 335 | } 336 | }() 337 | wg.Wait() 338 | if blocked { 339 | t.Fatalf("send op to an unbounded channel blocked") 340 | } 341 | c.Close() 342 | }) 343 | 344 | t.Run("recv", func(t *testing.T) { 345 | // Ensure that receive op from unbounded chan can happen on 346 | // the same goroutine of send op. 347 | c := chann.New[interface{}]() 348 | wg.Add(1) 349 | go func() { 350 | defer wg.Done() 351 | c.In() <- true 352 | <-c.Out() 353 | }() 354 | wg.Wait() 355 | c.Close() 356 | }) 357 | t.Run("order", func(t *testing.T) { 358 | // Ensure that the unbounded channel processes everything FIFO. 359 | c := chann.New[interface{}]() 360 | for i := 0; i < 1<<11; i++ { 361 | c.In() <- i 362 | } 363 | for i := 0; i < 1<<11; i++ { 364 | if val := <-c.Out(); val != i { 365 | t.Fatalf("unbounded channel passes messages in a non-FIFO order, got %v want %v", val, i) 366 | } 367 | } 368 | c.Close() 369 | }) 370 | }) 371 | t.Run("struct{}", func(t *testing.T) { 372 | t.Run("send", func(t *testing.T) { 373 | // Ensure send to an unbounded channel does not block. 374 | c := chann.New[struct{}]() 375 | blocked := false 376 | wg.Add(1) 377 | go func() { 378 | defer wg.Done() 379 | select { 380 | case c.In() <- struct{}{}: 381 | default: 382 | blocked = true 383 | } 384 | }() 385 | <-c.Out() 386 | wg.Wait() 387 | if blocked { 388 | t.Fatalf("send op to an unbounded channel blocked") 389 | } 390 | c.Close() 391 | }) 392 | 393 | t.Run("recv", func(t *testing.T) { 394 | // Ensure that receive op from unbounded chan can happen on 395 | // the same goroutine of send op. 396 | c := chann.New[struct{}]() 397 | wg.Add(1) 398 | go func() { 399 | defer wg.Done() 400 | c.In() <- struct{}{} 401 | <-c.Out() 402 | }() 403 | wg.Wait() 404 | c.Close() 405 | }) 406 | t.Run("order", func(t *testing.T) { 407 | // Ensure that the unbounded channel processes everything FIFO. 408 | c := chann.New[struct{}]() 409 | for i := 0; i < 1<<11; i++ { 410 | c.In() <- struct{}{} 411 | } 412 | n := 0 413 | for i := 0; i < 1<<11; i++ { 414 | if _, ok := <-c.Out(); ok { 415 | n++ 416 | } 417 | } 418 | if n != 1<<11 { 419 | t.Fatalf("unbounded channel missed a message, got %v want %v", n, 1<<11) 420 | } 421 | c.Close() 422 | }) 423 | }) 424 | } 425 | } 426 | 427 | func TestUnboundedChannClose(t *testing.T) { 428 | t.Run("close-status", func(t *testing.T) { 429 | 430 | ch := chann.New[any]() 431 | for i := 0; i < 100; i++ { 432 | ch.In() <- 0 433 | } 434 | ch.Close() 435 | 436 | // Theoretically, this is not a dead loop. If the channel 437 | // is closed, then this loop must terminate at somepoint. 438 | // If not, we will meet timeout in the test. 439 | for !chann.IsClosed(ch) { 440 | t.Log("unbounded channel is still not entirely closed") 441 | } 442 | }) 443 | 444 | t.Run("struct{}", func(t *testing.T) { 445 | grs := runtime.NumGoroutine() 446 | N := 10 447 | n := 0 448 | done := make(chan struct{}) 449 | ch := chann.New[struct{}]() 450 | for i := 0; i < N; i++ { 451 | ch.In() <- struct{}{} 452 | } 453 | go func() { 454 | for range ch.Out() { 455 | n++ 456 | } 457 | done <- struct{}{} 458 | }() 459 | ch.Close() 460 | <-done 461 | runtime.GC() 462 | if runtime.NumGoroutine() > grs+2 { 463 | t.Fatalf("leaking goroutines: %v", n) 464 | } 465 | if n != N { 466 | t.Fatalf("After close, not all elements are received, got %v, want %v", n, N) 467 | } 468 | }) 469 | 470 | t.Run("interface{}", func(t *testing.T) { 471 | grs := runtime.NumGoroutine() 472 | N := 10 473 | n := 0 474 | done := make(chan struct{}) 475 | ch := chann.New[interface{}]() 476 | for i := 0; i < N; i++ { 477 | ch.In() <- true 478 | } 479 | go func() { 480 | for range ch.Out() { 481 | n++ 482 | } 483 | done <- struct{}{} 484 | }() 485 | ch.Close() 486 | <-done 487 | runtime.GC() 488 | if runtime.NumGoroutine() > grs+2 { 489 | t.Fatalf("leaking goroutines: %v", n) 490 | } 491 | if n != N { 492 | t.Fatalf("After close, not all elements are received, got %v, want %v", n, N) 493 | } 494 | }) 495 | } 496 | 497 | func BenchmarkUnboundedChann(b *testing.B) { 498 | b.Run("interface{}", func(b *testing.B) { 499 | b.Run("sync", func(b *testing.B) { 500 | c := chann.New[interface{}]() 501 | b.ResetTimer() 502 | b.ReportAllocs() 503 | for i := 0; i < b.N; i++ { 504 | c.In() <- struct{}{} 505 | <-c.Out() 506 | } 507 | }) 508 | b.Run("chann", func(b *testing.B) { 509 | c := chann.New[interface{}]() 510 | b.ResetTimer() 511 | b.ReportAllocs() 512 | for i := 0; i < b.N; i++ { 513 | go func() { c.In() <- struct{}{} }() 514 | <-c.Out() 515 | } 516 | }) 517 | }) 518 | b.Run("struct{}", func(b *testing.B) { 519 | b.Run("sync", func(b *testing.B) { 520 | c := chann.New[struct{}]() 521 | b.ResetTimer() 522 | b.ReportAllocs() 523 | for i := 0; i < b.N; i++ { 524 | c.In() <- struct{}{} 525 | <-c.Out() 526 | } 527 | }) 528 | b.Run("chann", func(b *testing.B) { 529 | c := chann.New[struct{}]() 530 | b.ResetTimer() 531 | b.ReportAllocs() 532 | for i := 0; i < b.N; i++ { 533 | go func() { c.In() <- struct{}{} }() 534 | <-c.Out() 535 | } 536 | }) 537 | }) 538 | } 539 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The golang.design Initiative Authors. 2 | // All rights reserved. Use of this source code is governed 3 | // by a MIT license that can be found in the LICENSE file. 4 | // 5 | // Written by Changkun Ou 6 | 7 | package chann_test 8 | 9 | import ( 10 | "fmt" 11 | 12 | "golang.design/x/chann" 13 | ) 14 | 15 | func ExampleNew() { 16 | ch := chann.New[int]() 17 | 18 | go func() { 19 | for i := 0; i < 10; i++ { 20 | ch.In() <- i // never block 21 | } 22 | ch.Close() 23 | }() 24 | 25 | sum := 0 26 | for i := range ch.Out() { 27 | sum += i 28 | } 29 | fmt.Println(sum) 30 | // Output: 31 | // 45 32 | } 33 | -------------------------------------------------------------------------------- /export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The golang.design Initiative Authors. 2 | // All rights reserved. Use of this source code is governed 3 | // by a MIT license that can be found in the LICENSE file. 4 | // 5 | // Written by Changkun Ou 6 | 7 | package chann 8 | 9 | // IsClosed checks if a channel is entirely closed or not. 10 | // This function is only exported for testing. 11 | func IsClosed[T any](ch *Chann[T]) bool { 12 | return ch.isClosed() 13 | } 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module golang.design/x/chann 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /lb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The golang.design Initiative Authors. 2 | // All rights reserved. Use of this source code is governed 3 | // by a MIT license that can be found in the LICENSE file. 4 | // 5 | // Written by Changkun Ou 6 | 7 | package chann 8 | 9 | import ( 10 | "math/rand" 11 | "sync" 12 | ) 13 | 14 | // Fanin provides a generic fan-in functionality for variadic channels. 15 | func Fanin[T any](chans ...*Chann[T]) *Chann[T] { 16 | buf := 0 17 | for _, ch := range chans { 18 | if ch.Len() > buf { 19 | buf = ch.Len() 20 | } 21 | } 22 | out := New[T](Cap(buf)) 23 | wg := sync.WaitGroup{} 24 | wg.Add(len(chans)) 25 | for _, ch := range chans { 26 | go func(ch *Chann[T]) { 27 | for v := range ch.Out() { 28 | out.In() <- v 29 | } 30 | wg.Done() 31 | }(ch) 32 | } 33 | go func() { 34 | wg.Wait() 35 | out.Close() 36 | }() 37 | return out 38 | } 39 | 40 | // Fanout provides a generic fan-out functionality for variadic channels. 41 | func Fanout[T any](randomizer func(max int) int, in *Chann[T], outs ...*Chann[T]) { 42 | l := len(outs) 43 | for v := range in.Out() { 44 | i := randomizer(l) 45 | if i < 0 || i > l { 46 | i = rand.Intn(l) 47 | } 48 | go func(v T) { outs[i].In() <- v }(v) 49 | } 50 | } 51 | 52 | // LB load balances the given input channels to the output channels. 53 | func LB[T any](randomizer func(max int) int, ins []*Chann[T], outs []*Chann[T]) { 54 | Fanout(randomizer, Fanin(ins...), outs...) 55 | } 56 | -------------------------------------------------------------------------------- /lb_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The golang.design Initiative Authors. 2 | // All rights reserved. Use of this source code is governed 3 | // by a MIT license that can be found in the LICENSE file. 4 | // 5 | // Written by Changkun Ou 6 | 7 | package chann_test 8 | 9 | import ( 10 | "math/rand" 11 | "testing" 12 | 13 | "golang.design/x/chann" 14 | ) 15 | 16 | func getInputChan() *chann.Chann[int] { 17 | input := chann.New[int](chann.Cap(20)) 18 | numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 19 | go func() { 20 | for num := range numbers { 21 | input.In() <- num 22 | } 23 | input.Close() 24 | }() 25 | return input 26 | } 27 | 28 | func TestFanin(t *testing.T) { 29 | chs := make([]*chann.Chann[int], 10) 30 | for i := 0; i < 10; i++ { 31 | chs[i] = getInputChan() 32 | } 33 | 34 | out := chann.Fanin(chs...) 35 | count := 0 36 | for range out.Out() { 37 | count++ 38 | } 39 | if count != 100 { 40 | t.Fatalf("Fanin failed") 41 | } 42 | } 43 | 44 | func TestLB(t *testing.T) { 45 | ins := make([]*chann.Chann[int], 10) 46 | for i := 0; i < 10; i++ { 47 | ins[i] = getInputChan() 48 | } 49 | outs := make([]*chann.Chann[int], 10) 50 | for i := 0; i < 10; i++ { 51 | outs[i] = chann.New[int](chann.Cap(10)) 52 | } 53 | chann.LB(func(m int) int { return rand.Intn(m) }, ins, outs) 54 | } 55 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The golang.design Initiative Authors. 2 | // All rights reserved. Use of this source code is governed 3 | // by a MIT license that can be found in the LICENSE file. 4 | // 5 | // Written by Changkun Ou 6 | 7 | package chann 8 | 9 | import "runtime" 10 | 11 | // Ranger returns a Sender and a Receiver. The Receiver provides a 12 | // Next method to retrieve values. The Sender provides a Send method 13 | // to send values and a Close method to stop sending values. The Next 14 | // method indicates when the Sender has been closed, and the Send 15 | // method indicates when the Receiver has been freed. 16 | // 17 | // This is a convenient way to exit a goroutine sending values when 18 | // the receiver stops reading them. 19 | func Ranger[T any]() (*Sender[T], *Receiver[T]) { 20 | c := New[T]() 21 | d := New[bool]() 22 | s := &Sender[T]{values: c, done: d} 23 | r := &Receiver[T]{values: c, done: d} 24 | runtime.SetFinalizer(r, func(r *Receiver[T]) { r.finalize() }) 25 | return s, r 26 | } 27 | 28 | // A sender is used to send values to a Receiver. 29 | type Sender[T any] struct { 30 | values *Chann[T] 31 | done *Chann[bool] 32 | } 33 | 34 | // Send sends a value to the receiver. It returns whether any more 35 | // values may be sent; if it returns false the value was not sent. 36 | func (s *Sender[T]) Send(v T) bool { 37 | select { 38 | case s.values.In() <- v: 39 | return true 40 | case <-s.done.Out(): 41 | return false 42 | } 43 | } 44 | 45 | // Close tells the receiver that no more values will arrive. 46 | // After Close is called, the Sender may no longer be used. 47 | func (s *Sender[T]) Close() { s.values.Close() } 48 | 49 | // A Receiver receives values from a Sender. 50 | type Receiver[T any] struct { 51 | values *Chann[T] 52 | done *Chann[bool] 53 | } 54 | 55 | // Next returns the next value from the channel. The bool result 56 | // indicates whether the value is valid, or whether the Sender has 57 | // been closed and no more values will be received. 58 | func (r *Receiver[T]) Next() (T, bool) { 59 | v, ok := <-r.values.Out() 60 | return v, ok 61 | } 62 | 63 | // finalize is a finalizer for the receiver. 64 | func (r *Receiver[T]) finalize() { r.done.Close() } 65 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The golang.design Initiative Authors. 2 | // All rights reserved. Use of this source code is governed 3 | // by a MIT license that can be found in the LICENSE file. 4 | // 5 | // Written by Changkun Ou 6 | 7 | package chann_test 8 | 9 | import ( 10 | "testing" 11 | 12 | "golang.design/x/chann" 13 | ) 14 | 15 | func TestRanger(t *testing.T) { 16 | s, r := chann.Ranger[int]() 17 | 18 | go func() { 19 | s.Send(42) 20 | }() 21 | 22 | n, ok := r.Next() 23 | if !ok { 24 | t.Fatalf("cannot receive from senter") 25 | } 26 | t.Log(n) 27 | } 28 | --------------------------------------------------------------------------------