diff --git a/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift b/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift index a70312e3..ef9f539f 100644 --- a/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift +++ b/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift @@ -200,6 +200,7 @@ public final class WebWorkerTaskExecutor: TaskExecutor { parentTaskExecutor = executor // Store the thread ID to the worker. This notifies the main thread that the worker is started. self.tid.store(tid, ordering: .sequentiallyConsistent) + trace("Worker.start tid=\(tid)") } /// Process jobs in the queue. @@ -212,7 +213,14 @@ public final class WebWorkerTaskExecutor: TaskExecutor { guard let executor = parentTaskExecutor else { preconditionFailure("The worker must be started with a parent executor.") } - assert(state.load(ordering: .sequentiallyConsistent) == .running, "Invalid state: not running") + do { + // Assert the state at the beginning of the run. + let state = state.load(ordering: .sequentiallyConsistent) + assert( + state == .running || state == .terminated, + "Invalid state: not running (tid=\(self.tid.load(ordering: .sequentiallyConsistent)), \(state))" + ) + } while true { // Pop a job from the queue. let job = jobQueue.withLock { queue -> UnownedJob? in @@ -247,7 +255,7 @@ public final class WebWorkerTaskExecutor: TaskExecutor { /// Terminate the worker. func terminate() { - trace("Worker.terminate") + trace("Worker.terminate tid=\(tid.load(ordering: .sequentiallyConsistent))") state.store(.terminated, ordering: .sequentiallyConsistent) let tid = self.tid.load(ordering: .sequentiallyConsistent) guard tid != 0 else { @@ -283,6 +291,7 @@ public final class WebWorkerTaskExecutor: TaskExecutor { self.worker = worker } } + trace("Executor.start") // Start worker threads via pthread_create. for worker in workers { // NOTE: The context must be allocated on the heap because diff --git a/Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift b/Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift index 2aab292f..726f4da7 100644 --- a/Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift +++ b/Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift @@ -38,6 +38,7 @@ final class WebWorkerTaskExecutorTests: XCTestCase { func testAwaitInsideTask() async throws { let executor = try await WebWorkerTaskExecutor(numberOfThreads: 1) + defer { executor.terminate() } let task = Task(executorPreference: executor) { await Task.yield() @@ -46,8 +47,6 @@ final class WebWorkerTaskExecutorTests: XCTestCase { } let taskRunOnMainThread = try await task.value XCTAssertFalse(taskRunOnMainThread) - - executor.terminate() } func testSleepInsideTask() async throws { @@ -170,6 +169,7 @@ final class WebWorkerTaskExecutorTests: XCTestCase { let result = await task.value XCTAssertEqual(result, 100) XCTAssertEqual(Check.value, 42) + executor.terminate() } func testLazyThreadLocalPerThreadInitialization() async throws { @@ -198,6 +198,7 @@ final class WebWorkerTaskExecutorTests: XCTestCase { let result = await task.value XCTAssertEqual(result, 100) XCTAssertEqual(Check.countOfInitialization, 2) + executor.terminate() } func testJSValueDecoderOnWorker() async throws { @@ -211,10 +212,10 @@ final class WebWorkerTaskExecutorTests: XCTestCase { let prop_3: Bool let prop_7: Float let prop_8: String + let prop_9: [String] } - let executor = try await WebWorkerTaskExecutor(numberOfThreads: 1) - let task = Task(executorPreference: executor) { + func decodeJob() throws { let json = """ { "prop_1": { @@ -223,20 +224,46 @@ final class WebWorkerTaskExecutorTests: XCTestCase { "prop_2": 100, "prop_3": true, "prop_7": 3.14, - "prop_8": "Hello, World!" + "prop_8": "Hello, World!", + "prop_9": ["a", "b", "c"] } """ let object = JSObject.global.JSON.parse(json) let decoder = JSValueDecoder() - let decoded = try decoder.decode(DecodeMe.self, from: object) - return decoded + let result = try decoder.decode(DecodeMe.self, from: object) + XCTAssertEqual(result.prop_1.nested_prop, 42) + XCTAssertEqual(result.prop_2, 100) + XCTAssertEqual(result.prop_3, true) + XCTAssertEqual(result.prop_7, 3.14) + XCTAssertEqual(result.prop_8, "Hello, World!") + XCTAssertEqual(result.prop_9, ["a", "b", "c"]) + } + // Run the job on the main thread first to initialize the object cache + try decodeJob() + + let executor = try await WebWorkerTaskExecutor(numberOfThreads: 1) + defer { executor.terminate() } + let task = Task(executorPreference: executor) { + // Run the job on the worker thread to test the object cache + // is not shared with the main thread + try decodeJob() + } + try await task.value + } + + func testJSArrayCountOnWorker() async throws { + let executor = try await WebWorkerTaskExecutor(numberOfThreads: 1) + func check() { + let object = JSObject.global.Array.function!.new(1, 2, 3, 4, 5) + let array = JSArray(object)! + XCTAssertEqual(array.count, 5) } - let result = try await task.value - XCTAssertEqual(result.prop_1.nested_prop, 42) - XCTAssertEqual(result.prop_2, 100) - XCTAssertEqual(result.prop_3, true) - XCTAssertEqual(result.prop_7, 3.14) - XCTAssertEqual(result.prop_8, "Hello, World!") + check() + let task = Task(executorPreference: executor) { + check() + } + await task.value + executor.terminate() } /*