Skip to content

Commit 9351942

Browse files
author
Pushkar Kulkarni
committed
Fix for a race condition in URLSession
1 parent a2f4eb6 commit 9351942

File tree

1 file changed

+37
-19
lines changed

1 file changed

+37
-19
lines changed

Foundation/NSURLSession/MultiHandle.swift

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ extension URLSession {
3939
let group = DispatchGroup()
4040
fileprivate var easyHandles: [_EasyHandle] = []
4141
fileprivate var timeoutSource: _TimeoutSource? = nil
42-
42+
43+
//SR-4567: we need to synchronize the register/unregister commands to the epoll machinery in libdispatch
44+
fileprivate let commandQueue: DispatchQueue = DispatchQueue(label: "Register-unregister synchronization")
45+
fileprivate var cancelInProgress: DispatchSemaphore? = nil
46+
4347
init(configuration: URLSession._Configuration, workQueue: DispatchQueue) {
4448
queue = DispatchQueue(label: "MultiHandle.isolation", target: workQueue)
4549
setupCallbacks()
@@ -99,25 +103,33 @@ fileprivate extension URLSession._MultiHandle {
99103
// through libdispatch (DispatchSource) and store the source(s) inside
100104
// a `SocketSources` which we in turn store inside libcurl's multi handle
101105
// by means of curl_multi_assign() -- we retain the object fist.
102-
let action = _SocketRegisterAction(rawValue: CFURLSessionPoll(value: what))
103-
var socketSources = _SocketSources.from(socketSourcePtr: socketSourcePtr)
104-
if socketSources == nil && action.needsSource {
105-
let s = _SocketSources()
106-
let p = Unmanaged.passRetained(s).toOpaque()
107-
CFURLSessionMultiHandleAssign(rawHandle, socket, UnsafeMutableRawPointer(p))
108-
socketSources = s
109-
} else if socketSources != nil && action == .unregister {
110-
// We need to release the stored pointer:
111-
if let opaque = socketSourcePtr {
112-
Unmanaged<_SocketSources>.fromOpaque(opaque).release()
106+
commandQueue.async {
107+
self.cancelInProgress?.wait()
108+
self.cancelInProgress = nil
109+
110+
let action = _SocketRegisterAction(rawValue: CFURLSessionPoll(value: what))
111+
var socketSources = _SocketSources.from(socketSourcePtr: socketSourcePtr)
112+
if socketSources == nil && action.needsSource {
113+
let s = _SocketSources()
114+
let p = Unmanaged.passRetained(s).toOpaque()
115+
CFURLSessionMultiHandleAssign(self.rawHandle, socket, UnsafeMutableRawPointer(p))
116+
socketSources = s
117+
} else if socketSources != nil && action == .unregister {
118+
//the beginning of an unregister operation
119+
self.cancelInProgress = DispatchSemaphore(value: 0)
120+
// We need to release the stored pointer:
121+
if let opaque = socketSourcePtr {
122+
Unmanaged<_SocketSources>.fromOpaque(opaque).release()
123+
}
124+
socketSources?.tearDown(self.cancelInProgress)
125+
socketSources = nil
113126
}
114-
socketSources = nil
115-
}
116-
if let ss = socketSources {
117-
let handler = DispatchWorkItem { [weak self] in
118-
self?.performAction(for: socket)
127+
if let ss = socketSources {
128+
let handler = DispatchWorkItem { [weak self] in
129+
self?.performAction(for: socket)
130+
}
131+
ss.createSources(with: action, fileDescriptor: Int(socket), queue: self.queue, handler: handler)
119132
}
120-
ss.createSources(with: action, fileDescriptor: Int(socket), queue: queue, handler: handler)
121133
}
122134
return 0
123135
}
@@ -398,12 +410,18 @@ fileprivate class _SocketSources {
398410
s.resume()
399411
}
400412

401-
func tearDown() {
413+
func tearDown(_ cancelInProgress: DispatchSemaphore?) {
414+
let cancelHandler = DispatchWorkItem {
415+
//the real end of an unregister operation!
416+
cancelInProgress?.signal()
417+
}
402418
if let s = readSource {
419+
s.setCancelHandler(handler: cancelHandler)
403420
s.cancel()
404421
}
405422
readSource = nil
406423
if let s = writeSource {
424+
s.setCancelHandler(handler: cancelHandler)
407425
s.cancel()
408426
}
409427
writeSource = nil

0 commit comments

Comments
 (0)