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