Skip to content

Commit 02aa7b7

Browse files
authored
Merge pull request #969 from pushkarnk/urlsession-race-cond-3.1
2 parents 737905c + 9b3df62 commit 02aa7b7

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
@@ -40,7 +40,11 @@ extension URLSession {
4040
let group = DispatchGroup()
4141
fileprivate var easyHandles: [_EasyHandle] = []
4242
fileprivate var timeoutSource: _TimeoutSource? = nil
43-
43+
44+
//SR-4567: we need to synchronize the register/unregister commands to the epoll machinery in libdispatch
45+
fileprivate let commandQueue: DispatchQueue = DispatchQueue(label: "Register-unregister synchronization")
46+
fileprivate var cancelInProgress: DispatchSemaphore? = nil
47+
4448
init(configuration: URLSession._Configuration, workQueue: DispatchQueue) {
4549
//queue.setTarget(queue: workQueue)
4650
queue = DispatchQueue(label: "MultiHandle.isolation", target: workQueue)
@@ -101,25 +105,33 @@ fileprivate extension URLSession._MultiHandle {
101105
// through libdispatch (DispatchSource) and store the source(s) inside
102106
// a `SocketSources` which we in turn store inside libcurl's multi handle
103107
// by means of curl_multi_assign() -- we retain the object fist.
104-
let action = _SocketRegisterAction(rawValue: CFURLSessionPoll(value: what))
105-
var socketSources = _SocketSources.from(socketSourcePtr: socketSourcePtr)
106-
if socketSources == nil && action.needsSource {
107-
let s = _SocketSources()
108-
let p = Unmanaged.passRetained(s).toOpaque()
109-
CFURLSessionMultiHandleAssign(rawHandle, socket, UnsafeMutableRawPointer(p))
110-
socketSources = s
111-
} else if socketSources != nil && action == .unregister {
112-
// We need to release the stored pointer:
113-
if let opaque = socketSourcePtr {
114-
Unmanaged<_SocketSources>.fromOpaque(opaque).release()
108+
commandQueue.async {
109+
self.cancelInProgress?.wait()
110+
self.cancelInProgress = nil
111+
112+
let action = _SocketRegisterAction(rawValue: CFURLSessionPoll(value: what))
113+
var socketSources = _SocketSources.from(socketSourcePtr: socketSourcePtr)
114+
if socketSources == nil && action.needsSource {
115+
let s = _SocketSources()
116+
let p = Unmanaged.passRetained(s).toOpaque()
117+
CFURLSessionMultiHandleAssign(self.rawHandle, socket, UnsafeMutableRawPointer(p))
118+
socketSources = s
119+
} else if socketSources != nil && action == .unregister {
120+
//the beginning of an unregister operation
121+
self.cancelInProgress = DispatchSemaphore(value: 0)
122+
// We need to release the stored pointer:
123+
if let opaque = socketSourcePtr {
124+
Unmanaged<_SocketSources>.fromOpaque(opaque).release()
125+
}
126+
socketSources?.tearDown(self.cancelInProgress)
127+
socketSources = nil
115128
}
116-
socketSources = nil
117-
}
118-
if let ss = socketSources {
119-
let handler = DispatchWorkItem { [weak self] in
120-
self?.performAction(for: socket)
129+
if let ss = socketSources {
130+
let handler = DispatchWorkItem { [weak self] in
131+
self?.performAction(for: socket)
132+
}
133+
ss.createSources(with: action, fileDescriptor: Int(socket), queue: self.queue, handler: handler)
121134
}
122-
ss.createSources(with: action, fileDescriptor: Int(socket), queue: queue, handler: handler)
123135
}
124136
return 0
125137
}
@@ -397,12 +409,18 @@ fileprivate class _SocketSources {
397409
s.resume()
398410
}
399411

400-
func tearDown() {
412+
func tearDown(_ cancelInProgress: DispatchSemaphore?) {
413+
let cancelHandler = DispatchWorkItem {
414+
//the real end of an unregister operation!
415+
cancelInProgress?.signal()
416+
}
401417
if let s = readSource {
418+
s.setCancelHandler(handler: cancelHandler)
402419
s.cancel()
403420
}
404421
readSource = nil
405422
if let s = writeSource {
423+
s.setCancelHandler(handler: cancelHandler)
406424
s.cancel()
407425
}
408426
writeSource = nil

0 commit comments

Comments
 (0)