Skip to content

Commit 7432349

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

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()
@@ -72,6 +76,7 @@ fileprivate extension URLSession._MultiHandle {
7276
}
7377
}
7478

79+
7580
fileprivate extension URLSession._MultiHandle {
7681
/// Forward the libcurl callbacks into Swift methods
7782
func setupCallbacks() {
@@ -99,25 +104,32 @@ fileprivate extension URLSession._MultiHandle {
99104
// through libdispatch (DispatchSource) and store the source(s) inside
100105
// a `SocketSources` which we in turn store inside libcurl's multi handle
101106
// 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()
107+
commandQueue.async {
108+
self.cancelInProgress?.wait()
109+
self.cancelInProgress = nil
110+
111+
let action = _SocketRegisterAction(rawValue: CFURLSessionPoll(value: what))
112+
var socketSources = _SocketSources.from(socketSourcePtr: socketSourcePtr)
113+
if socketSources == nil && action.needsSource {
114+
let s = _SocketSources()
115+
let p = Unmanaged.passRetained(s).toOpaque()
116+
CFURLSessionMultiHandleAssign(self.rawHandle, socket, UnsafeMutableRawPointer(p))
117+
socketSources = s
118+
} else if socketSources != nil && action == .unregister {
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,8 +410,13 @@ fileprivate class _SocketSources {
398410
s.resume()
399411
}
400412

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

0 commit comments

Comments
 (0)