Skip to content

Commit 8b34a28

Browse files
authored
Merge pull request #1934 from karthikkeyan/karthik/session-invalidate-cancel
[SR-10428] URLSession: Methods invalidateAndCancel and getAllTasks implemented
2 parents 7d8e523 + c9d1eda commit 8b34a28

File tree

3 files changed

+103
-5
lines changed

3 files changed

+103
-5
lines changed

Foundation/URLSession/TaskRegistry.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ extension URLSession._TaskRegistry {
9494
var isEmpty: Bool {
9595
return tasks.isEmpty
9696
}
97+
98+
var allTasks: [URLSessionTask] {
99+
return tasks.map { $0.value }
100+
}
97101
}
98102
extension URLSession._TaskRegistry {
99103
/// The behaviour that's registered for the given task.

Foundation/URLSession/URLSession.swift

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,15 +316,46 @@ open class URLSession : NSObject {
316316
* cancellation is subject to the state of the task, and some tasks may
317317
* have already have completed at the time they are sent -cancel.
318318
*/
319-
open func invalidateAndCancel() { NSUnimplemented() }
319+
open func invalidateAndCancel() {
320+
/*
321+
As per documentation,
322+
Calling this method on the session returned by the sharedSession method has no effect.
323+
*/
324+
guard self !== URLSession.shared else { return }
325+
326+
workQueue.sync {
327+
self.invalidated = true
328+
}
329+
330+
for task in taskRegistry.allTasks {
331+
task.cancel()
332+
}
333+
334+
// Don't allow creation of new tasks from this point onwards
335+
workQueue.async {
336+
guard let sessionDelegate = self.delegate else { return }
337+
338+
self.delegateQueue.addOperation {
339+
sessionDelegate.urlSession(self, didBecomeInvalidWithError: nil)
340+
self.delegate = nil
341+
}
342+
}
343+
}
320344

321345
open func reset(completionHandler: @escaping () -> Void) { NSUnimplemented() } /* empty all cookies, cache and credential stores, removes disk files, issues -flushWithCompletionHandler:. Invokes completionHandler() on the delegate queue if not nil. */
322346

323347
open func flush(completionHandler: @escaping () -> Void) { NSUnimplemented() }/* flush storage to disk and clear transient network caches. Invokes completionHandler() on the delegate queue if not nil. */
324348

325349
open func getTasksWithCompletionHandler(completionHandler: @escaping ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) { NSUnimplemented() }/* invokes completionHandler with outstanding data, upload and download tasks. */
326350

327-
open func getAllTasks(completionHandler: @escaping ([URLSessionTask]) -> Void) { NSUnimplemented() }/* invokes completionHandler with all outstanding tasks. */
351+
/* invokes completionHandler with all outstanding tasks. */
352+
open func getAllTasks(completionHandler: @escaping ([URLSessionTask]) -> Void) {
353+
workQueue.async {
354+
self.delegateQueue.addOperation {
355+
completionHandler(self.taskRegistry.allTasks.filter { $0.state == .running || $0.state == .suspended })
356+
}
357+
}
358+
}
328359

329360
/*
330361
* URLSessionTask objects are always created in a suspended state and

TestFoundation/TestURLSession.swift

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class TestURLSession : LoopbackServerTest {
4848
("test_redirectionWithSetCookies", test_redirectionWithSetCookies),
4949
("test_postWithEmptyBody", test_postWithEmptyBody),
5050
("test_basicAuthWithUnauthorizedHeader", test_basicAuthWithUnauthorizedHeader),
51+
("test_checkErrorTypeAfterInvalidateAndCancel", test_checkErrorTypeAfterInvalidateAndCancel),
52+
("test_taskCountAfterInvalidateAndCancel", test_taskCountAfterInvalidateAndCancel),
53+
("test_sessionDelegateAfterInvalidateAndCancel", test_sessionDelegateAfterInvalidateAndCancel),
5154
]
5255
}
5356

@@ -773,6 +776,62 @@ class TestURLSession : LoopbackServerTest {
773776
task.resume()
774777
waitForExpectations(timeout: 12, handler: nil)
775778
}
779+
780+
func test_checkErrorTypeAfterInvalidateAndCancel() {
781+
let urlString = "https://developer.apple.com"
782+
let expect = expectation(description: "Check error code of tasks after invalidateAndCancel")
783+
let delegate = SessionDelegate()
784+
let url = URL(string: urlString)!
785+
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
786+
let task = session.dataTask(with: url) { (_, _, error) in
787+
XCTAssertNotNil(error as? URLError)
788+
if let urlError = error as? URLError {
789+
XCTAssertEqual(urlError._nsError.code, NSURLErrorCancelled)
790+
}
791+
792+
expect.fulfill()
793+
}
794+
task.resume()
795+
session.invalidateAndCancel()
796+
waitForExpectations(timeout: 5)
797+
}
798+
799+
func test_taskCountAfterInvalidateAndCancel() {
800+
let expect = expectation(description: "Check task count after invalidateAndCancel")
801+
802+
let session = URLSession(configuration: .default)
803+
let task1 = session.dataTask(with: URL(string: "https://www.apple.com")!)
804+
let task2 = session.dataTask(with: URL(string: "https://developer.apple.com")!)
805+
let task3 = session.dataTask(with: URL(string: "https://developer.apple.com/swift")!)
806+
807+
task1.resume()
808+
task2.resume()
809+
session.invalidateAndCancel()
810+
sleep(1)
811+
812+
session.getAllTasks { tasksBeforeResume in
813+
XCTAssertEqual(tasksBeforeResume.count, 0)
814+
815+
// Resume a task after invalidating a session shouldn't change the task's status
816+
task3.resume()
817+
818+
session.getAllTasks { tasksAfterResume in
819+
XCTAssertEqual(tasksAfterResume.count, 0)
820+
expect.fulfill()
821+
}
822+
}
823+
824+
waitForExpectations(timeout: 5)
825+
}
826+
827+
func test_sessionDelegateAfterInvalidateAndCancel() {
828+
let delegate = SessionDelegate()
829+
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
830+
session.invalidateAndCancel()
831+
sleep(2)
832+
XCTAssertNil(session.delegate)
833+
}
834+
776835
}
777836

778837
class SharedDelegate: NSObject {
@@ -792,12 +851,16 @@ extension SharedDelegate: URLSessionDownloadDelegate {
792851

793852

794853
class SessionDelegate: NSObject, URLSessionDelegate {
795-
let invalidateExpectation: XCTestExpectation
796-
init(invalidateExpectation: XCTestExpectation){
854+
let invalidateExpectation: XCTestExpectation?
855+
override init() {
856+
invalidateExpectation = nil
857+
super.init()
858+
}
859+
init(invalidateExpectation: XCTestExpectation) {
797860
self.invalidateExpectation = invalidateExpectation
798861
}
799862
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
800-
invalidateExpectation.fulfill()
863+
invalidateExpectation?.fulfill()
801864
}
802865
}
803866

0 commit comments

Comments
 (0)