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  }