diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aad5a3555..2a4625d3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,11 +17,11 @@ jobs: - { os: ubuntu-22.04, toolchain: wasm-5.10.0-RELEASE, wasi-backend: Node } # Ensure that test succeeds with all toolchains and wasi backend combinations - - { os: ubuntu-20.04, toolchain: wasm-5.7.3-RELEASE, wasi-backend: Node } - { os: ubuntu-20.04, toolchain: wasm-5.8.0-RELEASE, wasi-backend: Node } - - { os: ubuntu-20.04, toolchain: wasm-5.7.3-RELEASE, wasi-backend: MicroWASI } + - { os: ubuntu-20.04, toolchain: wasm-5.10.0-RELEASE, wasi-backend: Node } - { os: ubuntu-20.04, toolchain: wasm-5.8.0-RELEASE, wasi-backend: MicroWASI } - { os: ubuntu-20.04, toolchain: wasm-5.9.1-RELEASE, wasi-backend: MicroWASI } + - { os: ubuntu-20.04, toolchain: wasm-5.10.0-RELEASE, wasi-backend: MicroWASI } - os: ubuntu-22.04 toolchain: DEVELOPMENT-SNAPSHOT-2024-05-01-a swift-sdk: @@ -76,8 +76,6 @@ jobs: strategy: matrix: include: - - os: macos-12 - xcode: Xcode_14.0 - os: macos-13 xcode: Xcode_14.3 - os: macos-14 diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift index 8f09279af..7a0364a5c 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift @@ -57,7 +57,28 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { } /// A singleton instance of the Executor - public static let shared: JavaScriptEventLoop = { + public static var shared: JavaScriptEventLoop { + return _shared + } + + #if compiler(>=6.0) && _runtime(_multithreaded) + // In multi-threaded environment, we have an event loop executor per + // thread (per Web Worker). A job enqueued in one thread should be + // executed in the same thread under this global executor. + private static var _shared: JavaScriptEventLoop { + if let tls = swjs_thread_local_event_loop { + let eventLoop = Unmanaged.fromOpaque(tls).takeUnretainedValue() + return eventLoop + } + let eventLoop = create() + swjs_thread_local_event_loop = Unmanaged.passRetained(eventLoop).toOpaque() + return eventLoop + } + #else + private static let _shared: JavaScriptEventLoop = create() + #endif + + private static func create() -> JavaScriptEventLoop { let promise = JSPromise(resolver: { resolver -> Void in resolver(.success(.undefined)) }) @@ -79,7 +100,7 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { } ) return eventLoop - }() + } private static var didInstallGlobalExecutor = false @@ -124,7 +145,7 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { JavaScriptEventLoop.shared.unsafeEnqueue(job) } swift_task_enqueueMainExecutor_hook = unsafeBitCast(swift_task_enqueueMainExecutor_hook_impl, to: UnsafeMutableRawPointer?.self) - + didInstallGlobalExecutor = true } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 1883cbf32..861758497 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -137,7 +137,16 @@ public class JSObject: Equatable { /// A `JSObject` of the global scope object. /// This allows access to the global properties and global names by accessing the `JSObject` returned. - public static let global = JSObject(id: _JS_Predef_Value_Global) + public static var global: JSObject { return _global } + + // `JSObject` storage itself is immutable, and use of `JSObject.global` from other + // threads maintains the same semantics as `globalThis` in JavaScript. + #if compiler(>=5.10) + nonisolated(unsafe) + static let _global = JSObject(id: _JS_Predef_Value_Global) + #else + static let _global = JSObject(id: _JS_Predef_Value_Global) + #endif deinit { swjs_release(id) } diff --git a/Sources/_CJavaScriptEventLoop/_CJavaScriptEventLoop.c b/Sources/_CJavaScriptEventLoop/_CJavaScriptEventLoop.c index e69de29bb..009672933 100644 --- a/Sources/_CJavaScriptEventLoop/_CJavaScriptEventLoop.c +++ b/Sources/_CJavaScriptEventLoop/_CJavaScriptEventLoop.c @@ -0,0 +1,3 @@ +#include "_CJavaScriptEventLoop.h" + +_Thread_local void *swjs_thread_local_event_loop; diff --git a/Sources/_CJavaScriptEventLoop/include/_CJavaScriptEventLoop.h b/Sources/_CJavaScriptEventLoop/include/_CJavaScriptEventLoop.h index 2880772d6..890e26a01 100644 --- a/Sources/_CJavaScriptEventLoop/include/_CJavaScriptEventLoop.h +++ b/Sources/_CJavaScriptEventLoop/include/_CJavaScriptEventLoop.h @@ -61,4 +61,9 @@ typedef SWIFT_CC(swift) void (*swift_task_asyncMainDrainQueue_override)( SWIFT_EXPORT_FROM(swift_Concurrency) extern void *_Nullable swift_task_asyncMainDrainQueue_hook; + +/// MARK: - thread local storage + +extern _Thread_local void * _Nullable swjs_thread_local_event_loop; + #endif