github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/timer/x_timing_wheels_v2_test.go (about)

     1  package timer
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"os"
     7  	"sort"
     8  	"sync/atomic"
     9  	"testing"
    10  	"time"
    11  	"unsafe"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	"go.opentelemetry.io/otel"
    16  	"go.opentelemetry.io/otel/sdk/metric"
    17  
    18  	"github.com/benz9527/xboot/lib/id"
    19  	"github.com/benz9527/xboot/observability"
    20  )
    21  
    22  func testSimpleAfterFuncSdkDefaultTimeV2(t *testing.T) {
    23  	ctx, cancel := context.WithTimeoutCause(context.Background(), 2100*time.Millisecond, errors.New("timeout"))
    24  	defer cancel()
    25  	idGen, err := id.StandardSnowFlakeID(0, 0, func() time.Time { return time.Now().UTC() })
    26  	require.NoError(t, err)
    27  	tw := NewXTimingWheelsV2(
    28  		ctx,
    29  		WithTimingWheelTimeSource(SdkDefaultTime),
    30  		WithTimingWheelsIDGen(idGen),
    31  		withTimingWheelsDebugStatsInit(2),
    32  		WithTimingWheelsStats(),
    33  	)
    34  	defer func() {
    35  		mp, ok := otel.GetMeterProvider().(*metric.MeterProvider)
    36  		if ok && mp != nil {
    37  			_ = mp.Shutdown(ctx)
    38  		}
    39  	}()
    40  
    41  	delays := []time.Duration{
    42  		time.Millisecond,
    43  		2 * time.Millisecond,
    44  		5 * time.Millisecond,
    45  		10 * time.Millisecond,
    46  		15 * time.Millisecond,
    47  		18 * time.Millisecond,
    48  		20 * time.Millisecond,
    49  		21 * time.Millisecond,
    50  		22 * time.Millisecond,
    51  		23 * time.Millisecond,
    52  		50 * time.Millisecond,
    53  		51 * time.Millisecond,
    54  		100 * time.Millisecond,
    55  		200 * time.Millisecond,
    56  		400 * time.Millisecond,
    57  		500 * time.Millisecond,
    58  		time.Second,
    59  	}
    60  
    61  	expectedExecCount := int64(len(delays))
    62  	actualExecCounter := atomic.Int64{}
    63  	startTs := time.Now().UTC().UnixMilli()
    64  	for i := 0; i < len(delays); i++ {
    65  		_, err := tw.AfterFunc(delays[i], func(ctx context.Context, md JobMetadata) {
    66  			actualExecCounter.Add(1)
    67  			t.Logf("exec diff: %v; delay: %v\n", time.Now().UTC().UnixMilli()-startTs, delays[i])
    68  		})
    69  		assert.NoError(t, err)
    70  	}
    71  	<-ctx.Done()
    72  	time.Sleep(50 * time.Millisecond)
    73  	assert.Equal(t, expectedExecCount, actualExecCounter.Load())
    74  }
    75  
    76  func TestXTimingWheelsV2_SimpleAfterFunc(t *testing.T) {
    77  	loops := 1
    78  	for i := 0; i < loops; i++ {
    79  		t.Logf("loop %d\n", i)
    80  		testSimpleAfterFuncSdkDefaultTimeV2(t)
    81  	}
    82  }
    83  
    84  func TestXTimingWheelsV2_AlignmentAndSize(t *testing.T) {
    85  	tw := &xTimingWheelsV2{}
    86  	t.Logf("tw alignment: %d\n", unsafe.Alignof(tw))
    87  	t.Logf("tw ctx alignment: %d\n", unsafe.Alignof(tw.ctx))
    88  	t.Logf("tw tw alignment: %d\n", unsafe.Alignof(tw.tw))
    89  	t.Logf("tw stopC alignment: %d\n", unsafe.Alignof(tw.stopC))
    90  	t.Logf("tw twEventC alignment: %d\n", unsafe.Alignof(tw.twEventDisruptor))
    91  	t.Logf("tw twEventPool alignment: %d\n", unsafe.Alignof(tw.twEventPool))
    92  	t.Logf("tw expiredSlotC alignment: %d\n", unsafe.Alignof(tw.expiredSlotC))
    93  	t.Logf("tw isRunning alignment: %d\n", unsafe.Alignof(tw.isRunning))
    94  	t.Logf("tw dq alignment: %d\n", unsafe.Alignof(tw.dq))
    95  	t.Logf("tw tasksMap alignment: %d\n", unsafe.Alignof(tw.tasksMap))
    96  
    97  	t.Logf("tw size: %d\n", unsafe.Sizeof(*tw))
    98  	t.Logf("tw ctx size: %d\n", unsafe.Sizeof(tw.ctx))
    99  	t.Logf("tw tw size: %d\n", unsafe.Sizeof(tw.tw))
   100  	t.Logf("tw stopC size: %d\n", unsafe.Sizeof(tw.stopC))
   101  	t.Logf("tw twEventC size: %d\n", unsafe.Sizeof(tw.twEventDisruptor))
   102  	t.Logf("tw twEventPool size: %d\n", unsafe.Sizeof(tw.twEventPool))
   103  	t.Logf("tw expiredSlotC size: %d\n", unsafe.Sizeof(tw.expiredSlotC))
   104  	t.Logf("tw isRunning size: %d\n", unsafe.Sizeof(tw.isRunning))
   105  	t.Logf("tw dq size: %d\n", unsafe.Sizeof(tw.dq))
   106  	t.Logf("tw tasksMap size: %d\n", unsafe.Sizeof(tw.tasksMap))
   107  }
   108  
   109  func TestXTimingWheelsV2_ScheduleFunc_ConcurrentFinite(t *testing.T) {
   110  	ctx, cancel := context.WithTimeoutCause(context.Background(), 2100*time.Millisecond, errors.New("timeout"))
   111  	defer cancel()
   112  	tw := NewXTimingWheelsV2(
   113  		ctx,
   114  		withTimingWheelsDebugStatsInit(2),
   115  		WithTimingWheelsStats(),
   116  	)
   117  
   118  	delays := []time.Duration{
   119  		time.Millisecond,
   120  		2 * time.Millisecond,
   121  		5 * time.Millisecond,
   122  		10 * time.Millisecond,
   123  		15 * time.Millisecond,
   124  		18 * time.Millisecond,
   125  		20 * time.Millisecond,
   126  		21 * time.Millisecond,
   127  		50 * time.Millisecond,
   128  		100 * time.Millisecond,
   129  		200 * time.Millisecond,
   130  		500 * time.Millisecond,
   131  		time.Second,
   132  	}
   133  	schedFn := func() Scheduler {
   134  		return NewFiniteScheduler(delays...)
   135  	}
   136  	assert.NotNil(t, schedFn)
   137  	execCounter := &atomic.Int32{}
   138  
   139  	go func() {
   140  		task, err := tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) {
   141  			execCounter.Add(1)
   142  		})
   143  		assert.NoError(t, err)
   144  		t.Logf("task1: %s\n", task.GetJobID())
   145  	}()
   146  	go func() {
   147  		task, err := tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) {
   148  			execCounter.Add(1)
   149  		})
   150  		assert.NoError(t, err)
   151  		t.Logf("task2: %s\n", task.GetJobID())
   152  	}()
   153  
   154  	t.Logf("tw tickMs: %d\n", tw.GetTickMs())
   155  	t.Logf("tw startMs: %d\n", tw.GetStartMs())
   156  	<-ctx.Done()
   157  	time.Sleep(100 * time.Millisecond)
   158  	expectedExecCount := len(delays) * 2
   159  	actualExecCount := execCounter.Load()
   160  	assert.Equal(t, expectedExecCount, int(actualExecCount))
   161  }
   162  
   163  func TestXTimingWheelsV2_ScheduleFunc_sdkClock_1MsInfinite(t *testing.T) {
   164  	observability.InitAppStats(context.Background(), "sdk1msInfinite")
   165  	ctx, cancel := context.WithTimeoutCause(context.Background(), 5*time.Second, errors.New("timeout"))
   166  	defer cancel()
   167  	tw := NewXTimingWheelsV2(
   168  		ctx,
   169  		withTimingWheelsDebugStatsInit(5),
   170  		WithTimingWheelsStats(),
   171  	)
   172  
   173  	delays := []time.Duration{
   174  		time.Millisecond,
   175  	}
   176  	schedFn := func() Scheduler {
   177  		return NewInfiniteScheduler(delays...)
   178  	}
   179  	assert.NotNil(t, schedFn())
   180  	loop := 20
   181  	tasks := make([]Task, loop)
   182  	for i := range loop {
   183  		var err error
   184  		tasks[i], err = tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) {})
   185  		assert.NoError(t, err)
   186  		time.Sleep(2 * time.Millisecond)
   187  	}
   188  
   189  	<-ctx.Done()
   190  	time.Sleep(100 * time.Millisecond)
   191  }
   192  
   193  func TestXTimingWheelsV2_ScheduleFunc_sdkClock_2MsInfinite(t *testing.T) {
   194  	observability.InitAppStats(context.Background(), "sdk2msInfinite")
   195  	ctx, cancel := context.WithTimeoutCause(context.Background(), 5*time.Second, errors.New("timeout"))
   196  	defer cancel()
   197  	tw := NewXTimingWheelsV2(
   198  		ctx,
   199  		WithTimingWheelsTickMs(2*time.Millisecond),
   200  		WithTimingWheelsSlotSize(20),
   201  		withTimingWheelsDebugStatsInit(5),
   202  		WithTimingWheelsStats(),
   203  	)
   204  
   205  	delays := []time.Duration{
   206  		2 * time.Millisecond,
   207  	}
   208  	schedFn := func() Scheduler {
   209  		return NewInfiniteScheduler(delays...)
   210  	}
   211  	assert.NotNil(t, schedFn())
   212  	loop := 20
   213  	tasks := make([]Task, loop)
   214  	for i := range loop {
   215  		var err error
   216  		tasks[i], err = tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) {})
   217  		assert.NoError(t, err)
   218  		time.Sleep(2 * time.Millisecond)
   219  	}
   220  
   221  	<-ctx.Done()
   222  	time.Sleep(100 * time.Millisecond)
   223  }
   224  
   225  func TestXTimingWheelsV2_ScheduleFunc_5MsInfinite(t *testing.T) {
   226  	observability.InitAppStats(context.Background(), "sdk5msInfinite")
   227  	ctx, cancel := context.WithTimeoutCause(context.Background(), 5*time.Second, errors.New("timeout"))
   228  	defer cancel()
   229  	tw := NewXTimingWheelsV2(
   230  		ctx,
   231  		WithTimingWheelsTickMs(5*time.Millisecond),
   232  		WithTimingWheelsSlotSize(20),
   233  		withTimingWheelsDebugStatsInit(5),
   234  		WithTimingWheelsStats(),
   235  	)
   236  
   237  	delays := []time.Duration{
   238  		8 * time.Millisecond,
   239  		18 * time.Millisecond,
   240  	}
   241  	schedFn := func() Scheduler {
   242  		return NewInfiniteScheduler(delays...)
   243  	}
   244  	loop := 20
   245  	tasks := make([]Task, loop)
   246  	for i := range loop {
   247  		var err error
   248  		tasks[i], err = tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) {})
   249  		assert.NoError(t, err)
   250  		time.Sleep(2 * time.Millisecond)
   251  	}
   252  
   253  	<-ctx.Done()
   254  	time.Sleep(100 * time.Millisecond)
   255  }
   256  
   257  func TestXTimingWheelsV2_AfterFunc_Slots(t *testing.T) {
   258  	_, debugLogDisabled := os.LookupEnv("DISABLE_TEST_DEBUG_LOG")
   259  	ctx, cancel := context.WithTimeoutCause(context.Background(), 500*time.Millisecond, errors.New("timeout"))
   260  	defer cancel()
   261  	ctx = context.WithValue(ctx, disableTimingWheelsScheduleCancelTask, true)
   262  	ctx = context.WithValue(ctx, disableTimingWheelsSchedulePoll, true)
   263  
   264  	tw := NewXTimingWheelsV2(
   265  		ctx,
   266  	)
   267  
   268  	delays := []time.Duration{
   269  		3 * time.Millisecond,
   270  		4 * time.Millisecond,
   271  		5 * time.Millisecond,
   272  		10 * time.Millisecond,
   273  		15 * time.Millisecond,
   274  		18 * time.Millisecond,
   275  		20 * time.Millisecond,
   276  		21 * time.Millisecond,
   277  		40 * time.Millisecond,
   278  		50 * time.Millisecond,
   279  		100 * time.Millisecond,
   280  		200 * time.Millisecond,
   281  		400 * time.Millisecond,
   282  		500 * time.Millisecond,
   283  		time.Second,
   284  	}
   285  
   286  	for i := 0; i < len(delays); i++ {
   287  		_, err := tw.AfterFunc(delays[i], func(ctx context.Context, md JobMetadata) {})
   288  		assert.NoError(t, err)
   289  	}
   290  	t.Logf("tw tickMs: %d\n", tw.GetTickMs())
   291  	t.Logf("tw startMs: %d\n", tw.GetStartMs())
   292  
   293  	<-time.After(100 * time.Millisecond)
   294  	jobIDs := make([]string, 0, len(delays))
   295  	keys := tw.(*xTimingWheelsV2).tasksMap.ListKeys()
   296  	for _, k := range keys {
   297  		jobIDs = append(jobIDs, string(k))
   298  	}
   299  	sort.Strings(jobIDs)
   300  	for _, jobID := range jobIDs {
   301  		v, _ := tw.(*xTimingWheelsV2).tasksMap.Get(JobID(jobID))
   302  		if !debugLogDisabled {
   303  			t.Logf("job ID: %s, slot level: %d, ID %d, %d\n", jobID, v.GetSlot().GetLevel(), v.GetSlot().GetSlotID(), v.GetExpiredMs())
   304  		}
   305  	}
   306  	<-ctx.Done()
   307  }
   308  
   309  func BenchmarkNewTimingWheelsV2_AfterFunc(b *testing.B) {
   310  	ctx := context.Background()
   311  	ctx = context.WithValue(ctx, disableTimingWheelsScheduleCancelTask, true)
   312  	ctx = context.WithValue(ctx, disableTimingWheelsSchedulePoll, true)
   313  	tw := NewXTimingWheelsV2(
   314  		ctx,
   315  		WithTimingWheelsTickMs(1*time.Millisecond),
   316  		WithTimingWheelsSlotSize(20),
   317  	)
   318  	defer tw.Shutdown()
   319  	b.ResetTimer()
   320  	for i := 0; i < b.N; i++ {
   321  		_, err := tw.AfterFunc(time.Duration(i+1)*time.Millisecond, func(ctx context.Context, md JobMetadata) {
   322  		})
   323  		assert.NoError(b, err)
   324  	}
   325  	b.ReportAllocs()
   326  }