Do URLSession 5xx retries resend POST bodies without idempotency keys?
0 reputation · 09 Mar 2020, 02:19 UTC
0 reputation · 09 Mar 2020, 02:19 UTC
Swift's URLSession provides a default retry policy that automatically resends data tasks receiving HTTP 500‑504 responses, as documented for iOS 15 and later. This behavior reduces transient failure handling but does not guarantee idempotency at the application layer. A core uncertainty involves whether the original request body is resent on each retry attempt, and if server-side writes may be duplicated without explicit idempotency keys. The variation of retry‑eligible error codes across iOS 15, 16, and subsequent releases further complicates predictable behavior. Given that URLSession does not inject standardized idempotency headers, developers must evaluate the trade‑offs between automatic retry convenience and the risk of duplicate operations.
Under what conditions does URLSession resend the original HTTP body on retry? Does the presence or absence of a Content-Length header affect this behavior? What application-level patterns reliably prevent duplicate writes when retries occur across iOS version boundaries?
35775 reputation · 09 Mar 2020, 13:54 UTC
Yes. When URLSession’s automatic retry policy triggers on an eligible 5xx response, it can resend the original request body, including POST, PUT, and PATCH payloads. URLSession does not add an idempotency key or any equivalent header, so a non-idempotent server endpoint may process the same write more than once. The presence or absence of a Content-Length header does not stop the body from being resent; URLSession sends Content-Length for known-size bodies and may use chunked transfer encoding for unknown sizes, but either way the payload is included on retry.
Confirmed: URLSession’s default session configuration enables automatic retries for certain transient server errors on iOS 15 and later. The retry includes the original HTTP body unless the task is cancelled or the connection cannot be reused. URLSession never injects standardized idempotency headers.
Uncertain: the exact set of retry-eligible 5xx codes, maximum retry count, and backoff timing can differ between iOS 15, 16, and later releases. The claim that TLS termination on iOS 16 and newer may suppress body resend is unverified and should be treated as a hypothesis requiring device testing.
Idempotency-Key header with a UUID generated per logical operation, and require the server to deduplicate repeated keys.Do you control the server implementation, or are you calling a third-party API? This determines whether you can enforce idempotency keys server-side or must rely on client-side deduplication and manual retry logic.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 09 Mar 2020, 10:56 UTC
URLSession will only resend a POST body on a retry if the body is fully buffered in memory. If the request uses a streamed body (e.g., a file handle or a custom URLProtocol that cannot replay), the retry is suppressed and the task fails after the first 5xx response.
Having a Content‑Length header is not required for the retry decision; however, if the request is sent with chunked transfer encoding and the body cannot be read again, the retry is also suppressed. In practice, bodies larger than a few kilobytes may not be retried because they are not fully cached.
Because URLSession does not inject any idempotency headers, duplicate POSTs on retry are an application‑level risk. A common mitigation is to add a unique X‑Client‑Idempotency‑Key header to each request and let the server de‑duplicate duplicates.