1
1
#if compiler(>=6.1) && _runtime(_multithreaded)
2
+ import Synchronization
2
3
import XCTest
3
4
import _CJavaScriptKit // For swjs_get_worker_thread_id
4
5
@testable import JavaScriptKit
@@ -22,6 +23,7 @@ func pthread_mutex_lock(_ mutex: UnsafeMutablePointer<pthread_mutex_t>) -> Int32
22
23
}
23
24
#endif
24
25
26
+ @available ( macOS 15 . 0 , iOS 18 . 0 , watchOS 11 . 0 , tvOS 18 . 0 , visionOS 2 . 0 , * )
25
27
final class WebWorkerTaskExecutorTests : XCTestCase {
26
28
func testTaskRunOnMainThread( ) async throws {
27
29
let executor = try await WebWorkerTaskExecutor ( numberOfThreads: 1 )
@@ -97,6 +99,168 @@ final class WebWorkerTaskExecutorTests: XCTestCase {
97
99
executor. terminate ( )
98
100
}
99
101
102
+ func testScheduleJobWithinMacroTask1( ) async throws {
103
+ let executor = try await WebWorkerTaskExecutor ( numberOfThreads: 1 )
104
+ defer { executor. terminate ( ) }
105
+
106
+ final class Context : @unchecked Sendable {
107
+ let hasEndedFirstWorkerWakeLoop = Atomic < Bool > ( false )
108
+ let hasEnqueuedFromMain = Atomic < Bool > ( false )
109
+ let hasReachedNextMacroTask = Atomic < Bool > ( false )
110
+ let hasJobBEnded = Atomic < Bool > ( false )
111
+ let hasJobCEnded = Atomic < Bool > ( false )
112
+ }
113
+
114
+ // Scenario 1.
115
+ // | Main | Worker |
116
+ // | +---------------------+--------------------------+
117
+ // | | | Start JS macrotask |
118
+ // | | | Start 1st wake-loop |
119
+ // | | | Enq JS microtask A |
120
+ // | | | End 1st wake-loop |
121
+ // | | | Start a JS microtask A |
122
+ // time | Enq job B to Worker | [PAUSE] |
123
+ // | | | Enq Swift job C |
124
+ // | | | End JS microtask A |
125
+ // | | | Start 2nd wake-loop |
126
+ // | | | Run Swift job B |
127
+ // | | | Run Swift job C |
128
+ // | | | End 2nd wake-loop |
129
+ // v | | End JS macrotask |
130
+ // +---------------------+--------------------------+
131
+
132
+ let context = Context ( )
133
+ Task {
134
+ while !context. hasEndedFirstWorkerWakeLoop. load ( ordering: . sequentiallyConsistent) {
135
+ try ! await Task . sleep ( nanoseconds: 1_000 )
136
+ }
137
+ // Enqueue job B to Worker
138
+ Task ( executorPreference: executor) {
139
+ XCTAssertFalse ( isMainThread ( ) )
140
+ XCTAssertFalse ( context. hasReachedNextMacroTask. load ( ordering: . sequentiallyConsistent) )
141
+ context. hasJobBEnded. store ( true , ordering: . sequentiallyConsistent)
142
+ }
143
+ XCTAssertTrue ( isMainThread ( ) )
144
+ // Resume worker thread to let it enqueue job C
145
+ context. hasEnqueuedFromMain. store ( true , ordering: . sequentiallyConsistent)
146
+ }
147
+
148
+ // Start worker
149
+ await Task ( executorPreference: executor) {
150
+ // Schedule a new macrotask to detect if the current macrotask has completed
151
+ JSObject . global. setTimeout. function!( JSOneshotClosure { _ in
152
+ context. hasReachedNextMacroTask. store ( true , ordering: . sequentiallyConsistent)
153
+ return . undefined
154
+ } , 0 )
155
+
156
+ // Enqueue a microtask, not managed by WebWorkerTaskExecutor
157
+ JSObject . global. queueMicrotask. function!( JSOneshotClosure { _ in
158
+ // Resume the main thread and let it enqueue job B
159
+ context. hasEndedFirstWorkerWakeLoop. store ( true , ordering: . sequentiallyConsistent)
160
+ // Wait until the enqueue has completed
161
+ while !context. hasEnqueuedFromMain. load ( ordering: . sequentiallyConsistent) { }
162
+ // Should be still in the same macrotask
163
+ XCTAssertFalse ( context. hasReachedNextMacroTask. load ( ordering: . sequentiallyConsistent) )
164
+ // Enqueue job C
165
+ Task ( executorPreference: executor) {
166
+ // Should be still in the same macrotask
167
+ XCTAssertFalse ( context. hasReachedNextMacroTask. load ( ordering: . sequentiallyConsistent) )
168
+ // Notify that job C has completed
169
+ context. hasJobCEnded. store ( true , ordering: . sequentiallyConsistent)
170
+ }
171
+ return . undefined
172
+ } , 0 )
173
+ // Wait until job B, C and the next macrotask have completed
174
+ while !context. hasJobBEnded. load ( ordering: . sequentiallyConsistent) ||
175
+ !context. hasJobCEnded. load ( ordering: . sequentiallyConsistent) ||
176
+ !context. hasReachedNextMacroTask. load ( ordering: . sequentiallyConsistent) {
177
+ try ! await Task . sleep ( nanoseconds: 1_000 )
178
+ }
179
+ } . value
180
+ }
181
+
182
+ func testScheduleJobWithinMacroTask2( ) async throws {
183
+ let executor = try await WebWorkerTaskExecutor ( numberOfThreads: 1 )
184
+ defer { executor. terminate ( ) }
185
+
186
+ final class Context : @unchecked Sendable {
187
+ let hasEndedFirstWorkerWakeLoop = Atomic < Bool > ( false )
188
+ let hasEnqueuedFromMain = Atomic < Bool > ( false )
189
+ let hasReachedNextMacroTask = Atomic < Bool > ( false )
190
+ let hasJobBEnded = Atomic < Bool > ( false )
191
+ let hasJobCEnded = Atomic < Bool > ( false )
192
+ }
193
+
194
+ // Scenario 2.
195
+ // (The order of enqueue of job B and C are reversed from Scenario 1)
196
+ //
197
+ // | Main | Worker |
198
+ // | +---------------------+--------------------------+
199
+ // | | | Start JS macrotask |
200
+ // | | | Start 1st wake-loop |
201
+ // | | | Enq JS microtask A |
202
+ // | | | End 1st wake-loop |
203
+ // | | | Start a JS microtask A |
204
+ // | | | Enq Swift job C |
205
+ // time | Enq job B to Worker | [PAUSE] |
206
+ // | | | End JS microtask A |
207
+ // | | | Start 2nd wake-loop |
208
+ // | | | Run Swift job B |
209
+ // | | | Run Swift job C |
210
+ // | | | End 2nd wake-loop |
211
+ // v | | End JS macrotask |
212
+ // +---------------------+--------------------------+
213
+
214
+ let context = Context ( )
215
+ Task {
216
+ while !context. hasEndedFirstWorkerWakeLoop. load ( ordering: . sequentiallyConsistent) {
217
+ try ! await Task . sleep ( nanoseconds: 1_000 )
218
+ }
219
+ // Enqueue job B to Worker
220
+ Task ( executorPreference: executor) {
221
+ XCTAssertFalse ( isMainThread ( ) )
222
+ XCTAssertFalse ( context. hasReachedNextMacroTask. load ( ordering: . sequentiallyConsistent) )
223
+ context. hasJobBEnded. store ( true , ordering: . sequentiallyConsistent)
224
+ }
225
+ XCTAssertTrue ( isMainThread ( ) )
226
+ // Resume worker thread to let it enqueue job C
227
+ context. hasEnqueuedFromMain. store ( true , ordering: . sequentiallyConsistent)
228
+ }
229
+
230
+ // Start worker
231
+ await Task ( executorPreference: executor) {
232
+ // Schedule a new macrotask to detect if the current macrotask has completed
233
+ JSObject . global. setTimeout. function!( JSOneshotClosure { _ in
234
+ context. hasReachedNextMacroTask. store ( true , ordering: . sequentiallyConsistent)
235
+ return . undefined
236
+ } , 0 )
237
+
238
+ // Enqueue a microtask, not managed by WebWorkerTaskExecutor
239
+ JSObject . global. queueMicrotask. function!( JSOneshotClosure { _ in
240
+ // Enqueue job C
241
+ Task ( executorPreference: executor) {
242
+ // Should be still in the same macrotask
243
+ XCTAssertFalse ( context. hasReachedNextMacroTask. load ( ordering: . sequentiallyConsistent) )
244
+ // Notify that job C has completed
245
+ context. hasJobCEnded. store ( true , ordering: . sequentiallyConsistent)
246
+ }
247
+ // Resume the main thread and let it enqueue job B
248
+ context. hasEndedFirstWorkerWakeLoop. store ( true , ordering: . sequentiallyConsistent)
249
+ // Wait until the enqueue has completed
250
+ while !context. hasEnqueuedFromMain. load ( ordering: . sequentiallyConsistent) { }
251
+ // Should be still in the same macrotask
252
+ XCTAssertFalse ( context. hasReachedNextMacroTask. load ( ordering: . sequentiallyConsistent) )
253
+ return . undefined
254
+ } , 0 )
255
+ // Wait until job B, C and the next macrotask have completed
256
+ while !context. hasJobBEnded. load ( ordering: . sequentiallyConsistent) ||
257
+ !context. hasJobCEnded. load ( ordering: . sequentiallyConsistent) ||
258
+ !context. hasReachedNextMacroTask. load ( ordering: . sequentiallyConsistent) {
259
+ try ! await Task . sleep ( nanoseconds: 1_000 )
260
+ }
261
+ } . value
262
+ }
263
+
100
264
func testTaskGroupRunOnSameThread( ) async throws {
101
265
let executor = try await WebWorkerTaskExecutor ( numberOfThreads: 3 )
102
266
0 commit comments