backoff

repository·master·Indexed 20 days ago

https://github.com/jpillora/backoff

A simple exponential backoff counter implementation for Go (Golang) used to manage retry delays. It provides a Backoff struct with configurable Min, Max, and Factor fields, as well as Jitter for randomization to prevent synchronized retry spikes. Features include a stateful Duration() method, a concurrent-safe ForAttempt() method for calculating durations for specific attempts, and a Reset() method to restart the counter.

Tokens
1.6K
Snippets
9
Records
11
Agent score
21%

What's inside backoff

  1. How the Backoff counter works

    master

    The backoff.Backoff struct acts as a time.Duration counter used to implement exponential backoff strategies.

    Core Logic:

    • It starts at the value defined in Min.
    • Every call to Duration() multiplies the current duration by the Factor.
    • The duration is capped at the Max value.
    • Calling Reset() returns the counter to the Min value.
    • Setting Jitter to true adds randomness to the returned duration to prevent synchronized retry spikes.
  2. Configure the Backoff struct

    master

    You can initialize a backoff.Backoff struct with the following fields:

    FieldTypeDescription
    Mintime.DurationThe starting duration. (Default: 100ms)
    Maxtime.DurationThe maximum allowed duration. (Default: 10s)
    Factorfloat64The multiplier applied after each call to Duration(). (Default: 2)
    JitterboolWhether to add randomization to the duration. (Default: false)
  3. Enable Jitter for randomized backoff

    master

    To prevent 'thundering herd' problems, set Jitter: true. This adds randomness to the duration returned by Duration(). While not strictly required, seeding math/rand can help produce repeatable results during testing.

    import "math/rand"
    
    b := &backoff.Backoff{
    	Jitter: true,
    }
    
    // Optional: seed for repeatable results
    rand.Seed(42)
    
    fmt.Printf("%s\n", b.Duration())
    fmt.Printf("%s\n", b.Duration())
  4. Implement exponential backoff for network reconnections

    master

    When performing network operations (like net.Dial), use b.Duration() to determine the sleep time between failed attempts and b.Reset() when a connection is successfully established.

    b := &backoff.Backoff{
        Max:    5 * time.Minute,
    }
    
    for {
    	conn, err := net.Dial("tcp", "example.com:5309")
    	if err != nil {
    		d := b.Duration()
    		fmt.Printf("%s, reconnecting in %s", err, d)
    		time.Sleep(d)
    		continue
    	}
    	//connected
    	b.Reset()
    	conn.Write([]byte("hello world!"))
    	// ... Read ... Write ... etc
    	conn.Close()
    }
  5. Use Backoff for simple duration increments

    master

    To use the backoff counter for simple timing, initialize the struct and call Duration() repeatedly. Each call returns the next incremented duration.

    b := &backoff.Backoff{
    	Min:    100 * time.Millisecond,
    	Max:    10 * time.Second,
    	Factor: 2,
    	Jitter: false,
    }
    
    fmt.Printf("%s\n", b.Duration()) // 100ms
    fmt.Printf("%s\n", b.Duration()) // 200ms
    fmt.Printf("%s\n", b.Duration()) // 400ms
    
    b.Reset()
    fmt.Printf("%s\n", b.Duration()) // 100ms
  6. Reset the backoff counter

    master

    The Reset() method restarts the internal attempt counter at zero. This is typically used when a successful operation occurs and you want to start the backoff sequence from the minimum duration again.

    b.Reset()
  7. Calculate duration for a specific attempt with ForAttempt

    master

    The ForAttempt(attempt float64) method returns the duration for a specific attempt number without modifying the internal state of the Backoff struct. This is useful for calculating backoff values for many independent processes using a single shared configuration object to save memory.

    • The first attempt should be 0.
    • This method is concurrent-safe.
    • It applies the configured Min, Max, Factor, and Jitter settings.
    b := &backoff.Backoff{
    	Min:    100 * time.Millisecond,
    	Max:    10 * time.Second,
    	Factor: 2,
    }
    
    // Calculate duration for the 5th attempt (index 4)
    dur := b.ForAttempt(4)
  8. Use the Backoff struct for exponential backoff

    master

    The Backoff struct implements an exponential-backoff algorithm. It tracks an internal attempt counter and calculates a time.Duration that increases by a Factor after each call to Duration(), up to a specified Max value.

    Configuration Fields

    FieldTypeDefaultDescription
    Factorfloat642The multiplier applied to the duration at each increment step.
    JitterboolfalseIf true, randomizes the backoff step within the range [Min, current_duration] to ease contention.
    Mintime.Duration100msThe minimum duration returned.
    Maxtime.Duration10sThe maximum duration returned.

    Note on Concurrency: The Backoff struct is not generally concurrent-safe for stateful operations like Duration() or Reset(), but the ForAttempt method is concurrent-safe.

    b := &backoff.Backoff{
    	Min:    500 * time.Millisecond,
    	Max:    30 * time.Second,
    	Factor: 2,
    	Jitter: true,
    }
    
    // Use Duration() to get the next backoff interval and increment the counter
    dur := b.Duration()
    time.Sleep(dur)
  9. Create a configuration copy with Copy

    master

    The Copy() method returns a new Backoff instance with the same configuration constraints (Factor, Jitter, Min, and Max) as the original, but with a fresh attempt counter starting at zero.

    newBackoff := b.Copy()