Python urllib timeout vs Sume's 30 second sync wait

A client read timeout shorter than Sume's sync wait fails before the server answers. A tested demo of the timeout behavior and how to pick a margin above 30 s.

3 min readSume
All posts

If you submit in sync mode with a wait of up to 30 seconds, your client's read timeout must be longer than the wait, or your code gives up while the server is still holding the connection. A client timeout does not cancel the job, so you have then lost the response of a job that still runs and bills.

The demo uses a local server that sleeps two seconds, standing in for a longer wait, and compares two client timeouts.

The demo

With a 1 second timeout the client raised TimeoutError after 1.0 seconds. With a 5 second timeout it received the response, in about 2 to 3 seconds in the run.

Note that urllib's timeout is not a total deadline. It applies to individual socket operations, such as connect and each read, so a server that trickles bytes can keep a request alive longer than the number you passed. For sync mode the server sends nothing until it has the answer or its wait ends, so the read timeout behaves like the deadline you expect. If you need a hard total limit, run the call in a thread or an executor with its own overall timeout, and treat expiry the same way as a client timeout: the job may exist.

import http.server, threading, time, urllib.request

class Slow(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        time.sleep(2)                    # stands in for a 30 s sync wait
        self.send_response(200); self.end_headers(); self.wfile.write(b"{}")
    def log_message(self, *a): pass

srv = http.server.HTTPServer(("127.0.0.1", 0), Slow)
threading.Thread(target=srv.serve_forever, daemon=True).start()
url = f"http://127.0.0.1:{srv.server_port}/"
for timeout in (1, 5):                   # real code: 5 -> 35 against wait_timeout_seconds=30
    t0 = time.time()
    try:
        urllib.request.urlopen(url, timeout=timeout)
        print(f"timeout={timeout}s: got a response after {time.time()-t0:.1f}s")
    except TimeoutError:
        print(f"timeout={timeout}s: TimeoutError after {time.time()-t0:.1f}s")

Choosing the timeout

Sume documents that wait_timeout_seconds is clamped to 0..30, and that a timed-out wait is still a 2xx with the job id plus sync.timed_out or sync.capacity_exhausted. So a client with a read timeout somewhat above 30 seconds should always get a response. The margin is yours to pick; the docs do not name one.

Timeout situations (read 2026-10-06, Sume docs)
SituationWhat to do
Your timeout fires before the server answersJob may exist; look it up before submitting again
Response is 2xx with sync.timed_outPoll the job id; do not resubmit
You resubmit with the same Idempotency-KeyReturns the original job
You resubmit without a keyMay create a second paid job

Why the key matters here

The lost response is the case Idempotency-Key exists for. Send one on every submit, and a retry after your own timeout will return the original job instead of creating another.

Tradeoffs

A long read timeout ties up a worker thread for up to 30 seconds. If you submit many jobs, use async or webhook mode and avoid the wait altogether.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume