---
title: "context.Context em Go: Timeout e Cancelamento"
url: "https://golang.com.br/blog/context-timeout-cancelamento-go/"
markdown_url: "https://golang.com.br/blog/context-timeout-cancelamento-go.MD"
description: "Aprenda context.Context em Go para timeouts, cancelamento, deadlines, APIs HTTP, banco de dados, workers, logs e shutdown gracioso em produção."
date: "2026-05-25"
author: "Golang Brasil"
---

# context.Context em Go: Timeout e Cancelamento

Aprenda context.Context em Go para timeouts, cancelamento, deadlines, APIs HTTP, banco de dados, workers, logs e shutdown gracioso em produção.


`context.Context` é uma das peças mais importantes para escrever Go confiável em produção. Ele não é apenas um parâmetro que aparece no começo das funções. Ele carrega **cancelamento, timeout, deadline e valores de escopo de requisição** ao longo de chamadas HTTP, queries no banco, workers, filas, clients externos e rotinas concorrentes.

Quando um usuário desiste da requisição, uma chamada externa estoura o tempo esperado, um deploy precisa encerrar o processo ou um worker deve parar de consumir jobs, alguém precisa avisar o restante do código. Sem `context`, cada função continua trabalhando como se nada tivesse acontecido: query pendurada, goroutine vazando, request HTTP esperando indefinidamente, log sem correlação e shutdown travado.

Este guia mostra como usar `context.Context` em APIs e serviços Go: quando criar timeout, como propagar cancelamento, onde evitar `context.Background()`, como integrar com banco de dados, HTTP client, worker pool, logs e testes. Se você está montando uma aplicação web, leia também [API REST em Go](/aprenda/api-rest-go/), [observabilidade em Go](/tutoriais/go-observability/) e [worker pool em Go](/blog/worker-pool-go-fila-jobs/).

## O que context.Context resolve

`context.Context` resolve um problema de coordenação: uma operação começa em um ponto do sistema e atravessa várias camadas. Em uma API, por exemplo, a requisição chega no handler, chama serviço de domínio, consulta banco, chama uma API externa, publica evento e grava logs. Se o cliente desconecta ou o limite de tempo expira, todas essas camadas precisam saber.

O pacote `context` oferece quatro ideias principais:

- Cancelamento: um sinal dizendo que a operação não deve continuar.
- Deadline: um horário máximo para a operação terminar.
- Timeout: uma duração máxima, que vira deadline internamente.
- Values: dados pequenos e transversais, como `request_id`, quando realmente necessários.

O mais importante é entender que `context` não mata goroutine à força. Ele emite um sinal. Seu código precisa observar `ctx.Done()` ou chamar APIs que já respeitam `ctx`, como `http.NewRequestWithContext`, `db.QueryContext` e métodos de clients modernos.

## A regra prática de assinatura

Em código Go de produção, funções que fazem I/O, esperam, bloqueiam, consultam banco, chamam rede ou executam trabalho potencialmente longo devem receber `context.Context` como primeiro argumento:

```go
func (s *UserService) FindUser(ctx context.Context, id string) (*User, error) {
    return s.repo.FindByID(ctx, id)
}
```

Essa convenção torna a propagação explícita. O handler recebe um contexto da requisição HTTP e passa adiante. O serviço não inventa um contexto novo. O repositório usa o mesmo contexto na query. Se a requisição for cancelada, a query também pode ser cancelada.

Evite armazenar `context.Context` dentro de structs. Contexto é por operação, não por componente. Um `UserService` vive muito mais que uma requisição; guardar o contexto nele mistura ciclos de vida diferentes e costuma gerar bugs sutis.

## Timeout no handler HTTP

O servidor HTTP do Go já cria um contexto por requisição. Você acessa com `r.Context()`. Quando o cliente desconecta, esse contexto é cancelado. Para impor um limite interno menor, derive um contexto com timeout:

