# How to Fix Go Context Canceled
Context canceled errors occur when an operation is cancelled before completion. Proper handling is essential for graceful shutdowns and timeouts.
Error Patterns
Context Canceled
context canceled
Get "https://example.com": context canceled
operation was canceledContext Deadline Exceeded
context deadline exceeded
context deadline exceeded (Client.Timeout exceeded)Understanding Context Errors
```go import ( "context" "errors" )
// Two main context errors var ( Canceled = errors.New("context canceled") DeadlineExceeded = errors.New("context deadline exceeded") )
// Both implement the error interface // Check with errors.Is() ```
Common Causes
- 1.Explicit cancellation - Parent context was cancelled
- 2.Timeout exceeded - Operation took longer than deadline
- 3.Request aborted - Client disconnected
- 4.Application shutdown - Server stopping gracefully
- 5.Parent scope ended - Context created in function that returned
Solutions
Solution 1: Check Context Errors Properly
```go import ( "context" "errors" )
func handleRequest(ctx context.Context) error { result, err := doWork(ctx) if err != nil { // Check specific context errors if errors.Is(err, context.Canceled) { return fmt.Errorf("request canceled: %w", err) } if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("request timed out: %w", err) } return fmt.Errorf("work failed: %w", err) } return nil } ```
Solution 2: Propagate Context Correctly
```go func fetchUser(ctx context.Context, id string) (*User, error) { // Pass context to all operations req, err := http.NewRequestWithContext(ctx, "GET", apiUrl, nil) if err != nil { return nil, err }
resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err // Context error propagated } defer resp.Body.Close()
// Continue processing... return parseUser(resp.Body) } ```
Solution 3: Check Context Early in Long Operations
```go func processLargeData(ctx context.Context, data []Item) error { for i, item := range data { // Check context periodically if ctx.Err() != nil { return ctx.Err() // Exit early }
if err := processItem(item); err != nil { return err }
// Or check at certain intervals if i % 100 == 0 { select { case <-ctx.Done(): return ctx.Err() default: // Continue } } } return nil } ```
Solution 4: Use Select for Context Cancellation
```go func worker(ctx context.Context, jobs <-chan Job) chan Result { results := make(chan Result)
go func() { defer close(results) for { select { case <-ctx.Done(): // Context cancelled, clean up and exit return case job, ok := <-jobs: if !ok { return // Jobs channel closed } result := process(job) select { case results <- result: case <-ctx.Done(): return // Don't block if cancelled } } } }()
return results } ```
Solution 5: Set Appropriate Timeout
```go func withTimeout(parent context.Context) (context.Context, context.CancelFunc) { // Create context with reasonable timeout ctx, cancel := context.WithTimeout(parent, 30*time.Second) return ctx, cancel }
// Usage func main() { ctx, cancel := withTimeout(context.Background()) defer cancel() // Always call cancel!
result, err := doWork(ctx) } ```
Solution 6: Derive Context Correctly
```go func handleHTTP(w http.ResponseWriter, r *http.Request) { // Use request context as parent ctx := r.Context()
// Derive child context with timeout timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel()
// Use derived context result, err := processData(timeoutCtx) if errors.Is(err, context.Canceled) { // Check if parent was cancelled (client disconnected) if ctx.Err() != nil { // Client disconnected, no need to respond return } // Our timeout was exceeded http.Error(w, "Timeout", http.StatusGatewayTimeout) return }
// Handle other errors... } ```
Solution 7: Graceful Shutdown Pattern
```go type Server struct { shutdownCtx context.Context shutdownCancel context.CancelFunc }
func (s *Server) Start() { // Create shutdown context s.shutdownCtx, s.shutdownCancel = context.WithCancel(context.Background())
// Start workers for i := 0; i < 10; i++ { go s.worker(s.shutdownCtx) } }
func (s *Server) Stop(timeout time.Duration) { // Signal all workers to stop s.shutdownCancel()
// Wait for graceful shutdown with timeout ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel()
select { case <-s.allWorkersDone(): fmt.Println("Graceful shutdown complete") case <-ctx.Done(): fmt.Println("Shutdown timeout, forcing exit") } } ```
Solution 8: Wrap Errors with Context
func operation(ctx context.Context) error {
err := doSomething(ctx)
if err != nil {
// Preserve context error information
if ctx.Err() != nil {
return fmt.Errorf("operation failed: %w (context: %v)", err, ctx.Err())
}
return err
}
return nil
}Testing Context Cancellation
```go func TestCancellation(t *testing.T) { ctx, cancel := context.WithCancel(context.Background())
// Cancel immediately cancel()
err := longOperation(ctx)
if !errors.Is(err, context.Canceled) { t.Errorf("expected context.Canceled, got: %v", err) } }
func TestTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) defer cancel()
err := slowOperation(ctx)
if !errors.Is(err, context.DeadlineExceeded) { t.Errorf("expected context.DeadlineExceeded, got: %v", err) } } ```
Common Patterns
HTTP Handler with Context
```go func handler(w http.ResponseWriter, r *http.Request) { ctx := r.Context()
select { case <-ctx.Done(): // Client disconnected log.Println("Client disconnected") return default: }
// Process request with context result, err := process(ctx) if errors.Is(err, context.Canceled) { return // Client gone, don't write response }
json.NewEncoder(w).Encode(result) } ```
Database Query with Context
```go func queryUser(ctx context.Context, db *sql.DB, id int) (*User, error) { row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id)
user := &User{} err := row.Scan(&user.ID, &user.Name, &user.Email)
if errors.Is(err, context.Canceled) { return nil, fmt.Errorf("query cancelled: %w", err) } if errors.Is(err, context.DeadlineExceeded) { return nil, fmt.Errorf("query timeout: %w", err) }
return user, err } ```
Prevention Tips
- 1.Always call cancel() - Use defer cancel() immediately after creating
- 2.Pass context as first parameter - Standard Go convention
- 3.Check ctx.Err() in long loops - Exit early when cancelled
- 4.Don't store context in structs - Pass explicitly to functions
- 5.Use errors.Is() - Not == for context error comparison
Related Errors
timeout exceeded- Deadline passed before completionclient disconnected- HTTP client closed connectionrequest aborted- Request cancelled externally