Skip to content

Commit 8feb798

Browse files
committed
Generic singleflight
1 parent f856ab8 commit 8feb798

4 files changed

Lines changed: 39 additions & 37 deletions

File tree

.github/workflows/go.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ jobs:
1414
- name: Set up Go
1515
uses: actions/setup-go@v2
1616
with:
17-
go-version: 1.16
17+
stable: 'false'
18+
go-version: '1.18.0-beta1'
1819

1920
- name: Checkout
2021
uses: actions/checkout@v1
@@ -31,11 +32,12 @@ jobs:
3132
${{ runner.OS }}-build-
3233
${{ runner.OS }}-
3334
34-
- name: Lint
35-
uses: golangci/golangci-lint-action@v2
36-
with:
37-
version: v1.40.1
38-
args: --timeout 10m
35+
# todo: enable when go 1.18 is supported
36+
# - name: Lint
37+
# uses: golangci/golangci-lint-action@v2
38+
# with:
39+
# version: v1.43.0
40+
# args: --timeout 10m
3941

4042
- name: Vet
4143
if: matrix.os == 'ubuntu-latest'

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module resenje.org/singleflight
22

3-
go 1.13
3+
go 1.18

singleflight.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import (
1515

1616
// Group represents a class of work and forms a namespace in
1717
// which units of work can be executed with duplicate suppression.
18-
type Group struct {
19-
calls map[string]*call // lazily initialized
18+
type Group[K comparable, V any] struct {
19+
calls map[K]*call[V] // lazily initialized
2020
mu sync.Mutex // protects calls
2121
}
2222

@@ -31,10 +31,10 @@ type Group struct {
3131
// effect the execution and returned values of others.
3232
//
3333
// The return value shared indicates whether v was given to multiple callers.
34-
func (g *Group) Do(ctx context.Context, key string, fn func(ctx context.Context) (interface{}, error)) (v interface{}, shared bool, err error) {
34+
func (g *Group[K, V]) Do(ctx context.Context, key K, fn func(ctx context.Context) (V, error)) (v V, shared bool, err error) {
3535
g.mu.Lock()
3636
if g.calls == nil {
37-
g.calls = make(map[string]*call)
37+
g.calls = make(map[K]*call[V])
3838
}
3939

4040
if c, ok := g.calls[key]; ok {
@@ -47,7 +47,7 @@ func (g *Group) Do(ctx context.Context, key string, fn func(ctx context.Context)
4747

4848
callCtx, cancel := context.WithCancel(context.Background())
4949

50-
c := &call{
50+
c := &call[V]{
5151
done: make(chan struct{}),
5252
cancel: cancel,
5353
counter: 1,
@@ -64,7 +64,7 @@ func (g *Group) Do(ctx context.Context, key string, fn func(ctx context.Context)
6464
}
6565

6666
// wait for function passed to Do to finish or context to be done.
67-
func (g *Group) wait(ctx context.Context, key string, c *call) (v interface{}, shared bool, err error) {
67+
func (g *Group[K, V]) wait(ctx context.Context, key K, c *call[V]) (v V, shared bool, err error) {
6868
select {
6969
case <-c.done:
7070
v = c.val
@@ -87,7 +87,7 @@ func (g *Group) wait(ctx context.Context, key string, c *call) (v interface{}, s
8787
// Forget tells the singleflight to forget about a key. Future calls
8888
// to Do for this key will call the function rather than waiting for
8989
// an earlier call to complete.
90-
func (g *Group) Forget(key string) {
90+
func (g *Group[K, V]) Forget(key K) {
9191
g.mu.Lock()
9292
if c, ok := g.calls[key]; ok {
9393
c.forgotten = true
@@ -97,9 +97,9 @@ func (g *Group) Forget(key string) {
9797
}
9898

9999
// call stores information about as single function call passed to Do function.
100-
type call struct {
100+
type call[V any] struct {
101101
// val and err hold the state about results of the function call.
102-
val interface{}
102+
val V
103103
err error
104104

105105
// done channel signals that the function call is done.

singleflight_test.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ import (
2222
)
2323

2424
func TestDo(t *testing.T) {
25-
var g singleflight.Group
25+
var g singleflight.Group[string, string]
2626

2727
want := "val"
28-
got, shared, err := g.Do(context.Background(), "key", func(_ context.Context) (interface{}, error) {
28+
got, shared, err := g.Do(context.Background(), "key", func(_ context.Context) (string, error) {
2929
return want, nil
3030
})
3131
if err != nil {
@@ -40,21 +40,21 @@ func TestDo(t *testing.T) {
4040
}
4141

4242
func TestDo_error(t *testing.T) {
43-
var g singleflight.Group
43+
var g singleflight.Group[string, string]
4444
wantErr := errors.New("test error")
45-
got, _, err := g.Do(context.Background(), "key", func(_ context.Context) (interface{}, error) {
46-
return nil, wantErr
45+
got, _, err := g.Do(context.Background(), "key", func(_ context.Context) (string, error) {
46+
return "", wantErr
4747
})
4848
if err != wantErr {
4949
t.Errorf("got error %v, want %v", err, wantErr)
5050
}
51-
if got != nil {
51+
if got != "" {
5252
t.Errorf("unexpected value %#v", got)
5353
}
5454
}
5555

5656
func TestDo_multipleCalls(t *testing.T) {
57-
var g singleflight.Group
57+
var g singleflight.Group[string, string]
5858

5959
want := "val"
6060
var counter int32
@@ -68,7 +68,7 @@ func TestDo_multipleCalls(t *testing.T) {
6868
for i := 0; i < n; i++ {
6969
go func(i int) {
7070
defer wg.Done()
71-
got[i], shared[i], err[i] = g.Do(context.Background(), "key", func(_ context.Context) (interface{}, error) {
71+
got[i], shared[i], err[i] = g.Do(context.Background(), "key", func(_ context.Context) (string, error) {
7272
atomic.AddInt32(&counter, 1)
7373
time.Sleep(100 * time.Millisecond)
7474
return want, nil
@@ -95,11 +95,11 @@ func TestDo_multipleCalls(t *testing.T) {
9595
}
9696

9797
func TestDo_callRemoval(t *testing.T) {
98-
var g singleflight.Group
98+
var g singleflight.Group[string, string]
9999

100100
wantPrefix := "val"
101101
counter := 0
102-
fn := func(_ context.Context) (interface{}, error) {
102+
fn := func(_ context.Context) (string, error) {
103103
counter++
104104
return wantPrefix + strconv.Itoa(counter), nil
105105
}
@@ -131,7 +131,7 @@ func TestDo_cancelContext(t *testing.T) {
131131
done := make(chan struct{})
132132
defer close(done)
133133

134-
var g singleflight.Group
134+
var g singleflight.Group[string, string]
135135

136136
want := "val"
137137
ctx, cancel := context.WithCancel(context.Background())
@@ -140,7 +140,7 @@ func TestDo_cancelContext(t *testing.T) {
140140
cancel()
141141
}()
142142
start := time.Now()
143-
got, shared, err := g.Do(ctx, "key", func(_ context.Context) (interface{}, error) {
143+
got, shared, err := g.Do(ctx, "key", func(_ context.Context) (string, error) {
144144
select {
145145
case <-time.After(time.Second):
146146
case <-done:
@@ -156,7 +156,7 @@ func TestDo_cancelContext(t *testing.T) {
156156
if shared {
157157
t.Error("the value should not be shared")
158158
}
159-
if got != nil {
159+
if got != "" {
160160
t.Errorf("unexpected value %#v", got)
161161
}
162162
}
@@ -165,10 +165,10 @@ func TestDo_cancelContextSecond(t *testing.T) {
165165
done := make(chan struct{})
166166
defer close(done)
167167

168-
var g singleflight.Group
168+
var g singleflight.Group[string, string]
169169

170170
want := "val"
171-
fn := func(_ context.Context) (interface{}, error) {
171+
fn := func(_ context.Context) (string, error) {
172172
select {
173173
case <-time.After(time.Second):
174174
case <-done:
@@ -196,7 +196,7 @@ func TestDo_cancelContextSecond(t *testing.T) {
196196
if !shared {
197197
t.Error("the value should be shared")
198198
}
199-
if got != nil {
199+
if got != "" {
200200
t.Errorf("unexpected value %#v", got)
201201
}
202202
}
@@ -205,12 +205,12 @@ func TestForget(t *testing.T) {
205205
done := make(chan struct{})
206206
defer close(done)
207207

208-
var g singleflight.Group
208+
var g singleflight.Group[string, string]
209209

210210
wantPrefix := "val"
211211
var counter uint64
212212
firstCall := make(chan struct{})
213-
fn := func(_ context.Context) (interface{}, error) {
213+
fn := func(_ context.Context) (string, error) {
214214
c := atomic.AddUint64(&counter, 1)
215215
if c == 1 {
216216
close(firstCall)
@@ -252,7 +252,7 @@ func TestDo_multipleCallsCanceled(t *testing.T) {
252252
done := make(chan struct{})
253253
defer close(done)
254254

255-
var g singleflight.Group
255+
var g singleflight.Group[string, string]
256256

257257
var counter int32
258258

@@ -271,7 +271,7 @@ func TestDo_multipleCallsCanceled(t *testing.T) {
271271
contexts[i] = ctx
272272
cancelFuncs[i] = cancel
273273
mu.Unlock()
274-
_, _, _ = g.Do(ctx, "key", func(ctx context.Context) (interface{}, error) {
274+
_, _, _ = g.Do(ctx, "key", func(ctx context.Context) (string, error) {
275275
atomic.AddInt32(&counter, 1)
276276
close(fnCalled)
277277
var err error
@@ -288,7 +288,7 @@ func TestDo_multipleCallsCanceled(t *testing.T) {
288288

289289
fnErrChan <- err
290290

291-
return nil, nil
291+
return "", nil
292292
})
293293
}(i)
294294
}

0 commit comments

Comments
 (0)