Skip to content

Commit b4d02a2

Browse files
committed
syncs: add new package for extra sync types
1 parent 57f2206 commit b4d02a2

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

syncs/syncs.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package syncs contains addition sync types.
6+
package syncs
7+
8+
import "sync/atomic"
9+
10+
// ClosedChan returns a channel that's already closed.
11+
func ClosedChan() <-chan struct{} { return closedChan }
12+
13+
var closedChan = initClosedChan()
14+
15+
func initClosedChan() <-chan struct{} {
16+
ch := make(chan struct{})
17+
close(ch)
18+
return ch
19+
}
20+
21+
// WaitGroupChan is like a sync.WaitGroup, but has a chan that closes
22+
// on completion that you can wait on. (This, you can only use the
23+
// value once)
24+
// Also, its zero value is not usable. Use the constructor.
25+
type WaitGroupChan struct {
26+
n int64 // atomic
27+
done chan struct{} // closed on transition to zero
28+
}
29+
30+
// NewWaitGroupChan returns a new single-use WaitGroupChan.
31+
func NewWaitGroupChan() *WaitGroupChan {
32+
return &WaitGroupChan{done: make(chan struct{})}
33+
}
34+
35+
// DoneChan returns a channel that's closed on completion.
36+
func (c *WaitGroupChan) DoneChan() <-chan struct{} { return c.done }
37+
38+
// Add adds delta, which may be negative, to the WaitGroupChan
39+
// counter. If the counter becomes zero, all goroutines blocked on
40+
// Wait or the Done chan are released. If the counter goes negative,
41+
// Add panics.
42+
//
43+
// Note that calls with a positive delta that occur when the counter
44+
// is zero must happen before a Wait. Calls with a negative delta, or
45+
// calls with a positive delta that start when the counter is greater
46+
// than zero, may happen at any time. Typically this means the calls
47+
// to Add should execute before the statement creating the goroutine
48+
// or other event to be waited for.
49+
func (c *WaitGroupChan) Add(delta int) {
50+
n := atomic.AddInt64(&c.n, int64(delta))
51+
if n == 0 {
52+
close(c.done)
53+
}
54+
}
55+
56+
// Decr decrements the WaitGroup counter by one.
57+
//
58+
// (It is like sync.WaitGroup's Done method, but we don't use Done in
59+
// this type, because it's ambiguous between Context.Done and
60+
// WaitGroup.Done. So we use DoneChan and Decr instead.)
61+
func (wg *WaitGroupChan) Decr() {
62+
wg.Add(-1)
63+
}
64+
65+
// Wait blocks until the WaitGroupChan counter is zero.
66+
func (wg *WaitGroupChan) Wait() { <-wg.done }

syncs/syncs_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package syncs
6+
7+
import "testing"
8+
9+
func TestWaitGroupChan(t *testing.T) {
10+
wg := NewWaitGroupChan()
11+
12+
wantNotDone := func() {
13+
t.Helper()
14+
select {
15+
case <-wg.DoneChan():
16+
t.Fatal("done too early")
17+
default:
18+
}
19+
}
20+
21+
wantDone := func() {
22+
t.Helper()
23+
select {
24+
case <-wg.DoneChan():
25+
default:
26+
t.Fatal("expected to be done")
27+
}
28+
}
29+
30+
wg.Add(2)
31+
wantNotDone()
32+
33+
wg.Decr()
34+
wantNotDone()
35+
36+
wg.Decr()
37+
wantDone()
38+
wantDone()
39+
}
40+
41+
func TestClosedChan(t *testing.T) {
42+
ch := ClosedChan()
43+
for i := 0; i < 2; i++ {
44+
select {
45+
case <-ch:
46+
default:
47+
t.Fatal("not closed")
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)