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  }