Does Combine's timeout operator cancel the upstream publisher after firing?
0 reputation · 21 Jul 2021, 23:08 UTC
0 reputation · 21 Jul 2021, 23:08 UTC
I'm designing a networking layer in Swift using Combine (iOS 15+) where requests are wrapped with .timeout(_:scheduler:) so subscribers receive an error if no value arrives in time. My goal is to guarantee that when a timeout fires, the underlying work — for example a URLSession.DataTaskPublisher request — stops consuming resources rather than continuing in the background.
The Combine documentation indicates that timeout emits a failure downstream but does not cancel the upstream publisher, which suggests the data task may keep running after the timeout error is delivered. I'm unclear whether this holds for all upstream types, whether the scheduler choice affects it, and whether holding the pipeline's AnyCancellable versus releasing it changes the outcome.
My questions:
timeout emits its error, does the upstream DataTaskPublisher actually keep its URLSession task alive, and how can I verify this reliably (e.g., Instruments or task state logging)?timeout operator entirely?Future, Just with delay, and DataTaskPublisher?33775 reputation · 22 Jul 2021, 03:18 UTC
Combine's .timeout(_:scheduler:) emits a failure downstream when the specified interval elapses, but it does not cancel the upstream publisher.
URLSessionDataTask continues running after the timeout error is delivered unless the subscription is explicitly cancelled.AnyCancellable while a timeout fires does not stop upstream work; only releasing or calling .cancel() on the cancellable triggers upstream teardown..handleEvents(receiveCancel: { print("Cancelled") }) immediately before the .timeout operator. If the log does not appear when the timeout fires, the upstream remains active.URLSessionTask.state after the timeout error is received. A state of .running confirms the task survived the timeout.To ensure resources are freed, you must explicitly cancel the subscription when the timeout failure arrives. This can be achieved by calling cancel() on the stored AnyCancellable within the sink completion handler or using handleEvents.
var cancellable: AnyCancellable?
cancellable = URLSession.shared.dataTaskPublisher(for: request)
.timeout(5, scheduler: DispatchQueue.main)
.handleEvents(receiveCompletion: { completion in
if case .failure = completion { cancellable?.cancel() }
})
.sink(receiveCompletion: { _ in }, receiveValue: { _ in })
Future is eager, the work starts upon creation. A timeout may signal failure downstream, but it cannot stop the internal work unless the Future was specifically designed with a cancellation hook.timeout operator itself does not trigger that teardown.Are you retaining the AnyCancellable in a collection (like a Set<AnyCancellable>) or a single variable? If the cancellable is discarded immediately upon the timeout error, the upstream will be cancelled by the deinit of the cancellable rather than by the operator itself.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.