Merge pull request #640 from pushkarnk/sr2631

diff --git a/Foundation/NSURLSession/NSURLSession.swift b/Foundation/NSURLSession/NSURLSession.swift
index c5b8df1..87583e0 100644
--- a/Foundation/NSURLSession/NSURLSession.swift
+++ b/Foundation/NSURLSession/NSURLSession.swift
@@ -189,8 +189,9 @@
     /// This queue is used to make public attributes on `URLSessionTask` instances thread safe.
     /// - Note: It's a **concurrent** queue.
     internal let taskAttributesIsolation: DispatchQueue 
-    fileprivate let taskRegistry = URLSession._TaskRegistry()
+    internal let taskRegistry = URLSession._TaskRegistry()
     fileprivate let identifier: Int32
+    fileprivate var invalidated = false
     
     /*
      * The shared session uses the currently set global NSURLCache,
@@ -237,7 +238,7 @@
     }
     
     open let delegateQueue: OperationQueue
-    open let delegate: URLSessionDelegate?
+    open var delegate: URLSessionDelegate?
     open let configuration: URLSessionConfiguration
     
     /*
@@ -258,7 +259,27 @@
      * session with the same identifier until URLSession:didBecomeInvalidWithError: has
      * been issued.
      */
-    open func finishTasksAndInvalidate() { NSUnimplemented() }
+    open func finishTasksAndInvalidate() {
+       //we need to return immediately
+       workQueue.async {
+           //don't allow creation of new tasks from this point onwards
+           self.invalidated = true
+
+           //wait for running tasks to finish
+           if !self.taskRegistry.isEmpty {
+               let tasksCompletion = DispatchSemaphore(value: 0)
+               self.taskRegistry.notify(on: tasksCompletion)
+               tasksCompletion.wait()
+           }
+
+           //invoke the delegate method and break the delegate link
+           guard let sessionDelegate = self.delegate else { return }
+           self.delegateQueue.addOperation {
+               sessionDelegate.urlSession(self, didBecomeInvalidWithError: nil)
+               self.delegate = nil
+           }
+       }
+    }
     
     /* -invalidateAndCancel acts as -finishTasksAndInvalidate, but issues
      * -cancel to all outstanding tasks for this session.  Note task
@@ -367,6 +388,7 @@
     ///
     /// All public methods funnel into this one.
     func dataTask(with request: _Request, behaviour: _TaskRegistry._Behaviour) -> URLSessionDataTask {
+        guard !self.invalidated else { fatalError("Session invalidated") }
         let r = createConfiguredRequest(from: request)
         let i = createNextTaskIdentifier()
         let task = URLSessionDataTask(session: self, request: r, taskIdentifier: i)
@@ -380,6 +402,7 @@
     ///
     /// All public methods funnel into this one.
     func uploadTask(with request: _Request, body: URLSessionTask._Body, behaviour: _TaskRegistry._Behaviour) -> URLSessionUploadTask {
+        guard !self.invalidated else { fatalError("Session invalidated") }
         let r = createConfiguredRequest(from: request)
         let i = createNextTaskIdentifier()
         let task = URLSessionUploadTask(session: self, request: r, taskIdentifier: i, body: body)
@@ -391,6 +414,7 @@
     
     /// Create a download task
     func downloadTask(with request: _Request, behavior: _TaskRegistry._Behaviour) -> URLSessionDownloadTask {
+        guard !self.invalidated else { fatalError("Session invalidated") }
         let r = createConfiguredRequest(from: request)
         let i = createNextTaskIdentifier()
         let task = URLSessionDownloadTask(session: self, request: r, taskIdentifier: i)
diff --git a/Foundation/NSURLSession/NSURLSessionTask.swift b/Foundation/NSURLSession/NSURLSessionTask.swift
index c5bd0e4..f8b9569 100644
--- a/Foundation/NSURLSession/NSURLSessionTask.swift
+++ b/Foundation/NSURLSession/NSURLSessionTask.swift
@@ -29,7 +29,7 @@
     fileprivate var suspendCount = 1
     fileprivate var easyHandle: _EasyHandle!
     fileprivate var totalDownloaded = 0
-    fileprivate unowned let session: URLSessionProtocol
+    fileprivate var session: URLSessionProtocol! //change to nil when task completes
     fileprivate let body: _Body
     fileprivate let tempFileURL: URL
     
@@ -62,6 +62,10 @@
             }
             if case .taskCompleted = internalState {
                 updateTaskState()
+                guard let s = session as? URLSession else { fatalError() }
+                s.workQueue.async {
+                    s.taskRegistry.remove(self)
+                }
             }
         }
     }
@@ -825,16 +829,19 @@
         guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else {
             fatalError("Trying to complete the task, but its transfer isn't complete.")
         }
-        internalState = .taskCompleted
         self.response = response
+
+        //because we deregister the task with the session on internalState being set to taskCompleted
+        //we need to do the latter after the delegate/handler was notified/invoked
         switch session.behaviour(for: self) {
         case .taskDelegate(let delegate):
             guard let s = session as? URLSession else { fatalError() }
             s.delegateQueue.addOperation {
                 delegate.urlSession(s, task: self, didCompleteWithError: nil)
+                self.internalState = .taskCompleted
             }
         case .noDelegate:
-            break
+            internalState = .taskCompleted
         case .dataCompletionHandler(let completion):
             guard case .inMemory(let bodyData) = bodyDataDrain else {
                 fatalError("Task has data completion handler, but data drain is not in-memory.")
@@ -849,6 +856,8 @@
 
             s.delegateQueue.addOperation {
                 completion(data, response, nil)
+                self.internalState = .taskCompleted
+                self.session = nil
             }
         case .downloadCompletionHandler(let completion):
             guard case .toFile(let url, let fileHandle?) = bodyDataDrain else {
@@ -861,6 +870,8 @@
             
             s.delegateQueue.addOperation {
                 completion(url, response, nil) 
+                self.internalState = .taskCompleted
+                self.session = nil
             }
             
         }
@@ -869,24 +880,26 @@
         guard case .transferFailed = internalState else {
             fatalError("Trying to complete the task, but its transfer isn't complete / failed.")
         }
-        internalState = .taskCompleted
         switch session.behaviour(for: self) {
         case .taskDelegate(let delegate):
             guard let s = session as? URLSession else { fatalError() }
             s.delegateQueue.addOperation {
                 delegate.urlSession(s, task: self, didCompleteWithError: error as Error)
+                self.internalState = .taskCompleted
             }
         case .noDelegate:
-            break
+            internalState = .taskCompleted
         case .dataCompletionHandler(let completion):
             guard let s = session as? URLSession else { fatalError() }
             s.delegateQueue.addOperation {
                 completion(nil, nil, error)
+                self.internalState = .taskCompleted
             }
         case .downloadCompletionHandler(let completion): 
             guard let s = session as? URLSession else { fatalError() }
             s.delegateQueue.addOperation {
                 completion(nil, nil, error)
+                self.internalState = .taskCompleted
             }
         }
     }
diff --git a/Foundation/NSURLSession/TaskRegistry.swift b/Foundation/NSURLSession/TaskRegistry.swift
index 2731d78..9b1809c 100644
--- a/Foundation/NSURLSession/TaskRegistry.swift
+++ b/Foundation/NSURLSession/TaskRegistry.swift
@@ -17,7 +17,7 @@
 // -----------------------------------------------------------------------------
 
 import CoreFoundation
-
+import Dispatch
 
 extension URLSession {
     /// This helper class keeps track of all tasks, and their behaviours.
@@ -45,6 +45,7 @@
         
         fileprivate var tasks: [Int: URLSessionTask] = [:]
         fileprivate var behaviours: [Int: _Behaviour] = [:]
+        fileprivate var tasksFinished: DispatchSemaphore?
     }
 }
 
@@ -79,6 +80,19 @@
             fatalError("Trying to remove task's behaviour, but it's not in the registry.")
         }
         behaviours.remove(at: behaviourIdx)
+
+        guard let allTasksFinished = tasksFinished else { return }
+        if self.isEmpty {
+            allTasksFinished.signal()
+        }
+    }
+
+    func notify(on semaphore: DispatchSemaphore) {
+        tasksFinished = semaphore
+    }
+
+    var isEmpty: Bool {
+        return tasks.count == 0
     }
 }
 extension URLSession._TaskRegistry {