Skip to main content

Put a Timeout on Every Network Call

Updated 2 min readMarkdown

Here is a bug that never shows up in testing: a service you call stops answering. It doesn't fail. It doesn't return an error. It just holds the connection open and says nothing.

If your code has no timeout, it waits. So does the next request, and the one after that. Soon every worker thread or connection in your pool is politely waiting on a server that has already left the building, and your whole app looks down even though only one dependency is sick.

Many HTTP clients have no default timeout at all. Python's requests library will wait forever unless you tell it otherwise:

import requests

# Bad: can hang indefinitely
resp = requests.get("https://api.example.com/items")

# Better: 3s to connect, 10s to read
resp = requests.get("https://api.example.com/items", timeout=(3, 10))

In JavaScript, fetch has no timeout either, but AbortSignal makes it easy:

const resp = await fetch("https://api.example.com/items", {
  signal: AbortSignal.timeout(10000),
});

A few rules of thumb:

  1. Set a timeout on every outbound call, including database and cache clients, not just HTTP.
  2. Base the number on what the caller can afford, not on how slow the dependency usually is. If your endpoint must answer in 2 seconds, a 30-second downstream timeout is a lie.
  3. Keep timeouts shorter as you go deeper. The outer request should give up last.
  4. Treat a timeout as a normal failure: log it, count it, and decide whether a retry is safe.

A timeout is a small line of code that turns "everything is frozen" into "one feature is degraded." That trade is almost always worth making.

About the author

No comments yet. Be the first to comment.