Repository navigation
Expand file tree
/
Copy pathJobQueue.swift
More file actions
139 lines (120 loc) · 5.01 KB
/
Copy pathJobQueue.swift
File metadata and controls
139 lines (120 loc) · 5.01 KB
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// This file contains the job queue implementation which re-order jobs based on their priority.
// Jobs form runs, one per priority in descending order, FIFO within a run. The first job of each
// run points to its last job, so an insert steps over whole runs.
import _Concurrency
import _CJavaScriptEventLoop
#if compiler(>=5.5)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
struct QueueState: Sendable {
fileprivate var headJob: UnownedJob? = nil
fileprivate var isSpinning: Bool = false
}
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
extension JavaScriptEventLoop {
func insertJobQueue(job newJob: UnownedJob) {
withUnsafeMutablePointer(to: &queueState.headJob) { headJobPtr in
var position: UnsafeMutablePointer<UnownedJob?> = headJobPtr
while let runHead = position.pointee, runHead.rawPriority >= newJob.rawPriority {
let runTail = runHead.lastInRun().pointee!
if runHead.rawPriority == newJob.rawPriority {
newJob.nextInQueue().pointee = runTail.nextInQueue().pointee
runTail.nextInQueue().pointee = newJob
runHead.lastInRun().pointee = newJob
return
}
position = runTail.nextInQueue()
}
newJob.nextInQueue().pointee = position.pointee
newJob.lastInRun().pointee = newJob
position.pointee = newJob
}
// TODO: use CAS when supporting multi-threaded environment
if !queueState.isSpinning {
self.queueState.isSpinning = true
JavaScriptEventLoop.shared.queueMicrotask {
self.runAllJobs()
}
}
}
func runAllJobs() {
assert(queueState.isSpinning)
// The clock is only read while `yieldInterval` is set.
var start: DrainInstant? = yieldInterval == nil ? nil : drainClockNow()
// A clock read costs more than a small job. Read it every `clockStride` jobs, and double
// the stride (up to 256) while the drain is far from the interval.
var clockStride = 1
var jobsUntilClockRead = 1
while let job = self.claimNextFromQueue() {
#if compiler(>=5.9)
job.runSynchronously(on: self.asUnownedSerialExecutor())
#else
job._runSynchronously(on: self.asUnownedSerialExecutor())
#endif
if let yieldInterval, queueState.headJob != nil {
if let start {
jobsUntilClockRead -= 1
if jobsUntilClockRead > 0 { continue }
let elapsed = drainElapsed(since: start)
if elapsed >= yieldInterval {
// Keep `isSpinning` set so new jobs wait for the continuation.
queueHostTask { self.runAllJobs() }
return
}
if elapsed * 4 < yieldInterval {
clockStride = min(clockStride * 2, 256)
}
jobsUntilClockRead = clockStride
} else {
start = drainClockNow()
}
}
}
queueState.isSpinning = false
}
func claimNextFromQueue() -> UnownedJob? {
if let job = self.queueState.headJob {
let nextJob = job.nextInQueue().pointee
if let nextJob {
let runTail = job.lastInRun().pointee!
if !runTail.isSameJob(as: job) {
nextJob.lastInRun().pointee = runTail
}
}
self.queueState.headJob = nextJob
return job
}
return nil
}
}
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension UnownedJob {
private func asImpl() -> UnsafeMutablePointer<_CJavaScriptEventLoop.Job> {
unsafeBitCast(self, to: UnsafeMutablePointer<_CJavaScriptEventLoop.Job>.self)
}
fileprivate var flags: JobFlags {
JobFlags(bits: asImpl().pointee.Flags)
}
fileprivate var rawPriority: UInt32 { flags.priority }
fileprivate func nextInQueue() -> UnsafeMutablePointer<UnownedJob?> {
return withUnsafeMutablePointer(to: &asImpl().pointee.SchedulerPrivate.0) { rawNextJobPtr in
let nextJobPtr = UnsafeMutableRawPointer(rawNextJobPtr).bindMemory(to: UnownedJob?.self, capacity: 1)
return nextJobPtr
}
}
/// The last job of the run. Valid only on the first job of a run.
fileprivate func lastInRun() -> UnsafeMutablePointer<UnownedJob?> {
return withUnsafeMutablePointer(to: &asImpl().pointee.SchedulerPrivate.1) { rawLastJobPtr in
UnsafeMutableRawPointer(rawLastJobPtr).bindMemory(to: UnownedJob?.self, capacity: 1)
}
}
fileprivate func isSameJob(as other: UnownedJob) -> Bool {
asImpl() == other.asImpl()
}
}
private struct JobFlags {
var bits: UInt32 = 0
var priority: UInt32 {
(bits & 0xFF00) >> 8
}
}
#endif