gRPC Context and Cancellation
Every gRPC call carries a context. The context holds the deadline, the cancellation signal, and the metadata for that call. Handlers that respect the context stop wasted work the moment a caller loses interest.
What a Context Holds
| Item | Purpose |
|---|---|
| Deadline | The time after which the call must stop |
| Cancellation signal | A flag that tells running code to quit |
| Metadata | Headers such as tokens and request IDs |
| Peer information | Details about the caller |
The Food Order Analogy
You order a pizza through an app and cancel two minutes later. The app tells the restaurant. The restaurant tells the kitchen. The kitchen stops kneading the dough. Without cancellation, every step finishes its work for a customer who no longer wants the pizza.
Cancellation Travels Down the Chain
Client ──▶ Service A ──▶ Service B ──▶ Database
│
└─ cancels the call
│
▼
Service A sees "cancelled" ──▶ Service B sees "cancelled" ──▶ Query stops
The signal flows automatically when each service passes the incoming context to its outgoing calls.
Cancel from the Client
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel()
}()
reply, err := client.ProcessReport(ctx, req)
// err carries the code CANCELLED when cancel() fires
React on the Server
The server must check the context during long work. A loop that never looks at the context keeps burning CPU after the client leaves.
func (s *server) ProcessReport(ctx context.Context, req *pb.ReportRequest) (*pb.ReportReply, error) {
for i := 0; i < len(req.Pages); i++ {
select {
case <-ctx.Done():
return nil, status.FromContextError(ctx.Err()).Err()
default:
}
renderPage(req.Pages[i])
}
return &pb.ReportReply{}, nil
}
Python Version
def ProcessReport(self, request, context):
for page in request.pages:
if not context.is_active():
return report_pb2.ReportReply()
render_page(page)
return report_pb2.ReportReply()
Pass the Context Forward
Reuse the incoming context for every downstream call. A fresh background context breaks the chain, and the downstream service never learns about the cancellation or the deadline.
// Correct: inherits deadline and cancellation
resp, err := inventoryClient.Check(ctx, &pb.CheckRequest{})
// Wrong: severs the chain
resp, err := inventoryClient.Check(context.Background(), &pb.CheckRequest{})
Database Calls
Most database drivers accept a context. Pass it along and the driver aborts running queries on cancellation.
row := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = ?", id)Cleanup with Defer
Cancelled calls still need tidy endings. Release locks, close files, and roll back transactions in deferred code so that cancellation never leaves half finished work behind.
Common Mistakes
- Ignoring the context inside long loops
- Starting background goroutines that outlive the request and never watch the context
- Replacing the incoming context with a new one
- Forgetting to call the cancel function after creating a derived context
Status Codes for Context Endings
Two status codes describe a context that ended early. CANCELLED means the caller gave up. DEADLINE_EXCEEDED means the time limit passed. Log both at a low severity, because they describe normal behavior and rarely point to a bug. A sudden rise in either code deserves attention, since it often signals slow dependencies.
Context ends
├─ Caller aborts → CANCELLED
└─ Time limit passes → DEADLINE_EXCEEDED
Reading Metadata from the Context
Server handlers read incoming headers through the same context object. Tokens, request IDs, and language settings all arrive this way. Keep the reading code in one helper function so that handlers stay clean.
md, ok := metadata.FromIncomingContext(ctx)
if ok {
ids := md.Get("x-request-id")
if len(ids) > 0 {
log.Printf("request id %s", ids[0])
}
}
Summary
The context carries deadlines, cancellation, and metadata through every gRPC call. Check it in long running code, pass it to every downstream call, and clean up with deferred statements. Respectful cancellation saves resources across the whole system.
