1. go
  2. /concurrency
  3. /timeouts

Understanding Timeouts in Go Programming

Timeouts are essential for building robust and responsive applications. This guide covers everything you need to know about implementing and managing timeouts effectively in Go.

Timeout Basics

Basic Timeout Patterns

// Channel timeout
select {
case result := <-ch:
    fmt.Println("Received:", result)
case <-time.After(time.Second):
    fmt.Println("Timeout")
}

// Context timeout
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

select {
case <-ctx.Done():
    if ctx.Err() == context.DeadlineExceeded {
        fmt.Println("Operation timed out")
    }
case result := <-doWork(ctx):
    fmt.Println("Result:", result)
}

Timeout with Channels

func withTimeout(timeout time.Duration) (string, error) {
    ch := make(chan string, 1)
    
    go func() {
        result := longRunningOperation()
        ch <- result
    }()
    
    select {
    case result := <-ch:
        return result, nil
    case <-time.After(timeout):
        return "", errors.New("operation timed out")
    }
}

// Usage
result, err := withTimeout(2 * time.Second)
if err != nil {
    log.Fatal(err)
}

Best Practices

1. Context Timeouts

func processWithContext(ctx context.Context) error {
    // Create derived context with timeout
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    done := make(chan error, 1)
    go func() {
        done <- processData(ctx)
    }()
    
    select {
    case err := <-done:
        return err
    case <-ctx.Done():
        return ctx.Err()
    }
}

// Usage in HTTP handler
func handler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()
    
    if err := processWithContext(ctx); err != nil {
        if err == context.DeadlineExceeded {
            http.Error(w, "Request timed out", http.StatusGatewayTimeout)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

2. Cleanup on Timeout

func processWithCleanup(ctx context.Context) error {
    cleanup := func() {
        // Cleanup resources
    }
    
    done := make(chan error, 1)
    go func() {
        defer cleanup()
        done <- processData(ctx)
    }()
    
    select {
    case err := <-done:
        return err
    case <-ctx.Done():
        cleanup()
        return ctx.Err()
    }
}

3. Configurable Timeouts

type Config struct {
    ReadTimeout  time.Duration
    WriteTimeout time.Duration
    IdleTimeout  time.Duration
}

type Server struct {
    config Config
}

func (s *Server) processRequest(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, s.config.ReadTimeout)
    defer cancel()
    
    data, err := s.read(ctx)
    if err != nil {
        return fmt.Errorf("read error: %w", err)
    }
    
    ctx, cancel = context.WithTimeout(ctx, s.config.WriteTimeout)
    defer cancel()
    
    return s.write(ctx, data)
}

Common Patterns

1. HTTP Client Timeouts

// Complete HTTP client with timeouts
client := &http.Client{
    Timeout: 10 * time.Second,
    Transport: &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   5 * time.Second,
            KeepAlive: 30 * time.Second,
        }).DialContext,
        TLSHandshakeTimeout:   5 * time.Second,
        ResponseHeaderTimeout: 5 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
        IdleConnTimeout:       90 * time.Second,
    },
}

// Usage with context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

req, err := http.NewRequestWithContext(ctx, "GET", "https://api.example.com", nil)
if err != nil {
    return err
}

resp, err := client.Do(req)
if err != nil {
    return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()

2. Database Timeouts

type DB struct {
    conn    *sql.DB
    timeout time.Duration
}

func (db *DB) QueryWithTimeout(query string, args ...interface{}) (*sql.Rows, error) {
    ctx, cancel := context.WithTimeout(context.Background(), db.timeout)
    defer cancel()
    
    return db.conn.QueryContext(ctx, query, args...)
}

// Usage with connection pool
func NewDB(dsn string) (*DB, error) {
    conn, err := sql.Open("postgres", dsn)
    if err != nil {
        return nil, err
    }
    
    conn.SetConnMaxLifetime(time.Minute * 3)
    conn.SetMaxOpenConns(10)
    conn.SetMaxIdleConns(10)
    
    return &DB{
        conn:    conn,
        timeout: 5 * time.Second,
    }, nil
}

3. Worker Pool with Timeouts

type WorkerPool struct {
    workers    int
    timeout    time.Duration
    jobs       chan Job
    results    chan Result
    done       chan struct{}
}

func (p *WorkerPool) worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        case job := <-p.jobs:
            ctx, cancel := context.WithTimeout(ctx, p.timeout)
            result := p.processJob(ctx, job)
            cancel()
            
            select {
            case p.results <- result:
            case <-ctx.Done():
                return
            }
        }
    }
}

func (p *WorkerPool) processJob(ctx context.Context, job Job) Result {
    done := make(chan Result, 1)
    
    go func() {
        done <- job.Process()
    }()
    
    select {
    case result := <-done:
        return result
    case <-ctx.Done():
        return Result{Error: ctx.Err()}
    }
}

Performance Considerations

1. Timer Management

// Bad: Creates new timer for each operation
func badTimerUsage() {
    for {
        select {
        case <-time.After(time.Second):
            // Handle timeout
        case data := <-dataChan:
            // Process data
        }
    }
}

// Good: Reuse timer
func goodTimerUsage() {
    timer := time.NewTimer(time.Second)
    defer timer.Stop()
    
    for {
        timer.Reset(time.Second)
        select {
        case <-timer.C:
            // Handle timeout
        case data := <-dataChan:
            // Process data
        }
    }
}

2. Context Chain Depth

// Bad: Deep context chain
func deepContextChain() error {
    ctx := context.Background()
    ctx1, cancel1 := context.WithTimeout(ctx, 5*time.Second)
    defer cancel1()
    
    ctx2, cancel2 := context.WithTimeout(ctx1, 3*time.Second)
    defer cancel2()
    
    ctx3, cancel3 := context.WithTimeout(ctx2, 1*time.Second)
    defer cancel3()
    
    return doWork(ctx3)
}

// Good: Use appropriate timeout level
func appropriateTimeout() error {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    return doWork(ctx)
}

Common Mistakes

1. Not Canceling Context

// Wrong: Context not canceled
func wrong() {
    ctx, _ := context.WithTimeout(context.Background(), time.Second)
    // Context leak: cancel function not called
    doWork(ctx)
}

// Right: Always cancel context
func right() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()  // Ensures context is canceled
    doWork(ctx)
}

2. Incorrect Timeout Handling

// Wrong: Timeout doesn't cancel operation
func wrongTimeout() {
    done := make(chan bool)
    go func() {
        longRunningOperation()
        done <- true
    }()
    
    select {
    case <-done:
        return
    case <-time.After(time.Second):
        return // Operation continues in background
    }
}

// Right: Operation can be canceled
func rightTimeout(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, time.Second)
    defer cancel()
    
    return longRunningOperation(ctx)
}

3. Resource Leaks

// Wrong: Resources not cleaned up on timeout
func wrongCleanup() {
    resource := acquireResource()
    
    select {
    case result := <-process(resource):
        return result
    case <-time.After(time.Second):
        return fmt.Errorf("timeout")  // Resource leak
    }
}

// Right: Ensure cleanup
func rightCleanup() {
    resource := acquireResource()
    defer releaseResource(resource)
    
    select {
    case result := <-process(resource):
        return result
    case <-time.After(time.Second):
        return fmt.Errorf("timeout")
    }
}

Next Steps

  1. Learn about context
  2. Explore channels
  3. Study error handling
  4. Practice with worker pools

Additional Resources