Go

How do deadlines propagate across service boundaries (e.g., gRPC), and how do you manage a time budget across multiple downstream calls?

Question 285MediumGo 1.22 to 1.25

A context does not travel over the network by itself; frameworks serialize parts of it. gRPC sends the remaining time in the grpc-timeout header. The server rebuilds a context with that deadline, so a 2s budget at the edge shrinks as it flows through services A to B to C. Cancellation also propagates: if the client cancels, the stream is reset and the server's context is canceled. Tracing systems (OpenTelemetry) carry span context in headers the same way.

For budgeting inside a service:

  • Give each downstream call a sub-timeout derived from the incoming context, so none of them can exceed the overall deadline.
  • Fail fast: if not enough time remains for a useful call, skip it or degrade instead of starting doomed work.
func (s *Svc) Quote(ctx context.Context, id string) (*Quote, error) {
	if dl, ok := ctx.Deadline(); ok && time.Until(dl) < 50*time.Millisecond {
		return nil, status.Error(codes.DeadlineExceeded, "insufficient budget")
	}

	pctx, cancel := context.WithTimeout(ctx, 300*time.Millisecond)
	defer cancel()
	price, err := s.pricing.Get(pctx, id) // bounded by min(300ms, remaining)
	if err != nil {
		return nil, err
	}

	return &Quote{ID: id, Price: price, Promo: s.promoOrDefault(ctx, id)}, nil
}

Gotcha: HTTP does not carry deadlines by default. A hand-built service mesh must pass the budget along in a header, or downstream services keep working after the caller has given up.

More on Context

All 35 Context questions