What do timeoutIntervalForRequest, timeoutIntervalForResource and the per-request timeout measure, and how do they interact?
Predict first
What happens when no data arrives, and what are the defaults?
import Foundation
final class Hang: URLProtocol {
override class func canInit(with request: URLRequest) -> Bool { true }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }
override func startLoading() {}
override func stopLoading() {}
}
let cfg = URLSessionConfiguration.ephemeral
cfg.protocolClasses = [Hang.self]
cfg.timeoutIntervalForRequest = 0.3
let s = URLSession(configuration: cfg)
let start = ContinuousClock.now
do { _ = try await s.data(from: URL(string: "https://api.test/hang")!) }
catch let e as URLError { print("error timedOut:", e.code == .timedOut, "within 2 s:", ContinuousClock.now - start < .seconds(2)) }
catch { print(error) }
print("defaults:", URLRequest(url: URL(string: "https://api.test/x")!).timeoutInterval,
URLSessionConfiguration.default.timeoutIntervalForRequest, URLSessionConfiguration.default.timeoutIntervalForResource)
- error timedOut: true within 2 s: true / defaults: 60.0 60.0 604800.0
- error timedOut: false / defaults: 30.0 60.0 86400.0
- It hangs for 60 seconds
Mental model. The idle timeout is a patient who leaves if nobody speaks for a minute. The resource timeout is the clinic closing for the day. A chatty person can keep the first from ever firing.
There are three numbers and they measure different things. timeoutIntervalForRequest (default 60 s) is an idle timeout: how long the connection may go without receiving any data before the request fails with URLError.timedOut. Every chunk of data resets it, so a slow but steady download never trips it. In the check a request that never gets a byte times out after the configured 0.3 s. timeoutIntervalForResource (default 7 days) is the total time allowed for the whole transfer including retries and waiting for connectivity; it is what bounds a long upload or a background transfer. The per-request URLRequest.timeoutInterval (also 60 s by default) overrides the idle timeout for one request.
The consequence is that “60 s” is not “the user waits at most 60 s”: a server that trickles a byte every 50 seconds keeps the request alive for days. For user-facing calls, set a deadline on the whole operation: shorter idle timeouts (10 to 15 s for interactive calls), a task-level timeout (race the request against Task.sleep in a task group and cancel the loser), and a sensible resource timeout. Different calls need different budgets (a login, a search and an upload), so configure separate sessions or set the per-request value, and remember waitsForConnectivity makes the request wait (up to the resource timeout) instead of failing fast when offline. Surface the timeout to the user with a retry, and log it with the URL path and timings to find slow endpoints.
The idle timeout fires without data; the defaults are 60 s, 60 s and seven days.
Common traps
- Believing the default 60 s means a hard cap. It is an idle timeout; add your own deadline.
- Leaving the 7-day resource timeout on interactive sessions with
waitsForConnectivity. A request can sit waiting for days.
They might follow up with
- How do I implement a total timeout with async/await? A throwing task group racing the request and a sleep, cancelling the loser (Q293).
- Which timeout applies to a background session? The resource timeout matters most, since the system schedules the transfer.
Related questions
- 12.8.3 How do you classify what URLSession reports: which failures are URLError, which are not, and which are worth…
- 12.8.2 Are HTTP header names case-sensitive in URLSession, and how do you read them safely?
- 12.1.1 What do the URLSession configurations control, and how do default, ephemeral and background sessions differ?
- 12.4.3 Why use NWPathMonitor instead of reachability, and how do you use connectivity information correctly?
- 10.8.2 What happens to the other children when one child in a throwing task group fails, and what does a discarding…
← How do you classify what URLSession reports: which failures are…