URLSession: Ensure thread safety when a task is canceled.
This PR ensures thread safety when a URLSession.data(for:) task is canceled.
Issue
- Thread 1 calls withTaskCancellationHandler(operation:onCancel:).
- Thread 2 invokes Task.cancel(), so the
onCancelblock runs immediately on Thread 2. (swift_task_cancelImpl) - At the same moment, the
operationblock executes on Thread 1. - Both threads access
var dataTask: URLSessionDataTask?simultaneously.
Fix
Access to dataTask is now guarded by a lock, guaranteeing the expected behavior even when cancellation happens concurrently.
I might suggest the use of UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1) (rather than os_unfair_lock()) to allocate the memory for the unfair lock. (See this Stack Overflow answer.) This is the pattern used in stdlib.
Nowadays, you’d use OSAllocatedUnfairLock, which does this for you. Or Mutex. But given that this is for backward support for old OS versions, those probably aren’t options.
Other options include just falling back to NSLock. Or wrap the the data session task in an actor.