retry

package
v1.0.0-alpha.32 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 12, 2026 License: MIT Imports: 6 Imported by: 0

README

Retry Package

Simple exponential backoff retry for the SemStreams framework.

Overview

This package provides a minimal retry mechanism with exponential backoff. It's designed to be simple, predictable, and actually used throughout the framework.

Quick Start

import "github.com/c360/semstreams/pkg/retry"

// Use defaults (3 attempts, 100ms initial delay)
err := retry.Do(ctx, retry.DefaultConfig(), func() error {
    return someOperation()
})

// Or configure as needed
cfg := retry.Config{
    MaxAttempts:  5,
    InitialDelay: 200 * time.Millisecond,
    MaxDelay:     10 * time.Second,
    Multiplier:   2.0,
    AddJitter:    true,
}
err := retry.Do(ctx, cfg, func() error {
    return someOperation()
})

Framework Patterns

Pattern 1: KV Bucket Access

Use retry when accessing KV buckets that might not exist yet:

cfg := retry.Quick() // 10 attempts, fast retries
bucket, err := retry.DoWithResult(ctx, cfg, func() (jetstream.KeyValue, error) {
    return js.KeyValue(ctx, bucketName)
})

Or better, use CreateKeyValueBucket which handles this internally:

bucket, err := natsClient.CreateKeyValueBucket(ctx, kvConfig)
Pattern 2: Component Initialization

Components should retry connecting to dependencies during startup:

func (c *Component) Start(ctx context.Context) error {
    cfg := retry.Persistent() // 30 attempts, longer delays

    return retry.Do(ctx, cfg, func() error {
        // Try to connect to required services
        if err := c.connectToNATS(); err != nil {
            return err
        }
        if err := c.initializeKVBuckets(); err != nil {
            return err
        }
        return nil
    })
}
Pattern 3: Network Operations

Always retry network operations:

func (c *Client) SendMessage(msg *Message) error {
    cfg := retry.DefaultConfig() // 3 attempts, standard backoff

    return retry.Do(ctx, cfg, func() error {
        return c.conn.Send(msg)
    })
}

Configuration

Pre-defined Configs
  • DefaultConfig(): 3 attempts, 100ms-5s delay, 2x multiplier
  • Quick(): 10 attempts, 50ms-1s delay, 1.5x multiplier (for startup)
  • Persistent(): 30 attempts, 200ms-10s delay, 2x multiplier (for critical resources)
Custom Config
cfg := retry.Config{
    MaxAttempts:  5,           // Number of tries
    InitialDelay: time.Second, // First retry delay
    MaxDelay:     time.Minute, // Maximum retry delay
    Multiplier:   3.0,         // Delay multiplier
    AddJitter:    true,        // Add randomness
}

How It Works

  1. Executes your function
  2. On success, returns immediately
  3. On failure, waits with exponential backoff
  4. Continues until success or max attempts reached
  5. Respects context cancellation at all times

Example timing with default config:

  • Attempt 1: immediate
  • Attempt 2: wait 100ms
  • Attempt 3: wait 200ms
  • Total time: ~300ms + execution time

Best Practices

DO:

  • Use Quick() during component startup
  • Use Persistent() for critical resources
  • Use DefaultConfig() for normal operations
  • Always pass a context for cancellation
  • Use CreateKeyValueBucket() for KV buckets

DON'T:

  • Retry non-transient errors (e.g., invalid input)
  • Use excessive attempts (>30)
  • Use very short delays (<50ms)
  • Ignore context cancellation
  • Add retry to already-resilient operations

Why This Design?

  • Simple: ~150 lines total vs 850+ lines before
  • Focused: Just exponential backoff, no circuit breakers or metrics
  • Used: Patterns that components actually need
  • Predictable: Clear timing, optional jitter
  • Compatible: Works with existing code

Documentation

Overview

Package retry provides simple exponential backoff retry logic for transient failures.

Overview

This package offers a minimal retry mechanism with exponential backoff, designed to handle transient failures in network operations, resource initialization, and component startup.

core Functions

  • Do: Execute function with retry and exponential backoff
  • DoWithResult: Execute function with retry, returns both result and error

Configuration Presets

  • DefaultConfig(): 3 attempts, 100ms-5s delay (normal operations)
  • Quick(): 10 attempts, 50ms-1s delay (component startup)
  • Persistent(): 30 attempts, 200ms-10s delay (critical resources)

Usage Examples

Basic retry with defaults:

err := retry.Do(ctx, retry.DefaultConfig(), func() error {
    return client.Connect()
})

Component startup with quick retries:

cfg := retry.Quick()
err := retry.Do(ctx, cfg, func() error {
    return component.Initialize()
})

Retry with result:

bucket, err := retry.DoWithResult(ctx, retry.DefaultConfig(), func() (jetstream.KeyValue, error) {
    return js.KeyValue(ctx, bucketName)
})

Custom configuration:

cfg := retry.Config{
    MaxAttempts:  5,
    InitialDelay: 200 * time.Millisecond,
    MaxDelay:     10 * time.Second,
    Multiplier:   2.0,
    AddJitter:    true,
}
err := retry.Do(ctx, cfg, operation)

Design Philosophy

This package is intentionally minimal:

  • No circuit breakers (use service mesh or separate package)
  • No metrics collection (use instrumentation at call site)
  • No complex error classification (caller decides what to retry)
  • Just exponential backoff with jitter

Context Cancellation

All retry operations respect context cancellation and will immediately stop retrying when the context is cancelled, either during operation execution or during backoff delay.

Thread Safety

All functions are safe for concurrent use. The jitter mechanism uses a thread-safe random source to avoid contention.

Package retry provides simple exponential backoff retry logic for the framework

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Do

func Do(ctx context.Context, cfg Config, fn func() error) error

Do executes fn with exponential backoff retry

Example

Example for documentation

ctx := context.Background()
cfg := DefaultConfig()

err := Do(ctx, cfg, func() error {
	// Your operation that might fail
	return connectToService()
})

_ = err // Handle error after all retries exhausted

func DoWithResult

func DoWithResult[T any](ctx context.Context, cfg Config, fn func() (T, error)) (T, error)

DoWithResult executes fn with retry and returns both result and error

func IsNonRetryable

func IsNonRetryable(err error) bool

IsNonRetryable checks if an error is marked as non-retryable

func NonRetryable

func NonRetryable(err error) error

NonRetryable wraps an error to indicate it should not be retried

Types

type Config

type Config struct {
	MaxAttempts  int           // Maximum number of attempts (0 = no retry, just run once)
	InitialDelay time.Duration // Initial delay between attempts
	MaxDelay     time.Duration // Maximum delay between attempts
	Multiplier   float64       // Backoff multiplier (typically 2.0)
	AddJitter    bool          // Add randomness to prevent thundering herd
}

Config provides retry configuration

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns sensible defaults for retry operations

func Persistent

func Persistent() Config

Persistent returns a config for long-running retries (useful for critical resources)

func Quick

func Quick() Config

Quick returns a config for fast retries (useful during startup)

type NonRetryableError

type NonRetryableError struct {
	Err error
}

NonRetryableError wraps errors that should not be retried

func (*NonRetryableError) Error

func (e *NonRetryableError) Error() string

func (*NonRetryableError) Unwrap

func (e *NonRetryableError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL