File: TaskRegistry.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (122 lines) | stat: -rw-r--r-- 5,041 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Foundation/URLSession/TaskRegistry.swift - URLSession & libcurl
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
// -----------------------------------------------------------------------------
///
/// These are libcurl helpers for the URLSession API code.
/// - SeeAlso: https://curl.haxx.se/libcurl/c/
/// - SeeAlso: URLSession.swift
///
// -----------------------------------------------------------------------------

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import SwiftFoundation
#else
import Foundation
#endif

import Dispatch

extension URLSession {
    /// This helper class keeps track of all tasks, and their behaviours.
    ///
    /// Each `URLSession` has a `TaskRegistry` for its running tasks. The
    /// *behaviour* defines what action is to be taken e.g. upon completion.
    /// The behaviour stores the completion handler for tasks that are
    /// completion handler based.
    ///
    /// - Note: This must **only** be accessed on the owning session's work queue.
    class _TaskRegistry {
        /// Completion handler for `URLSessionDataTask`, and `URLSessionUploadTask`.
        typealias DataTaskCompletion = @Sendable (Data?, URLResponse?, Error?) -> Void
        /// Completion handler for `URLSessionDownloadTask`.
        typealias DownloadTaskCompletion = @Sendable (URL?, URLResponse?, Error?) -> Void
        /// What to do upon events (such as completion) of a specific task.
        enum _Behaviour {
            /// Call the `URLSession`s delegate
            case callDelegate
            /// Default action for all events, except for completion.
            case dataCompletionHandler(DataTaskCompletion)
            /// Default action for all asynchronous events.
            case dataCompletionHandlerWithTaskDelegate(DataTaskCompletion, URLSessionTaskDelegate?)
            /// Default action for all events, except for completion.
            case downloadCompletionHandler(DownloadTaskCompletion)
            /// Default action for all asynchronous events.
            case downloadCompletionHandlerWithTaskDelegate(DownloadTaskCompletion, URLSessionTaskDelegate?)
        }
        
        fileprivate var tasks: [Int: URLSessionTask] = [:]
        fileprivate var behaviours: [Int: _Behaviour] = [:]
        fileprivate var tasksFinishedCallback: (() -> Void)?
    }
}

extension URLSession._TaskRegistry {
    /// Add a task
    ///
    /// - Note: This must **only** be accessed on the owning session's work queue.
    func add(_ task: URLSessionTask, behaviour: _Behaviour) {
        let identifier = task.taskIdentifier
        guard identifier != 0 else { fatalError("Invalid task identifier") }
        guard tasks.index(forKey: identifier) == nil else {
            if tasks[identifier] === task {
                fatalError("Trying to re-insert a task that's already in the registry.")
            } else {
                fatalError("Trying to insert a task, but a different task with the same identifier is already in the registry.")
            }
        }
        tasks[identifier] = task
        behaviours[identifier] = behaviour
    }
    /// Remove a task
    ///
    /// - Note: This must **only** be accessed on the owning session's work queue.
    func remove(_ task: URLSessionTask) {
        let identifier = task.taskIdentifier
        guard identifier != 0 else { fatalError("Invalid task identifier") }
        guard let tasksIdx = tasks.index(forKey: identifier) else {
            fatalError("Trying to remove task, but it's not in the registry.")
        }
        tasks.remove(at: tasksIdx)
        guard let behaviourIdx = behaviours.index(forKey: identifier) else {
            fatalError("Trying to remove task's behaviour, but it's not in the registry.")
        }
        behaviours.remove(at: behaviourIdx)

        guard let allTasksFinished = tasksFinishedCallback else { return }
        if self.isEmpty {
            allTasksFinished()
        }
    }

    func notify(on tasksCompletion: @escaping () -> Void) {
        tasksFinishedCallback = tasksCompletion
    }

    var isEmpty: Bool {
        return tasks.isEmpty
    }
    
    var allTasks: [URLSessionTask] {
        return tasks.map { $0.value }
    }
}
extension URLSession._TaskRegistry {
    /// The behaviour that's registered for the given task.
    ///
    /// - Note: It is a programming error to pass a task that isn't registered.
    /// - Note: This must **only** be accessed on the owning session's work queue.
    func behaviour(for task: URLSessionTask) -> _Behaviour {
        guard let b = behaviours[task.taskIdentifier] else {
            fatalError("Trying to access a behaviour for a task that in not in the registry.")
        }
        return b
    }
}