Skip to content

Commit f0a9fe4

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

File tree

1 file changed

+36
-19
lines changed

1 file changed

+36
-19
lines changed

Foundation/NSURLSession/MultiHandle.swift

Lines changed: 36 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,32 @@ 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+
self.cancelInProgress = DispatchSemaphore(value: 0)
119+
// We need to release the stored pointer:
120+
if let opaque = socketSourcePtr {
121+
Unmanaged<_SocketSources>.fromOpaque(opaque).release()
122+
}
123+
socketSources?.tearDown(self.cancelInProgress)
124+
socketSources = nil
113125
}
114-
socketSources = nil
115-
}
116-
if let ss = socketSources {
117-
let handler = DispatchWorkItem { [weak self] in
118-
self?.performAction(for: socket)
126+
if let ss = socketSources {
127+
let handler = DispatchWorkItem { [weak self] in
128+
self?.performAction(for: socket)
129+
}
130+
ss.createSources(with: action, fileDescriptor: Int(socket), queue: self.queue, handler: handler)
119131
}
120-
ss.createSources(with: action, fileDescriptor: Int(socket), queue: queue, handler: handler)
121132
}
122133
return 0
123134
}
@@ -398,12 +409,18 @@ fileprivate class _SocketSources {
398409
s.resume()
399410
}
400411

401-
func tearDown() {
412+
func tearDown(_ cancelInProgress: DispatchSemaphore?) {
413+
let cancelHandler = DispatchWorkItem {
414+
//the real end of an unregister operation!
415+
cancelInProgress?.signal()
416+
}
402417
if let s = readSource {
418+
s.setCancelHandler(handler: cancelHandler)
403419
s.cancel()
404420
}
405421
readSource = nil
406422
if let s = writeSource {
423+
s.setCancelHandler(handler: cancelHandler)
407424
s.cancel()
408425
}
409426
writeSource = nil

0 commit comments

Comments
 (0)