Skip to content

Commit 56c815c

Browse files
authored
Merge pull request #120 from multiformats/marco/synctest-friendly-oncefunc
refactor(lazyClientConn): Use synctest friendly once func
2 parents 41b0995 + 95a76d7 commit 56c815c

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

lazyClient.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package multistream
33
import (
44
"fmt"
55
"io"
6-
"sync"
76
)
87

98
// NewMSSelect returns a new Multistream which is able to perform
@@ -12,6 +11,9 @@ func NewMSSelect[T StringLike](c io.ReadWriteCloser, proto T) LazyConn {
1211
return &lazyClientConn[T]{
1312
protos: []T{ProtocolID, proto},
1413
con: c,
14+
15+
rhandshakeOnce: newOnce(),
16+
whandshakeOnce: newOnce(),
1517
}
1618
}
1719

@@ -22,7 +24,38 @@ func NewMultistream[T StringLike](c io.ReadWriteCloser, proto T) LazyConn {
2224
return &lazyClientConn[T]{
2325
protos: []T{proto},
2426
con: c,
27+
28+
rhandshakeOnce: newOnce(),
29+
whandshakeOnce: newOnce(),
30+
}
31+
}
32+
33+
// once is a sync.Once that can be used by synctest.
34+
// For Multistream, it is a bit better than sync.Once because it doesn't
35+
// spin when acquiring the lock.
36+
type once struct {
37+
sem chan struct{}
38+
}
39+
40+
func newOnce() *once {
41+
o := once{
42+
sem: make(chan struct{}, 1),
43+
}
44+
o.sem <- struct{}{}
45+
return &o
46+
}
47+
48+
func (o *once) Do(f func()) {
49+
// We only ever pull a single value from the channel. But we want to block
50+
// Do until the first call to Do has completed. The first call will close
51+
// the channel, so by checking if it's closed we know we don't need to do
52+
// anything.
53+
_, ok := <-o.sem
54+
if !ok {
55+
return
2556
}
57+
defer close(o.sem)
58+
f()
2659
}
2760

2861
// lazyClientConn is a ReadWriteCloser adapter that lazily negotiates a protocol
@@ -33,11 +66,11 @@ func NewMultistream[T StringLike](c io.ReadWriteCloser, proto T) LazyConn {
3366
// See: https://github.com/multiformats/go-multistream/issues/20
3467
type lazyClientConn[T StringLike] struct {
3568
// Used to ensure we only trigger the write half of the handshake once.
36-
rhandshakeOnce sync.Once
69+
rhandshakeOnce *once
3770
rerr error
3871

3972
// Used to ensure we only trigger the read half of the handshake once.
40-
whandshakeOnce sync.Once
73+
whandshakeOnce *once
4174
werr error
4275

4376
// The sequence of protocols to negotiate.

multistream_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net"
1010
"sort"
1111
"strings"
12+
"sync"
1213
"sync/atomic"
1314
"testing"
1415
"time"
@@ -938,3 +939,27 @@ func TestComparableErrors(t *testing.T) {
938939
t.Fatalf("Should be read as ErrNotSupported")
939940
}
940941
}
942+
943+
func TestOnceFunc(t *testing.T) {
944+
o := newOnce()
945+
start := make(chan struct{})
946+
var runCount int
947+
var wg sync.WaitGroup
948+
const workers = 3
949+
wg.Add(workers)
950+
for range workers {
951+
go func() {
952+
defer wg.Done()
953+
<-start
954+
o.Do(func() { runCount++ })
955+
if runCount != 1 {
956+
t.Errorf("Do returned before func was run")
957+
}
958+
}()
959+
}
960+
close(start)
961+
wg.Wait()
962+
if runCount != 1 {
963+
t.Fatalf("should have run only once")
964+
}
965+
}

0 commit comments

Comments
 (0)