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 }