github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/timer/options.go (about) 1 package timer 2 3 import ( 4 "fmt" 5 "log/slog" 6 "math" 7 "os" 8 "runtime" 9 "strings" 10 "sync/atomic" 11 "time" 12 13 "go.opentelemetry.io/otel" 14 "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" 15 "go.opentelemetry.io/otel/sdk/metric" 16 17 "github.com/benz9527/xboot/lib/hrtime" 18 "github.com/benz9527/xboot/lib/id" 19 ) 20 21 const ( 22 defaultMinEventBufferSize = 1024 23 defaultMinWorkerPoolSize = 128 24 defaultMinSlotIncrementSize = 10 25 defaultMinIntervalMilliseconds = 20 // lt 20ms will overflow 26 defaultMinTickAccuracyMilliseconds = 1 27 ) 28 29 type xTimingWheelsOption struct { 30 name string 31 basicTickMs int64 32 slotIncrSize int64 33 idGenerator id.Gen 34 stats *xTimingWheelsStats 35 clock hrtime.Clock 36 bufferSize int 37 workPoolSize int 38 isValueChecked *atomic.Bool 39 enableStats bool 40 } 41 42 func (opt *xTimingWheelsOption) getBasicTickMilliseconds() int64 { 43 if opt.isValueChecked == nil || !opt.isValueChecked.Load() { 44 panic("value unchecked") 45 } 46 if opt.basicTickMs < defaultMinTickAccuracyMilliseconds { 47 return defaultMinTickAccuracyMilliseconds 48 } 49 return opt.basicTickMs 50 } 51 52 func (opt *xTimingWheelsOption) getEventBufferSize() int { 53 if opt.isValueChecked == nil || !opt.isValueChecked.Load() { 54 panic("value unchecked") 55 } 56 if opt.bufferSize < defaultMinEventBufferSize { 57 return defaultMinEventBufferSize 58 } 59 return opt.bufferSize 60 } 61 62 func (opt *xTimingWheelsOption) getSlotIncrementSize() int64 { 63 if opt.isValueChecked == nil || !opt.isValueChecked.Load() { 64 panic("value unchecked") 65 } 66 if opt.slotIncrSize < defaultMinSlotIncrementSize { 67 return defaultMinSlotIncrementSize 68 } 69 return opt.slotIncrSize 70 } 71 72 func (opt *xTimingWheelsOption) getWorkerPoolSize() int { 73 if opt.isValueChecked == nil || !opt.isValueChecked.Load() { 74 panic("value unchecked") 75 } 76 if opt.workPoolSize < defaultMinWorkerPoolSize { 77 return defaultMinWorkerPoolSize 78 } 79 return opt.workPoolSize 80 } 81 82 func (opt *xTimingWheelsOption) getExpiredSlotBufferSize() int { 83 if opt.isValueChecked == nil || !opt.isValueChecked.Load() { 84 panic("value unchecked") 85 } 86 return int(opt.getSlotIncrementSize() + 8) 87 } 88 89 func (opt *xTimingWheelsOption) getClock() hrtime.Clock { 90 if opt.isValueChecked == nil || !opt.isValueChecked.Load() { 91 panic("value unchecked") 92 } 93 if opt.clock == nil { 94 return hrtime.SdkClock 95 } 96 return opt.clock 97 } 98 99 func (opt *xTimingWheelsOption) getIDGenerator() id.Gen { 100 if opt.isValueChecked == nil || !opt.isValueChecked.Load() { 101 panic("value unchecked") 102 } 103 if opt.idGenerator == nil { 104 gen, err := id.MonotonicNonZeroID() 105 if err != nil { 106 panic(err) 107 } 108 opt.idGenerator = gen.Number 109 } 110 return opt.idGenerator 111 } 112 113 func (opt *xTimingWheelsOption) getStats() *xTimingWheelsStats { 114 return opt.stats 115 } 116 117 func (opt *xTimingWheelsOption) defaultDelayQueueCapacity() int { 118 return 128 119 } 120 121 func (opt *xTimingWheelsOption) getName() string { 122 if opt.isValueChecked == nil || !opt.isValueChecked.Load() { 123 panic("value unchecked") 124 } 125 if opt.name == "" { 126 return fmt.Sprintf("xtw-%s-%d", runtime.GOOS, opt.getIDGenerator()()) 127 } 128 return opt.name 129 } 130 131 func (opt *xTimingWheelsOption) Validate() { 132 opt.isValueChecked = &atomic.Bool{} 133 if opt.basicTickMs < 1 { 134 opt.basicTickMs = defaultMinTickAccuracyMilliseconds 135 slog.Warn("[x-timing-wheels options] adjust the tick accuracy milliseconds", "from", 0, "to", opt.basicTickMs) 136 } 137 if opt.basicTickMs > 0 && opt.slotIncrSize > 0 && 138 opt.basicTickMs*opt.slotIncrSize < defaultMinIntervalMilliseconds { 139 from := opt.slotIncrSize 140 opt.slotIncrSize = int64(math.Ceil(float64(defaultMinIntervalMilliseconds) / float64(opt.basicTickMs))) 141 slog.Warn("[x-timing-wheels options] adjust the slot increment size", "from", from, "to", opt.slotIncrSize) 142 } 143 if opt.basicTickMs >= 1 && opt.slotIncrSize < 1 { 144 opt.slotIncrSize = int64(math.Ceil(float64(defaultMinIntervalMilliseconds) / float64(opt.basicTickMs))) 145 slog.Warn("[x-timing-wheels options] adjust the slot increment size", "from", 0, "to", opt.slotIncrSize) 146 } 147 opt.isValueChecked.Store(true) 148 if opt.enableStats { 149 opt.stats = newTimingWheelStats(opt) 150 } 151 } 152 153 type TimingWheelsOption func(option *xTimingWheelsOption) 154 155 func WithTimingWheelsTickMs(basicTickMs time.Duration) TimingWheelsOption { 156 return func(opt *xTimingWheelsOption) { 157 if basicTickMs.Milliseconds() < defaultMinTickAccuracyMilliseconds { 158 panic(fmt.Sprintf("timing-wheels' tick accuracy must be greater than or equals to %dms", defaultMinTickAccuracyMilliseconds)) 159 } 160 opt.basicTickMs = basicTickMs.Milliseconds() 161 } 162 } 163 164 func WithTimingWheelsSlotSize(slotSize int64) TimingWheelsOption { 165 return func(opt *xTimingWheelsOption) { 166 if slotSize < 1 { 167 panic("empty slot increment size") 168 } 169 opt.slotIncrSize = slotSize 170 } 171 } 172 173 func WithTimingWheelsName(name string) TimingWheelsOption { 174 return func(opt *xTimingWheelsOption) { 175 if len(strings.TrimSpace(name)) <= 0 { 176 panic("timing-wheels' name must not be empty or blank") 177 } 178 opt.name = name 179 } 180 } 181 182 func WithTimingWheelsIDGen(gen id.Gen) TimingWheelsOption { 183 return func(opt *xTimingWheelsOption) { 184 opt.idGenerator = gen 185 } 186 } 187 188 func WithTimingWheelsStats() TimingWheelsOption { 189 return func(opt *xTimingWheelsOption) { 190 opt.enableStats = true 191 } 192 } 193 194 func WithTimingWheelsWorkerPoolSize(poolSize int) TimingWheelsOption { 195 return func(opt *xTimingWheelsOption) { 196 if poolSize < defaultMinWorkerPoolSize { 197 panic(fmt.Sprintf("timing-wheels' work pool size must be greater than or equals to %d", defaultMinWorkerPoolSize)) 198 } 199 opt.workPoolSize = poolSize 200 } 201 } 202 203 func WithTimingWheelsEventBufferSize(size int) TimingWheelsOption { 204 return func(opt *xTimingWheelsOption) { 205 if size < defaultMinEventBufferSize { 206 panic(fmt.Sprintf("timing-wheels' event buffer size must be greater than or equals to %d", defaultMinEventBufferSize)) 207 } 208 opt.bufferSize = size 209 } 210 } 211 212 func withTimingWheelsDebugStatsInit(interval int64) TimingWheelsOption { 213 return func(opt *xTimingWheelsOption) { 214 _, debugLogDisabled := os.LookupEnv("DISABLE_TEST_DEBUG_LOG") 215 if debugLogDisabled { 216 return 217 } 218 219 exp, err := stdoutmetric.New( 220 stdoutmetric.WithWriter(os.Stdout), 221 ) 222 if err != nil { 223 panic(err) 224 } 225 mp := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(exp, metric.WithInterval(time.Duration(interval)*time.Second)))) 226 otel.SetMeterProvider(mp) 227 } 228 }