|
6 | 6 | // Copyright © 2020 Alex Belozierov. All rights reserved.
|
7 | 7 | //
|
8 | 8 |
|
9 |
| -import Dispatch |
10 |
| - |
11 | 9 | internal final class SharedCoroutineDispatcher: CoroutineTaskExecutor {
|
12 | 10 |
|
13 | 11 | internal struct Task {
|
14 | 12 | let scheduler: CoroutineScheduler, task: () -> Void
|
15 | 13 | }
|
16 | 14 |
|
17 |
| - private let mutex = PsxLock() |
18 |
| - private let stackSize: Int |
19 |
| - private var tasks = FifoQueue<Task>() |
20 |
| - |
21 |
| - private var contextsCount: Int |
22 |
| - private var freeQueues = [SharedCoroutineQueue]() |
23 |
| - private var suspendedQueues = Set<SharedCoroutineQueue>() |
24 |
| - private var freeCount: AtomicInt |
| 15 | + private let queuesCount: Int |
| 16 | + private let queues: UnsafeMutablePointer<SharedCoroutineQueue> |
| 17 | + private var freeQueuesMask = AtomicBitMask() |
| 18 | + private var suspendedQueuesMask = AtomicBitMask() |
| 19 | + private var tasks = ThreadSafeFifoQueues<Task>() |
25 | 20 |
|
26 | 21 | internal init(contextsCount: Int, stackSize: Int) {
|
27 |
| - self.stackSize = stackSize |
28 |
| - self.contextsCount = contextsCount |
29 |
| - freeCount = AtomicInt(value: contextsCount) |
30 |
| - freeQueues.reserveCapacity(contextsCount) |
31 |
| - suspendedQueues.reserveCapacity(contextsCount) |
32 |
| - startDispatchSource() |
| 22 | + queuesCount = min(contextsCount, 63) |
| 23 | + queues = .allocate(capacity: queuesCount) |
| 24 | + (0..<queuesCount).forEach { |
| 25 | + freeQueuesMask.insert($0) |
| 26 | + (queues + $0).initialize(to: .init(tag: $0, stackSize: stackSize)) |
| 27 | + } |
33 | 28 | }
|
34 | 29 |
|
35 |
| - // MARK: - Start |
| 30 | + // MARK: - Free |
36 | 31 |
|
37 |
| - internal func execute(on scheduler: CoroutineScheduler, task: @escaping () -> Void) { |
38 |
| - func perform() { |
39 |
| - freeCount.update { max(0, $0 - 1) } |
40 |
| - mutex.lock() |
41 |
| - if let queue = freeQueue { |
42 |
| - mutex.unlock() |
43 |
| - queue.start(dispatcher: self, task: .init(scheduler: scheduler, task: task)) |
44 |
| - } else { |
45 |
| - tasks.push(.init(scheduler: scheduler, task: task)) |
46 |
| - mutex.unlock() |
47 |
| - } |
48 |
| - } |
49 |
| - if freeCount.value == 0 { |
50 |
| - mutex.lock() |
51 |
| - defer { mutex.unlock() } |
52 |
| - if freeCount.value == 0 { |
53 |
| - return tasks.push(.init(scheduler: scheduler, task: task)) |
54 |
| - } |
55 |
| - } |
56 |
| - scheduler.scheduleTask(perform) |
| 32 | + private var hasFree: Bool { |
| 33 | + !freeQueuesMask.isEmpty || !suspendedQueuesMask.isEmpty |
57 | 34 | }
|
58 | 35 |
|
59 | 36 | private var freeQueue: SharedCoroutineQueue? {
|
60 |
| - if let queue = freeQueues.popLast() { return queue } |
61 |
| - if contextsCount > 0 { |
62 |
| - contextsCount -= 1 |
63 |
| - return SharedCoroutineQueue(stackSize: stackSize) |
64 |
| - } else if suspendedQueues.count < 2 { |
65 |
| - return suspendedQueues.popFirst() |
| 37 | + if !freeQueuesMask.isEmpty, let index = freeQueuesMask.pop() { return queues[index] } |
| 38 | + if !suspendedQueuesMask.isEmpty, let index = suspendedQueuesMask |
| 39 | + .pop(offset: suspendedQueuesMask.rawValue % queuesCount) { |
| 40 | + return queues[index] |
66 | 41 | }
|
67 |
| - var min = suspendedQueues.first! |
68 |
| - for queue in suspendedQueues { |
69 |
| - if queue.started == 1 { |
70 |
| - return suspendedQueues.remove(queue) |
71 |
| - } else if queue.started < min.started { |
72 |
| - min = queue |
73 |
| - } |
74 |
| - } |
75 |
| - return suspendedQueues.remove(min) |
| 42 | + return nil |
76 | 43 | }
|
77 | 44 |
|
78 |
| - // MARK: - Resume |
79 |
| - |
80 |
| - internal func resume(_ coroutine: SharedCoroutine) { |
81 |
| - mutex.lock() |
82 |
| - if suspendedQueues.remove(coroutine.queue) == nil { |
83 |
| - coroutine.queue.push(coroutine) |
84 |
| - mutex.unlock() |
85 |
| - } else { |
86 |
| - mutex.unlock() |
87 |
| - freeCount.decrease() |
88 |
| - coroutine.scheduler.scheduleTask { |
89 |
| - coroutine.queue.resume(coroutine: coroutine) |
90 |
| - } |
91 |
| - } |
| 45 | + private func pushTask(_ task: Task) { |
| 46 | + tasks.push(task) |
| 47 | + if hasFree { tasks.pop().map(startTask) } |
92 | 48 | }
|
93 | 49 |
|
94 |
| - // MARK: - Next |
| 50 | + // MARK: - Start |
95 | 51 |
|
96 |
| - internal func performNext(for queue: SharedCoroutineQueue) { |
97 |
| - mutex.lock() |
98 |
| - if let coroutine = queue.pop() { |
99 |
| - mutex.unlock() |
100 |
| - coroutine.scheduler.scheduleTask { |
101 |
| - queue.resume(coroutine: coroutine) |
102 |
| - } |
103 |
| - } else if let task = tasks.pop() { |
104 |
| - mutex.unlock() |
105 |
| - task.scheduler.scheduleTask { |
| 52 | + internal func execute(on scheduler: CoroutineScheduler, task: @escaping () -> Void) { |
| 53 | + hasFree |
| 54 | + ? startTask(.init(scheduler: scheduler, task: task)) |
| 55 | + : pushTask(.init(scheduler: scheduler, task: task)) |
| 56 | + } |
| 57 | + |
| 58 | + private func startTask(_ task: Task) { |
| 59 | + task.scheduler.scheduleTask { |
| 60 | + if let queue = self.freeQueue { |
106 | 61 | queue.start(dispatcher: self, task: task)
|
107 |
| - } |
108 |
| - } else { |
109 |
| - if queue.started == 0 { |
110 |
| - freeQueues.append(queue) |
111 | 62 | } else {
|
112 |
| - suspendedQueues.insert(queue) |
| 63 | + self.pushTask(task) |
113 | 64 | }
|
114 |
| - freeCount.increase() |
115 |
| - mutex.unlock() |
116 | 65 | }
|
117 | 66 | }
|
118 | 67 |
|
119 |
| - // MARK: - DispatchSourceMemoryPressure |
120 |
| - |
121 |
| - #if os(Linux) |
122 |
| - |
123 |
| - private func startDispatchSource() {} |
124 |
| - |
125 |
| - #else |
126 |
| - |
127 |
| - private lazy var memoryPressureSource: DispatchSourceMemoryPressure = { |
128 |
| - let source = DispatchSource.makeMemoryPressureSource(eventMask: [.warning, .critical]) |
129 |
| - source.setEventHandler { [unowned self] in self.reset() } |
130 |
| - return source |
131 |
| - }() |
| 68 | + // MARK: - Resume |
132 | 69 |
|
133 |
| - private func startDispatchSource() { |
134 |
| - if #available(OSX 10.12, iOS 10.0, *) { |
135 |
| - memoryPressureSource.activate() |
| 70 | + internal func resume(_ coroutine: SharedCoroutine) { |
| 71 | + coroutine.queue.mutex.lock() |
| 72 | + if suspendedQueuesMask.remove(coroutine.queue.tag) { |
| 73 | + coroutine.queue.mutex.unlock() |
| 74 | + coroutine.resumeOnQueue() |
136 | 75 | } else {
|
137 |
| - memoryPressureSource.resume() |
| 76 | + coroutine.queue.prepared.push(coroutine) |
| 77 | + coroutine.queue.mutex.unlock() |
138 | 78 | }
|
139 | 79 | }
|
140 | 80 |
|
141 |
| - #endif |
| 81 | + // MARK: - Next |
142 | 82 |
|
143 |
| - internal func reset() { |
144 |
| - mutex.lock() |
145 |
| - contextsCount += freeQueues.count |
146 |
| - freeCount.add(freeQueues.count) |
147 |
| - freeQueues.removeAll(keepingCapacity: true) |
148 |
| - mutex.unlock() |
| 83 | + internal func performNext(for queue: SharedCoroutineQueue) { |
| 84 | + queue.mutex.lock() |
| 85 | + if let coroutine = queue.prepared.pop() { |
| 86 | + queue.mutex.unlock() |
| 87 | + coroutine.resumeOnQueue() |
| 88 | + } else { |
| 89 | + queue.started == 0 |
| 90 | + ? freeQueuesMask.insert(queue.tag) |
| 91 | + : suspendedQueuesMask.insert(queue.tag) |
| 92 | + queue.mutex.unlock() |
| 93 | + if hasFree { tasks.pop().map(startTask) } |
| 94 | + } |
149 | 95 | }
|
150 | 96 |
|
151 | 97 | deinit {
|
152 |
| - mutex.free() |
| 98 | + queues.deinitialize(count: queuesCount) |
| 99 | + queues.deallocate() |
153 | 100 | }
|
154 | 101 |
|
155 | 102 | }
|
156 | 103 |
|
| 104 | +extension SharedCoroutine { |
| 105 | + |
| 106 | + fileprivate func resumeOnQueue() { |
| 107 | + scheduler.scheduleTask { self.queue.resume(coroutine: self) } |
| 108 | + } |
| 109 | + |
| 110 | +} |
0 commit comments