github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/aeron/idlestrategy/backoffidlestrategy.go (about) 1 package idlestrategy 2 3 import ( 4 "fmt" 5 "runtime" 6 "time" 7 ) 8 9 // BackoffIdleStrategy is an idling strategy for threads when they have no work to do. 10 // Spin for maxSpins, then yield with runtime.Gosched for maxYields, 11 // then park with time.Sleep on an exponential backoff to maxParkPeriodNs. 12 type BackoffIdleStrategy struct { 13 // configured max spins, yield, and min / max park period 14 maxSpins, maxYields, minParkPeriodNs, maxParkPeriodNs int64 15 // current state 16 state backoffState 17 // current number of spins, yield, and park period. 18 spins, yields, parkPeriodNs int64 19 } 20 21 // NewBackoffIdleStrategy returns a BackoffIdleStrategy with the given parameters. 22 func NewBackoffIdleStrategy(maxSpins, maxYields, minParkPeriodNs, maxParkPeriodNs int64) *BackoffIdleStrategy { 23 return &BackoffIdleStrategy{ 24 maxSpins: maxSpins, 25 maxYields: maxYields, 26 minParkPeriodNs: minParkPeriodNs, 27 maxParkPeriodNs: maxParkPeriodNs, 28 } 29 } 30 31 // DefaultMaxSpins is the default number of times the strategy will spin without work before going to next state. 32 const DefaultMaxSpins = 10 33 34 // DefaultMaxYields is the default number of times the strategy will yield without work before going to next state. 35 const DefaultMaxYields = 20 36 37 // DefaultMinParkNs is the default interval the strategy will park the thread on entering the park state. 38 const DefaultMinParkNs = 1000 39 40 // DefaultMaxParkNs is the default interval the strategy will park the thread will expand interval to as a max. 41 const DefaultMaxParkNs = int64(1 * time.Millisecond) 42 43 // NewDefaultBackoffIdleStrategy returns a BackoffIdleStrategy using DefaultMaxSpins, DefaultMaxYields, 44 // DefaultMinParkNs, and DefaultMaxParkNs. 45 func NewDefaultBackoffIdleStrategy() *BackoffIdleStrategy { 46 return NewBackoffIdleStrategy(DefaultMaxSpins, DefaultMaxYields, DefaultMinParkNs, DefaultMaxParkNs) 47 } 48 49 type backoffState int8 50 51 const ( 52 // Denotes a non-idle state. 53 backoffNotIdle backoffState = iota 54 // Denotes a spinning state. 55 backoffSpinning 56 // Denotes an yielding state. 57 backoffYielding 58 // Denotes a parking state. 59 backoffParking 60 ) 61 62 func (s *BackoffIdleStrategy) Idle(workCount int) { 63 if workCount > 0 { 64 s.reset() 65 } else { 66 s.idle() 67 } 68 } 69 70 func (s *BackoffIdleStrategy) String() string { 71 return fmt.Sprintf("BackoffIdleStrategy(MaxSpins:%d, MaxYields:%d, MinParkPeriodNs:%d, MaxParkPeriodNs:%d)", 72 s.maxSpins, s.maxYields, s.minParkPeriodNs, s.maxParkPeriodNs) 73 } 74 75 func (s *BackoffIdleStrategy) reset() { 76 s.spins = 0 77 s.yields = 0 78 s.parkPeriodNs = s.minParkPeriodNs 79 s.state = backoffNotIdle 80 } 81 82 func (s *BackoffIdleStrategy) idle() { 83 switch s.state { 84 case backoffNotIdle: 85 s.state = backoffSpinning 86 s.spins++ 87 case backoffSpinning: 88 // We should call procyield here, see https://golang.org/src/runtime/lock_futex.go 89 s.spins++ 90 if s.spins > s.maxSpins { 91 s.state = backoffYielding 92 s.yields = 0 93 } 94 case backoffYielding: 95 s.yields++ 96 if s.yields > s.maxYields { 97 s.state = backoffParking 98 s.parkPeriodNs = s.minParkPeriodNs 99 } else { 100 runtime.Gosched() 101 } 102 case backoffParking: 103 time.Sleep(time.Nanosecond * time.Duration(s.parkPeriodNs)) 104 s.parkPeriodNs = s.parkPeriodNs << 1 105 if s.parkPeriodNs > s.maxParkPeriodNs { 106 s.parkPeriodNs = s.maxParkPeriodNs 107 } 108 } 109 }