Skip to content

Commit db54d31

Browse files
authored
Merge pull request #640 from pushkarnk/sr2631
2 parents 2d80441 + 6809054 commit db54d31

File tree

3 files changed

+60
-9
lines changed

3 files changed

+60
-9
lines changed

Foundation/NSURLSession/NSURLSession.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ open class URLSession : NSObject {
189189
/// This queue is used to make public attributes on `URLSessionTask` instances thread safe.
190190
/// - Note: It's a **concurrent** queue.
191191
internal let taskAttributesIsolation: DispatchQueue
192-
fileprivate let taskRegistry = URLSession._TaskRegistry()
192+
internal let taskRegistry = URLSession._TaskRegistry()
193193
fileprivate let identifier: Int32
194+
fileprivate var invalidated = false
194195

195196
/*
196197
* The shared session uses the currently set global NSURLCache,
@@ -237,7 +238,7 @@ open class URLSession : NSObject {
237238
}
238239

239240
open let delegateQueue: OperationQueue
240-
open let delegate: URLSessionDelegate?
241+
open var delegate: URLSessionDelegate?
241242
open let configuration: URLSessionConfiguration
242243

243244
/*
@@ -258,7 +259,27 @@ open class URLSession : NSObject {
258259
* session with the same identifier until URLSession:didBecomeInvalidWithError: has
259260
* been issued.
260261
*/
261-
open func finishTasksAndInvalidate() { NSUnimplemented() }
262+
open func finishTasksAndInvalidate() {
263+
//we need to return immediately
264+
workQueue.async {
265+
//don't allow creation of new tasks from this point onwards
266+
self.invalidated = true
267+
268+
//wait for running tasks to finish
269+
if !self.taskRegistry.isEmpty {
270+
let tasksCompletion = DispatchSemaphore(value: 0)
271+
self.taskRegistry.notify(on: tasksCompletion)
272+
tasksCompletion.wait()
273+
}
274+
275+
//invoke the delegate method and break the delegate link
276+
guard let sessionDelegate = self.delegate else { return }
277+
self.delegateQueue.addOperation {
278+
sessionDelegate.urlSession(self, didBecomeInvalidWithError: nil)
279+
self.delegate = nil
280+
}
281+
}
282+
}
262283

263284
/* -invalidateAndCancel acts as -finishTasksAndInvalidate, but issues
264285
* -cancel to all outstanding tasks for this session. Note task
@@ -367,6 +388,7 @@ fileprivate extension URLSession {
367388
///
368389
/// All public methods funnel into this one.
369390
func dataTask(with request: _Request, behaviour: _TaskRegistry._Behaviour) -> URLSessionDataTask {
391+
guard !self.invalidated else { fatalError("Session invalidated") }
370392
let r = createConfiguredRequest(from: request)
371393
let i = createNextTaskIdentifier()
372394
let task = URLSessionDataTask(session: self, request: r, taskIdentifier: i)
@@ -380,6 +402,7 @@ fileprivate extension URLSession {
380402
///
381403
/// All public methods funnel into this one.
382404
func uploadTask(with request: _Request, body: URLSessionTask._Body, behaviour: _TaskRegistry._Behaviour) -> URLSessionUploadTask {
405+
guard !self.invalidated else { fatalError("Session invalidated") }
383406
let r = createConfiguredRequest(from: request)
384407
let i = createNextTaskIdentifier()
385408
let task = URLSessionUploadTask(session: self, request: r, taskIdentifier: i, body: body)
@@ -391,6 +414,7 @@ fileprivate extension URLSession {
391414

392415
/// Create a download task
393416
func downloadTask(with request: _Request, behavior: _TaskRegistry._Behaviour) -> URLSessionDownloadTask {
417+
guard !self.invalidated else { fatalError("Session invalidated") }
394418
let r = createConfiguredRequest(from: request)
395419
let i = createNextTaskIdentifier()
396420
let task = URLSessionDownloadTask(session: self, request: r, taskIdentifier: i)

Foundation/NSURLSession/NSURLSessionTask.swift

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ open class URLSessionTask : NSObject, NSCopying {
2929
fileprivate var suspendCount = 1
3030
fileprivate var easyHandle: _EasyHandle!
3131
fileprivate var totalDownloaded = 0
32-
fileprivate unowned let session: URLSessionProtocol
32+
fileprivate var session: URLSessionProtocol! //change to nil when task completes
3333
fileprivate let body: _Body
3434
fileprivate let tempFileURL: URL
3535

@@ -62,6 +62,10 @@ open class URLSessionTask : NSObject, NSCopying {
6262
}
6363
if case .taskCompleted = internalState {
6464
updateTaskState()
65+
guard let s = session as? URLSession else { fatalError() }
66+
s.workQueue.async {
67+
s.taskRegistry.remove(self)
68+
}
6569
}
6670
}
6771
}
@@ -825,16 +829,19 @@ extension URLSessionTask {
825829
guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else {
826830
fatalError("Trying to complete the task, but its transfer isn't complete.")
827831
}
828-
internalState = .taskCompleted
829832
self.response = response
833+
834+
//because we deregister the task with the session on internalState being set to taskCompleted
835+
//we need to do the latter after the delegate/handler was notified/invoked
830836
switch session.behaviour(for: self) {
831837
case .taskDelegate(let delegate):
832838
guard let s = session as? URLSession else { fatalError() }
833839
s.delegateQueue.addOperation {
834840
delegate.urlSession(s, task: self, didCompleteWithError: nil)
841+
self.internalState = .taskCompleted
835842
}
836843
case .noDelegate:
837-
break
844+
internalState = .taskCompleted
838845
case .dataCompletionHandler(let completion):
839846
guard case .inMemory(let bodyData) = bodyDataDrain else {
840847
fatalError("Task has data completion handler, but data drain is not in-memory.")
@@ -849,6 +856,8 @@ extension URLSessionTask {
849856

850857
s.delegateQueue.addOperation {
851858
completion(data, response, nil)
859+
self.internalState = .taskCompleted
860+
self.session = nil
852861
}
853862
case .downloadCompletionHandler(let completion):
854863
guard case .toFile(let url, let fileHandle?) = bodyDataDrain else {
@@ -861,6 +870,8 @@ extension URLSessionTask {
861870

862871
s.delegateQueue.addOperation {
863872
completion(url, response, nil)
873+
self.internalState = .taskCompleted
874+
self.session = nil
864875
}
865876

866877
}
@@ -869,24 +880,26 @@ extension URLSessionTask {
869880
guard case .transferFailed = internalState else {
870881
fatalError("Trying to complete the task, but its transfer isn't complete / failed.")
871882
}
872-
internalState = .taskCompleted
873883
switch session.behaviour(for: self) {
874884
case .taskDelegate(let delegate):
875885
guard let s = session as? URLSession else { fatalError() }
876886
s.delegateQueue.addOperation {
877887
delegate.urlSession(s, task: self, didCompleteWithError: error as Error)
888+
self.internalState = .taskCompleted
878889
}
879890
case .noDelegate:
880-
break
891+
internalState = .taskCompleted
881892
case .dataCompletionHandler(let completion):
882893
guard let s = session as? URLSession else { fatalError() }
883894
s.delegateQueue.addOperation {
884895
completion(nil, nil, error)
896+
self.internalState = .taskCompleted
885897
}
886898
case .downloadCompletionHandler(let completion):
887899
guard let s = session as? URLSession else { fatalError() }
888900
s.delegateQueue.addOperation {
889901
completion(nil, nil, error)
902+
self.internalState = .taskCompleted
890903
}
891904
}
892905
}

Foundation/NSURLSession/TaskRegistry.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// -----------------------------------------------------------------------------
1818

1919
import CoreFoundation
20-
20+
import Dispatch
2121

2222
extension URLSession {
2323
/// This helper class keeps track of all tasks, and their behaviours.
@@ -45,6 +45,7 @@ extension URLSession {
4545

4646
fileprivate var tasks: [Int: URLSessionTask] = [:]
4747
fileprivate var behaviours: [Int: _Behaviour] = [:]
48+
fileprivate var tasksFinished: DispatchSemaphore?
4849
}
4950
}
5051

@@ -79,6 +80,19 @@ extension URLSession._TaskRegistry {
7980
fatalError("Trying to remove task's behaviour, but it's not in the registry.")
8081
}
8182
behaviours.remove(at: behaviourIdx)
83+
84+
guard let allTasksFinished = tasksFinished else { return }
85+
if self.isEmpty {
86+
allTasksFinished.signal()
87+
}
88+
}
89+
90+
func notify(on semaphore: DispatchSemaphore) {
91+
tasksFinished = semaphore
92+
}
93+
94+
var isEmpty: Bool {
95+
return tasks.count == 0
8296
}
8397
}
8498
extension URLSession._TaskRegistry {

0 commit comments

Comments
 (0)