```go
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()

    user, err := h.users.FindUser(ctx, r.PathValue("id"))
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            http.Error(w, "tempo limite excedido", http.StatusGatewayTimeout)
            return
        }
        if errors.Is(err, context.Canceled) {
            return
        }
        http.Error(w, "erro interno", http.StatusInternalServerError)
        return
    }

    json.NewEncoder(w).Encode(user)
}
```

O `defer cancel()` é obrigatório mesmo quando o timeout vai expirar sozinho. Ele libera timers e recursos associados ao contexto derivado. Em handlers curtos, o custo é pequeno; em servidores com muito tráfego, esquecer cancelamento vira desperdício acumulado.

Não use o mesmo timeout para tudo. Um endpoint de autocomplete pode ter 200 ms. Um relatório pode ter 10 segundos. Uma chamada interna crítica pode ter 800 ms. Timeouts devem refletir experiência do usuário, custo operacional e SLO do serviço chamado.

## Banco de dados com QueryContext

O pacote `database/sql` e drivers modernos respeitam contexto nas operações com sufixo `Context`:

```go
func (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) {
    row := r.db.QueryRowContext(ctx, `
        SELECT id, name, email
        FROM users
        WHERE id = $1
    `, id)

    var user User
    if err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {
        return nil, err
    }
    return &user, nil
}
```

Isso é mais seguro que criar timeout apenas no banco ou apenas no load balancer. A requisição tem um orçamento total. Cada camada consome parte desse orçamento. Se o contexto expira, a query deve parar e devolver erro, em vez de continuar ocupando conexão no pool.

Em sistemas com PostgreSQL, MySQL ou Redis, combine `context` com configuração correta de pool: máximo de conexões, tempo de vida, idle timeout e métricas. `context` ajuda a cancelar trabalho, mas não substitui dimensionamento do pool. Para a base de persistência, veja também [Go com PostgreSQL](/aprenda/golang-postgresql/) e [migrations em Go](/blog/migrations-go-banco-dados-producao/).

## HTTP client com contexto

Chamadas externas sem timeout são um dos bugs mais comuns em APIs. O cliente remoto pode ficar lento, o DNS pode falhar, a rede pode oscilar ou o provedor pode aceitar conexão sem responder. Em Go, crie a requisição com contexto:

```go
func (c *BillingClient) GetInvoice(ctx context.Context, id string) (*Invoice, error) {
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/invoices/"+id, nil)
    if err != nil {
        return nil, err
    }

    resp, err := c.http.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("billing retornou status %d", resp.StatusCode)
    }

    var invoice Invoice
    if err := json.NewDecoder(resp.Body).Decode(&invoice); err != nil {
        return nil, err
    }
    return &invoice, nil
}
```

Além do contexto por operação, configure `http.Client` com timeout e transporte adequado. O contexto expressa o orçamento daquela chamada. O client protege contra problemas de conexão e leitura em nível mais baixo. Em serviços críticos, também entram retry com backoff, circuit breaker, rate limiting e observabilidade.

## Cancelamento em goroutines e workers

Goroutines longas precisam observar cancelamento. Um loop que lê fila, processa jobs ou faz polling deve parar quando `ctx.Done()` fecha:

```go
func worker(ctx context.Context, jobs <-chan Job, logger *slog.Logger) {
    for {
        select {
        case <-ctx.Done():
            logger.Info("worker encerrado", slog.Any("err", ctx.Err()))
            return
        case job, ok := <-jobs:
            if !ok {
                return
            }
            if err := processJob(ctx, job); err != nil {
                logger.Error("falha ao processar job", slog.String("job_id", job.ID), slog.Any("err", err))
            }
        }
    }
}
```

Esse padrão é essencial para shutdown gracioso. Quando o processo recebe `SIGTERM`, o `main` cancela o contexto raiz, o servidor HTTP para de aceitar novas requisições e os workers terminam o que for seguro terminar. Sem isso, deploys ficam lentos, jobs duplicam ou goroutines ficam presas em chamadas externas.

