github.com/benz9527/toy-box/algo@v0.0.0-20240221120937-66c0c6bd5abd/timer/x_timing_wheels_test.go (about) 1 package timer 2 3 import ( 4 "context" 5 "errors" 6 "github.com/stretchr/testify/assert" 7 "log/slog" 8 "sort" 9 "sync/atomic" 10 "testing" 11 "time" 12 "unsafe" 13 ) 14 15 func TestTimingWheel_AlignmentAndSize(t *testing.T) { 16 tw := &timingWheel{} 17 t.Logf("tw alignment: %d\n", unsafe.Alignof(tw)) 18 t.Logf("tw ctx alignment: %d\n", unsafe.Alignof(tw.ctx)) 19 t.Logf("tw slot alignment: %d\n", unsafe.Alignof(tw.slots)) 20 t.Logf("tw tickMs alignment: %d\n", unsafe.Alignof(tw.tickMs)) 21 t.Logf("tw startMs alignment: %d\n", unsafe.Alignof(tw.startMs)) 22 t.Logf("tw slotSize alignment: %d\n", unsafe.Alignof(tw.slotSize)) 23 t.Logf("tw globalSlotCounterRef alignment: %d\n", unsafe.Alignof(tw.globalSlotCounterRef)) 24 t.Logf("tw overflowWheelRef alignment: %d\n", unsafe.Alignof(tw.overflowWheelRef)) 25 t.Logf("tw globalDqRef alignment: %d\n", unsafe.Alignof(tw.globalDqRef)) 26 27 t.Logf("tw size: %d\n", unsafe.Sizeof(*tw)) 28 t.Logf("tw ctx size: %d\n", unsafe.Sizeof(tw.ctx)) 29 t.Logf("tw slot size: %d\n", unsafe.Sizeof(tw.slots)) 30 t.Logf("tw tickMs size: %d\n", unsafe.Sizeof(tw.tickMs)) 31 t.Logf("tw startMs size: %d\n", unsafe.Sizeof(tw.startMs)) 32 t.Logf("tw slotSize size: %d\n", unsafe.Sizeof(tw.slotSize)) 33 t.Logf("tw globalSlotCounterRef size: %d\n", unsafe.Sizeof(tw.globalSlotCounterRef)) 34 t.Logf("tw overflowWheelRef size: %d\n", unsafe.Sizeof(tw.overflowWheelRef)) 35 t.Logf("tw globalDqRef size: %d\n", unsafe.Sizeof(tw.globalDqRef)) 36 } 37 38 func TestXTimingWheels_AlignmentAndSize(t *testing.T) { 39 tw := &xTimingWheels{} 40 t.Logf("tw alignment: %d\n", unsafe.Alignof(tw)) 41 t.Logf("tw ctx alignment: %d\n", unsafe.Alignof(tw.ctx)) 42 t.Logf("tw tw alignment: %d\n", unsafe.Alignof(tw.tw)) 43 t.Logf("tw stopC alignment: %d\n", unsafe.Alignof(tw.stopC)) 44 t.Logf("tw twEventC alignment: %d\n", unsafe.Alignof(tw.twEventC)) 45 t.Logf("tw twEventPool alignment: %d\n", unsafe.Alignof(tw.twEventPool)) 46 t.Logf("tw expiredSlotC alignment: %d\n", unsafe.Alignof(tw.expiredSlotC)) 47 t.Logf("tw taskCounter alignment: %d\n", unsafe.Alignof(tw.taskCounter)) 48 t.Logf("tw slotCounter alignment: %d\n", unsafe.Alignof(tw.slotCounter)) 49 t.Logf("tw isRunning alignment: %d\n", unsafe.Alignof(tw.isRunning)) 50 t.Logf("tw dq alignment: %d\n", unsafe.Alignof(tw.dq)) 51 t.Logf("tw tasksMap alignment: %d\n", unsafe.Alignof(tw.tasksMap)) 52 53 t.Logf("tw size: %d\n", unsafe.Sizeof(*tw)) 54 t.Logf("tw ctx size: %d\n", unsafe.Sizeof(tw.ctx)) 55 t.Logf("tw tw size: %d\n", unsafe.Sizeof(tw.tw)) 56 t.Logf("tw stopC size: %d\n", unsafe.Sizeof(tw.stopC)) 57 t.Logf("tw twEventC size: %d\n", unsafe.Sizeof(tw.twEventC)) 58 t.Logf("tw twEventPool size: %d\n", unsafe.Sizeof(tw.twEventPool)) 59 t.Logf("tw expiredSlotC size: %d\n", unsafe.Sizeof(tw.expiredSlotC)) 60 t.Logf("tw taskCounter size: %d\n", unsafe.Sizeof(tw.taskCounter)) 61 t.Logf("tw slotCounter size: %d\n", unsafe.Sizeof(tw.slotCounter)) 62 t.Logf("tw isRunning size: %d\n", unsafe.Sizeof(tw.isRunning)) 63 t.Logf("tw dq size: %d\n", unsafe.Sizeof(tw.dq)) 64 t.Logf("tw tasksMap size: %d\n", unsafe.Sizeof(tw.tasksMap)) 65 } 66 67 func TestNewTimingWheels(t *testing.T) { 68 ctx, cancel := context.WithTimeoutCause(context.TODO(), time.Second, errors.New("timeout")) 69 defer cancel() 70 tw := NewTimingWheels( 71 ctx, 72 time.Now().UTC().UnixMilli(), 73 WithTimingWheelTickMs(100), 74 WithTimingWheelSlotSize(32), 75 ) 76 t.Logf("tw tickMs: %d\n", tw.GetTickMs()) 77 t.Logf("tw startMs: %d\n", tw.GetStartMs()) 78 t.Logf("tw slotSize: %d\n", tw.GetSlotSize()) 79 t.Logf("tw globalTaskCounterRef: %d\n", tw.GetTaskCounter()) 80 <-ctx.Done() 81 time.Sleep(100 * time.Millisecond) 82 } 83 84 func testAfterFunc(t *testing.T) { 85 ctx, cancel := context.WithTimeoutCause(context.Background(), 2100*time.Millisecond, errors.New("timeout")) 86 defer cancel() 87 tw := NewTimingWheels( 88 ctx, 89 time.Now().UTC().UnixMilli(), 90 ) 91 92 delays := []time.Duration{ 93 time.Millisecond, 94 2 * time.Millisecond, 95 5 * time.Millisecond, 96 10 * time.Millisecond, 97 15 * time.Millisecond, 98 18 * time.Millisecond, 99 20 * time.Millisecond, 100 21 * time.Millisecond, 101 50 * time.Millisecond, 102 100 * time.Millisecond, 103 200 * time.Millisecond, 104 500 * time.Millisecond, 105 time.Second, 106 } 107 108 expectedExecCount := len(delays) 109 actualExecCount := 0 110 for i := 0; i < len(delays); i++ { 111 //lowResolutionBeginTs := time.Now().UTC().UnixMilli() 112 _, err := tw.AfterFunc(delays[i], func(ctx context.Context, md JobMetadata) { 113 actualExecCount++ 114 //execAt := time.Now().UTC().UnixMilli() 115 //slog.Info("after func", "expired ms", md.GetExpiredMs(), "exec at", execAt, 116 // "interval", md.GetExpiredMs()-lowResolutionBeginTs, "diff", execAt-md.GetExpiredMs()) 117 }) 118 assert.NoError(t, err) 119 } 120 t.Logf("begin tw tasks: %d\n", tw.GetTaskCounter()) 121 <-ctx.Done() 122 time.Sleep(50 * time.Millisecond) 123 t.Logf("final tw tasks: %d\n", tw.GetTaskCounter()) 124 if actualExecCount != expectedExecCount || tw.GetTaskCounter() > 1 || tw.GetTaskCounter() < 0 { 125 t.Logf("actual exec count: %d, expected exec count: %d\n", actualExecCount, expectedExecCount) 126 panic("exec count not match") 127 } 128 } 129 130 func TestXTimingWheels_AfterFunc(t *testing.T) { 131 loops := 100 132 for i := 0; i < loops; i++ { 133 t.Logf("loop %d\n", i) 134 testAfterFunc(t) 135 } 136 } 137 138 func TestXTimingWheels_ScheduleFunc_ConcurrentFinite(t *testing.T) { 139 ctx, cancel := context.WithTimeoutCause(context.Background(), 2100*time.Millisecond, errors.New("timeout")) 140 defer cancel() 141 tw := NewTimingWheels( 142 ctx, 143 time.Now().UTC().UnixMilli(), 144 ) 145 146 delays := []time.Duration{ 147 time.Millisecond, 148 2 * time.Millisecond, 149 5 * time.Millisecond, 150 10 * time.Millisecond, 151 15 * time.Millisecond, 152 18 * time.Millisecond, 153 20 * time.Millisecond, 154 21 * time.Millisecond, 155 50 * time.Millisecond, 156 100 * time.Millisecond, 157 200 * time.Millisecond, 158 500 * time.Millisecond, 159 time.Second, 160 } 161 schedFn := func() Scheduler { 162 return NewFiniteScheduler(delays...) 163 } 164 assert.NotNil(t, schedFn) 165 execCounter := &atomic.Int32{} 166 167 go func() { 168 task, err := tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) { 169 execCounter.Add(1) 170 }) 171 assert.NoError(t, err) 172 t.Logf("task1: %s\n", task.GetJobID()) 173 }() 174 go func() { 175 task, err := tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) { 176 execCounter.Add(1) 177 }) 178 assert.NoError(t, err) 179 t.Logf("task2: %s\n", task.GetJobID()) 180 }() 181 182 t.Logf("tw tickMs: %d\n", tw.GetTickMs()) 183 t.Logf("tw startMs: %d\n", tw.GetStartMs()) 184 t.Logf("tw slotSize: %d\n", tw.GetSlotSize()) 185 t.Logf("tw tasks: %d\n", tw.GetTaskCounter()) 186 <-ctx.Done() 187 time.Sleep(100 * time.Millisecond) 188 t.Logf("final tw tasks: %d\n", tw.GetTaskCounter()) 189 expectedExecCount := len(delays) * 2 190 actualExecCount := execCounter.Load() 191 assert.Equal(t, expectedExecCount, int(actualExecCount)) 192 assert.Equal(t, int64(0), tw.GetTaskCounter()) 193 } 194 195 func TestXTimingWheels_ScheduleFunc_1MsInfinite(t *testing.T) { 196 ctx, cancel := context.WithTimeoutCause(context.Background(), 1*time.Second, errors.New("timeout")) 197 defer cancel() 198 tw := NewTimingWheels( 199 ctx, 200 time.Now().UTC().UnixMilli(), 201 ) 202 203 delays := []time.Duration{ 204 time.Millisecond, 205 } 206 schedFn := func() Scheduler { 207 return NewInfiniteScheduler(delays...) 208 } 209 assert.NotNil(t, schedFn()) 210 // FIXME 1ms infinite scheduling will occur critical delay execution error 211 task, err := tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) { 212 execAt := time.Now().UTC().UnixMilli() 213 slog.Info("infinite sched1 after func", "expired ms", md.GetExpiredMs(), "exec at", execAt, "diff", 214 execAt-md.GetExpiredMs()) 215 }) 216 assert.NoError(t, err) 217 t.Logf("task1: %s\n", task.GetJobID()) 218 219 t.Logf("tw tickMs: %d\n", tw.GetTickMs()) 220 t.Logf("tw startMs: %d\n", tw.GetStartMs()) 221 t.Logf("tw slotSize: %d\n", tw.GetSlotSize()) 222 t.Logf("tw tasks: %d\n", tw.GetTaskCounter()) 223 <-ctx.Done() 224 time.Sleep(100 * time.Millisecond) 225 t.Logf("final tw tasks: %d\n", tw.GetTaskCounter()) 226 } 227 228 func TestXTimingWheels_ScheduleFunc_32MsInfinite(t *testing.T) { 229 ctx, cancel := context.WithTimeoutCause(context.Background(), 3*time.Second, errors.New("timeout")) 230 defer cancel() 231 tw := NewTimingWheels( 232 ctx, 233 time.Now().UTC().UnixMilli(), 234 ) 235 236 delays := []time.Duration{ 237 18 * time.Millisecond, 238 } 239 schedFn := func() Scheduler { 240 return NewInfiniteScheduler(delays...) 241 } 242 assert.NotNil(t, schedFn()) 243 task, err := tw.ScheduleFunc(schedFn, func(ctx context.Context, md JobMetadata) { 244 execAt := time.Now().UTC().UnixMilli() 245 slog.Info("infinite sched32 after func", "expired ms", md.GetExpiredMs(), "exec at", execAt, "diff", 246 execAt-md.GetExpiredMs()) 247 }) 248 assert.NoError(t, err) 249 t.Logf("task1: %s\n", task.GetJobID()) 250 251 t.Logf("tw tickMs: %d\n", tw.GetTickMs()) 252 t.Logf("tw startMs: %d\n", tw.GetStartMs()) 253 t.Logf("tw slotSize: %d\n", tw.GetSlotSize()) 254 t.Logf("tw tasks: %d\n", tw.GetTaskCounter()) 255 <-ctx.Done() 256 time.Sleep(100 * time.Millisecond) 257 t.Logf("final tw tasks: %d\n", tw.GetTaskCounter()) 258 } 259 260 func TestXTimingWheels_AfterFunc_Slots(t *testing.T) { 261 ctx, cancel := context.WithTimeoutCause(context.Background(), 500*time.Millisecond, errors.New("timeout")) 262 defer cancel() 263 ctx = context.WithValue(ctx, disableTimingWheelsScheduleCancelTask, true) 264 ctx = context.WithValue(ctx, disableTimingWheelsScheduleExpiredSlot, true) 265 ctx = context.WithValue(ctx, disableTimingWheelsSchedulePoll, true) 266 267 tw := NewTimingWheels( 268 ctx, 269 time.Now().UTC().UnixMilli(), 270 ) 271 272 delays := []time.Duration{ 273 3 * time.Millisecond, 274 4 * time.Millisecond, 275 5 * time.Millisecond, 276 10 * time.Millisecond, 277 15 * time.Millisecond, 278 18 * time.Millisecond, 279 20 * time.Millisecond, 280 21 * time.Millisecond, 281 40 * time.Millisecond, 282 50 * time.Millisecond, 283 100 * time.Millisecond, 284 200 * time.Millisecond, 285 400 * time.Millisecond, 286 500 * time.Millisecond, 287 time.Second, 288 } 289 290 for i := 0; i < len(delays); i++ { 291 _, err := tw.AfterFunc(delays[i], func(ctx context.Context, md JobMetadata) {}) 292 assert.NoError(t, err) 293 } 294 t.Logf("tw tickMs: %d\n", tw.GetTickMs()) 295 t.Logf("tw startMs: %d\n", tw.GetStartMs()) 296 t.Logf("tw slotSize: %d\n", tw.GetSlotSize()) 297 t.Logf("tw tasks: %d\n", tw.GetTaskCounter()) 298 299 <-time.After(100 * time.Millisecond) 300 taskIDs := make([]string, 0, len(delays)) 301 for k := range tw.(*xTimingWheels).tasksMap { 302 taskIDs = append(taskIDs, string(k)) 303 } 304 sort.Strings(taskIDs) 305 for _, taskID := range taskIDs { 306 v := tw.(*xTimingWheels).tasksMap[JobID(taskID)] 307 t.Logf("slot level: %d, ID %d, %d\n", v.GetSlot().GetLevel(), v.GetSlot().GetSlotID(), v.GetExpiredMs()) 308 } 309 <-ctx.Done() 310 } 311 312 func BenchmarkNewTimingWheels_AfterFunc(b *testing.B) { 313 ctx := context.Background() 314 ctx = context.WithValue(ctx, disableTimingWheelsScheduleCancelTask, true) 315 ctx = context.WithValue(ctx, disableTimingWheelsScheduleExpiredSlot, true) 316 ctx = context.WithValue(ctx, disableTimingWheelsSchedulePoll, true) 317 tw := NewTimingWheels( 318 ctx, 319 time.Now().UTC().UnixMilli(), 320 WithTimingWheelTickMs(1), 321 WithTimingWheelSlotSize(20), 322 ) 323 defer tw.Shutdown() 324 b.ResetTimer() 325 for i := 0; i < b.N; i++ { 326 _, err := tw.AfterFunc(time.Duration(i+1)*time.Millisecond, func(ctx context.Context, md JobMetadata) { 327 }) 328 assert.NoError(b, err) 329 } 330 b.ReportAllocs() 331 }