github.com/koko1123/flow-go-1@v0.29.6/module/jobqueue/consumer_behavior_test.go (about) 1 package jobqueue_test 2 3 import ( 4 "sort" 5 "strconv" 6 "sync" 7 "testing" 8 "time" 9 10 badgerdb "github.com/dgraph-io/badger/v3" 11 "github.com/rs/zerolog" 12 "github.com/stretchr/testify/require" 13 14 "github.com/koko1123/flow-go-1/module" 15 "github.com/koko1123/flow-go-1/module/jobqueue" 16 "github.com/koko1123/flow-go-1/storage" 17 "github.com/koko1123/flow-go-1/storage/badger" 18 "github.com/koko1123/flow-go-1/utils/unittest" 19 ) 20 21 const ( 22 DefaultIndex = uint64(0) 23 ConsumerTag = "consumer" 24 ) 25 26 // 0# means job at index 0 is processed. 27 // +1 means received a job 1 28 // 1! means job 1 is being processed. 29 // 1* means job 1 is finished 30 31 func TestConsumer(t *testing.T) { 32 t.Parallel() 33 34 // [] => [0#] 35 // on startup, if there is no more job, nothing is processed 36 t.Run("testOnStartup", testOnStartup) 37 38 // [+1] => [0#, 1!] 39 // when received job 1, it will be processed 40 t.Run("testOnReceiveOneJob", testOnReceiveOneJob) 41 42 // [+1, 1*] => [0#, 1#] 43 // when job 1 is finished, it will be marked as processed 44 t.Run("testOnJobFinished", testOnJobFinished) 45 46 // [+1, +2, 1*, 2*] => [0#, 1#, 2#] 47 // when job 2 and 1 are finished, they will be marked as processed 48 t.Run("testOnJobsFinished", testOnJobsFinished) 49 50 // [+1, +2, +3, +4] => [0#, 1!, 2!, 3!, 4] 51 // when more jobs are arrived than the max number of workers, only the first 3 jobs will be processed 52 t.Run("testMaxWorker", testMaxWorker) 53 54 // [+1, +2, +3, +4, +5, +6] => [0#, !1, *2, *3, *4, *5, 6, +7] => [0#, *1, *2, *3, *4, *5, !6, !7] 55 // when processing lags behind, the consumer is paused until processing catches up 56 t.Run("testPauseResume", testPauseResume) 57 58 // [+1, +2, +3, +4, 3*] => [0#, 1!, 2!, 3*, 4!] 59 // when job 3 is finished, which is not the next processing job 1, the processed index won't change 60 t.Run("testNonNextFinished", testNonNextFinished) 61 62 // [+1, +2, +3, +4, 3*, 2*] => [0#, 1!, 2*, 3*, 4!] 63 // when job 3 and 2 are finished, the processed index won't change, because 1 is still not finished 64 t.Run("testTwoNonNextFinished", testTwoNonNextFinished) 65 66 // [+1, +2, +3, +4, 3*, 2*, +5] => [0#, 1!, 2*, 3*, 4!, 5!] 67 // when job 5 is received, it will be processed, because the worker has capacity 68 t.Run("testProcessingWithNonNextFinished", testProcessingWithNonNextFinished) 69 70 // [+1, +2, +3, +4, 3*, 2*, +5, +6] => [0#, 1!, 2*, 3*, 4!, 5!, 6] 71 // when job 6 is received, no more worker can process it, it will be buffered 72 t.Run("testMaxWorkerWithFinishedNonNexts", testMaxWorkerWithFinishedNonNexts) 73 74 // [+1, +2, +3, +4, 3*, 2*, +5, 1*] => [0#, 1#, 2#, 3#, 4!, 5!] 75 // when job 1 is finally finished, it will fast forward the processed index to 3 76 t.Run("testFastforward", testFastforward) 77 78 // [+1, +2, +3, +4, 3*, 2*, +5, 1*, +6, +7, 6*], restart => [0#, 1#, 2#, 3#, 4!, 5!, 6*, 7!] 79 // when job queue crashed and restarted, the queue can be resumed 80 t.Run("testWorkOnNextAfterFastforward", testWorkOnNextAfterFastforward) 81 82 t.Run("testMovingProcessedIndex", testMovingProcessedIndex) 83 84 // [+1, +2, +3, +4, Stop, 2*] => [0#, 1!, 2*, 3!, 4] 85 // when Stop is called, it won't work on any job any more 86 t.Run("testStopRunning", testStopRunning) 87 88 t.Run("testConcurrency", testConcurrency) 89 } 90 91 func testOnStartup(t *testing.T) { 92 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 93 require.NoError(t, c.Start(DefaultIndex)) 94 assertProcessed(t, cp, 0) 95 }) 96 } 97 98 func TestProcessedOrder(t *testing.T) { 99 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 100 require.NoError(t, c.Start(5)) 101 assertProcessed(t, cp, 5) 102 }) 103 } 104 105 // [+1] => [0#, 1!] 106 // when received job 1, it will be processed 107 func testOnReceiveOneJob(t *testing.T) { 108 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 109 require.NoError(t, c.Start(DefaultIndex)) 110 require.NoError(t, j.PushOne()) // +1 111 112 c.Check() 113 114 time.Sleep(100 * time.Millisecond) 115 116 w.AssertCalled(t, []int64{1}) 117 assertProcessed(t, cp, 0) 118 }) 119 } 120 121 // [+1, 1*] => [0#, 1#] 122 // when job 1 is finished, it will be marked as processed 123 func testOnJobFinished(t *testing.T) { 124 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 125 require.NoError(t, c.Start(DefaultIndex)) 126 require.NoError(t, j.PushOne()) // +1 127 128 c.Check() 129 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(1)) 130 131 time.Sleep(1 * time.Millisecond) 132 133 w.AssertCalled(t, []int64{1}) 134 assertProcessed(t, cp, 1) 135 }) 136 } 137 138 // [+1, +2, 1*, 2*] => [0#, 1#, 2#] 139 // when job 2 and 1 are finished, they will be marked as processed 140 func testOnJobsFinished(t *testing.T) { 141 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 142 require.NoError(t, c.Start(DefaultIndex)) 143 require.NoError(t, j.PushOne()) // +1 144 c.Check() 145 146 require.NoError(t, j.PushOne()) // +2 147 c.Check() 148 149 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(1)) // 1* 150 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(2)) // 2* 151 152 time.Sleep(1 * time.Millisecond) 153 154 w.AssertCalled(t, []int64{1, 2}) 155 assertProcessed(t, cp, 2) 156 }) 157 } 158 159 // [+1, +2, +3, +4] => [0#, 1!, 2!, 3!, 4] 160 // when more jobs are arrived than the max number of workers, only the first 3 jobs will be processed 161 func testMaxWorker(t *testing.T) { 162 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 163 require.NoError(t, c.Start(DefaultIndex)) 164 require.NoError(t, j.PushOne()) // +1 165 c.Check() 166 167 require.NoError(t, j.PushOne()) // +2 168 c.Check() 169 170 require.NoError(t, j.PushOne()) // +3 171 c.Check() 172 173 require.NoError(t, j.PushOne()) // +4 174 c.Check() 175 176 time.Sleep(1 * time.Millisecond) 177 178 w.AssertCalled(t, []int64{1, 2, 3}) 179 assertProcessed(t, cp, 0) 180 }) 181 } 182 183 // [+1, +2, +3, +4, +5, +6] => [0#, !1, *2, *3, *4, *5, 6, +7] => [0#, *1, *2, *3, *4, *5, !6, !7] 184 // when processing lags behind, the consumer is paused until processing catches up 185 func testPauseResume(t *testing.T) { 186 runWithSeatchAhead(t, 5, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 187 require.NoError(t, c.Start(DefaultIndex)) 188 require.NoError(t, j.PushOne()) // +1 189 c.Check() 190 191 require.NoError(t, j.PushOne()) // +2 192 c.Check() 193 194 require.NoError(t, j.PushOne()) // +3 195 c.Check() 196 197 require.NoError(t, j.PushOne()) // +4 198 c.Check() 199 200 require.NoError(t, j.PushOne()) // +5 201 c.Check() 202 time.Sleep(1 * time.Millisecond) 203 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(2)) // 2* 204 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(3)) // 3* 205 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(4)) // 4* 206 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(5)) // 5* 207 208 require.NoError(t, j.PushOne()) // +6 209 c.Check() 210 211 time.Sleep(1 * time.Millisecond) 212 213 // all jobs so far are processed, except 1 and 6 214 w.AssertCalled(t, []int64{1, 2, 3, 4, 5}) 215 assertProcessed(t, cp, 0) 216 217 require.NoError(t, j.PushOne()) // +7 218 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(1)) // 1* 219 c.Check() 220 221 time.Sleep(1 * time.Millisecond) 222 223 // processing resumed after job 1 finished 224 w.AssertCalled(t, []int64{1, 2, 3, 4, 5, 6, 7}) 225 assertProcessed(t, cp, 5) 226 }) 227 } 228 229 // [+1, +2, +3, +4, 3*] => [0#, 1!, 2!, 3*, 4!] 230 // when job 3 is finished, which is not the next processing job 1, the processed index won't change 231 func testNonNextFinished(t *testing.T) { 232 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 233 require.NoError(t, c.Start(DefaultIndex)) 234 require.NoError(t, j.PushOne()) // +1 235 c.Check() 236 237 require.NoError(t, j.PushOne()) // +2 238 c.Check() 239 240 require.NoError(t, j.PushOne()) // +3 241 c.Check() 242 243 require.NoError(t, j.PushOne()) // +4 244 c.Check() 245 246 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(3)) // 3* 247 248 time.Sleep(1 * time.Millisecond) 249 250 w.AssertCalled(t, []int64{1, 2, 3, 4}) 251 }) 252 } 253 254 // testMovingProcessedIndex evaluates that the processedIndex only moves to job at index i when all job indices preceding that are 255 // already processed. 256 // +: added job to consumer 257 // *: finished job 258 // #: processed job 259 // 260 // [+1, +2, +3, +3, +4] => [1, 2, 3*, 4] => [1, 2, 3*, 4*] => => [1#, 2, 3*, 4*] => [1#, 2#, 3#, 4#] 261 func testMovingProcessedIndex(t *testing.T) { 262 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 263 require.NoError(t, c.Start(DefaultIndex)) 264 require.NoError(t, j.PushOne()) // +1 265 c.Check() 266 267 require.NoError(t, j.PushOne()) // +2 268 c.Check() 269 270 require.NoError(t, j.PushOne()) // +3 271 c.Check() 272 273 require.NoError(t, j.PushOne()) // +4 274 c.Check() 275 276 // when job 3 is done, the processed index should not move, since all jobs 277 // preceding it are not processed. 278 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(3)) // 3* 279 time.Sleep(1 * time.Millisecond) 280 processedIndex, err := cp.ProcessedIndex() 281 require.NoError(t, err) 282 require.Equal(t, processedIndex, uint64(0)) 283 284 // when job 4 is done, the processed index should not move, since all jobs 285 // preceding it are not processed. 286 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(4)) // 4* 287 time.Sleep(1 * time.Millisecond) 288 processedIndex, err = cp.ProcessedIndex() 289 require.NoError(t, err) 290 require.Equal(t, processedIndex, uint64(0)) 291 292 // when job 1 is done, the processed index should move to 1, since all jobs 293 // preceding it are processed. 294 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(1)) // 1* -> 1# 295 time.Sleep(1 * time.Millisecond) 296 processedIndex, err = cp.ProcessedIndex() 297 require.NoError(t, err) 298 require.Equal(t, processedIndex, uint64(1)) 299 300 // when job 2 is done, the processed index should move to 4, since all jobs 301 // preceding it are processed. 302 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(2)) // 2* -> 4# 303 time.Sleep(1 * time.Millisecond) 304 processedIndex, err = cp.ProcessedIndex() 305 require.NoError(t, err) 306 require.Equal(t, processedIndex, uint64(4)) 307 308 w.AssertCalled(t, []int64{1, 2, 3, 4}) 309 }) 310 } 311 312 // [+1, +2, +3, +4, 3*, 2*] => [0#, 1!, 2*, 3*, 4!] 313 // when job 3 and 2 are finished, the processed index won't change, because 1 is still not finished 314 func testTwoNonNextFinished(t *testing.T) { 315 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 316 require.NoError(t, c.Start(DefaultIndex)) 317 require.NoError(t, j.PushOne()) // +1 318 c.Check() 319 320 require.NoError(t, j.PushOne()) // +2 321 c.Check() 322 323 require.NoError(t, j.PushOne()) // +3 324 c.Check() 325 326 require.NoError(t, j.PushOne()) // +4 327 c.Check() 328 329 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(3)) // 3* 330 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(2)) // 2* 331 332 time.Sleep(1 * time.Millisecond) 333 334 w.AssertCalled(t, []int64{1, 2, 3, 4}) 335 assertProcessed(t, cp, 0) 336 }) 337 } 338 339 // [+1, +2, +3, +4, 3*, 2*, +5] => [0#, 1!, 2*, 3*, 4!, 5!] 340 // when job 5 is received, it will be processed, because the worker has capacity 341 func testProcessingWithNonNextFinished(t *testing.T) { 342 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 343 require.NoError(t, c.Start(DefaultIndex)) 344 require.NoError(t, j.PushOne()) // +1 345 c.Check() 346 347 require.NoError(t, j.PushOne()) // +2 348 c.Check() 349 350 require.NoError(t, j.PushOne()) // +3 351 c.Check() 352 353 require.NoError(t, j.PushOne()) // +4 354 c.Check() 355 356 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(3)) // 3* 357 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(2)) // 2* 358 359 require.NoError(t, j.PushOne()) // +5 360 c.Check() 361 362 time.Sleep(1 * time.Millisecond) 363 364 w.AssertCalled(t, []int64{1, 2, 3, 4, 5}) 365 assertProcessed(t, cp, 0) 366 }) 367 } 368 369 // [+1, +2, +3, +4, 3*, 2*, +5, +6] => [0#, 1!, 2*, 3*, 4!, 5!, 6] 370 // when job 6 is received, no more worker can process it, it will be buffered 371 func testMaxWorkerWithFinishedNonNexts(t *testing.T) { 372 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 373 require.NoError(t, c.Start(DefaultIndex)) 374 require.NoError(t, j.PushOne()) // +1 375 c.Check() 376 377 require.NoError(t, j.PushOne()) // +2 378 c.Check() 379 380 require.NoError(t, j.PushOne()) // +3 381 c.Check() 382 383 require.NoError(t, j.PushOne()) // +4 384 c.Check() 385 386 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(3)) // 3* 387 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(2)) // 2* 388 389 require.NoError(t, j.PushOne()) // +5 390 c.Check() 391 392 require.NoError(t, j.PushOne()) // +6 393 c.Check() 394 395 time.Sleep(1 * time.Millisecond) 396 397 w.AssertCalled(t, []int64{1, 2, 3, 4, 5}) 398 assertProcessed(t, cp, 0) 399 }) 400 } 401 402 // [+1, +2, +3, +4, 3*, 2*, +5, 1*] => [0#, 1#, 2#, 3#, 4!, 5!] 403 // when job 1 is finally finished, it will fast forward the processed index to 3 404 func testFastforward(t *testing.T) { 405 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 406 require.NoError(t, c.Start(DefaultIndex)) 407 require.NoError(t, j.PushOne()) // +1 408 c.Check() 409 410 require.NoError(t, j.PushOne()) // +2 411 c.Check() 412 413 require.NoError(t, j.PushOne()) // +3 414 c.Check() 415 416 require.NoError(t, j.PushOne()) // +4 417 c.Check() 418 419 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(3)) // 3* 420 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(2)) // 2* 421 422 require.NoError(t, j.PushOne()) // +5 423 c.Check() 424 425 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(1)) // 1* 426 427 time.Sleep(1 * time.Millisecond) 428 429 w.AssertCalled(t, []int64{1, 2, 3, 4, 5}) 430 assertProcessed(t, cp, 3) 431 }) 432 } 433 434 // [+1, +2, +3, +4, 3*, 2*, +5, 1*, +6, +7, 6*], restart => [0#, 1#, 2#, 3#, 4!, 5!, 6*, 7!] 435 // when job queue crashed and restarted, the queue can be resumed 436 func testWorkOnNextAfterFastforward(t *testing.T) { 437 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 438 require.NoError(t, c.Start(DefaultIndex)) 439 require.NoError(t, j.PushOne()) // +1 440 c.Check() 441 442 require.NoError(t, j.PushOne()) // +2 443 c.Check() 444 445 require.NoError(t, j.PushOne()) // +3 446 c.Check() 447 448 require.NoError(t, j.PushOne()) // +4 449 c.Check() 450 451 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(3)) // 3* 452 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(2)) // 2* 453 454 require.NoError(t, j.PushOne()) // +5 455 c.Check() 456 457 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(1)) // 1* 458 459 require.NoError(t, j.PushOne()) // +6 460 c.Check() 461 462 require.NoError(t, j.PushOne()) // +7 463 c.Check() 464 465 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(6)) // 6* 466 467 time.Sleep(1 * time.Millisecond) 468 469 // rebuild a consumer with the dependencies to simulate a restart 470 // jobs need to be reused, since it stores all the jobs 471 reWorker := newMockWorker() 472 reProgress := badger.NewConsumerProgress(db, ConsumerTag) 473 reConsumer := newTestConsumer(reProgress, j, reWorker, 0) 474 475 err := reConsumer.Start(DefaultIndex) 476 require.NoError(t, err) 477 478 time.Sleep(1 * time.Millisecond) 479 480 reWorker.AssertCalled(t, []int64{4, 5, 6}) 481 assertProcessed(t, reProgress, 3) 482 }) 483 } 484 485 // [+1, +2, +3, +4, Stop, 2*] => [0#, 1!, 2*, 3!, 4] 486 // when Stop is called, it won't work on any job any more 487 func testStopRunning(t *testing.T) { 488 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 489 require.NoError(t, c.Start(DefaultIndex)) 490 for i := 0; i < 4; i++ { 491 require.NoError(t, j.PushOne()) 492 c.Check() 493 } 494 495 // graceful shutdown and wait for goroutines that 496 // are calling worker.Run to finish 497 c.Stop() 498 499 // it won't work on 4 because it stopped before 2 is finished 500 w.AssertCalled(t, []int64{1, 2, 3}) 501 assertProcessed(t, cp, 0) 502 503 // still allow the existing job to finish 504 c.NotifyJobIsDone(jobqueue.JobIDAtIndex(1)) 505 assertProcessed(t, cp, 1) 506 }) 507 } 508 509 func testConcurrency(t *testing.T) { 510 runWith(t, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 511 require.NoError(t, c.Start(DefaultIndex)) 512 var finishAll sync.WaitGroup 513 finishAll.Add(100) 514 // Finish job concurrently 515 w.fn = func(j Job) { 516 go func() { 517 c.NotifyJobIsDone(j.ID()) 518 finishAll.Done() 519 }() 520 } 521 522 // Pushing job and checking job concurrently 523 var pushAll sync.WaitGroup 524 for i := 0; i < 100; i++ { 525 pushAll.Add(1) 526 go func() { 527 require.NoError(t, j.PushOne()) 528 c.Check() 529 pushAll.Done() 530 }() 531 } 532 533 // wait until pushed all 534 pushAll.Wait() 535 536 // wait until finished all 537 finishAll.Wait() 538 539 called := make([]int64, 0) 540 for i := 1; i <= 100; i++ { 541 called = append(called, int64(i)) 542 } 543 544 w.AssertCalled(t, called) 545 assertProcessed(t, cp, 100) 546 }) 547 } 548 549 type JobID = module.JobID 550 type Job = module.Job 551 552 func runWith(t testing.TB, runTestWith func(module.JobConsumer, storage.ConsumerProgress, *mockWorker, *jobqueue.MockJobs, *badgerdb.DB)) { 553 runWithSeatchAhead(t, 0, runTestWith) 554 } 555 556 func runWithSeatchAhead(t testing.TB, maxSearchAhead uint64, runTestWith func(module.JobConsumer, storage.ConsumerProgress, *mockWorker, *jobqueue.MockJobs, *badgerdb.DB)) { 557 unittest.RunWithBadgerDB(t, func(db *badgerdb.DB) { 558 jobs := jobqueue.NewMockJobs() 559 worker := newMockWorker() 560 progress := badger.NewConsumerProgress(db, ConsumerTag) 561 consumer := newTestConsumer(progress, jobs, worker, maxSearchAhead) 562 runTestWith(consumer, progress, worker, jobs, db) 563 }) 564 } 565 566 func assertProcessed(t testing.TB, cp storage.ConsumerProgress, expectProcessed uint64) { 567 processed, err := cp.ProcessedIndex() 568 require.NoError(t, err) 569 require.Equal(t, expectProcessed, processed) 570 } 571 572 func newTestConsumer(cp storage.ConsumerProgress, jobs module.Jobs, worker jobqueue.Worker, maxSearchAhead uint64) module.JobConsumer { 573 log := unittest.Logger().With().Str("module", "consumer").Logger() 574 maxProcessing := uint64(3) 575 return jobqueue.NewConsumer(log, jobs, cp, worker, maxProcessing, maxSearchAhead) 576 } 577 578 // a Mock worker that stores all the jobs that it was asked to work on 579 type mockWorker struct { 580 sync.RWMutex 581 log zerolog.Logger 582 called []Job 583 fn func(job Job) 584 } 585 586 func newMockWorker() *mockWorker { 587 return &mockWorker{ 588 log: unittest.Logger().With().Str("module", "worker").Logger(), 589 called: make([]Job, 0), 590 fn: func(Job) {}, 591 } 592 } 593 594 func (w *mockWorker) Run(job Job) error { 595 w.Lock() 596 defer w.Unlock() 597 598 w.log.Debug().Str("job_id", string(job.ID())).Msg("worker called with job") 599 600 w.called = append(w.called, job) 601 w.fn(job) 602 603 return nil 604 } 605 606 // return the IDs of the jobs 607 func (w *mockWorker) AssertCalled(t *testing.T, expectCalled []int64) { 608 called := make([]int, 0) 609 w.RLock() 610 for _, c := range w.called { 611 jobID, err := strconv.Atoi(string(c.ID())) 612 require.NoError(t, err) 613 called = append(called, jobID) 614 } 615 w.RUnlock() 616 sort.Ints(called) 617 618 called64 := make([]int64, 0) 619 for _, c := range called { 620 called64 = append(called64, int64(c)) 621 } 622 require.Equal(t, expectCalled, called64) 623 } 624 625 // if a job can be finished as soon as it's consumed, 626 // benchmark to see how fast it consume jobs. 627 // the latest result is 628 // 0.16 ms to push job 629 // 0.22 ms to finish job 630 func BenchmarkPushAndConsume(b *testing.B) { 631 b.StopTimer() 632 runWith(b, func(c module.JobConsumer, cp storage.ConsumerProgress, w *mockWorker, j *jobqueue.MockJobs, db *badgerdb.DB) { 633 var wg sync.WaitGroup 634 wg.Add(b.N) 635 636 // Finish job as soon as it's consumed 637 w.fn = func(j Job) { 638 go func() { 639 c.NotifyJobIsDone(j.ID()) 640 wg.Done() 641 }() 642 } 643 644 require.NoError(b, c.Start(DefaultIndex)) 645 646 b.StartTimer() 647 for i := 0; i < b.N; i++ { 648 err := j.PushOne() 649 if err != nil { 650 b.Error(err) 651 } 652 c.Check() 653 } 654 wg.Wait() 655 b.StopTimer() 656 }) 657 }