AsyncCompatibilityKit icon indicating copy to clipboard operation
AsyncCompatibilityKit copied to clipboard

URLSession: Ensure thread safety when a task is canceled.

Open pookjw opened this issue 8 months ago • 1 comments

This PR ensures thread safety when a URLSession.data(for:) task is canceled.

Issue

  1. Thread 1 calls withTaskCancellationHandler(operation:onCancel:).
  2. Thread 2 invokes Task.cancel(), so the onCancel block runs immediately on Thread 2. (swift_task_cancelImpl)
  3. At the same moment, the operation block executes on Thread 1.
  4. 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.

pookjw avatar May 17 '25 12:05 pookjw

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.

robertmryan avatar Aug 05 '25 20:08 robertmryan