`POST /reports` kicks off a job that takes ninety seconds to generate — querying a large dataset, rendering it, writing the output. Holding the HTTP connection open for the entire ninety seconds and returning the finished result synchronously seems like the simple option, right up until it meets the infrastructure a real request actually travels through: a load balancer or reverse proxy with its own idle-connection timeout, very often set well under ninety seconds because most endpoints never need longer; a client SDK with a default timeout tuned for ordinary requests; a mobile connection that drops mid-request for reasons that have nothing to do with the server at all.
When any of those layers gives up before the job finishes, the client sees a connection error — and that error is genuinely ambiguous. The report generation might have failed. It might also have completed successfully three seconds after the proxy gave up, with the client having no way to tell those two situations apart, and no way to check later, because nothing about a purely synchronous design gave the operation an identity that outlives the one HTTP connection it happened to start on.
The fix is to stop pretending a long-running operation fits inside one HTTP request at all: accept the request immediately, hand back a durable identity for the operation itself, and let the client check on that identity — by polling or by subscribing to a callback — for as long as the job actually takes, decoupled entirely from whether any particular HTTP connection stayed open.
Analogy🏏Cricket
🏏 Think of it like cricket: A pitch and soil sample sent off for detailed groundstaff analysis before a major tour doesn't come back with an answer while the courier is still standing there waiting — the lab logs the sample under a reference number the moment it's received, hands that reference number back immediately, and the actual analysis, which can take days, happens independently of whether anyone is still watching. Nobody keeps a phone line open for two days waiting for the lab to finish; the ground's curator checks back against the reference number whenever it's convenient, and the lab's own process doesn't care whether anyone checked in the meantime or not — the sample gets analyzed regardless, and the result is simply waiting under that reference number whenever someone asks. A curator design that instead required staying on a live call with the lab for the full duration of testing would fail the moment any call dropped, with no way to know afterward whether the analysis had actually finished or the whole thing needed to be resubmitted from scratch. Just as a lab logs a sample under a durable reference number and lets the actual analysis run independently of any one phone call, a server should log a long-running operation under a durable identity and let the actual work run independently of any one HTTP connection. Just as a dropped call doesn't invalidate lab work already in progress, a dropped HTTP connection shouldn't invalidate — or leave ambiguous — an operation the server already accepted and started. The insight is that decoupling "is the work still happening" from "is this specific connection still open" is what makes a long-running process actually reliable, rather than merely convenient to code on the happy path.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.