Deadlines and cancellation

gRPC · Practical · 12 min

Calls without deadlines are how one slow service takes down a whole system.

A deadline is an absolute point in time by which the client wants an answer. gRPC sends it to the server as the grpc-timeout header, and if it passes, the call fails with DEADLINE_EXCEEDED on the client and is cancelled on the server.

By default, many gRPC implementations have no deadline: a call to a hung server waits forever while holding resources. Always set one, sized to the operation (e.g. 300 ms for a lookup, 30 s for a report).

In grpc-js you pass { deadline: Date.now() + 500 } as call options (a Date or millisecond timestamp). On the server:

  • call.getDeadline() tells you the remaining budget.
  • call.cancelled and the cancelled event tell you the client gave up, timed out, or disconnected. Stop work and free resources.

Deadline propagation: when your handler calls other services, give those calls the incoming deadline (minus a little margin), not a fresh fixed timeout. Otherwise downstream services keep working long after the original caller has gone. Several gRPC libraries (Go, Java) propagate it through the context automatically; in Node you pass it yourself, e.g. { deadline: call.getDeadline() }, or with propagate_flags and parent.

Clients can also cancel explicitly with call.cancel(), which yields CANCELLED.

Example

import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
const { hello } = grpc.loadPackageDefinition(protoLoader.loadSync("hello.proto"));

const server = new grpc.Server();
server.addService(hello.v1.Greeter.service, {
  SayHello: (call, callback) => {
    const timer = setTimeout(() => callback(null, { message: "late" }), 2000);
    call.on("cancelled", () => { clearTimeout(timer); console.log("server: cancelled"); });
  },
});
server.bindAsync("127.0.0.1:50054", grpc.ServerCredentials.createInsecure(), () => {
  const client = new hello.v1.Greeter("127.0.0.1:50054", grpc.credentials.createInsecure());
  client.SayHello({ name: "Ann" }, { deadline: Date.now() + 200 }, (err) => {
    console.log("client:", grpc.status[err.code]); // client: DEADLINE_EXCEEDED
    setTimeout(() => server.forceShutdown(), 100);
  });
});
// also printed: server: cancelled

Remember

  • Set a deadline on every call; the default is often none
  • Deadline passing = DEADLINE_EXCEEDED for client, cancellation for server
  • Propagate the incoming deadline to downstream calls
  • Stop work on the cancelled event

Watch out for

  • Using a fixed 30 s timeout on each hop, so a 5-hop chain can run far past the user's patience.

Check yourself

  1. Why propagate the incoming deadline to downstream calls instead of a fresh timeout?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: grpc.io/docs/guides/deadlines/