Use cuidado ao passar o mesmo contexto cancelado para retries. Se a operação original expirou, um retry usando o mesmo contexto vai falhar imediatamente. Isso pode ser correto em um handler HTTP, porque o usuário já esperou demais. Em um worker assíncrono, talvez seja melhor criar um novo contexto por tentativa, com limite próprio, dentro do ciclo de vida do job.

## Values: use pouco

`context.WithValue` existe, mas não deve virar um mapa global invisível. Use values para dados transversais que atravessam fronteiras de API: `request_id`, trace span, usuário autenticado em middleware, logger enriquecido ou metadados de observabilidade. Não use para dependências, configuração, conexão com banco ou parâmetros normais de função.

Um bom teste mental: se o dado é obrigatório para a regra de negócio, ele provavelmente deve ser argumento explícito. Se ele é infraestrutura de requisição, pode fazer sentido no contexto.

```go
type contextKey string

const requestIDKey contextKey = "request_id"

func WithRequestID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, requestIDKey, id)
}

func RequestID(ctx context.Context) string {
    id, _ := ctx.Value(requestIDKey).(string)
    return id
}
```

Prefira tipos de chave não exportados para evitar colisões entre pacotes. Evite chaves `string` genéricas como `"user"` ou `"id"`.

## Erros comuns com context em Go

O primeiro erro é criar `context.Background()` no meio da aplicação. `Background` é adequado no `main`, em testes, em inicialização ou como raiz de processo. Dentro de um handler, serviço ou repositório, criar `Background` corta o cancelamento da requisição.

O segundo erro é ignorar `ctx.Err()`. Quando uma operação falha, diferenciar `context.Canceled` de `context.DeadlineExceeded` ajuda a responder corretamente. Cancelamento por cliente desconectado não precisa virar erro 500 barulhento. Timeout de dependência pode virar 504, métrica e log de alerta.

O terceiro erro é timeout excessivamente curto. Se tudo tem 100 ms, qualquer variação normal vira erro. Se tudo tem 60 segundos, o sistema demora demais para degradar. Comece por orçamento realista, meça p95/p99 e ajuste com dados.

O quarto erro é usar context como desculpa para não fechar recursos. Mesmo com cancelamento, ainda feche `resp.Body`, `rows.Close()`, arquivos, channels de saída e timers quando aplicável.

## Checklist de produção

Antes de considerar o uso de `context.Context` maduro em uma API Go, revise:

- Handlers usam `r.Context()` e propagam para serviços.
- Funções com I/O recebem `ctx context.Context` como primeiro argumento.
- Queries usam `QueryContext`, `ExecContext` ou equivalente do driver.
- HTTP clients usam `http.NewRequestWithContext`.
- Timeouts são definidos por operação, não copiados cegamente.
- Goroutines longas fazem `select` em `ctx.Done()`.
- Shutdown gracioso cancela o contexto raiz e aguarda encerramento.
- Logs distinguem cancelamento, deadline e erro real de dependência.
- Testes cobrem timeout e cancelamento quando o fluxo é crítico.

Esse checklist aparece muito em entrevistas backend porque mostra maturidade operacional. Saber escrever goroutine é básico; saber quando ela deve parar é produção.

## Conclusão

`context.Context` é o fio que conecta requisição, serviço, banco, rede, worker e observabilidade em Go. Ele não substitui arquitetura, mas torna explícito o ciclo de vida das operações. Em aplicações pequenas, isso evita bugs chatos. Em produção, evita vazamento de goroutines, queries inúteis, chamadas externas penduradas e deploys que não encerram.

O próximo passo é aplicar o padrão nos pontos onde o custo de espera é maior: handlers HTTP, repositórios, clients externos e workers. Depois, combine isso com [slog para logging estruturado](/blog/slog-go-logging-estruturado/), [rate limiting em Go](/blog/rate-limiting-go-api-producao/) e métricas de latência. Para comparar como outra stack brasileira trata APIs, timeouts e cancelamento em frameworks mais opinativos, veja o <a href="https://python.dev.br/blog/apis-rest-com-fastapi/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">guia de APIs REST com FastAPI no Python Dev Brasil</a>.
