| 110 | } |
| 111 | |
| 112 | func TestDoDupSuppress(t *testing.T) { |
| 113 | var g Group[string, any] |
| 114 | var wg1, wg2 sync.WaitGroup |
| 115 | c := make(chan string, 1) |
| 116 | var calls int32 |
| 117 | fn := func() (any, error) { |
| 118 | if atomic.AddInt32(&calls, 1) == 1 { |
| 119 | // First invocation. |
| 120 | wg1.Done() |
| 121 | } |
| 122 | v := <-c |
| 123 | c <- v // pump; make available for any future calls |
| 124 | |
| 125 | time.Sleep(10 * time.Millisecond) // let more goroutines enter Do |
| 126 | |
| 127 | return v, nil |
| 128 | } |
| 129 | |
| 130 | const n = 10 |
| 131 | wg1.Add(1) |
| 132 | for i := 0; i < n; i++ { |
| 133 | wg1.Add(1) |
| 134 | wg2.Add(1) |
| 135 | go func() { |
| 136 | defer wg2.Done() |
| 137 | wg1.Done() |
| 138 | v, err, _ := g.Do("key", fn) |
| 139 | if err != nil { |
| 140 | t.Errorf("Do error: %v", err) |
| 141 | return |
| 142 | } |
| 143 | if s, _ := v.(string); s != "bar" { |
| 144 | t.Errorf("Do = %T %v; want %q", v, v, "bar") |
| 145 | } |
| 146 | }() |
| 147 | } |
| 148 | wg1.Wait() |
| 149 | // At least one goroutine is in fn now and all of them have at |
| 150 | // least reached the line before the Do. |
| 151 | c <- "bar" |
| 152 | wg2.Wait() |
| 153 | if got := atomic.LoadInt32(&calls); got <= 0 || got >= n { |
| 154 | t.Errorf("number of calls = %d; want over 0 and less than %d", got, n) |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | // Test that singleflight behaves correctly after Forget called. |
| 159 | // See https://github.com/golang/go/issues/31420 |