Skip to content

Build1 publisher3 min readPublished

Pre-assigning a request ID turns a timed-out generation call into a resumable attempt

A dev.to write-up on one image generation service argues that a lost HTTP response leaves acceptance undecided, so an attempt needs a local identity and a held credit reservation before the provider is ever called.

The Engineer · Build desk

What happened

  • A backend submits an image request, the HTTP call times out, and the provider may already be generating, so a second submission can create a second paid task.
  • A documented rejection establishes that the request was never accepted. A timeout or a lost response leaves it open, because the provider may have accepted before the reply went missing.
  • A local credit reservation still lets a duplicate provider task through, and provider-side deduplication still leaves the application's credit ledger non-idempotent.
  • The design gives the attempt a local request ID before the provider is called, then persists the association between that ID and the provider task ID when a submission response arrives.
  • An ambiguous submission is stored as status_unknown with a SUBMISSION_UNKNOWN reason, and the settlement path leaves the credit reservation in place.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Local deduplication depends on the client's storage. An app that mints a fresh request ID after a relaunch gives the backend nothing to match, and the retry goes upstream as a new intention.
  • cost The user's credits stay reserved through every ambiguous case, so unresolved attempts accumulate as held balances that someone at the operator has to clear by hand.
  • decision Each team adopting the model still has to choose its own expiry or compensation rule, because the write-up stops at the state machine and says it does not settle that policy.

An asynchronous generation API still opens with a synchronous exchange. You post the request, you get a task ID, and that first exchange can fail independently of the work it starts [2]. When the response is lost there is no ID to look up, and the job may be running anyway [12]. Two costs come off a duplicate submission: another upstream charge for the second provider task, and a second deduction from the user's application credits [4].

The ownership check is easy to skip, and a request ID by itself does not make submission idempotent [7]. Reusing the ID has to land on the stored attempt, and the service has to confirm the caller owns that attempt and that the parameters match the first try, because the same ID with different input should not quietly return an unrelated result [7]. In the author's service the existing-attempt path checks identity and input consistency, and submission coordinates competing requests through persisted state and a lease [9].

The state model carries seven names [10]. In the published flow only completed and failed have no exit, so five of the seven describe work that has not resolved [1]. status_unknown can sit there without any provider task ID, because it also covers a submission whose response vanished before an ID could be recorded [12]. A state whose only content is that nobody knows is awkward in a schema review, and it is still the accurate name for that case. storage_failed is the opposite problem: the image was generated and the save or the deliverable preparation failed, so the repair is recovering delivery of the existing task [13]. failed is entered only when failure has been established [14].

Credits are reserved before submission, with enough persisted state to tie the reservation to the attempt [15]. Confirmed, available delivery settles the reservation, and confirmed failure releases it [17]. An ambiguous submission is marked status_unknown with SUBMISSION_UNKNOWN, and a lost response leaves the reservation in place [18]. A missing task ID or a failed status query leaves open whether generation failed [16].

Porting this needs one thing from the provider: a task ID that can be queried later, with a query that distinguishes still processing from failed, and a result that is retrieved and saved before the attempt is treated as delivered [22]. Reconciliation needs the outcome recorded somewhere other than the response you lost. The author notes the code examples are simplified illustrations [21].

One credit rule applies whatever provider you use. Returning a user's credits as compensation does not prove the provider never performed the work, so the reason for that decision is recorded separately from the provider outcome [19]. Settling a completed delivery takes more than an upstream success response while the image is still unavailable to the user [23].

What to watch

  • Whether image providers begin accepting a caller-supplied key at submit time. That would move deduplication upstream of the application.
  • How the lease behaves when two application servers race on the same request ID after a client retry.
  • Whether provider status queries stay answerable long enough after submission for a delayed reconciliation run to resolve anything.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories