github.com/puzpuzpuz/xsync/v3@v3.1.1-0.20240225193106-cbe4ec1e954f/mpmcqueueof_test.go (about) 1 //go:build go1.19 2 // +build go1.19 3 4 // Copyright notice. The following tests are partially based on 5 // the following file from the Go Programming Language core repo: 6 // https://github.com/golang/go/blob/831f9376d8d730b16fb33dfd775618dffe13ce7a/src/runtime/chan_test.go 7 8 package xsync_test 9 10 import ( 11 "runtime" 12 "strconv" 13 "sync" 14 "sync/atomic" 15 "testing" 16 "time" 17 18 . "github.com/puzpuzpuz/xsync/v3" 19 ) 20 21 func TestQueueOf_InvalidSize(t *testing.T) { 22 defer func() { recover() }() 23 NewMPMCQueueOf[int](0) 24 t.Fatal("no panic detected") 25 } 26 27 func TestQueueOfEnqueueDequeueInt(t *testing.T) { 28 q := NewMPMCQueueOf[int](10) 29 for i := 0; i < 10; i++ { 30 q.Enqueue(i) 31 } 32 for i := 0; i < 10; i++ { 33 if got := q.Dequeue(); got != i { 34 t.Fatalf("got %v, want %d", got, i) 35 } 36 } 37 } 38 39 func TestQueueOfEnqueueDequeueString(t *testing.T) { 40 q := NewMPMCQueueOf[string](10) 41 for i := 0; i < 10; i++ { 42 q.Enqueue(strconv.Itoa(i)) 43 } 44 for i := 0; i < 10; i++ { 45 if got := q.Dequeue(); got != strconv.Itoa(i) { 46 t.Fatalf("got %v, want %d", got, i) 47 } 48 } 49 } 50 51 func TestQueueOfEnqueueDequeueStruct(t *testing.T) { 52 type foo struct { 53 bar int 54 baz int 55 } 56 q := NewMPMCQueueOf[foo](10) 57 for i := 0; i < 10; i++ { 58 q.Enqueue(foo{i, i}) 59 } 60 for i := 0; i < 10; i++ { 61 if got := q.Dequeue(); got.bar != i || got.baz != i { 62 t.Fatalf("got %v, want %d", got, i) 63 } 64 } 65 } 66 67 func TestQueueOfEnqueueDequeueStructRef(t *testing.T) { 68 type foo struct { 69 bar int 70 baz int 71 } 72 q := NewMPMCQueueOf[*foo](11) 73 for i := 0; i < 10; i++ { 74 q.Enqueue(&foo{i, i}) 75 } 76 q.Enqueue(nil) 77 for i := 0; i < 10; i++ { 78 if got := q.Dequeue(); got.bar != i || got.baz != i { 79 t.Fatalf("got %v, want %d", got, i) 80 } 81 } 82 if last := q.Dequeue(); last != nil { 83 t.Fatalf("got %v, want nil", last) 84 } 85 } 86 87 func TestQueueOfEnqueueBlocksOnFull(t *testing.T) { 88 q := NewMPMCQueueOf[string](1) 89 q.Enqueue("foo") 90 cdone := make(chan bool) 91 flag := int32(0) 92 go func() { 93 q.Enqueue("bar") 94 if atomic.LoadInt32(&flag) == 0 { 95 t.Error("enqueue on full queue didn't wait for dequeue") 96 } 97 cdone <- true 98 }() 99 time.Sleep(50 * time.Millisecond) 100 atomic.StoreInt32(&flag, 1) 101 if got := q.Dequeue(); got != "foo" { 102 t.Fatalf("got %v, want foo", got) 103 } 104 <-cdone 105 } 106 107 func TestQueueOfDequeueBlocksOnEmpty(t *testing.T) { 108 q := NewMPMCQueueOf[string](2) 109 cdone := make(chan bool) 110 flag := int32(0) 111 go func() { 112 q.Dequeue() 113 if atomic.LoadInt32(&flag) == 0 { 114 t.Error("dequeue on empty queue didn't wait for enqueue") 115 } 116 cdone <- true 117 }() 118 time.Sleep(50 * time.Millisecond) 119 atomic.StoreInt32(&flag, 1) 120 q.Enqueue("foobar") 121 <-cdone 122 } 123 124 func TestQueueOfTryEnqueueDequeue(t *testing.T) { 125 q := NewMPMCQueueOf[int](10) 126 for i := 0; i < 10; i++ { 127 if !q.TryEnqueue(i) { 128 t.Fatalf("failed to enqueue for %d", i) 129 } 130 } 131 for i := 0; i < 10; i++ { 132 if got, ok := q.TryDequeue(); !ok || got != i { 133 t.Fatalf("got %v, want %d, for status %v", got, i, ok) 134 } 135 } 136 } 137 138 func TestQueueOfTryEnqueueOnFull(t *testing.T) { 139 q := NewMPMCQueueOf[string](1) 140 if !q.TryEnqueue("foo") { 141 t.Error("failed to enqueue initial item") 142 } 143 if q.TryEnqueue("bar") { 144 t.Error("got success for enqueue on full queue") 145 } 146 } 147 148 func TestQueueOfTryDequeueBlocksOnEmpty(t *testing.T) { 149 q := NewMPMCQueueOf[int](2) 150 if _, ok := q.TryDequeue(); ok { 151 t.Error("got success for enqueue on empty queue") 152 } 153 } 154 155 func hammerQueueOfBlockingCalls(t *testing.T, gomaxprocs, numOps, numThreads int) { 156 runtime.GOMAXPROCS(gomaxprocs) 157 q := NewMPMCQueueOf[int](numThreads) 158 startwg := sync.WaitGroup{} 159 startwg.Add(1) 160 csum := make(chan int, numThreads) 161 // Start producers. 162 for i := 0; i < numThreads; i++ { 163 go func(n int) { 164 startwg.Wait() 165 for j := n; j < numOps; j += numThreads { 166 q.Enqueue(j) 167 } 168 }(i) 169 } 170 // Start consumers. 171 for i := 0; i < numThreads; i++ { 172 go func(n int) { 173 startwg.Wait() 174 sum := 0 175 for j := n; j < numOps; j += numThreads { 176 item := q.Dequeue() 177 sum += item 178 } 179 csum <- sum 180 }(i) 181 } 182 startwg.Done() 183 // Wait for all the sums from producers. 184 sum := 0 185 for i := 0; i < numThreads; i++ { 186 s := <-csum 187 sum += s 188 } 189 // Assert the total sum. 190 expectedSum := numOps * (numOps - 1) / 2 191 if sum != expectedSum { 192 t.Fatalf("sums don't match for %d num ops, %d num threads: got %d, want %d", 193 numOps, numThreads, sum, expectedSum) 194 } 195 } 196 197 func TestQueueOfBlockingCalls(t *testing.T) { 198 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) 199 n := 100 200 if testing.Short() { 201 n = 10 202 } 203 hammerQueueOfBlockingCalls(t, 1, 100*n, n) 204 hammerQueueOfBlockingCalls(t, 1, 1000*n, 10*n) 205 hammerQueueOfBlockingCalls(t, 4, 100*n, n) 206 hammerQueueOfBlockingCalls(t, 4, 1000*n, 10*n) 207 hammerQueueOfBlockingCalls(t, 8, 100*n, n) 208 hammerQueueOfBlockingCalls(t, 8, 1000*n, 10*n) 209 } 210 211 func hammerQueueOfNonBlockingCalls(t *testing.T, gomaxprocs, numOps, numThreads int) { 212 runtime.GOMAXPROCS(gomaxprocs) 213 q := NewMPMCQueueOf[int](numThreads) 214 startwg := sync.WaitGroup{} 215 startwg.Add(1) 216 csum := make(chan int, numThreads) 217 // Start producers. 218 for i := 0; i < numThreads; i++ { 219 go func(n int) { 220 startwg.Wait() 221 for j := n; j < numOps; j += numThreads { 222 for !q.TryEnqueue(j) { 223 // busy spin until success 224 } 225 } 226 }(i) 227 } 228 // Start consumers. 229 for i := 0; i < numThreads; i++ { 230 go func(n int) { 231 startwg.Wait() 232 sum := 0 233 for j := n; j < numOps; j += numThreads { 234 var ( 235 item int 236 ok bool 237 ) 238 for { 239 // busy spin until success 240 if item, ok = q.TryDequeue(); ok { 241 sum += item 242 break 243 } 244 } 245 } 246 csum <- sum 247 }(i) 248 } 249 startwg.Done() 250 // Wait for all the sums from producers. 251 sum := 0 252 for i := 0; i < numThreads; i++ { 253 s := <-csum 254 sum += s 255 } 256 // Assert the total sum. 257 expectedSum := numOps * (numOps - 1) / 2 258 if sum != expectedSum { 259 t.Fatalf("sums don't match for %d num ops, %d num threads: got %d, want %d", 260 numOps, numThreads, sum, expectedSum) 261 } 262 } 263 264 func TestQueueOfNonBlockingCalls(t *testing.T) { 265 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) 266 n := 10 267 if testing.Short() { 268 n = 1 269 } 270 hammerQueueOfNonBlockingCalls(t, 1, n, n) 271 hammerQueueOfNonBlockingCalls(t, 2, 10*n, 2*n) 272 hammerQueueOfNonBlockingCalls(t, 4, 100*n, 4*n) 273 } 274 275 func benchmarkQueueOfProdCons(b *testing.B, queueSize, localWork int) { 276 callsPerSched := queueSize 277 procs := runtime.GOMAXPROCS(-1) / 2 278 if procs == 0 { 279 procs = 1 280 } 281 N := int32(b.N / callsPerSched) 282 c := make(chan bool, 2*procs) 283 q := NewMPMCQueueOf[int](queueSize) 284 for p := 0; p < procs; p++ { 285 go func() { 286 foo := 0 287 for atomic.AddInt32(&N, -1) >= 0 { 288 for g := 0; g < callsPerSched; g++ { 289 for i := 0; i < localWork; i++ { 290 foo *= 2 291 foo /= 2 292 } 293 q.Enqueue(1) 294 } 295 } 296 q.Enqueue(0) 297 c <- foo == 42 298 }() 299 go func() { 300 foo := 0 301 for { 302 v := q.Dequeue() 303 if v == 0 { 304 break 305 } 306 for i := 0; i < localWork; i++ { 307 foo *= 2 308 foo /= 2 309 } 310 } 311 c <- foo == 42 312 }() 313 } 314 for p := 0; p < procs; p++ { 315 <-c 316 <-c 317 } 318 } 319 320 func BenchmarkQueueOfProdCons(b *testing.B) { 321 benchmarkQueueOfProdCons(b, 1000, 0) 322 } 323 324 func BenchmarkOfQueueProdConsWork100(b *testing.B) { 325 benchmarkQueueOfProdCons(b, 1000, 100) 326 }