singleflight_test.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Copyright 2013 The Go Authors. All rights reserved.
  4. // Use of this source code is governed by a BSD-style
  5. // license that can be found in the LICENSE file.
  6. package singleflight
  7. import (
  8. "bytes"
  9. "errors"
  10. "fmt"
  11. "os"
  12. "os/exec"
  13. "runtime"
  14. "runtime/debug"
  15. "strings"
  16. "sync"
  17. "sync/atomic"
  18. "testing"
  19. "time"
  20. )
  21. func TestDo(t *testing.T) {
  22. var g Group[string, any]
  23. v, err, _ := g.Do("key", func() (interface{}, error) {
  24. return "bar", nil
  25. })
  26. if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
  27. t.Errorf("Do = %v; want %v", got, want)
  28. }
  29. if err != nil {
  30. t.Errorf("Do error = %v", err)
  31. }
  32. }
  33. func TestDoErr(t *testing.T) {
  34. var g Group[string, any]
  35. someErr := errors.New("Some error")
  36. v, err, _ := g.Do("key", func() (interface{}, error) {
  37. return nil, someErr
  38. })
  39. if err != someErr {
  40. t.Errorf("Do error = %v; want someErr %v", err, someErr)
  41. }
  42. if v != nil {
  43. t.Errorf("unexpected non-nil value %#v", v)
  44. }
  45. }
  46. func TestDoDupSuppress(t *testing.T) {
  47. var g Group[string, any]
  48. var wg1, wg2 sync.WaitGroup
  49. c := make(chan string, 1)
  50. var calls int32
  51. fn := func() (interface{}, error) {
  52. if atomic.AddInt32(&calls, 1) == 1 {
  53. // First invocation.
  54. wg1.Done()
  55. }
  56. v := <-c
  57. c <- v // pump; make available for any future calls
  58. time.Sleep(10 * time.Millisecond) // let more goroutines enter Do
  59. return v, nil
  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. }
  88. // Test that singleflight behaves correctly after Forget called.
  89. // See https://github.com/golang/go/issues/31420
  90. func TestForget(t *testing.T) {
  91. var g Group[string, any]
  92. var (
  93. firstStarted = make(chan struct{})
  94. unblockFirst = make(chan struct{})
  95. firstFinished = make(chan struct{})
  96. )
  97. go func() {
  98. g.Do("key", func() (i interface{}, e error) {
  99. close(firstStarted)
  100. <-unblockFirst
  101. close(firstFinished)
  102. return
  103. })
  104. }()
  105. <-firstStarted
  106. g.Forget("key")
  107. unblockSecond := make(chan struct{})
  108. secondResult := g.DoChan("key", func() (i interface{}, e error) {
  109. <-unblockSecond
  110. return 2, nil
  111. })
  112. close(unblockFirst)
  113. <-firstFinished
  114. thirdResult := g.DoChan("key", func() (i interface{}, e error) {
  115. return 3, nil
  116. })
  117. close(unblockSecond)
  118. <-secondResult
  119. r := <-thirdResult
  120. if r.Val != 2 {
  121. t.Errorf("We should receive result produced by second call, expected: 2, got %d", r.Val)
  122. }
  123. }
  124. func TestDoChan(t *testing.T) {
  125. var g Group[string, any]
  126. ch := g.DoChan("key", func() (interface{}, error) {
  127. return "bar", nil
  128. })
  129. res := <-ch
  130. v := res.Val
  131. err := res.Err
  132. if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
  133. t.Errorf("Do = %v; want %v", got, want)
  134. }
  135. if err != nil {
  136. t.Errorf("Do error = %v", err)
  137. }
  138. }
  139. // Test singleflight behaves correctly after Do panic.
  140. // See https://github.com/golang/go/issues/41133
  141. func TestPanicDo(t *testing.T) {
  142. var g Group[string, any]
  143. fn := func() (interface{}, error) {
  144. panic("invalid memory address or nil pointer dereference")
  145. }
  146. const n = 5
  147. waited := int32(n)
  148. panicCount := int32(0)
  149. done := make(chan struct{})
  150. for i := 0; i < n; i++ {
  151. go func() {
  152. defer func() {
  153. if err := recover(); err != nil {
  154. t.Logf("Got panic: %v\n%s", err, debug.Stack())
  155. atomic.AddInt32(&panicCount, 1)
  156. }
  157. if atomic.AddInt32(&waited, -1) == 0 {
  158. close(done)
  159. }
  160. }()
  161. g.Do("key", fn)
  162. }()
  163. }
  164. select {
  165. case <-done:
  166. if panicCount != n {
  167. t.Errorf("Expect %d panic, but got %d", n, panicCount)
  168. }
  169. case <-time.After(time.Second):
  170. t.Fatalf("Do hangs")
  171. }
  172. }
  173. func TestGoexitDo(t *testing.T) {
  174. var g Group[string, any]
  175. fn := func() (interface{}, error) {
  176. runtime.Goexit()
  177. return nil, nil
  178. }
  179. const n = 5
  180. waited := int32(n)
  181. done := make(chan struct{})
  182. for i := 0; i < n; i++ {
  183. go func() {
  184. var err error
  185. defer func() {
  186. if err != nil {
  187. t.Errorf("Error should be nil, but got: %v", err)
  188. }
  189. if atomic.AddInt32(&waited, -1) == 0 {
  190. close(done)
  191. }
  192. }()
  193. _, err, _ = g.Do("key", fn)
  194. }()
  195. }
  196. select {
  197. case <-done:
  198. case <-time.After(time.Second):
  199. t.Fatalf("Do hangs")
  200. }
  201. }
  202. func TestPanicDoChan(t *testing.T) {
  203. if runtime.GOOS == "js" {
  204. t.Skipf("js does not support exec")
  205. }
  206. if os.Getenv("TEST_PANIC_DOCHAN") != "" {
  207. defer func() {
  208. recover()
  209. }()
  210. g := new(Group[string, any])
  211. ch := g.DoChan("", func() (interface{}, error) {
  212. panic("Panicking in DoChan")
  213. })
  214. <-ch
  215. t.Fatalf("DoChan unexpectedly returned")
  216. }
  217. t.Parallel()
  218. cmd := exec.Command(os.Args[0], "-test.run="+t.Name(), "-test.v")
  219. cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1")
  220. out := new(bytes.Buffer)
  221. cmd.Stdout = out
  222. cmd.Stderr = out
  223. if err := cmd.Start(); err != nil {
  224. t.Fatal(err)
  225. }
  226. err := cmd.Wait()
  227. t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out)
  228. if err == nil {
  229. t.Errorf("Test subprocess passed; want a crash due to panic in DoChan")
  230. }
  231. if bytes.Contains(out.Bytes(), []byte("DoChan unexpectedly")) {
  232. t.Errorf("Test subprocess failed with an unexpected failure mode.")
  233. }
  234. if !bytes.Contains(out.Bytes(), []byte("Panicking in DoChan")) {
  235. t.Errorf("Test subprocess failed, but the crash isn't caused by panicking in DoChan")
  236. }
  237. }
  238. func TestPanicDoSharedByDoChan(t *testing.T) {
  239. if runtime.GOOS == "js" {
  240. t.Skipf("js does not support exec")
  241. }
  242. if os.Getenv("TEST_PANIC_DOCHAN") != "" {
  243. blocked := make(chan struct{})
  244. unblock := make(chan struct{})
  245. g := new(Group[string, any])
  246. go func() {
  247. defer func() {
  248. recover()
  249. }()
  250. g.Do("", func() (interface{}, error) {
  251. close(blocked)
  252. <-unblock
  253. panic("Panicking in Do")
  254. })
  255. }()
  256. <-blocked
  257. ch := g.DoChan("", func() (interface{}, error) {
  258. panic("DoChan unexpectedly executed callback")
  259. })
  260. close(unblock)
  261. <-ch
  262. t.Fatalf("DoChan unexpectedly returned")
  263. }
  264. t.Parallel()
  265. cmd := exec.Command(os.Args[0], "-test.run="+t.Name(), "-test.v")
  266. cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1")
  267. out := new(bytes.Buffer)
  268. cmd.Stdout = out
  269. cmd.Stderr = out
  270. if err := cmd.Start(); err != nil {
  271. t.Fatal(err)
  272. }
  273. err := cmd.Wait()
  274. t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out)
  275. if err == nil {
  276. t.Errorf("Test subprocess passed; want a crash due to panic in Do shared by DoChan")
  277. }
  278. if bytes.Contains(out.Bytes(), []byte("DoChan unexpectedly")) {
  279. t.Errorf("Test subprocess failed with an unexpected failure mode.")
  280. }
  281. if !bytes.Contains(out.Bytes(), []byte("Panicking in Do")) {
  282. t.Errorf("Test subprocess failed, but the crash isn't caused by panicking in Do")
  283. }
  284. }