github.com/weaviate/weaviate@v1.24.6/entities/cyclemanager/cyclecallbackgroup_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package cyclemanager
    13  
    14  import (
    15  	"context"
    16  	"sync/atomic"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/sirupsen/logrus/hooks/test"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func TestCycleCallback_Parallel(t *testing.T) {
    26  	logger, _ := test.NewNullLogger()
    27  	shouldNotAbort := func() bool { return false }
    28  
    29  	t.Run("no callbacks", func(t *testing.T) {
    30  		var executed bool
    31  
    32  		callbacks := NewCallbackGroup("id", logger, 2)
    33  
    34  		executed = callbacks.CycleCallback(shouldNotAbort)
    35  
    36  		assert.False(t, executed)
    37  	})
    38  
    39  	t.Run("2 executable callbacks", func(t *testing.T) {
    40  		executedCounter1 := 0
    41  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
    42  			time.Sleep(50 * time.Millisecond)
    43  			executedCounter1++
    44  			return true
    45  		}
    46  		executedCounter2 := 0
    47  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
    48  			time.Sleep(25 * time.Millisecond)
    49  			executedCounter2++
    50  			return true
    51  		}
    52  		var executed bool
    53  		var d time.Duration
    54  
    55  		callbacks := NewCallbackGroup("id", logger, 2)
    56  		callbacks.Register("c1", callback1)
    57  		callbacks.Register("c2", callback2)
    58  
    59  		start := time.Now()
    60  		executed = callbacks.CycleCallback(shouldNotAbort)
    61  		d = time.Since(start)
    62  
    63  		assert.True(t, executed)
    64  		assert.Equal(t, 1, executedCounter1)
    65  		assert.Equal(t, 1, executedCounter2)
    66  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
    67  	})
    68  
    69  	t.Run("2 non-executable callbacks", func(t *testing.T) {
    70  		executedCounter1 := 0
    71  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
    72  			time.Sleep(10 * time.Millisecond)
    73  			executedCounter1++
    74  			return false
    75  		}
    76  		executedCounter2 := 0
    77  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
    78  			time.Sleep(10 * time.Millisecond)
    79  			executedCounter2++
    80  			return false
    81  		}
    82  		var executed bool
    83  		var d time.Duration
    84  
    85  		callbacks := NewCallbackGroup("id", logger, 2)
    86  		callbacks.Register("c1", callback1)
    87  		callbacks.Register("c2", callback2)
    88  
    89  		start := time.Now()
    90  		executed = callbacks.CycleCallback(shouldNotAbort)
    91  		d = time.Since(start)
    92  
    93  		assert.False(t, executed)
    94  		assert.Equal(t, 1, executedCounter1)
    95  		assert.Equal(t, 1, executedCounter2)
    96  		assert.GreaterOrEqual(t, d, 10*time.Millisecond)
    97  	})
    98  
    99  	t.Run("3 executable callbacks, not all executed due to should abort", func(t *testing.T) {
   100  		executedCounter1 := 0
   101  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   102  			time.Sleep(25 * time.Millisecond)
   103  			executedCounter1++
   104  			return true
   105  		}
   106  		executedCounter2 := 0
   107  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   108  			time.Sleep(25 * time.Millisecond)
   109  			executedCounter2++
   110  			return true
   111  		}
   112  		executedCounter3 := 0
   113  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
   114  			time.Sleep(25 * time.Millisecond)
   115  			executedCounter3++
   116  			return true
   117  		}
   118  		// due to async calls of shouldAbort callback by main for loop
   119  		// and goroutines reading from shared channel it is hard to
   120  		// establish order of calls.
   121  		// with 3 callbacks and shouldAbort returning true on 6th call
   122  		// 1 or 2 callbacks should be executed, but not all 3.
   123  		shouldAbortCounter := uint32(0)
   124  		shouldAbort := func() bool {
   125  			return atomic.AddUint32(&shouldAbortCounter, 1) > 5
   126  		}
   127  		var executed bool
   128  		var d time.Duration
   129  
   130  		callbacks := NewCallbackGroup("id", logger, 2)
   131  		callbacks.Register("c1", callback1)
   132  		callbacks.Register("c2", callback2)
   133  		callbacks.Register("c3", callback3)
   134  
   135  		start := time.Now()
   136  		executed = callbacks.CycleCallback(shouldAbort)
   137  		d = time.Since(start)
   138  
   139  		assert.True(t, executed)
   140  		totalExecuted := executedCounter1 + executedCounter2 + executedCounter3
   141  		assert.Greater(t, totalExecuted, 0)
   142  		assert.Less(t, totalExecuted, 3)
   143  		assert.GreaterOrEqual(t, d, 25*time.Millisecond)
   144  	})
   145  
   146  	t.Run("register new while executing", func(t *testing.T) {
   147  		executedCounter1 := 0
   148  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   149  			time.Sleep(50 * time.Millisecond)
   150  			executedCounter1++
   151  			return true
   152  		}
   153  		executedCounter2 := 0
   154  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   155  			time.Sleep(50 * time.Millisecond)
   156  			executedCounter2++
   157  			return true
   158  		}
   159  		executedCounter3 := 0
   160  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
   161  			time.Sleep(50 * time.Millisecond)
   162  			executedCounter3++
   163  			return true
   164  		}
   165  		executedCounter4 := 0
   166  		callback4 := func(shouldAbort ShouldAbortCallback) bool {
   167  			time.Sleep(50 * time.Millisecond)
   168  			executedCounter4++
   169  			return true
   170  		}
   171  		chStarted := make(chan struct{}, 1)
   172  		chFinished := make(chan struct{}, 1)
   173  		var executed bool
   174  		var d time.Duration
   175  
   176  		callbacks := NewCallbackGroup("id", logger, 2)
   177  		callbacks.Register("c1", callback1)
   178  		callbacks.Register("c2", callback2)
   179  		callbacks.Register("c3", callback3)
   180  
   181  		// register 4th callback while other are executed,
   182  		//
   183  		// while 1st and 2nd are being processed (50ms),
   184  		// 3rd is waiting for available routine (without 3rd callback loop would be finished)
   185  		// 4th is registered (25ms) to be called next along with 3rd
   186  		go func() {
   187  			chStarted <- struct{}{}
   188  			start := time.Now()
   189  			executed = callbacks.CycleCallback(shouldNotAbort)
   190  			d = time.Since(start)
   191  			chFinished <- struct{}{}
   192  		}()
   193  		<-chStarted
   194  		time.Sleep(25 * time.Millisecond)
   195  		callbacks.Register("c4", callback4)
   196  		<-chFinished
   197  
   198  		assert.True(t, executed)
   199  		assert.Equal(t, 1, executedCounter1)
   200  		assert.Equal(t, 1, executedCounter2)
   201  		assert.Equal(t, 1, executedCounter3)
   202  		assert.Equal(t, 1, executedCounter4)
   203  		assert.GreaterOrEqual(t, d, 100*time.Millisecond)
   204  	})
   205  
   206  	t.Run("run with intervals", func(T *testing.T) {
   207  		ticker := NewFixedTicker(10 * time.Millisecond)
   208  		intervals2 := NewSeriesIntervals([]time.Duration{
   209  			10 * time.Millisecond, 30 * time.Millisecond, 50 * time.Millisecond,
   210  		})
   211  		intervals3 := NewFixedIntervals(60 * time.Millisecond)
   212  		now := time.Now()
   213  
   214  		executionTimes1 := []time.Duration{}
   215  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   216  			executionTimes1 = append(executionTimes1, time.Since(now))
   217  			return true
   218  		}
   219  		executionCounter2 := 0
   220  		executionTimes2 := []time.Duration{}
   221  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   222  			executionCounter2++
   223  			executionTimes2 = append(executionTimes2, time.Since(now))
   224  			// reports executed every 3 calls, should result in 10, 30, 50, 50, 10, 30, 50, 50, ... intervals
   225  			return executionCounter2%4 == 0
   226  		}
   227  		executionTimes3 := []time.Duration{}
   228  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
   229  			executionTimes3 = append(executionTimes3, time.Since(now))
   230  			return true
   231  		}
   232  
   233  		callbacks := NewCallbackGroup("id", logger, 2)
   234  		// should be called on every tick, with 10 intervals
   235  		callbacks.Register("c1", callback1)
   236  		// should be called with 10, 30, 50, 50, 10, 30, 50, 50, ... intervals
   237  		callbacks.Register("c2", callback2, WithIntervals(intervals2))
   238  		// should be called with 60, 60, ... intervals
   239  		callbacks.Register("c3", callback3, WithIntervals(intervals3))
   240  
   241  		cm := NewManager(ticker, callbacks.CycleCallback, logger)
   242  		cm.Start()
   243  		time.Sleep(400 * time.Millisecond)
   244  		cm.StopAndWait(context.Background())
   245  
   246  		// within 400 ms c1 should be called at least 30x
   247  		require.GreaterOrEqual(t, len(executionTimes1), 30)
   248  		// 1st call on 1st tick after 10ms
   249  		sumDuration := time.Duration(10)
   250  		for i := 0; i < 30; i++ {
   251  			assert.GreaterOrEqual(t, executionTimes1[i], sumDuration)
   252  			sumDuration += 10 * time.Millisecond
   253  		}
   254  
   255  		// within 400 ms c2 should be called at least 8x
   256  		require.GreaterOrEqual(t, len(executionTimes2), 8)
   257  		// 1st call on 1st tick after 10ms
   258  		sumDuration = time.Duration(0)
   259  		for i := 0; i < 8; i++ {
   260  			assert.GreaterOrEqual(t, executionTimes2[i], sumDuration)
   261  			switch (i + 1) % 4 {
   262  			case 0:
   263  				sumDuration += 10 * time.Millisecond
   264  			case 1:
   265  				sumDuration += 30 * time.Millisecond
   266  			case 2, 3:
   267  				sumDuration += 50 * time.Millisecond
   268  			}
   269  		}
   270  
   271  		// within 400 ms c3 should be called at least 6x
   272  		require.GreaterOrEqual(t, len(executionTimes3), 6)
   273  		// 1st call on 1st tick after 10ms
   274  		sumDuration = time.Duration(0)
   275  		for i := 0; i < 6; i++ {
   276  			assert.GreaterOrEqual(t, executionTimes3[i], sumDuration)
   277  			sumDuration += 60 * time.Millisecond
   278  		}
   279  	})
   280  }
   281  
   282  func TestCycleCallback_Parallel_Unregister(t *testing.T) {
   283  	ctx := context.Background()
   284  	logger, _ := test.NewNullLogger()
   285  	shouldNotAbort := func() bool { return false }
   286  
   287  	t.Run("1 executable callback, 1 unregistered", func(t *testing.T) {
   288  		executedCounter := 0
   289  		callback := func(shouldAbort ShouldAbortCallback) bool {
   290  			time.Sleep(50 * time.Millisecond)
   291  			executedCounter++
   292  			return true
   293  		}
   294  		var executed bool
   295  		var d time.Duration
   296  
   297  		callbacks := NewCallbackGroup("id", logger, 2)
   298  		ctrl := callbacks.Register("c1", callback)
   299  		require.Nil(t, ctrl.Unregister(ctx))
   300  
   301  		start := time.Now()
   302  		executed = callbacks.CycleCallback(shouldNotAbort)
   303  		d = time.Since(start)
   304  
   305  		assert.False(t, executed)
   306  		assert.Equal(t, 0, executedCounter)
   307  		assert.GreaterOrEqual(t, d, 0*time.Millisecond)
   308  	})
   309  
   310  	t.Run("2 executable callbacks, 2 unregistered", func(t *testing.T) {
   311  		executedCounter1 := 0
   312  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   313  			time.Sleep(50 * time.Millisecond)
   314  			executedCounter1++
   315  			return true
   316  		}
   317  		executedCounter2 := 0
   318  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   319  			time.Sleep(25 * time.Millisecond)
   320  			executedCounter2++
   321  			return true
   322  		}
   323  		var executed bool
   324  		var d time.Duration
   325  
   326  		callbacks := NewCallbackGroup("id", logger, 2)
   327  		ctrl1 := callbacks.Register("c1", callback1)
   328  		ctrl2 := callbacks.Register("c2", callback2)
   329  		require.Nil(t, ctrl1.Unregister(ctx))
   330  		require.Nil(t, ctrl2.Unregister(ctx))
   331  
   332  		start := time.Now()
   333  		executed = callbacks.CycleCallback(shouldNotAbort)
   334  		d = time.Since(start)
   335  
   336  		assert.False(t, executed)
   337  		assert.Equal(t, 0, executedCounter1)
   338  		assert.Equal(t, 0, executedCounter2)
   339  		assert.GreaterOrEqual(t, d, 0*time.Millisecond)
   340  	})
   341  
   342  	t.Run("2 executable callbacks, 1 unregistered", func(t *testing.T) {
   343  		executedCounter1 := 0
   344  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   345  			time.Sleep(50 * time.Millisecond)
   346  			executedCounter1++
   347  			return true
   348  		}
   349  		executedCounter2 := 0
   350  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   351  			time.Sleep(25 * time.Millisecond)
   352  			executedCounter2++
   353  			return true
   354  		}
   355  		var executed bool
   356  		var d time.Duration
   357  
   358  		callbacks := NewCallbackGroup("id", logger, 2)
   359  		ctrl1 := callbacks.Register("c1", callback1)
   360  		callbacks.Register("c2", callback2)
   361  		require.Nil(t, ctrl1.Unregister(ctx))
   362  
   363  		start := time.Now()
   364  		executed = callbacks.CycleCallback(shouldNotAbort)
   365  		d = time.Since(start)
   366  
   367  		assert.True(t, executed)
   368  		assert.Equal(t, 0, executedCounter1)
   369  		assert.Equal(t, 1, executedCounter2)
   370  		assert.GreaterOrEqual(t, d, 25*time.Millisecond)
   371  	})
   372  
   373  	t.Run("4 executable callbacks, all unregistered at different time", func(t *testing.T) {
   374  		executedCounter1 := 0
   375  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   376  			time.Sleep(25 * time.Millisecond)
   377  			executedCounter1++
   378  			return true
   379  		}
   380  		executedCounter2 := 0
   381  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   382  			time.Sleep(25 * time.Millisecond)
   383  			executedCounter2++
   384  			return true
   385  		}
   386  		executedCounter3 := 0
   387  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
   388  			time.Sleep(25 * time.Millisecond)
   389  			executedCounter3++
   390  			return true
   391  		}
   392  		executedCounter4 := 0
   393  		callback4 := func(shouldAbort ShouldAbortCallback) bool {
   394  			time.Sleep(25 * time.Millisecond)
   395  			executedCounter4++
   396  			return true
   397  		}
   398  		var executed1 bool
   399  		var executed2 bool
   400  		var executed3 bool
   401  		var executed4 bool
   402  		var d1 time.Duration
   403  		var d2 time.Duration
   404  		var d3 time.Duration
   405  		var d4 time.Duration
   406  
   407  		callbacks := NewCallbackGroup("id", logger, 2)
   408  		ctrl1 := callbacks.Register("c1", callback1)
   409  		ctrl2 := callbacks.Register("c2", callback2)
   410  		ctrl3 := callbacks.Register("c3", callback3)
   411  		ctrl4 := callbacks.Register("c4", callback4)
   412  		require.Nil(t, ctrl3.Unregister(ctx))
   413  
   414  		start := time.Now()
   415  		executed1 = callbacks.CycleCallback(shouldNotAbort)
   416  		d1 = time.Since(start)
   417  
   418  		require.Nil(t, ctrl1.Unregister(ctx))
   419  
   420  		start = time.Now()
   421  		executed2 = callbacks.CycleCallback(shouldNotAbort)
   422  		d2 = time.Since(start)
   423  
   424  		require.Nil(t, ctrl4.Unregister(ctx))
   425  
   426  		start = time.Now()
   427  		executed3 = callbacks.CycleCallback(shouldNotAbort)
   428  		d3 = time.Since(start)
   429  
   430  		require.Nil(t, ctrl2.Unregister(ctx))
   431  
   432  		start = time.Now()
   433  		executed4 = callbacks.CycleCallback(shouldNotAbort)
   434  		d4 = time.Since(start)
   435  
   436  		assert.True(t, executed1)
   437  		assert.True(t, executed2)
   438  		assert.True(t, executed3)
   439  		assert.False(t, executed4)
   440  		assert.Equal(t, 1, executedCounter1)
   441  		assert.Equal(t, 3, executedCounter2)
   442  		assert.Equal(t, 0, executedCounter3)
   443  		assert.Equal(t, 2, executedCounter4)
   444  		assert.GreaterOrEqual(t, d1, 50*time.Millisecond)
   445  		assert.GreaterOrEqual(t, d2, 25*time.Millisecond)
   446  		assert.GreaterOrEqual(t, d3, 25*time.Millisecond)
   447  		assert.GreaterOrEqual(t, d4, 0*time.Millisecond)
   448  	})
   449  
   450  	t.Run("unregister is waiting till the end of execution", func(t *testing.T) {
   451  		executedCounter := 0
   452  		callback := func(shouldAbort ShouldAbortCallback) bool {
   453  			time.Sleep(50 * time.Millisecond)
   454  			executedCounter++
   455  			return true
   456  		}
   457  		chStarted := make(chan struct{}, 1)
   458  		chFinished := make(chan struct{}, 1)
   459  		var executed bool
   460  		var d time.Duration
   461  
   462  		callbacks := NewCallbackGroup("id", logger, 2)
   463  		ctrl := callbacks.Register("c", callback)
   464  
   465  		go func() {
   466  			chStarted <- struct{}{}
   467  			start := time.Now()
   468  			executed = callbacks.CycleCallback(shouldNotAbort)
   469  			d = time.Since(start)
   470  			chFinished <- struct{}{}
   471  		}()
   472  		<-chStarted
   473  		start := time.Now()
   474  		time.Sleep(25 * time.Millisecond)
   475  		require.Nil(t, ctrl.Unregister(ctx))
   476  		du := time.Since(start)
   477  		<-chFinished
   478  
   479  		assert.True(t, executed)
   480  		assert.Equal(t, 1, executedCounter)
   481  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
   482  		assert.GreaterOrEqual(t, du, 40*time.Millisecond)
   483  	})
   484  
   485  	t.Run("unregister fails due to context timeout", func(t *testing.T) {
   486  		executedCounter := 0
   487  		callback := func(shouldAbort ShouldAbortCallback) bool {
   488  			time.Sleep(50 * time.Millisecond)
   489  			executedCounter++
   490  			return true
   491  		}
   492  		chStarted := make(chan struct{}, 1)
   493  		chFinished := make(chan struct{}, 1)
   494  		var executed1 bool
   495  		var executed2 bool
   496  		var d1 time.Duration
   497  		var d2 time.Duration
   498  
   499  		callbacks := NewCallbackGroup("id", logger, 2)
   500  		ctrl := callbacks.Register("c", callback)
   501  
   502  		go func() {
   503  			chStarted <- struct{}{}
   504  			start := time.Now()
   505  			executed1 = callbacks.CycleCallback(shouldNotAbort)
   506  			d1 = time.Since(start)
   507  			chFinished <- struct{}{}
   508  		}()
   509  		<-chStarted
   510  		start := time.Now()
   511  		time.Sleep(25 * time.Millisecond)
   512  		ctxTimeout, cancel := context.WithTimeout(ctx, 5*time.Millisecond)
   513  		defer cancel()
   514  		require.NotNil(t, ctrl.Unregister(ctxTimeout))
   515  		du := time.Since(start)
   516  		<-chFinished
   517  
   518  		go func() {
   519  			start := time.Now()
   520  			executed2 = callbacks.CycleCallback(shouldNotAbort)
   521  			d2 = time.Since(start)
   522  			chFinished <- struct{}{}
   523  		}()
   524  		<-chFinished
   525  
   526  		assert.True(t, executed1)
   527  		assert.True(t, executed2)
   528  		assert.Equal(t, 2, executedCounter)
   529  		assert.GreaterOrEqual(t, d1, 50*time.Millisecond)
   530  		assert.GreaterOrEqual(t, d2, 50*time.Millisecond)
   531  		assert.GreaterOrEqual(t, du, 30*time.Millisecond)
   532  	})
   533  
   534  	t.Run("unregister 3rd and 4th while executing", func(t *testing.T) {
   535  		executedCounter1 := 0
   536  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   537  			time.Sleep(50 * time.Millisecond)
   538  			executedCounter1++
   539  			return true
   540  		}
   541  		executedCounter2 := 0
   542  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   543  			time.Sleep(50 * time.Millisecond)
   544  			executedCounter2++
   545  			return true
   546  		}
   547  		executedCounter3 := 0
   548  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
   549  			time.Sleep(50 * time.Millisecond)
   550  			executedCounter3++
   551  			return true
   552  		}
   553  		executedCounter4 := 0
   554  		callback4 := func(shouldAbort ShouldAbortCallback) bool {
   555  			time.Sleep(50 * time.Millisecond)
   556  			executedCounter4++
   557  			return true
   558  		}
   559  		chStarted := make(chan struct{}, 1)
   560  		chFinished := make(chan struct{}, 1)
   561  		var executed bool
   562  		var d time.Duration
   563  
   564  		callbacks := NewCallbackGroup("id", logger, 2)
   565  		callbacks.Register("c1", callback1)
   566  		callbacks.Register("c2", callback2)
   567  		ctrl3 := callbacks.Register("c3", callback3)
   568  		ctrl4 := callbacks.Register("c4", callback4)
   569  
   570  		go func() {
   571  			chStarted <- struct{}{}
   572  			start := time.Now()
   573  			executed = callbacks.CycleCallback(shouldNotAbort)
   574  			d = time.Since(start)
   575  			chFinished <- struct{}{}
   576  		}()
   577  		<-chStarted
   578  		time.Sleep(25 * time.Millisecond)
   579  		require.Nil(t, ctrl3.Unregister(ctx))
   580  		require.Nil(t, ctrl4.Unregister(ctx))
   581  		<-chFinished
   582  
   583  		assert.True(t, executed)
   584  		assert.Equal(t, 1, executedCounter1)
   585  		assert.Equal(t, 1, executedCounter2)
   586  		assert.Equal(t, 0, executedCounter3)
   587  		assert.Equal(t, 0, executedCounter3)
   588  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
   589  	})
   590  
   591  	t.Run("unregister while running", func(t *testing.T) {
   592  		counter1 := 0
   593  		counter2 := 0
   594  		max := 25
   595  
   596  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   597  			for {
   598  				if shouldAbort() {
   599  					return false
   600  				}
   601  
   602  				time.Sleep(10 * time.Millisecond)
   603  				counter1++
   604  
   605  				// 10ms * 25 = 250ms
   606  				if counter1 > max {
   607  					return true
   608  				}
   609  			}
   610  		}
   611  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   612  			for {
   613  				if shouldAbort() {
   614  					return false
   615  				}
   616  
   617  				time.Sleep(10 * time.Millisecond)
   618  				counter2++
   619  
   620  				// 10ms * 25 = 250ms
   621  				if counter2 > max {
   622  					return true
   623  				}
   624  			}
   625  		}
   626  
   627  		chStarted := make(chan struct{}, 1)
   628  		chFinished := make(chan struct{}, 1)
   629  		var executed bool
   630  		var d time.Duration
   631  
   632  		callbacks := NewCallbackGroup("id", logger, 2)
   633  		ctrl1 := callbacks.Register("c1", callback1)
   634  		ctrl2 := callbacks.Register("c2", callback2)
   635  
   636  		go func() {
   637  			chStarted <- struct{}{}
   638  			start := time.Now()
   639  			executed = callbacks.CycleCallback(shouldNotAbort)
   640  			d = time.Since(start)
   641  			chFinished <- struct{}{}
   642  		}()
   643  		<-chStarted
   644  		time.Sleep(50 * time.Millisecond)
   645  		require.NoError(t, ctrl1.Unregister(ctx))
   646  		require.NoError(t, ctrl2.Unregister(ctx))
   647  		<-chFinished
   648  
   649  		assert.False(t, executed)
   650  		assert.LessOrEqual(t, counter1, max)
   651  		assert.LessOrEqual(t, counter2, max)
   652  		assert.LessOrEqual(t, d, 200*time.Millisecond)
   653  	})
   654  }
   655  
   656  func TestCycleCallback_Parallel_Deactivate(t *testing.T) {
   657  	ctx := context.Background()
   658  	logger, _ := test.NewNullLogger()
   659  	shouldNotAbort := func() bool { return false }
   660  
   661  	t.Run("1 executable callback, 1 deactivated", func(t *testing.T) {
   662  		executedCounter := 0
   663  		callback := func(shouldAbort ShouldAbortCallback) bool {
   664  			time.Sleep(50 * time.Millisecond)
   665  			executedCounter++
   666  			return true
   667  		}
   668  		var executed bool
   669  		var d time.Duration
   670  
   671  		callbacks := NewCallbackGroup("id", logger, 2)
   672  		ctrl := callbacks.Register("c1", callback)
   673  		require.Nil(t, ctrl.Deactivate(ctx))
   674  
   675  		start := time.Now()
   676  		executed = callbacks.CycleCallback(shouldNotAbort)
   677  		d = time.Since(start)
   678  
   679  		assert.False(t, executed)
   680  		assert.Equal(t, 0, executedCounter)
   681  		assert.GreaterOrEqual(t, d, 0*time.Millisecond)
   682  	})
   683  
   684  	t.Run("2 executable callbacks, 2 deactivated", func(t *testing.T) {
   685  		executedCounter1 := 0
   686  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   687  			time.Sleep(50 * time.Millisecond)
   688  			executedCounter1++
   689  			return true
   690  		}
   691  		executedCounter2 := 0
   692  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   693  			time.Sleep(25 * time.Millisecond)
   694  			executedCounter2++
   695  			return true
   696  		}
   697  		var executed bool
   698  		var d time.Duration
   699  
   700  		callbacks := NewCallbackGroup("id", logger, 2)
   701  		ctrl1 := callbacks.Register("c1", callback1)
   702  		ctrl2 := callbacks.Register("c2", callback2)
   703  		require.Nil(t, ctrl1.Deactivate(ctx))
   704  		require.Nil(t, ctrl2.Deactivate(ctx))
   705  
   706  		start := time.Now()
   707  		executed = callbacks.CycleCallback(shouldNotAbort)
   708  		d = time.Since(start)
   709  
   710  		assert.False(t, executed)
   711  		assert.Equal(t, 0, executedCounter1)
   712  		assert.Equal(t, 0, executedCounter2)
   713  		assert.GreaterOrEqual(t, d, 0*time.Millisecond)
   714  	})
   715  
   716  	t.Run("2 executable callbacks, 1 deactivated", func(t *testing.T) {
   717  		executedCounter1 := 0
   718  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   719  			time.Sleep(50 * time.Millisecond)
   720  			executedCounter1++
   721  			return true
   722  		}
   723  		executedCounter2 := 0
   724  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   725  			time.Sleep(25 * time.Millisecond)
   726  			executedCounter2++
   727  			return true
   728  		}
   729  		var executed bool
   730  		var d time.Duration
   731  
   732  		callbacks := NewCallbackGroup("id", logger, 2)
   733  		ctrl1 := callbacks.Register("c1", callback1)
   734  		callbacks.Register("c2", callback2)
   735  		require.Nil(t, ctrl1.Deactivate(ctx))
   736  
   737  		start := time.Now()
   738  		executed = callbacks.CycleCallback(shouldNotAbort)
   739  		d = time.Since(start)
   740  
   741  		assert.True(t, executed)
   742  		assert.Equal(t, 0, executedCounter1)
   743  		assert.Equal(t, 1, executedCounter2)
   744  		assert.GreaterOrEqual(t, d, 25*time.Millisecond)
   745  	})
   746  
   747  	t.Run("4 executable callbacks, all deactivated at different time", func(t *testing.T) {
   748  		executedCounter1 := 0
   749  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   750  			time.Sleep(25 * time.Millisecond)
   751  			executedCounter1++
   752  			return true
   753  		}
   754  		executedCounter2 := 0
   755  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   756  			time.Sleep(25 * time.Millisecond)
   757  			executedCounter2++
   758  			return true
   759  		}
   760  		executedCounter3 := 0
   761  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
   762  			time.Sleep(25 * time.Millisecond)
   763  			executedCounter3++
   764  			return true
   765  		}
   766  		executedCounter4 := 0
   767  		callback4 := func(shouldAbort ShouldAbortCallback) bool {
   768  			time.Sleep(25 * time.Millisecond)
   769  			executedCounter4++
   770  			return true
   771  		}
   772  		var executed1 bool
   773  		var executed2 bool
   774  		var executed3 bool
   775  		var executed4 bool
   776  		var d1 time.Duration
   777  		var d2 time.Duration
   778  		var d3 time.Duration
   779  		var d4 time.Duration
   780  
   781  		callbacks := NewCallbackGroup("id", logger, 2)
   782  		ctrl1 := callbacks.Register("c1", callback1)
   783  		ctrl2 := callbacks.Register("c2", callback2)
   784  		ctrl3 := callbacks.Register("c3", callback3)
   785  		ctrl4 := callbacks.Register("c4", callback4)
   786  		require.Nil(t, ctrl3.Deactivate(ctx))
   787  
   788  		start := time.Now()
   789  		executed1 = callbacks.CycleCallback(shouldNotAbort)
   790  		d1 = time.Since(start)
   791  
   792  		require.Nil(t, ctrl1.Deactivate(ctx))
   793  
   794  		start = time.Now()
   795  		executed2 = callbacks.CycleCallback(shouldNotAbort)
   796  		d2 = time.Since(start)
   797  
   798  		require.Nil(t, ctrl4.Deactivate(ctx))
   799  
   800  		start = time.Now()
   801  		executed3 = callbacks.CycleCallback(shouldNotAbort)
   802  		d3 = time.Since(start)
   803  
   804  		require.Nil(t, ctrl2.Deactivate(ctx))
   805  
   806  		start = time.Now()
   807  		executed4 = callbacks.CycleCallback(shouldNotAbort)
   808  		d4 = time.Since(start)
   809  
   810  		assert.True(t, executed1)
   811  		assert.True(t, executed2)
   812  		assert.True(t, executed3)
   813  		assert.False(t, executed4)
   814  		assert.Equal(t, 1, executedCounter1)
   815  		assert.Equal(t, 3, executedCounter2)
   816  		assert.Equal(t, 0, executedCounter3)
   817  		assert.Equal(t, 2, executedCounter4)
   818  		assert.GreaterOrEqual(t, d1, 50*time.Millisecond)
   819  		assert.GreaterOrEqual(t, d2, 25*time.Millisecond)
   820  		assert.GreaterOrEqual(t, d3, 25*time.Millisecond)
   821  		assert.GreaterOrEqual(t, d4, 0*time.Millisecond)
   822  	})
   823  
   824  	t.Run("deactivate is waiting till the end of execution", func(t *testing.T) {
   825  		executedCounter := 0
   826  		callback := func(shouldAbort ShouldAbortCallback) bool {
   827  			time.Sleep(50 * time.Millisecond)
   828  			executedCounter++
   829  			return true
   830  		}
   831  		chStarted := make(chan struct{}, 1)
   832  		chFinished := make(chan struct{}, 1)
   833  		var executed bool
   834  		var d time.Duration
   835  
   836  		callbacks := NewCallbackGroup("id", logger, 2)
   837  		ctrl := callbacks.Register("c", callback)
   838  
   839  		go func() {
   840  			chStarted <- struct{}{}
   841  			start := time.Now()
   842  			executed = callbacks.CycleCallback(shouldNotAbort)
   843  			d = time.Since(start)
   844  			chFinished <- struct{}{}
   845  		}()
   846  		<-chStarted
   847  		start := time.Now()
   848  		time.Sleep(25 * time.Millisecond)
   849  		require.Nil(t, ctrl.Deactivate(ctx))
   850  		du := time.Since(start)
   851  		<-chFinished
   852  
   853  		assert.True(t, executed)
   854  		assert.Equal(t, 1, executedCounter)
   855  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
   856  		assert.GreaterOrEqual(t, du, 40*time.Millisecond)
   857  	})
   858  
   859  	t.Run("deactivate fails due to context timeout", func(t *testing.T) {
   860  		executedCounter := 0
   861  		callback := func(shouldAbort ShouldAbortCallback) bool {
   862  			time.Sleep(50 * time.Millisecond)
   863  			executedCounter++
   864  			return true
   865  		}
   866  		chStarted := make(chan struct{}, 1)
   867  		chFinished := make(chan struct{}, 1)
   868  		var executed1 bool
   869  		var executed2 bool
   870  		var d1 time.Duration
   871  		var d2 time.Duration
   872  
   873  		callbacks := NewCallbackGroup("id", logger, 2)
   874  		ctrl := callbacks.Register("c", callback)
   875  
   876  		go func() {
   877  			chStarted <- struct{}{}
   878  			start := time.Now()
   879  			executed1 = callbacks.CycleCallback(shouldNotAbort)
   880  			d1 = time.Since(start)
   881  			chFinished <- struct{}{}
   882  		}()
   883  		<-chStarted
   884  		start := time.Now()
   885  		time.Sleep(25 * time.Millisecond)
   886  		ctxTimeout, cancel := context.WithTimeout(ctx, 5*time.Millisecond)
   887  		defer cancel()
   888  		require.NotNil(t, ctrl.Deactivate(ctxTimeout))
   889  		du := time.Since(start)
   890  		<-chFinished
   891  
   892  		go func() {
   893  			start := time.Now()
   894  			executed2 = callbacks.CycleCallback(shouldNotAbort)
   895  			d2 = time.Since(start)
   896  			chFinished <- struct{}{}
   897  		}()
   898  		<-chFinished
   899  
   900  		assert.True(t, executed1)
   901  		assert.True(t, executed2)
   902  		assert.Equal(t, 2, executedCounter)
   903  		assert.GreaterOrEqual(t, d1, 50*time.Millisecond)
   904  		assert.GreaterOrEqual(t, d2, 50*time.Millisecond)
   905  		assert.GreaterOrEqual(t, du, 30*time.Millisecond)
   906  	})
   907  
   908  	t.Run("deactivate 3rd and 4th while executing", func(t *testing.T) {
   909  		executedCounter1 := 0
   910  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   911  			time.Sleep(50 * time.Millisecond)
   912  			executedCounter1++
   913  			return true
   914  		}
   915  		executedCounter2 := 0
   916  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   917  			time.Sleep(50 * time.Millisecond)
   918  			executedCounter2++
   919  			return true
   920  		}
   921  		executedCounter3 := 0
   922  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
   923  			time.Sleep(50 * time.Millisecond)
   924  			executedCounter3++
   925  			return true
   926  		}
   927  		executedCounter4 := 0
   928  		callback4 := func(shouldAbort ShouldAbortCallback) bool {
   929  			time.Sleep(50 * time.Millisecond)
   930  			executedCounter4++
   931  			return true
   932  		}
   933  		chStarted := make(chan struct{}, 1)
   934  		chFinished := make(chan struct{}, 1)
   935  		var executed bool
   936  		var d time.Duration
   937  
   938  		callbacks := NewCallbackGroup("id", logger, 2)
   939  		callbacks.Register("c1", callback1)
   940  		callbacks.Register("c2", callback2)
   941  		ctrl3 := callbacks.Register("c3", callback3)
   942  		ctrl4 := callbacks.Register("c4", callback4)
   943  
   944  		go func() {
   945  			chStarted <- struct{}{}
   946  			start := time.Now()
   947  			executed = callbacks.CycleCallback(shouldNotAbort)
   948  			d = time.Since(start)
   949  			chFinished <- struct{}{}
   950  		}()
   951  		<-chStarted
   952  		time.Sleep(25 * time.Millisecond)
   953  		require.Nil(t, ctrl3.Deactivate(ctx))
   954  		require.Nil(t, ctrl4.Deactivate(ctx))
   955  		<-chFinished
   956  
   957  		assert.True(t, executed)
   958  		assert.Equal(t, 1, executedCounter1)
   959  		assert.Equal(t, 1, executedCounter2)
   960  		assert.Equal(t, 0, executedCounter3)
   961  		assert.Equal(t, 0, executedCounter3)
   962  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
   963  	})
   964  
   965  	t.Run("deactivate while running", func(t *testing.T) {
   966  		counter1 := 0
   967  		counter2 := 0
   968  		max := 25
   969  
   970  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
   971  			for {
   972  				if shouldAbort() {
   973  					return false
   974  				}
   975  
   976  				time.Sleep(10 * time.Millisecond)
   977  				counter1++
   978  
   979  				// 10ms * 25 = 250ms
   980  				if counter1 > max {
   981  					return true
   982  				}
   983  			}
   984  		}
   985  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
   986  			for {
   987  				if shouldAbort() {
   988  					return false
   989  				}
   990  
   991  				time.Sleep(10 * time.Millisecond)
   992  				counter2++
   993  
   994  				// 10ms * 25 = 250ms
   995  				if counter2 > max {
   996  					return true
   997  				}
   998  			}
   999  		}
  1000  
  1001  		chStarted := make(chan struct{}, 1)
  1002  		chFinished := make(chan struct{}, 1)
  1003  		var executed bool
  1004  		var d time.Duration
  1005  
  1006  		callbacks := NewCallbackGroup("id", logger, 2)
  1007  		ctrl1 := callbacks.Register("c1", callback1)
  1008  		ctrl2 := callbacks.Register("c2", callback2)
  1009  
  1010  		go func() {
  1011  			chStarted <- struct{}{}
  1012  			start := time.Now()
  1013  			executed = callbacks.CycleCallback(shouldNotAbort)
  1014  			d = time.Since(start)
  1015  			chFinished <- struct{}{}
  1016  		}()
  1017  		<-chStarted
  1018  		time.Sleep(50 * time.Millisecond)
  1019  		require.NoError(t, ctrl1.Deactivate(ctx))
  1020  		require.NoError(t, ctrl2.Deactivate(ctx))
  1021  		<-chFinished
  1022  
  1023  		assert.False(t, executed)
  1024  		assert.LessOrEqual(t, counter1, max)
  1025  		assert.LessOrEqual(t, counter2, max)
  1026  		assert.LessOrEqual(t, d, 200*time.Millisecond)
  1027  
  1028  		t.Run("does not abort after activated back again", func(t *testing.T) {
  1029  			require.NoError(t, ctrl1.Activate())
  1030  			require.NoError(t, ctrl2.Activate())
  1031  
  1032  			counter1 = 0
  1033  			counter2 = 0
  1034  			max = 10
  1035  
  1036  			go func() {
  1037  				start := time.Now()
  1038  				executed = callbacks.CycleCallback(shouldNotAbort)
  1039  				d = time.Since(start)
  1040  				chFinished <- struct{}{}
  1041  			}()
  1042  			<-chFinished
  1043  
  1044  			assert.True(t, executed)
  1045  			assert.Greater(t, counter1, max)
  1046  			assert.Greater(t, counter2, max)
  1047  			assert.GreaterOrEqual(t, d, 100*time.Millisecond)
  1048  		})
  1049  	})
  1050  }
  1051  
  1052  func TestCycleCallback_Sequential(t *testing.T) {
  1053  	logger, _ := test.NewNullLogger()
  1054  	shouldNotAbort := func() bool { return false }
  1055  
  1056  	t.Run("no callbacks", func(t *testing.T) {
  1057  		var executed bool
  1058  
  1059  		callbacks := NewCallbackGroup("id", logger, 1)
  1060  
  1061  		executed = callbacks.CycleCallback(shouldNotAbort)
  1062  
  1063  		assert.False(t, executed)
  1064  	})
  1065  
  1066  	t.Run("2 executable callbacks", func(t *testing.T) {
  1067  		executedCounter1 := 0
  1068  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1069  			time.Sleep(50 * time.Millisecond)
  1070  			executedCounter1++
  1071  			return true
  1072  		}
  1073  		executedCounter2 := 0
  1074  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1075  			time.Sleep(25 * time.Millisecond)
  1076  			executedCounter2++
  1077  			return true
  1078  		}
  1079  		var executed bool
  1080  		var d time.Duration
  1081  
  1082  		callbacks := NewCallbackGroup("id", logger, 1)
  1083  		callbacks.Register("c1", callback1)
  1084  		callbacks.Register("c2", callback2)
  1085  
  1086  		start := time.Now()
  1087  		executed = callbacks.CycleCallback(shouldNotAbort)
  1088  		d = time.Since(start)
  1089  
  1090  		assert.True(t, executed)
  1091  		assert.Equal(t, 1, executedCounter1)
  1092  		assert.Equal(t, 1, executedCounter2)
  1093  		assert.GreaterOrEqual(t, d, 75*time.Millisecond)
  1094  	})
  1095  
  1096  	t.Run("2 non-executable callbacks", func(t *testing.T) {
  1097  		executedCounter1 := 0
  1098  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1099  			time.Sleep(10 * time.Millisecond)
  1100  			executedCounter1++
  1101  			return false
  1102  		}
  1103  		executedCounter2 := 0
  1104  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1105  			time.Sleep(10 * time.Millisecond)
  1106  			executedCounter2++
  1107  			return false
  1108  		}
  1109  		var executed bool
  1110  		var d time.Duration
  1111  
  1112  		callbacks := NewCallbackGroup("id", logger, 1)
  1113  		callbacks.Register("c1", callback1)
  1114  		callbacks.Register("c2", callback2)
  1115  
  1116  		start := time.Now()
  1117  		executed = callbacks.CycleCallback(shouldNotAbort)
  1118  		d = time.Since(start)
  1119  
  1120  		assert.False(t, executed)
  1121  		assert.Equal(t, 1, executedCounter1)
  1122  		assert.Equal(t, 1, executedCounter2)
  1123  		assert.GreaterOrEqual(t, d, 10*time.Millisecond)
  1124  	})
  1125  
  1126  	t.Run("2 executable callbacks, not executed due to should abort", func(t *testing.T) {
  1127  		executedCounter1 := 0
  1128  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1129  			time.Sleep(25 * time.Millisecond)
  1130  			executedCounter1++
  1131  			return true
  1132  		}
  1133  		executedCounter2 := 0
  1134  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1135  			time.Sleep(25 * time.Millisecond)
  1136  			executedCounter2++
  1137  			return true
  1138  		}
  1139  		shouldAbortCounter := 0
  1140  		shouldAbort := func() bool {
  1141  			shouldAbortCounter++
  1142  			return shouldAbortCounter > 1
  1143  		}
  1144  
  1145  		var executed bool
  1146  		var d time.Duration
  1147  
  1148  		callbacks := NewCallbackGroup("id", logger, 1)
  1149  		callbacks.Register("c1", callback1)
  1150  		callbacks.Register("c2", callback2)
  1151  
  1152  		start := time.Now()
  1153  		executed = callbacks.CycleCallback(shouldAbort)
  1154  		d = time.Since(start)
  1155  
  1156  		assert.True(t, executed)
  1157  		assert.Equal(t, 1, executedCounter1)
  1158  		assert.Equal(t, 0, executedCounter2)
  1159  		assert.GreaterOrEqual(t, d, 25*time.Millisecond)
  1160  	})
  1161  
  1162  	t.Run("register new while executing", func(t *testing.T) {
  1163  		executedCounter1 := 0
  1164  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1165  			time.Sleep(50 * time.Millisecond)
  1166  			executedCounter1++
  1167  			return true
  1168  		}
  1169  		executedCounter2 := 0
  1170  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1171  			time.Sleep(50 * time.Millisecond)
  1172  			executedCounter2++
  1173  			return true
  1174  		}
  1175  		chStarted := make(chan struct{}, 1)
  1176  		chFinished := make(chan struct{}, 1)
  1177  		var executed bool
  1178  		var d time.Duration
  1179  
  1180  		callbacks := NewCallbackGroup("id", logger, 1)
  1181  		callbacks.Register("c1", callback1)
  1182  
  1183  		go func() {
  1184  			chStarted <- struct{}{}
  1185  			start := time.Now()
  1186  			executed = callbacks.CycleCallback(shouldNotAbort)
  1187  			d = time.Since(start)
  1188  			chFinished <- struct{}{}
  1189  		}()
  1190  		<-chStarted
  1191  		time.Sleep(25 * time.Millisecond)
  1192  		callbacks.Register("c2", callback2)
  1193  		<-chFinished
  1194  
  1195  		assert.True(t, executed)
  1196  		assert.Equal(t, 1, executedCounter1)
  1197  		assert.Equal(t, 1, executedCounter2)
  1198  		assert.GreaterOrEqual(t, d, 100*time.Millisecond)
  1199  	})
  1200  
  1201  	t.Run("run with intervals", func(T *testing.T) {
  1202  		ticker := NewFixedTicker(10 * time.Millisecond)
  1203  		intervals2 := NewSeriesIntervals([]time.Duration{
  1204  			10 * time.Millisecond, 30 * time.Millisecond, 50 * time.Millisecond,
  1205  		})
  1206  		intervals3 := NewFixedIntervals(60 * time.Millisecond)
  1207  		now := time.Now()
  1208  
  1209  		executionTimes1 := []time.Duration{}
  1210  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1211  			executionTimes1 = append(executionTimes1, time.Since(now))
  1212  			return true
  1213  		}
  1214  		executionCounter2 := 0
  1215  		executionTimes2 := []time.Duration{}
  1216  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1217  			executionCounter2++
  1218  			executionTimes2 = append(executionTimes2, time.Since(now))
  1219  			// reports executed every 3 calls, should result in 10, 30, 50, 50, 10, 30, 50, 50, ... intervals
  1220  			return executionCounter2%4 == 0
  1221  		}
  1222  		executionTimes3 := []time.Duration{}
  1223  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
  1224  			executionTimes3 = append(executionTimes3, time.Since(now))
  1225  			return true
  1226  		}
  1227  
  1228  		callbacks := NewCallbackGroup("id", logger, 1)
  1229  		// should be called on every tick, with 10 intervals
  1230  		callbacks.Register("c1", callback1)
  1231  		// should be called with 10, 30, 50, 50, 10, 30, 50, 50, ... intervals
  1232  		callbacks.Register("c2", callback2, WithIntervals(intervals2))
  1233  		// should be called with 60, 60, ... intervals
  1234  		callbacks.Register("c3", callback3, WithIntervals(intervals3))
  1235  
  1236  		cm := NewManager(ticker, callbacks.CycleCallback, logger)
  1237  		cm.Start()
  1238  		time.Sleep(400 * time.Millisecond)
  1239  		cm.StopAndWait(context.Background())
  1240  
  1241  		// within 400 ms c1 should be called at least 30x
  1242  		require.GreaterOrEqual(t, len(executionTimes1), 30)
  1243  		// 1st call on 1st tick after 10ms
  1244  		sumDuration := time.Duration(10)
  1245  		for i := 0; i < 30; i++ {
  1246  			assert.GreaterOrEqual(t, executionTimes1[i], sumDuration)
  1247  			sumDuration += 10 * time.Millisecond
  1248  		}
  1249  
  1250  		// within 400 ms c2 should be called at least 8x
  1251  		require.GreaterOrEqual(t, len(executionTimes2), 8)
  1252  		// 1st call on 1st tick after 10ms
  1253  		sumDuration = time.Duration(0)
  1254  		for i := 0; i < 8; i++ {
  1255  			assert.GreaterOrEqual(t, executionTimes2[i], sumDuration)
  1256  			switch (i + 1) % 4 {
  1257  			case 0:
  1258  				sumDuration += 10 * time.Millisecond
  1259  			case 1:
  1260  				sumDuration += 30 * time.Millisecond
  1261  			case 2, 3:
  1262  				sumDuration += 50 * time.Millisecond
  1263  			}
  1264  		}
  1265  
  1266  		// within 400 ms c3 should be called at least 6x
  1267  		require.GreaterOrEqual(t, len(executionTimes3), 6)
  1268  		// 1st call on 1st tick after 10ms
  1269  		sumDuration = time.Duration(0)
  1270  		for i := 0; i < 6; i++ {
  1271  			assert.GreaterOrEqual(t, executionTimes3[i], sumDuration)
  1272  			sumDuration += 60 * time.Millisecond
  1273  		}
  1274  	})
  1275  }
  1276  
  1277  func TestCycleCallback_Sequential_Unregister(t *testing.T) {
  1278  	ctx := context.Background()
  1279  	logger, _ := test.NewNullLogger()
  1280  	shouldNotAbort := func() bool { return false }
  1281  
  1282  	t.Run("1 executable callback, 1 unregistered", func(t *testing.T) {
  1283  		executedCounter := 0
  1284  		callback := func(shouldAbort ShouldAbortCallback) bool {
  1285  			time.Sleep(50 * time.Millisecond)
  1286  			executedCounter++
  1287  			return true
  1288  		}
  1289  		var executed bool
  1290  		var d time.Duration
  1291  
  1292  		callbacks := NewCallbackGroup("id", logger, 1)
  1293  		ctrl := callbacks.Register("c1", callback)
  1294  		require.Nil(t, ctrl.Unregister(ctx))
  1295  
  1296  		start := time.Now()
  1297  		executed = callbacks.CycleCallback(shouldNotAbort)
  1298  		d = time.Since(start)
  1299  
  1300  		assert.False(t, executed)
  1301  		assert.Equal(t, 0, executedCounter)
  1302  		assert.GreaterOrEqual(t, d, 0*time.Millisecond)
  1303  	})
  1304  
  1305  	t.Run("2 executable callbacks, 2 unregistered", func(t *testing.T) {
  1306  		executedCounter1 := 0
  1307  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1308  			time.Sleep(50 * time.Millisecond)
  1309  			executedCounter1++
  1310  			return true
  1311  		}
  1312  		executedCounter2 := 0
  1313  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1314  			time.Sleep(25 * time.Millisecond)
  1315  			executedCounter2++
  1316  			return true
  1317  		}
  1318  		var executed bool
  1319  		var d time.Duration
  1320  
  1321  		callbacks := NewCallbackGroup("id", logger, 1)
  1322  		ctrl1 := callbacks.Register("c1", callback1)
  1323  		ctrl2 := callbacks.Register("c2", callback2)
  1324  		require.Nil(t, ctrl1.Unregister(ctx))
  1325  		require.Nil(t, ctrl2.Unregister(ctx))
  1326  
  1327  		start := time.Now()
  1328  		executed = callbacks.CycleCallback(shouldNotAbort)
  1329  		d = time.Since(start)
  1330  
  1331  		assert.False(t, executed)
  1332  		assert.Equal(t, 0, executedCounter1)
  1333  		assert.Equal(t, 0, executedCounter2)
  1334  		assert.GreaterOrEqual(t, d, 0*time.Millisecond)
  1335  	})
  1336  
  1337  	t.Run("2 executable callbacks, 1 unregistered", func(t *testing.T) {
  1338  		executedCounter1 := 0
  1339  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1340  			time.Sleep(50 * time.Millisecond)
  1341  			executedCounter1++
  1342  			return true
  1343  		}
  1344  		executedCounter2 := 0
  1345  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1346  			time.Sleep(25 * time.Millisecond)
  1347  			executedCounter2++
  1348  			return true
  1349  		}
  1350  		var executed bool
  1351  		var d time.Duration
  1352  
  1353  		callbacks := NewCallbackGroup("id", logger, 1)
  1354  		ctrl1 := callbacks.Register("c1", callback1)
  1355  		callbacks.Register("c2", callback2)
  1356  		require.Nil(t, ctrl1.Unregister(ctx))
  1357  
  1358  		start := time.Now()
  1359  		executed = callbacks.CycleCallback(shouldNotAbort)
  1360  		d = time.Since(start)
  1361  
  1362  		assert.True(t, executed)
  1363  		assert.Equal(t, 0, executedCounter1)
  1364  		assert.Equal(t, 1, executedCounter2)
  1365  		assert.GreaterOrEqual(t, d, 25*time.Millisecond)
  1366  	})
  1367  
  1368  	t.Run("4 executable callbacks, all unregistered at different time", func(t *testing.T) {
  1369  		executedCounter1 := 0
  1370  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1371  			time.Sleep(25 * time.Millisecond)
  1372  			executedCounter1++
  1373  			return true
  1374  		}
  1375  		executedCounter2 := 0
  1376  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1377  			time.Sleep(25 * time.Millisecond)
  1378  			executedCounter2++
  1379  			return true
  1380  		}
  1381  		executedCounter3 := 0
  1382  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
  1383  			time.Sleep(25 * time.Millisecond)
  1384  			executedCounter3++
  1385  			return true
  1386  		}
  1387  		executedCounter4 := 0
  1388  		callback4 := func(shouldAbort ShouldAbortCallback) bool {
  1389  			time.Sleep(25 * time.Millisecond)
  1390  			executedCounter4++
  1391  			return true
  1392  		}
  1393  		var executed1 bool
  1394  		var executed2 bool
  1395  		var executed3 bool
  1396  		var executed4 bool
  1397  		var d1 time.Duration
  1398  		var d2 time.Duration
  1399  		var d3 time.Duration
  1400  		var d4 time.Duration
  1401  
  1402  		callbacks := NewCallbackGroup("id", logger, 1)
  1403  		ctrl1 := callbacks.Register("c1", callback1)
  1404  		ctrl2 := callbacks.Register("c2", callback2)
  1405  		ctrl3 := callbacks.Register("c3", callback3)
  1406  		ctrl4 := callbacks.Register("c4", callback4)
  1407  		require.Nil(t, ctrl3.Unregister(ctx))
  1408  
  1409  		start := time.Now()
  1410  		executed1 = callbacks.CycleCallback(shouldNotAbort)
  1411  		d1 = time.Since(start)
  1412  
  1413  		require.Nil(t, ctrl1.Unregister(ctx))
  1414  
  1415  		start = time.Now()
  1416  		executed2 = callbacks.CycleCallback(shouldNotAbort)
  1417  		d2 = time.Since(start)
  1418  
  1419  		require.Nil(t, ctrl4.Unregister(ctx))
  1420  
  1421  		start = time.Now()
  1422  		executed3 = callbacks.CycleCallback(shouldNotAbort)
  1423  		d3 = time.Since(start)
  1424  
  1425  		require.Nil(t, ctrl2.Unregister(ctx))
  1426  
  1427  		start = time.Now()
  1428  		executed4 = callbacks.CycleCallback(shouldNotAbort)
  1429  		d4 = time.Since(start)
  1430  
  1431  		assert.True(t, executed1)
  1432  		assert.True(t, executed2)
  1433  		assert.True(t, executed3)
  1434  		assert.False(t, executed4)
  1435  		assert.Equal(t, 1, executedCounter1)
  1436  		assert.Equal(t, 3, executedCounter2)
  1437  		assert.Equal(t, 0, executedCounter3)
  1438  		assert.Equal(t, 2, executedCounter4)
  1439  		assert.GreaterOrEqual(t, d1, 75*time.Millisecond)
  1440  		assert.GreaterOrEqual(t, d2, 50*time.Millisecond)
  1441  		assert.GreaterOrEqual(t, d3, 25*time.Millisecond)
  1442  		assert.GreaterOrEqual(t, d4, 0*time.Millisecond)
  1443  	})
  1444  
  1445  	t.Run("unregister is waiting till the end of execution", func(t *testing.T) {
  1446  		executedCounter := 0
  1447  		callback := func(shouldAbort ShouldAbortCallback) bool {
  1448  			time.Sleep(50 * time.Millisecond)
  1449  			executedCounter++
  1450  			return true
  1451  		}
  1452  		chStarted := make(chan struct{}, 1)
  1453  		chFinished := make(chan struct{}, 1)
  1454  		var executed bool
  1455  		var d time.Duration
  1456  
  1457  		callbacks := NewCallbackGroup("id", logger, 1)
  1458  		ctrl := callbacks.Register("c", callback)
  1459  
  1460  		go func() {
  1461  			chStarted <- struct{}{}
  1462  			start := time.Now()
  1463  			executed = callbacks.CycleCallback(shouldNotAbort)
  1464  			d = time.Since(start)
  1465  			chFinished <- struct{}{}
  1466  		}()
  1467  		<-chStarted
  1468  		start := time.Now()
  1469  		time.Sleep(25 * time.Millisecond)
  1470  		require.Nil(t, ctrl.Unregister(ctx))
  1471  		du := time.Since(start)
  1472  		<-chFinished
  1473  
  1474  		assert.True(t, executed)
  1475  		assert.Equal(t, 1, executedCounter)
  1476  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
  1477  		assert.GreaterOrEqual(t, du, 40*time.Millisecond)
  1478  	})
  1479  
  1480  	t.Run("unregister fails due to context timeout", func(t *testing.T) {
  1481  		executedCounter := 0
  1482  		callback := func(shouldAbort ShouldAbortCallback) bool {
  1483  			time.Sleep(50 * time.Millisecond)
  1484  			executedCounter++
  1485  			return true
  1486  		}
  1487  		chStarted := make(chan struct{}, 1)
  1488  		chFinished := make(chan struct{}, 1)
  1489  		var executed1 bool
  1490  		var executed2 bool
  1491  		var d1 time.Duration
  1492  		var d2 time.Duration
  1493  
  1494  		callbacks := NewCallbackGroup("id", logger, 1)
  1495  		ctrl := callbacks.Register("c", callback)
  1496  
  1497  		go func() {
  1498  			chStarted <- struct{}{}
  1499  			start := time.Now()
  1500  			executed1 = callbacks.CycleCallback(shouldNotAbort)
  1501  			d1 = time.Since(start)
  1502  			chFinished <- struct{}{}
  1503  		}()
  1504  		<-chStarted
  1505  		start := time.Now()
  1506  		time.Sleep(25 * time.Millisecond)
  1507  		ctxTimeout, cancel := context.WithTimeout(ctx, 5*time.Millisecond)
  1508  		defer cancel()
  1509  		require.NotNil(t, ctrl.Unregister(ctxTimeout))
  1510  		du := time.Since(start)
  1511  		<-chFinished
  1512  
  1513  		go func() {
  1514  			start := time.Now()
  1515  			executed2 = callbacks.CycleCallback(shouldNotAbort)
  1516  			d2 = time.Since(start)
  1517  			chFinished <- struct{}{}
  1518  		}()
  1519  		<-chFinished
  1520  
  1521  		assert.True(t, executed1)
  1522  		assert.True(t, executed2)
  1523  		assert.Equal(t, 2, executedCounter)
  1524  		assert.GreaterOrEqual(t, d1, 50*time.Millisecond)
  1525  		assert.GreaterOrEqual(t, d2, 50*time.Millisecond)
  1526  		assert.GreaterOrEqual(t, du, 30*time.Millisecond)
  1527  	})
  1528  
  1529  	t.Run("unregister 2nd and 3rd while executing", func(t *testing.T) {
  1530  		executedCounter1 := 0
  1531  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1532  			time.Sleep(50 * time.Millisecond)
  1533  			executedCounter1++
  1534  			return true
  1535  		}
  1536  		executedCounter2 := 0
  1537  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1538  			time.Sleep(50 * time.Millisecond)
  1539  			executedCounter2++
  1540  			return true
  1541  		}
  1542  		executedCounter3 := 0
  1543  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
  1544  			time.Sleep(50 * time.Millisecond)
  1545  			executedCounter3++
  1546  			return true
  1547  		}
  1548  		chStarted := make(chan struct{}, 1)
  1549  		chFinished := make(chan struct{}, 1)
  1550  		var executed bool
  1551  		var d time.Duration
  1552  
  1553  		callbacks := NewCallbackGroup("id", logger, 1)
  1554  		callbacks.Register("c1", callback1)
  1555  		ctrl2 := callbacks.Register("c2", callback2)
  1556  		ctrl3 := callbacks.Register("c3", callback3)
  1557  
  1558  		go func() {
  1559  			chStarted <- struct{}{}
  1560  			start := time.Now()
  1561  			executed = callbacks.CycleCallback(shouldNotAbort)
  1562  			d = time.Since(start)
  1563  			chFinished <- struct{}{}
  1564  		}()
  1565  		<-chStarted
  1566  		time.Sleep(25 * time.Millisecond)
  1567  		require.Nil(t, ctrl2.Unregister(ctx))
  1568  		require.Nil(t, ctrl3.Unregister(ctx))
  1569  		<-chFinished
  1570  
  1571  		assert.True(t, executed)
  1572  		assert.Equal(t, 1, executedCounter1)
  1573  		assert.Equal(t, 0, executedCounter2)
  1574  		assert.Equal(t, 0, executedCounter3)
  1575  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
  1576  	})
  1577  
  1578  	t.Run("unregister while running", func(t *testing.T) {
  1579  		counter := 0
  1580  		max := 25
  1581  		callback := func(shouldAbort ShouldAbortCallback) bool {
  1582  			for {
  1583  				if shouldAbort() {
  1584  					return false
  1585  				}
  1586  
  1587  				time.Sleep(10 * time.Millisecond)
  1588  				counter++
  1589  
  1590  				// 10ms * 25 = 250ms
  1591  				if counter > max {
  1592  					return true
  1593  				}
  1594  			}
  1595  		}
  1596  		chStarted := make(chan struct{}, 1)
  1597  		chFinished := make(chan struct{}, 1)
  1598  		var executed bool
  1599  		var d time.Duration
  1600  
  1601  		callbacks := NewCallbackGroup("id", logger, 1)
  1602  		ctrl := callbacks.Register("c", callback)
  1603  
  1604  		go func() {
  1605  			chStarted <- struct{}{}
  1606  			start := time.Now()
  1607  			executed = callbacks.CycleCallback(shouldNotAbort)
  1608  			d = time.Since(start)
  1609  			chFinished <- struct{}{}
  1610  		}()
  1611  		<-chStarted
  1612  		time.Sleep(50 * time.Millisecond)
  1613  		require.NoError(t, ctrl.Unregister(ctx))
  1614  		<-chFinished
  1615  
  1616  		assert.False(t, executed)
  1617  		assert.LessOrEqual(t, counter, max)
  1618  		assert.LessOrEqual(t, d, 200*time.Millisecond)
  1619  	})
  1620  }
  1621  
  1622  func TestCycleCallback_Sequential_Deactivate(t *testing.T) {
  1623  	ctx := context.Background()
  1624  	logger, _ := test.NewNullLogger()
  1625  	shouldNotAbort := func() bool { return false }
  1626  
  1627  	t.Run("1 executable callback, 1 deactivated", func(t *testing.T) {
  1628  		executedCounter := 0
  1629  		callback := func(shouldAbort ShouldAbortCallback) bool {
  1630  			time.Sleep(50 * time.Millisecond)
  1631  			executedCounter++
  1632  			return true
  1633  		}
  1634  		var executed bool
  1635  		var d time.Duration
  1636  
  1637  		callbacks := NewCallbackGroup("id", logger, 1)
  1638  		ctrl := callbacks.Register("c1", callback)
  1639  		require.Nil(t, ctrl.Deactivate(ctx))
  1640  
  1641  		start := time.Now()
  1642  		executed = callbacks.CycleCallback(shouldNotAbort)
  1643  		d = time.Since(start)
  1644  
  1645  		assert.False(t, executed)
  1646  		assert.Equal(t, 0, executedCounter)
  1647  		assert.GreaterOrEqual(t, d, 0*time.Millisecond)
  1648  	})
  1649  
  1650  	t.Run("2 executable callbacks, 2 deactivated", func(t *testing.T) {
  1651  		executedCounter1 := 0
  1652  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1653  			time.Sleep(50 * time.Millisecond)
  1654  			executedCounter1++
  1655  			return true
  1656  		}
  1657  		executedCounter2 := 0
  1658  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1659  			time.Sleep(25 * time.Millisecond)
  1660  			executedCounter2++
  1661  			return true
  1662  		}
  1663  		var executed bool
  1664  		var d time.Duration
  1665  
  1666  		callbacks := NewCallbackGroup("id", logger, 1)
  1667  		ctrl1 := callbacks.Register("c1", callback1)
  1668  		ctrl2 := callbacks.Register("c2", callback2)
  1669  		require.Nil(t, ctrl1.Deactivate(ctx))
  1670  		require.Nil(t, ctrl2.Deactivate(ctx))
  1671  
  1672  		start := time.Now()
  1673  		executed = callbacks.CycleCallback(shouldNotAbort)
  1674  		d = time.Since(start)
  1675  
  1676  		assert.False(t, executed)
  1677  		assert.Equal(t, 0, executedCounter1)
  1678  		assert.Equal(t, 0, executedCounter2)
  1679  		assert.GreaterOrEqual(t, d, 0*time.Millisecond)
  1680  	})
  1681  
  1682  	t.Run("2 executable callbacks, 1 deactivated", func(t *testing.T) {
  1683  		executedCounter1 := 0
  1684  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1685  			time.Sleep(50 * time.Millisecond)
  1686  			executedCounter1++
  1687  			return true
  1688  		}
  1689  		executedCounter2 := 0
  1690  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1691  			time.Sleep(25 * time.Millisecond)
  1692  			executedCounter2++
  1693  			return true
  1694  		}
  1695  		var executed bool
  1696  		var d time.Duration
  1697  
  1698  		callbacks := NewCallbackGroup("id", logger, 1)
  1699  		ctrl1 := callbacks.Register("c1", callback1)
  1700  		callbacks.Register("c2", callback2)
  1701  		require.Nil(t, ctrl1.Deactivate(ctx))
  1702  
  1703  		start := time.Now()
  1704  		executed = callbacks.CycleCallback(shouldNotAbort)
  1705  		d = time.Since(start)
  1706  
  1707  		assert.True(t, executed)
  1708  		assert.Equal(t, 0, executedCounter1)
  1709  		assert.Equal(t, 1, executedCounter2)
  1710  		assert.GreaterOrEqual(t, d, 25*time.Millisecond)
  1711  	})
  1712  
  1713  	t.Run("4 executable callbacks, all deactivated at different time", func(t *testing.T) {
  1714  		executedCounter1 := 0
  1715  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1716  			time.Sleep(25 * time.Millisecond)
  1717  			executedCounter1++
  1718  			return true
  1719  		}
  1720  		executedCounter2 := 0
  1721  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1722  			time.Sleep(25 * time.Millisecond)
  1723  			executedCounter2++
  1724  			return true
  1725  		}
  1726  		executedCounter3 := 0
  1727  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
  1728  			time.Sleep(25 * time.Millisecond)
  1729  			executedCounter3++
  1730  			return true
  1731  		}
  1732  		executedCounter4 := 0
  1733  		callback4 := func(shouldAbort ShouldAbortCallback) bool {
  1734  			time.Sleep(25 * time.Millisecond)
  1735  			executedCounter4++
  1736  			return true
  1737  		}
  1738  		var executed1 bool
  1739  		var executed2 bool
  1740  		var executed3 bool
  1741  		var executed4 bool
  1742  		var d1 time.Duration
  1743  		var d2 time.Duration
  1744  		var d3 time.Duration
  1745  		var d4 time.Duration
  1746  
  1747  		callbacks := NewCallbackGroup("id", logger, 1)
  1748  		ctrl1 := callbacks.Register("c1", callback1)
  1749  		ctrl2 := callbacks.Register("c2", callback2)
  1750  		ctrl3 := callbacks.Register("c3", callback3)
  1751  		ctrl4 := callbacks.Register("c4", callback4)
  1752  		require.Nil(t, ctrl3.Deactivate(ctx))
  1753  
  1754  		start := time.Now()
  1755  		executed1 = callbacks.CycleCallback(shouldNotAbort)
  1756  		d1 = time.Since(start)
  1757  
  1758  		require.Nil(t, ctrl1.Deactivate(ctx))
  1759  
  1760  		start = time.Now()
  1761  		executed2 = callbacks.CycleCallback(shouldNotAbort)
  1762  		d2 = time.Since(start)
  1763  
  1764  		require.Nil(t, ctrl4.Deactivate(ctx))
  1765  
  1766  		start = time.Now()
  1767  		executed3 = callbacks.CycleCallback(shouldNotAbort)
  1768  		d3 = time.Since(start)
  1769  
  1770  		require.Nil(t, ctrl2.Deactivate(ctx))
  1771  
  1772  		start = time.Now()
  1773  		executed4 = callbacks.CycleCallback(shouldNotAbort)
  1774  		d4 = time.Since(start)
  1775  
  1776  		assert.True(t, executed1)
  1777  		assert.True(t, executed2)
  1778  		assert.True(t, executed3)
  1779  		assert.False(t, executed4)
  1780  		assert.Equal(t, 1, executedCounter1)
  1781  		assert.Equal(t, 3, executedCounter2)
  1782  		assert.Equal(t, 0, executedCounter3)
  1783  		assert.Equal(t, 2, executedCounter4)
  1784  		assert.GreaterOrEqual(t, d1, 75*time.Millisecond)
  1785  		assert.GreaterOrEqual(t, d2, 50*time.Millisecond)
  1786  		assert.GreaterOrEqual(t, d3, 25*time.Millisecond)
  1787  		assert.GreaterOrEqual(t, d4, 0*time.Millisecond)
  1788  	})
  1789  
  1790  	t.Run("deactivate is waiting till the end of execution", func(t *testing.T) {
  1791  		executedCounter := 0
  1792  		callback := func(shouldAbort ShouldAbortCallback) bool {
  1793  			time.Sleep(50 * time.Millisecond)
  1794  			executedCounter++
  1795  			return true
  1796  		}
  1797  		chStarted := make(chan struct{}, 1)
  1798  		chFinished := make(chan struct{}, 1)
  1799  		var executed bool
  1800  		var d time.Duration
  1801  
  1802  		callbacks := NewCallbackGroup("id", logger, 1)
  1803  		ctrl := callbacks.Register("c", callback)
  1804  
  1805  		go func() {
  1806  			chStarted <- struct{}{}
  1807  			start := time.Now()
  1808  			executed = callbacks.CycleCallback(shouldNotAbort)
  1809  			d = time.Since(start)
  1810  			chFinished <- struct{}{}
  1811  		}()
  1812  		<-chStarted
  1813  		start := time.Now()
  1814  		time.Sleep(25 * time.Millisecond)
  1815  		require.Nil(t, ctrl.Deactivate(ctx))
  1816  		du := time.Since(start)
  1817  		<-chFinished
  1818  
  1819  		assert.True(t, executed)
  1820  		assert.Equal(t, 1, executedCounter)
  1821  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
  1822  		assert.GreaterOrEqual(t, du, 40*time.Millisecond)
  1823  	})
  1824  
  1825  	t.Run("deactivate fails due to context timeout", func(t *testing.T) {
  1826  		executedCounter := 0
  1827  		callback := func(shouldAbort ShouldAbortCallback) bool {
  1828  			time.Sleep(50 * time.Millisecond)
  1829  			executedCounter++
  1830  			return true
  1831  		}
  1832  		chStarted := make(chan struct{}, 1)
  1833  		chFinished := make(chan struct{}, 1)
  1834  		var executed1 bool
  1835  		var executed2 bool
  1836  		var d1 time.Duration
  1837  		var d2 time.Duration
  1838  
  1839  		callbacks := NewCallbackGroup("id", logger, 1)
  1840  		ctrl := callbacks.Register("c", callback)
  1841  
  1842  		go func() {
  1843  			chStarted <- struct{}{}
  1844  			start := time.Now()
  1845  			executed1 = callbacks.CycleCallback(shouldNotAbort)
  1846  			d1 = time.Since(start)
  1847  			chFinished <- struct{}{}
  1848  		}()
  1849  		<-chStarted
  1850  		start := time.Now()
  1851  		time.Sleep(25 * time.Millisecond)
  1852  		ctxTimeout, cancel := context.WithTimeout(ctx, 5*time.Millisecond)
  1853  		defer cancel()
  1854  		require.NotNil(t, ctrl.Deactivate(ctxTimeout))
  1855  		du := time.Since(start)
  1856  		<-chFinished
  1857  
  1858  		go func() {
  1859  			start := time.Now()
  1860  			executed2 = callbacks.CycleCallback(shouldNotAbort)
  1861  			d2 = time.Since(start)
  1862  			chFinished <- struct{}{}
  1863  		}()
  1864  		<-chFinished
  1865  
  1866  		assert.True(t, executed1)
  1867  		assert.True(t, executed2)
  1868  		assert.Equal(t, 2, executedCounter)
  1869  		assert.GreaterOrEqual(t, d1, 50*time.Millisecond)
  1870  		assert.GreaterOrEqual(t, d2, 50*time.Millisecond)
  1871  		assert.GreaterOrEqual(t, du, 30*time.Millisecond)
  1872  	})
  1873  
  1874  	t.Run("deactivate 2nd and 3rd while executing", func(t *testing.T) {
  1875  		executedCounter1 := 0
  1876  		callback1 := func(shouldAbort ShouldAbortCallback) bool {
  1877  			time.Sleep(50 * time.Millisecond)
  1878  			executedCounter1++
  1879  			return true
  1880  		}
  1881  		executedCounter2 := 0
  1882  		callback2 := func(shouldAbort ShouldAbortCallback) bool {
  1883  			time.Sleep(50 * time.Millisecond)
  1884  			executedCounter2++
  1885  			return true
  1886  		}
  1887  		executedCounter3 := 0
  1888  		callback3 := func(shouldAbort ShouldAbortCallback) bool {
  1889  			time.Sleep(50 * time.Millisecond)
  1890  			executedCounter3++
  1891  			return true
  1892  		}
  1893  		chStarted := make(chan struct{}, 1)
  1894  		chFinished := make(chan struct{}, 1)
  1895  		var executed bool
  1896  		var d time.Duration
  1897  
  1898  		callbacks := NewCallbackGroup("id", logger, 1)
  1899  		callbacks.Register("c1", callback1)
  1900  		ctrl2 := callbacks.Register("c2", callback2)
  1901  		ctrl3 := callbacks.Register("c3", callback3)
  1902  
  1903  		go func() {
  1904  			chStarted <- struct{}{}
  1905  			start := time.Now()
  1906  			executed = callbacks.CycleCallback(shouldNotAbort)
  1907  			d = time.Since(start)
  1908  			chFinished <- struct{}{}
  1909  		}()
  1910  		<-chStarted
  1911  		time.Sleep(25 * time.Millisecond)
  1912  		require.Nil(t, ctrl2.Deactivate(ctx))
  1913  		require.Nil(t, ctrl3.Deactivate(ctx))
  1914  		<-chFinished
  1915  
  1916  		assert.True(t, executed)
  1917  		assert.Equal(t, 1, executedCounter1)
  1918  		assert.Equal(t, 0, executedCounter2)
  1919  		assert.Equal(t, 0, executedCounter3)
  1920  		assert.GreaterOrEqual(t, d, 50*time.Millisecond)
  1921  	})
  1922  
  1923  	t.Run("deactivate while running", func(t *testing.T) {
  1924  		counter := 0
  1925  		max := 25
  1926  		callback := func(shouldAbort ShouldAbortCallback) bool {
  1927  			for {
  1928  				if shouldAbort() {
  1929  					return false
  1930  				}
  1931  
  1932  				time.Sleep(10 * time.Millisecond)
  1933  				counter++
  1934  
  1935  				// 10ms * 25 = 250ms
  1936  				if counter > max {
  1937  					return true
  1938  				}
  1939  			}
  1940  		}
  1941  		chStarted := make(chan struct{}, 1)
  1942  		chFinished := make(chan struct{}, 1)
  1943  		var executed bool
  1944  		var d time.Duration
  1945  
  1946  		callbacks := NewCallbackGroup("id", logger, 1)
  1947  		ctrl := callbacks.Register("c", callback)
  1948  
  1949  		go func() {
  1950  			chStarted <- struct{}{}
  1951  			start := time.Now()
  1952  			executed = callbacks.CycleCallback(shouldNotAbort)
  1953  			d = time.Since(start)
  1954  			chFinished <- struct{}{}
  1955  		}()
  1956  		<-chStarted
  1957  		time.Sleep(50 * time.Millisecond)
  1958  		require.NoError(t, ctrl.Deactivate(ctx))
  1959  		<-chFinished
  1960  
  1961  		assert.False(t, executed)
  1962  		assert.LessOrEqual(t, counter, max)
  1963  		assert.LessOrEqual(t, d, 200*time.Millisecond)
  1964  
  1965  		t.Run("does not abort after activated back again", func(t *testing.T) {
  1966  			require.NoError(t, ctrl.Activate())
  1967  
  1968  			counter = 0
  1969  			max = 10
  1970  
  1971  			go func() {
  1972  				start := time.Now()
  1973  				executed = callbacks.CycleCallback(shouldNotAbort)
  1974  				d = time.Since(start)
  1975  				chFinished <- struct{}{}
  1976  			}()
  1977  			<-chFinished
  1978  
  1979  			assert.True(t, executed)
  1980  			assert.Greater(t, counter, max)
  1981  			assert.GreaterOrEqual(t, d, 100*time.Millisecond)
  1982  		})
  1983  	})
  1984  }