package pacer import ( "math/rand" "time" "golang.org/x/time/rate" ) type ( // MinSleep configures the minimum sleep time of a Calculator MinSleep time.Duration // MaxSleep configures the maximum sleep time of a Calculator MaxSleep time.Duration // DecayConstant configures the decay constant time of a Calculator DecayConstant uint // AttackConstant configures the attack constant of a Calculator AttackConstant uint // Burst configures the number of API calls to allow without sleeping Burst int ) // Default is a truncated exponential attack and decay. // // On retries the sleep time is doubled, on non errors then sleeptime decays // according to the decay constant as set with SetDecayConstant. // // The sleep never goes below that set with SetMinSleep or above that set // with SetMaxSleep. type Default struct { minSleep time.Duration // minimum sleep time maxSleep time.Duration // maximum sleep time decayConstant uint // decay constant attackConstant uint // attack constant } // DefaultOption is the interface implemented by all options for the Default Calculator type DefaultOption interface { ApplyDefault(*Default) } // NewDefault creates a Calculator used by Pacer as the default. func NewDefault(opts ...DefaultOption) *Default { c := &Default{ minSleep: 10 * time.Millisecond, maxSleep: 2 * time.Second, decayConstant: 2, attackConstant: 1, } c.Update(opts...) return c } // Update applies the Calculator options. func (c *Default) Update(opts ...DefaultOption) { for _, opt := range opts { opt.ApplyDefault(c) } } // ApplyDefault updates the value on the Calculator func (o MinSleep) ApplyDefault(c *Default) { c.minSleep = time.Duration(o) } // ApplyDefault updates the value on the Calculator func (o MaxSleep) ApplyDefault(c *Default) { c.maxSleep = time.Duration(o) } // ApplyDefault updates the value on the Calculator func (o DecayConstant) ApplyDefault(c *Default) { c.decayConstant = uint(o) } // ApplyDefault updates the value on the Calculator func (o AttackConstant) ApplyDefault(c *Default) { c.attackConstant = uint(o) } // Calculate takes the current Pacer state and return the wait time until the next try. func (c *Default) Calculate(state State) time.Duration { if t, ok := IsRetryAfter(state.LastError); ok { if t < c.minSleep { return c.minSleep } return t } if state.ConsecutiveRetries > 0 { sleepTime := c.maxSleep if c.attackConstant != 0 { sleepTime = (state.SleepTime << c.attackConstant) / ((1 << c.attackConstant) - 1) } if sleepTime > c.maxSleep { sleepTime = c.maxSleep } return sleepTime } sleepTime := (state.SleepTime<> c.decayConstant if sleepTime < c.minSleep { sleepTime = c.minSleep } return sleepTime } // AmazonCloudDrive is a specialized pacer for Amazon Drive // // It implements a truncated exponential backoff strategy with randomization. // Normally operations are paced at the interval set with SetMinSleep. On errors // the sleep timer is set to 0..2**retries seconds. // // See https://developer.amazon.com/public/apis/experience/cloud-drive/content/restful-api-best-practices type AmazonCloudDrive struct { minSleep time.Duration // minimum sleep time } // AmazonCloudDriveOption is the interface implemented by all options for the AmazonCloudDrive Calculator type AmazonCloudDriveOption interface { ApplyAmazonCloudDrive(*AmazonCloudDrive) } // NewAmazonCloudDrive returns a new AmazonCloudDrive Calculator with default values func NewAmazonCloudDrive(opts ...AmazonCloudDriveOption) *AmazonCloudDrive { c := &AmazonCloudDrive{ minSleep: 10 * time.Millisecond, } c.Update(opts...) return c } // Update applies the Calculator options. func (c *AmazonCloudDrive) Update(opts ...AmazonCloudDriveOption) { for _, opt := range opts { opt.ApplyAmazonCloudDrive(c) } } // ApplyAmazonCloudDrive updates the value on the Calculator func (o MinSleep) ApplyAmazonCloudDrive(c *AmazonCloudDrive) { c.minSleep = time.Duration(o) } // Calculate takes the current Pacer state and return the wait time until the next try. func (c *AmazonCloudDrive) Calculate(state State) time.Duration { if t, ok := IsRetryAfter(state.LastError); ok { if t < c.minSleep { return c.minSleep } return t } consecutiveRetries := state.ConsecutiveRetries if consecutiveRetries == 0 { return c.minSleep } if consecutiveRetries > 9 { consecutiveRetries = 9 } // consecutiveRetries starts at 1 so // maxSleep is 2**(consecutiveRetries-1) seconds maxSleep := time.Second << uint(consecutiveRetries-1) // actual sleep is random from 0..maxSleep sleepTime := time.Duration(rand.Int63n(int64(maxSleep))) if sleepTime < c.minSleep { sleepTime = c.minSleep } return sleepTime } // AzureIMDS is a pacer for the Azure instance metadata service. type AzureIMDS struct { } // NewAzureIMDS returns a new Azure IMDS calculator. func NewAzureIMDS() *AzureIMDS { c := &AzureIMDS{} return c } // Calculate takes the current Pacer state and return the wait time until the next try. func (c *AzureIMDS) Calculate(state State) time.Duration { var addBackoff time.Duration if state.ConsecutiveRetries == 0 { // Initial condition: no backoff. return 0 } if state.ConsecutiveRetries > 4 { // The number of consecutive retries shouldn't exceed five. // In case it does for some reason, cap delay. addBackoff = 0 } else { addBackoff = time.Duration(2< 5 { consecutiveRetries = 5 } // consecutiveRetries starts at 1 so go from 1,2,3,4,5,5 => 1,2,4,8,16,16 // maxSleep is 2**(consecutiveRetries-1) seconds + random milliseconds return time.Second< 0 { if c.attackConstant == 0 { return c.maxSleep } if state.SleepTime == 0 { return c.minSleep } sleepTime := (state.SleepTime << c.attackConstant) / ((1 << c.attackConstant) - 1) if sleepTime > c.maxSleep { sleepTime = c.maxSleep } return sleepTime } sleepTime := (state.SleepTime<> c.decayConstant if sleepTime < c.minSleep { sleepTime = 0 } return sleepTime }