Skip to content

Commit 35084a7

Browse files
committed
Add needed singleflight dependency.
1 parent f3e4535 commit 35084a7

File tree

3 files changed

+200
-1
lines changed

3 files changed

+200
-1
lines changed

cmd/go/vcs.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"encoding/json"
1010
"errors"
1111
"fmt"
12-
"internal/singleflight"
1312
"log"
1413
"net/url"
1514
"os"
@@ -18,6 +17,8 @@ import (
1817
"regexp"
1918
"strings"
2019
"sync"
20+
21+
"github.com/gophergala2016/cmd-go-js/internal/singleflight"
2122
)
2223

2324
// A vcsCmd describes how to use a version control system

internal/singleflight/singleflight.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2013 The Go 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 singleflight provides a duplicate function call suppression
6+
// mechanism.
7+
package singleflight
8+
9+
import "sync"
10+
11+
// call is an in-flight or completed singleflight.Do call
12+
type call struct {
13+
wg sync.WaitGroup
14+
15+
// These fields are written once before the WaitGroup is done
16+
// and are only read after the WaitGroup is done.
17+
val interface{}
18+
err error
19+
20+
// These fields are read and written with the singleflight
21+
// mutex held before the WaitGroup is done, and are read but
22+
// not written after the WaitGroup is done.
23+
dups int
24+
chans []chan<- Result
25+
}
26+
27+
// Group represents a class of work and forms a namespace in
28+
// which units of work can be executed with duplicate suppression.
29+
type Group struct {
30+
mu sync.Mutex // protects m
31+
m map[string]*call // lazily initialized
32+
}
33+
34+
// Result holds the results of Do, so they can be passed
35+
// on a channel.
36+
type Result struct {
37+
Val interface{}
38+
Err error
39+
Shared bool
40+
}
41+
42+
// Do executes and returns the results of the given function, making
43+
// sure that only one execution is in-flight for a given key at a
44+
// time. If a duplicate comes in, the duplicate caller waits for the
45+
// original to complete and receives the same results.
46+
// The return value shared indicates whether v was given to multiple callers.
47+
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
48+
g.mu.Lock()
49+
if g.m == nil {
50+
g.m = make(map[string]*call)
51+
}
52+
if c, ok := g.m[key]; ok {
53+
c.dups++
54+
g.mu.Unlock()
55+
c.wg.Wait()
56+
return c.val, c.err, true
57+
}
58+
c := new(call)
59+
c.wg.Add(1)
60+
g.m[key] = c
61+
g.mu.Unlock()
62+
63+
g.doCall(c, key, fn)
64+
return c.val, c.err, c.dups > 0
65+
}
66+
67+
// DoChan is like Do but returns a channel that will receive the
68+
// results when they are ready.
69+
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
70+
ch := make(chan Result, 1)
71+
g.mu.Lock()
72+
if g.m == nil {
73+
g.m = make(map[string]*call)
74+
}
75+
if c, ok := g.m[key]; ok {
76+
c.dups++
77+
c.chans = append(c.chans, ch)
78+
g.mu.Unlock()
79+
return ch
80+
}
81+
c := &call{chans: []chan<- Result{ch}}
82+
c.wg.Add(1)
83+
g.m[key] = c
84+
g.mu.Unlock()
85+
86+
go g.doCall(c, key, fn)
87+
88+
return ch
89+
}
90+
91+
// doCall handles the single call for a key.
92+
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
93+
c.val, c.err = fn()
94+
c.wg.Done()
95+
96+
g.mu.Lock()
97+
delete(g.m, key)
98+
for _, ch := range c.chans {
99+
ch <- Result{c.val, c.err, c.dups > 0}
100+
}
101+
g.mu.Unlock()
102+
}
103+
104+
// Forget tells the singleflight to forget about a key. Future calls
105+
// to Do for this key will call the function rather than waiting for
106+
// an earlier call to complete.
107+
func (g *Group) Forget(key string) {
108+
g.mu.Lock()
109+
delete(g.m, key)
110+
g.mu.Unlock()
111+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2013 The Go 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 singleflight
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"sync"
11+
"sync/atomic"
12+
"testing"
13+
"time"
14+
)
15+
16+
func TestDo(t *testing.T) {
17+
var g Group
18+
v, err, _ := g.Do("key", func() (interface{}, error) {
19+
return "bar", nil
20+
})
21+
if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
22+
t.Errorf("Do = %v; want %v", got, want)
23+
}
24+
if err != nil {
25+
t.Errorf("Do error = %v", err)
26+
}
27+
}
28+
29+
func TestDoErr(t *testing.T) {
30+
var g Group
31+
someErr := errors.New("Some error")
32+
v, err, _ := g.Do("key", func() (interface{}, error) {
33+
return nil, someErr
34+
})
35+
if err != someErr {
36+
t.Errorf("Do error = %v; want someErr %v", err, someErr)
37+
}
38+
if v != nil {
39+
t.Errorf("unexpected non-nil value %#v", v)
40+
}
41+
}
42+
43+
func TestDoDupSuppress(t *testing.T) {
44+
var g Group
45+
var wg1, wg2 sync.WaitGroup
46+
c := make(chan string, 1)
47+
var calls int32
48+
fn := func() (interface{}, error) {
49+
if atomic.AddInt32(&calls, 1) == 1 {
50+
// First invocation.
51+
wg1.Done()
52+
}
53+
v := <-c
54+
c <- v // pump; make available for any future calls
55+
56+
time.Sleep(10 * time.Millisecond) // let more goroutines enter Do
57+
58+
return v, nil
59+
}
60+
61+
const n = 10
62+
wg1.Add(1)
63+
for i := 0; i < n; i++ {
64+
wg1.Add(1)
65+
wg2.Add(1)
66+
go func() {
67+
defer wg2.Done()
68+
wg1.Done()
69+
v, err, _ := g.Do("key", fn)
70+
if err != nil {
71+
t.Errorf("Do error: %v", err)
72+
return
73+
}
74+
if s, _ := v.(string); s != "bar" {
75+
t.Errorf("Do = %T %v; want %q", v, v, "bar")
76+
}
77+
}()
78+
}
79+
wg1.Wait()
80+
// At least one goroutine is in fn now and all of them have at
81+
// least reached the line before the Do.
82+
c <- "bar"
83+
wg2.Wait()
84+
if got := atomic.LoadInt32(&calls); got <= 0 || got >= n {
85+
t.Errorf("number of calls = %d; want over 0 and less than %d", got, n)
86+
}
87+
}

0 commit comments

Comments
 (0)