-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sync.go
84 lines (73 loc) · 1.88 KB
/
sync.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package x
import (
"context"
"sync"
"gitlab.com/tozd/go/errors"
)
var ErrSyncVarAlreadyStored = errors.Base("already stored")
// SyncVar allows multiple goroutines to wait for a value
// to be available while one other goroutine stored the value.
//
// It is useful if you do not know in advance which goroutine
// will be the one (and only one) which stores the value while
// all goroutines need the value.
//
// The zero value for a SyncVar is not usable. Use NewSyncVar.
type SyncVar[T any] struct {
lock *sync.RWMutex
cond *sync.Cond
v *T
}
// Load returns the value stored in the SyncVar. It blocks
// until the value is stored.
func (w *SyncVar[T]) Load() T { //nolint:ireturn
w.cond.L.Lock()
defer w.cond.L.Unlock()
for w.v == nil {
w.cond.Wait()
}
return *w.v
}
// LoadContext is similar to Load, but it stops waiting if ctx gets cancelled,
// returning an error in that case.
func (w *SyncVar[T]) LoadContext(ctx context.Context) (T, errors.E) { //nolint:ireturn
w.cond.L.Lock()
defer w.cond.L.Unlock()
// This is based on example for context.AfterFunc from the context package.
// See comments there for explanation how it works and why.
stop := context.AfterFunc(ctx, func() {
w.cond.L.Lock()
defer w.cond.L.Unlock()
w.cond.Broadcast()
})
defer stop()
for w.v == nil {
w.cond.Wait()
if ctx.Err() != nil {
return *new(T), errors.WithStack(ctx.Err())
}
}
return *w.v, nil
}
// Store stores the value in the SyncVar and unblocks
// any prior calls to Load.
// It can be called only once.
func (w *SyncVar[T]) Store(v T) errors.E {
w.lock.Lock()
defer w.lock.Unlock()
if w.v != nil {
return errors.WithStack(ErrSyncVarAlreadyStored)
}
w.v = &v
w.cond.Broadcast()
return nil
}
// NewSyncVar creates a new SyncVar.
func NewSyncVar[T any]() *SyncVar[T] {
l := &sync.RWMutex{}
return &SyncVar[T]{
lock: l,
cond: sync.NewCond(l.RLocker()),
v: nil,
}
}