github.com/benz9527/toy-box/algo@v0.0.0-20240221120937-66c0c6bd5abd/pubsub/x_sp_disruptor_test.go (about) 1 package pubsub 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/benz9527/toy-box/algo/bit" 7 "github.com/benz9527/toy-box/algo/bitmap" 8 "github.com/stretchr/testify/assert" 9 "log/slog" 10 "math/rand" 11 "os" 12 "path/filepath" 13 "strconv" 14 "sync" 15 "sync/atomic" 16 "testing" 17 "time" 18 ) 19 20 func TestCeilCapacity(t *testing.T) { 21 testcases := []struct { 22 capacity uint64 23 ceil uint64 24 }{ 25 {0, 2}, 26 {1, 2}, 27 {2, 2}, 28 {3, 4}, 29 {4, 4}, 30 {7, 8}, 31 {8, 8}, 32 {9, 16}, 33 {16, 16}, 34 {31, 32}, 35 {32, 32}, 36 {58, 64}, 37 {64, 64}, 38 } 39 for _, tc := range testcases { 40 t.Run(fmt.Sprintf("capacity: %d, ceil: %d", tc.capacity, tc.ceil), func(t *testing.T) { 41 assert.Equal(t, tc.ceil, bit.RoundupPowOf2ByCeil(tc.capacity)) 42 }) 43 } 44 } 45 46 func testXSinglePipelineDisruptorUint64( 47 t *testing.T, gTotal, tasks int, capacity uint64, 48 bs BlockStrategy, bitmapCheck bool, 49 reportFile *os.File, errorCounter *atomic.Uint64, 50 ) { 51 var ( 52 counter = &atomic.Int64{} 53 bm bitmap.Bitmap 54 checkBM bitmap.Bitmap 55 ) 56 checkBM = bitmap.NewX32Bitmap(uint64(gTotal * tasks)) 57 if bitmapCheck { 58 bm = bitmap.NewX32Bitmap(uint64(gTotal * tasks)) 59 } 60 wg := &sync.WaitGroup{} 61 wg.Add(gTotal) 62 rwg := &sync.WaitGroup{} 63 rwg.Add(gTotal * tasks) 64 disruptor := NewXSinglePipelineDisruptor[uint64](capacity, 65 bs, 66 func(event uint64) error { 67 defer rwg.Done() 68 counter.Add(1) 69 if bitmapCheck { 70 bm.SetBit(event, true) 71 } 72 return nil 73 }, 74 ) 75 if err := disruptor.Start(); err != nil { 76 t.Fatalf("disruptor start failed, err: %v", err) 77 } 78 for i := 0; i < gTotal; i++ { 79 for j := 0; j < tasks; j++ { 80 checkBM.SetBit(uint64(i*tasks+j), true) 81 } 82 } 83 beginTs := time.Now() 84 for i := 0; i < gTotal; i++ { 85 go func(idx int) { 86 defer wg.Done() 87 for j := 0; j < tasks; j++ { 88 if _, _, err := disruptor.Publish(uint64(idx*tasks + j)); err != nil { 89 t.Logf("publish failed, err: %v", err) 90 if errorCounter != nil { 91 errorCounter.Add(1) 92 } 93 break 94 } 95 } 96 }(i) 97 } 98 wg.Wait() 99 diff := time.Now().Sub(beginTs) 100 summary := fmt.Sprintf("published total: %d, tasks: %d, cost: %v, tps: %v/s", gTotal, tasks, diff, float64(gTotal*tasks)/diff.Seconds()) 101 t.Log(summary) 102 if reportFile != nil { 103 _, _ = reportFile.WriteString(summary + "\n") 104 } 105 rwg.Wait() 106 if reportFile == nil { 107 time.Sleep(time.Second) 108 assert.Equal(t, int64(gTotal*tasks), counter.Load()) 109 } 110 err := disruptor.Stop() 111 assert.NoError(t, err) 112 if bitmapCheck { 113 if reportFile != nil { 114 _, _ = reportFile.WriteString(fmt.Sprintf("gTotal(%d), tasks(%d):\n", gTotal, tasks)) 115 } 116 bm1bits := bm.GetBits() 117 bm2bits := checkBM.GetBits() 118 if !bm.EqualTo(checkBM) { 119 if reportFile != nil { 120 _, _ = reportFile.WriteString("bitmap check failed by not equal!\n") 121 } 122 if errorCounter != nil { 123 errorCounter.Add(1) 124 } 125 for i := 0; i < len(bm1bits); i++ { 126 if bytes.Compare(bm1bits[i:i+1], bm2bits[i:i+1]) != 0 { 127 if reportFile != nil { 128 _, _ = reportFile.WriteString(fmt.Sprintf("idx: %d, bm1: %08b, bm2: %08b\n", i, bm1bits[i:i+1], bm2bits[i:i+1])) 129 } 130 t.Logf("idx: %d, bm1: %08b, bm2: %08b\n", i, bm1bits[i:i+1], bm2bits[i:i+1]) 131 } 132 } 133 } 134 // check store whether contains zero bits 135 if reportFile != nil { 136 _, _ = reportFile.WriteString("check store whether contains zero bits(exclude the last one):\n") 137 for i := 0; i < len(bm2bits)-1; i++ { 138 if bm2bits[i]&0xf != 0xf { 139 _, _ = reportFile.WriteString(fmt.Sprintf("store idx: %d, bm2: %08b\n", i, bm2bits[i:i+1])) 140 } 141 } 142 _, _ = reportFile.WriteString("====== end report ======\n") 143 } 144 } 145 if bm != nil { 146 bm.Free() 147 } 148 if checkBM != nil { 149 checkBM.Free() 150 } 151 } 152 153 func testXSinglePipelineDisruptorString(t *testing.T, gTotal, tasks int, capacity uint64, bs BlockStrategy, bitmapCheck bool, reportFile *os.File, errorCounter *atomic.Uint64) { 154 var ( 155 counter = &atomic.Int64{} 156 bm bitmap.Bitmap 157 checkBM bitmap.Bitmap 158 ) 159 if bitmapCheck { 160 bm = bitmap.NewX32Bitmap(uint64(gTotal * tasks)) 161 checkBM = bitmap.NewX32Bitmap(uint64(gTotal * tasks)) 162 } 163 wg := &sync.WaitGroup{} 164 wg.Add(gTotal) 165 rwg := &sync.WaitGroup{} 166 rwg.Add(gTotal * tasks) 167 disruptor := NewXSinglePipelineDisruptor[string](capacity, 168 bs, 169 func(event string) error { 170 defer func() { 171 if r := recover(); r != nil { 172 t.Logf("error panic: %v", r) 173 if reportFile != nil { 174 _, _ = reportFile.WriteString(fmt.Sprintf("error panic: %v\n", r)) 175 } 176 if errorCounter != nil { 177 errorCounter.Add(1) 178 } 179 } 180 rwg.Done() 181 }() 182 counter.Add(1) 183 if bitmapCheck { 184 e, err := strconv.ParseUint(event, 10, 64) 185 if err != nil { 186 t.Logf("error parse uint64 failed, err: %v", err) 187 if reportFile != nil { 188 _, _ = reportFile.WriteString(fmt.Sprintf("error parse uint64 failed, err: %v\n", err)) 189 } 190 } 191 bm.SetBit(e, true) 192 } 193 if event == "" { 194 t.Logf("error event is empty, counter: %d", counter.Load()) 195 } 196 return nil 197 }, 198 ) 199 if err := disruptor.Start(); err != nil { 200 t.Fatalf("disruptor start failed, err: %v", err) 201 } 202 for i := 0; i < gTotal; i++ { 203 for j := 0; j < tasks; j++ { 204 checkBM.SetBit(uint64(i*tasks+j), true) 205 } 206 } 207 beginTs := time.Now() 208 for i := 0; i < gTotal; i++ { 209 go func(idx int) { 210 defer wg.Done() 211 for j := 0; j < tasks; j++ { 212 if _, _, err := disruptor.Publish(fmt.Sprintf("%d", idx*tasks+j)); err != nil { 213 t.Logf("publish failed, err: %v", err) 214 if errorCounter != nil { 215 errorCounter.Add(1) 216 } 217 break 218 } 219 } 220 }(i) 221 } 222 wg.Wait() 223 diff := time.Now().Sub(beginTs) 224 summary := fmt.Sprintf("published total: %d, tasks: %d, cost: %v, tps: %v/s", gTotal, tasks, diff, float64(gTotal*tasks)/diff.Seconds()) 225 t.Log(summary) 226 if reportFile != nil { 227 _, _ = reportFile.WriteString(summary + "\n") 228 } 229 rwg.Wait() 230 if reportFile == nil { 231 time.Sleep(time.Second) 232 assert.Equal(t, int64(gTotal*tasks), counter.Load()) 233 } 234 err := disruptor.Stop() 235 assert.NoError(t, err) 236 if bitmapCheck { 237 if reportFile != nil { 238 _, _ = reportFile.WriteString(fmt.Sprintf("gTotal(%d), tasks(%d):\n", gTotal, tasks)) 239 } 240 bm1bits := bm.GetBits() 241 bm2bits := checkBM.GetBits() 242 if !bm.EqualTo(checkBM) { 243 if reportFile != nil { 244 _, _ = reportFile.WriteString("bitmap check failed by not equal!\n") 245 } 246 if errorCounter != nil { 247 errorCounter.Add(1) 248 } 249 for i := 0; i < len(bm1bits); i++ { 250 if bytes.Compare(bm1bits[i:i+1], bm2bits[i:i+1]) != 0 { 251 if reportFile != nil { 252 _, _ = reportFile.WriteString(fmt.Sprintf("error store idx: %d, bm1: %08b, bm2: %08b\n", i, bm1bits[i:i+1], bm2bits[i:i+1])) 253 } 254 t.Logf("idx: %d, bm1: %08b, bm2: %08b\n", i, bm1bits[i:i+1], bm2bits[i:i+1]) 255 } 256 } 257 } 258 // check store whether contains zero bits 259 if reportFile != nil { 260 _, _ = reportFile.WriteString("check store whether contains zero bits(exclude the last one):\n") 261 for i := 0; i < len(bm2bits)-1; i++ { 262 if bm2bits[i]&0xf != 0xf { 263 _, _ = reportFile.WriteString(fmt.Sprintf("error store idx: %d, bm2: %08b\n", i, bm2bits[i:i+1])) 264 } 265 } 266 _, _ = reportFile.WriteString("====== end report ======\n") 267 } 268 } 269 if bm != nil { 270 bm.Free() 271 } 272 if checkBM != nil { 273 checkBM.Free() 274 } 275 } 276 277 func TestXSinglePipelineDisruptor(t *testing.T) { 278 testcases := []struct { 279 name string 280 gTotal int 281 tasks int 282 bs BlockStrategy 283 }{ 284 {"gosched 10*100", 10, 100, NewXGoSchedBlockStrategy()}, 285 {"gosched 100*10000", 100, 10000, NewXGoSchedBlockStrategy()}, 286 {"gosched 500*10000", 500, 10000, NewXGoSchedBlockStrategy()}, 287 {"gosched 1000*10000", 1000, 10000, NewXGoSchedBlockStrategy()}, 288 {"gosched 5000*10000", 5000, 10000, NewXGoSchedBlockStrategy()}, 289 {"gosched 10000*10000", 10000, 10000, NewXGoSchedBlockStrategy()}, 290 {"nochan 5000*10000", 5000, 10000, NewXCacheChannelBlockStrategy()}, 291 {"cond 5000*10000", 5000, 10000, NewXCondBlockStrategy()}, 292 } 293 for _, tc := range testcases { 294 t.Run(tc.name, func(t *testing.T) { 295 testXSinglePipelineDisruptorUint64(t, tc.gTotal, tc.tasks, 1024*1024, tc.bs, false, nil, nil) 296 }) 297 } 298 } 299 300 func TestXSinglePipelineDisruptorWithBitmapCheck(t *testing.T) { 301 testcases := []struct { 302 name string 303 gTotal int 304 tasks int 305 bs BlockStrategy 306 }{ 307 {"gosched 1*10000", 1, 10000, NewXGoSchedBlockStrategy()}, 308 {"nocachech 1*10000", 1, 10000, NewXCacheChannelBlockStrategy()}, 309 {"cond 1*10000", 1, 10000, NewXCondBlockStrategy()}, 310 //{"gosched 10*100", 10, 100, NewXGoSchedBlockStrategy()}, 311 //{"gosched 100*10000", 100, 10000, NewXGoSchedBlockStrategy()}, 312 //{"gosched 500*10000", 500, 10000, NewXGoSchedBlockStrategy()}, 313 //{"gosched 1000*10000", 1000, 10000, NewXGoSchedBlockStrategy()}, 314 //{"gosched 5000*10000", 5000, 10000, NewXGoSchedBlockStrategy()}, 315 //{"gosched 10000*10000", 10000, 10000, NewXGoSchedBlockStrategy()}, 316 //{"nochan 5000*10000", 5000, 10000, NewXCacheChannelBlockStrategy()}, 317 //{"cond 5000*10000", 5000, 10000, NewXCondBlockStrategy()}, 318 } 319 for _, tc := range testcases { 320 t.Run(tc.name, func(t *testing.T) { 321 testXSinglePipelineDisruptorUint64(t, tc.gTotal, tc.tasks, 1024*1024, tc.bs, true, nil, nil) 322 }) 323 } 324 } 325 326 func TestXSinglePipelineDisruptorWithBitmapCheckAndReport(t *testing.T) { 327 errorCounter := &atomic.Uint64{} 328 reportFile, err := os.OpenFile(filepath.Join(os.TempDir(), "pubsub-report-"+time.Now().Format(time.RFC3339)+".txt"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) 329 defer func() { 330 if reportFile != nil { 331 _ = reportFile.Close() 332 } 333 }() 334 assert.NoError(t, err) 335 testcases := []struct { 336 name string 337 loop int 338 gTotal int 339 tasks int 340 capacity uint64 341 bs BlockStrategy 342 }{ 343 {"gosched 1*10000", 10000, 1, 10000, 1024, NewXGoSchedBlockStrategy()}, 344 {"gosched 10*100", 1000, 10, 100, 512, NewXGoSchedBlockStrategy()}, 345 {"gosched 10*100", 1000, 10, 100, 1024, NewXGoSchedBlockStrategy()}, 346 {"gosched 100*10000", 200, 100, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 347 {"gosched 500*10000", 10, 500, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 348 {"gosched 1000*10000", 10, 1000, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 349 {"gosched 5000*10000", 10, 5000, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 350 {"gosched 10000*10000", 5, 10000, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 351 //{"chan 1*10000", 10000, 1, 10000, 1024, NewXCacheChannelBlockStrategy()}, 352 //{"chan 10*100", 1000, 10, 100, 512, NewXCacheChannelBlockStrategy()}, 353 //{"chan 10*100", 1000, 10, 100, 1024, NewXCacheChannelBlockStrategy()}, 354 //{"chan 100*10000", 200, 100, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 355 //{"chan 500*10000", 10, 500, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 356 //{"chan 1000*10000", 10, 1000, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 357 //{"chan 5000*10000", 10, 5000, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 358 //{"chan 10000*10000", 5, 10000, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 359 //{"cond 1*10000", 10000, 1, 10000, 1024, NewXCondBlockStrategy()}, 360 } 361 for _, tc := range testcases { 362 t.Run(tc.name, func(t *testing.T) { 363 for i := 0; i < tc.loop; i++ { 364 _, _ = reportFile.WriteString(fmt.Sprintf("\n====== begin uint64 report(%s, %d) ======\n", tc.name, i)) 365 testXSinglePipelineDisruptorUint64(t, tc.gTotal, tc.tasks, tc.capacity, tc.bs, true, reportFile, errorCounter) 366 } 367 }) 368 } 369 } 370 371 func TestXSinglePipelineDisruptorWithBitmapCheckAndReport_str(t *testing.T) { 372 errorCounter := &atomic.Uint64{} 373 reportFile, err := os.OpenFile(filepath.Join(os.TempDir(), "pubsub-report-str-"+time.Now().Format(time.RFC3339)+".txt"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) 374 defer func() { 375 if reportFile != nil { 376 _ = reportFile.Close() 377 } 378 }() 379 assert.NoError(t, err) 380 testcases := []struct { 381 name string 382 loop int 383 gTotal int 384 tasks int 385 capacity uint64 386 bs BlockStrategy 387 }{ 388 {"gosched 1*10000 str", 1000, 1, 10000, 1024, NewXGoSchedBlockStrategy()}, 389 {"gosched 10*100 str", 1000, 10, 100, 512, NewXGoSchedBlockStrategy()}, 390 {"gosched 10*100 str", 1000, 10, 100, 1024, NewXGoSchedBlockStrategy()}, 391 {"gosched 100*10000 str", 1000, 100, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 392 {"gosched 500*10000 str", 100, 500, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 393 {"gosched 1000*10000 str", 10, 1000, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 394 {"gosched 5000*10000 str", 10, 5000, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 395 {"gosched 10000*10000 str", 5, 10000, 10000, 1024 * 1024, NewXGoSchedBlockStrategy()}, 396 //{"chan 1*10000 str", 1000, 1, 10000, 1024, NewXCacheChannelBlockStrategy()}, 397 //{"chan 10*100 str", 1000, 10, 100, 512, NewXCacheChannelBlockStrategy()}, 398 //{"chan 10*100 str", 1000, 10, 100, 1024, NewXCacheChannelBlockStrategy()}, 399 //{"chan 100*10000 str", 1000, 100, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 400 //{"chan 500*10000 str", 10, 500, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 401 //{"chan 1000*10000 str", 10, 1000, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 402 //{"chan 5000*10000 str", 10, 5000, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 403 //{"chan 10000*10000 str", 5, 10000, 10000, 1024 * 1024, NewXCacheChannelBlockStrategy()}, 404 //{"cond 1*10000 str", 1000, 1, 10000, 1024, NewXCondBlockStrategy()}, 405 } 406 for _, tc := range testcases { 407 t.Run(tc.name, func(t *testing.T) { 408 for i := 0; i < tc.loop; i++ { 409 _, _ = reportFile.WriteString(fmt.Sprintf("\n====== begin string report(%s, %d) ======\n", tc.name, i)) 410 testXSinglePipelineDisruptorString(t, tc.gTotal, tc.tasks, tc.capacity, tc.bs, true, reportFile, errorCounter) 411 } 412 }) 413 } 414 t.Logf("errors: %d\n", errorCounter.Load()) 415 } 416 417 func testNoCacheChannel(t *testing.T, chSize, gTotal, tasks int) { 418 counter := &atomic.Int64{} 419 wg := &sync.WaitGroup{} 420 wg.Add(gTotal) 421 var ch chan int 422 if chSize > 0 { 423 ch = make(chan int, chSize) 424 } else { 425 ch = make(chan int) 426 } 427 go func() { 428 for range ch { 429 counter.Add(1) 430 } 431 }() 432 beginTs := time.Now() 433 for i := 0; i < gTotal; i++ { 434 go func() { 435 defer wg.Done() 436 for j := 0; j < tasks; j++ { 437 ch <- j 438 } 439 }() 440 } 441 wg.Wait() 442 diff := time.Now().Sub(beginTs) 443 t.Logf("total: %d, tasks: %d, cost: %v, tps: %v/s", gTotal, tasks, diff, float64(gTotal*tasks)/diff.Seconds()) 444 time.Sleep(time.Second) 445 assert.Equal(t, int64(gTotal*tasks), counter.Load()) 446 } 447 448 func TestNoCacheChannel(t *testing.T) { 449 testcases := []struct { 450 name string 451 gTotal int 452 tasks int 453 }{ 454 {"nochan 10*100", 10, 100}, 455 {"nochan 100*10000", 100, 10000}, 456 {"nochan 500*10000", 500, 10000}, 457 {"nochan 1000*10000", 1000, 10000}, 458 {"nochan 5000*10000", 5000, 10000}, 459 {"nochan 10000*10000", 10000, 10000}, 460 } 461 for _, tc := range testcases { 462 t.Run(tc.name, func(t *testing.T) { 463 testNoCacheChannel(t, 0, tc.gTotal, tc.tasks) 464 }) 465 } 466 } 467 468 func TestCacheChannel(t *testing.T) { 469 testcases := []struct { 470 name string 471 gTotal int 472 tasks int 473 }{ 474 {"cachechan 10*100", 10, 100}, 475 {"cachechan 100*10000", 100, 10000}, 476 {"cachechan 500*10000", 500, 10000}, 477 {"cachechan 1000*10000", 1000, 10000}, 478 {"cachechan 5000*10000", 5000, 10000}, 479 {"cachechan 10000*10000", 10000, 10000}, 480 } 481 for _, tc := range testcases { 482 t.Run(tc.name, func(t *testing.T) { 483 testNoCacheChannel(t, 1024*1024, tc.gTotal, tc.tasks) 484 }) 485 } 486 } 487 488 func testXSinglePipelineDisruptorWithRandomSleep(t *testing.T, num, capacity int) { 489 wg := &sync.WaitGroup{} 490 wg.Add(num) 491 results := map[string]struct{}{} 492 disruptor := NewXSinglePipelineDisruptor[string](uint64(capacity), 493 NewXCacheChannelBlockStrategy(), 494 func(event string) error { 495 nextInt := rand.Intn(100) 496 time.Sleep(time.Duration(nextInt) * time.Millisecond) 497 results[event] = struct{}{} 498 wg.Done() 499 return nil 500 }, 501 ) 502 if err := disruptor.Start(); err != nil { 503 t.Fatalf("disruptor start failed, err: %v", err) 504 } 505 for i := 0; i < num; i++ { 506 if _, _, err := disruptor.Publish(fmt.Sprintf("event-%d", i)); err != nil { 507 t.Logf("publish failed, err: %v", err) 508 } 509 } 510 wg.Wait() 511 err := disruptor.Stop() 512 assert.NoError(t, err) 513 assert.Equal(t, num, len(results)) 514 for i := 0; i < num; i++ { 515 assert.Contains(t, results, fmt.Sprintf("event-%d", i)) 516 } 517 } 518 519 func TestXSinglePipelineDisruptorWithRandomSleepEvent(t *testing.T) { 520 testcases := []struct { 521 num int 522 capacity int 523 }{ 524 {10, 2}, 525 {100, 4}, 526 {200, 10}, 527 {500, 20}, 528 } 529 loops := 2 530 for i := 0; i < loops; i++ { 531 for _, tc := range testcases { 532 t.Run(fmt.Sprintf("num: %d, capacity: %d", tc.num, tc.capacity), func(t *testing.T) { 533 testXSinglePipelineDisruptorWithRandomSleep(t, tc.num, tc.capacity) 534 }) 535 } 536 } 537 } 538 539 func TestXSinglePipelineDisruptor_PublishTimeout(t *testing.T) { 540 num := 10 541 disruptor := NewXSinglePipelineDisruptor[string](2, 542 NewXGoSchedBlockStrategy(), 543 func(event string) error { 544 nextInt := rand.Intn(10) 545 if nextInt == 0 { 546 nextInt = 2 547 } 548 time.Sleep(time.Duration(nextInt) * time.Millisecond) 549 slog.Info("handle event details", "name", event) 550 return nil 551 }, 552 ) 553 if err := disruptor.Start(); err != nil { 554 t.Fatalf("disruptor start failed, err: %v", err) 555 } 556 for i := 0; i < num; i++ { 557 event := fmt.Sprintf("event-%d", i) 558 disruptor.PublishTimeout(event, 5*time.Millisecond) 559 } 560 time.Sleep(500 * time.Millisecond) 561 err := disruptor.Stop() 562 assert.NoError(t, err) 563 }