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 }