Skip to content

Commit 4b5848f

Browse files
committed
As a workaround for golang-design/chann#5, vendor chann
1 parent f224b5e commit 4b5848f

File tree

3 files changed

+272
-1
lines changed

3 files changed

+272
-1
lines changed

chann/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This is a vendored version of https://github.com/amyangfei/chann/tree/should-consume-data-after-close - that exists here due to an unfixed bug in https://github.com/golang-design/chann/pull/5
2+
3+
`gparallel` uses a vendored version of `chann` instaad of a `replace` directive in the go.mod file, to be able to be installed with a simple `go install` - which doesn't work if there are any `replace` directives present.

chann/chann.go

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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 <changkun.de>
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
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+
}

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import (
1717

1818
"github.com/alessio/shellescape"
1919
"github.com/fatih/color"
20+
"github.com/karolba/gparallel/chann"
2021
"github.com/pkg/term/termios"
21-
"golang.design/x/chann"
2222
"golang.org/x/exp/slices"
2323
"golang.org/x/term"
2424
)

0 commit comments

Comments
 (0)