From 28f34719df62d30655a9f81f6081aa8db9ce3d38 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 4 Mar 2025 01:39:40 +0000
Subject: [PATCH 01/19] Concurrency: Use `LazyThreadLocal` without @PW syntax

Unfortunately, `@LazyThreadLocal static var` is considered as
concurrency-unsafe in Swift 6 mode even though the underlying PW storage
is read-only and concurrency-safe. Also Swift bans `static let` with
`@propertyWrapper` syntax, so we need to use `LazyThreadLocal` directly.

See the discussion in the Swift forum:
https://forums.swift.org/t/static-property-wrappers-and-strict-concurrency-in-5-10/70116/27
---
 Sources/JavaScriptKit/BasicObjects/JSArray.swift    |  5 ++---
 Sources/JavaScriptKit/BasicObjects/JSDate.swift     |  5 ++---
 Sources/JavaScriptKit/BasicObjects/JSError.swift    |  5 ++---
 .../JavaScriptKit/BasicObjects/JSTypedArray.swift   | 10 ++++------
 .../JavaScriptKit/FundamentalObjects/JSObject.swift | 13 +++++--------
 5 files changed, 15 insertions(+), 23 deletions(-)

diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift
index 95d14c637..56345d085 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift
@@ -2,9 +2,8 @@
 /// class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)
 /// that exposes its properties in a type-safe and Swifty way.
 public class JSArray: JSBridgedClass {
-    public static var constructor: JSFunction? { _constructor }
-    @LazyThreadLocal(initialize: { JSObject.global.Array.function })
-    private static var _constructor: JSFunction?
+    public static var constructor: JSFunction? { _constructor.wrappedValue }
+    private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Array.function })
 
     static func isArray(_ object: JSObject) -> Bool {
         constructor!.isArray!(object).boolean!
diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift
index da31aca06..c8a6623a1 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSDate.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift
@@ -8,9 +8,8 @@
  */
 public final class JSDate: JSBridgedClass {
     /// The constructor function used to create new `Date` objects.
-    public static var constructor: JSFunction? { _constructor }
-    @LazyThreadLocal(initialize: { JSObject.global.Date.function })
-    private static var _constructor: JSFunction?
+    public static var constructor: JSFunction? { _constructor.wrappedValue }
+    private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Date.function })
 
     /// The underlying JavaScript `Date` object.
     public let jsObject: JSObject
diff --git a/Sources/JavaScriptKit/BasicObjects/JSError.swift b/Sources/JavaScriptKit/BasicObjects/JSError.swift
index 559618e15..937581d4b 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSError.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSError.swift
@@ -4,9 +4,8 @@
  */
 public final class JSError: Error, JSBridgedClass {
     /// The constructor function used to create new JavaScript `Error` objects.
-    public static var constructor: JSFunction? { _constructor }
-    @LazyThreadLocal(initialize: { JSObject.global.Error.function })
-    private static var _constructor: JSFunction?
+    public static var constructor: JSFunction? { _constructor.wrappedValue }
+    private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Error.function })
 
     /// The underlying JavaScript `Error` object.
     public let jsObject: JSObject
diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
index bc80cd25c..dec834bbd 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
@@ -143,19 +143,17 @@ func valueForBitWidth<T>(typeName: String, bitWidth: Int, when32: T) -> T {
 }
 
 extension Int: TypedArrayElement {
-    public static var typedArrayClass: JSFunction { _typedArrayClass }
-    @LazyThreadLocal(initialize: {
+    public static var typedArrayClass: JSFunction { _typedArrayClass.wrappedValue }
+    private static let _typedArrayClass = LazyThreadLocal(initialize: {
         valueForBitWidth(typeName: "Int", bitWidth: Int.bitWidth, when32: JSObject.global.Int32Array).function!
     })
-    private static var _typedArrayClass: JSFunction
 }
 
 extension UInt: TypedArrayElement {
-    public static var typedArrayClass: JSFunction { _typedArrayClass }
-    @LazyThreadLocal(initialize: {
+    public static var typedArrayClass: JSFunction { _typedArrayClass.wrappedValue }
+    private static let _typedArrayClass = LazyThreadLocal(initialize: {
         valueForBitWidth(typeName: "UInt", bitWidth: Int.bitWidth, when32: JSObject.global.Uint32Array).function!
     })
-    private static var _typedArrayClass: JSFunction
 }
 
 extension Int8: TypedArrayElement {
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
index eb8fb643a..f74b337d8 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
@@ -24,9 +24,8 @@ import _CJavaScriptKit
 /// reference counting system.
 @dynamicMemberLookup
 public class JSObject: Equatable {
-    internal static var constructor: JSFunction { _constructor }
-    @LazyThreadLocal(initialize: { JSObject.global.Object.function! })
-    internal static var _constructor: JSFunction
+    internal static var constructor: JSFunction { _constructor.wrappedValue }
+    private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Object.function! })
 
     @_spi(JSObject_id)
     public var id: JavaScriptObjectRef
@@ -206,12 +205,10 @@ 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 var global: JSObject { return _global }
-
-    @LazyThreadLocal(initialize: {
-        return JSObject(id: _JS_Predef_Value_Global)
+    public static var global: JSObject { return _global.wrappedValue }
+    private static let _global = LazyThreadLocal(initialize: {
+        JSObject(id: _JS_Predef_Value_Global)
     })
-    private static var _global: JSObject
 
     deinit {
         assertOnOwnerThread(hint: "deinitializing")

From 917ab578aa4479055e87bbc59f17eeb90a4b6d3d Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 4 Mar 2025 01:45:10 +0000
Subject: [PATCH 02/19] Concurrency: Annotate `jsObject` property of `JSError`
 as `nonisolated(unsafe)`

Even though `JSObject` is not a `Sendable` type, `JSError` must be
`Sendable` because of `Error` conformance. For this reason, we need to
annotate the `jsObject` property as `nonisolated(unsafe)` to suppress
the compiler error. Accessing this property from a different isolation
domain scheduled on a different thread will result in a runtime
assertion failure, but better than corrupting memory.
---
 Sources/JavaScriptKit/BasicObjects/JSError.swift | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/Sources/JavaScriptKit/BasicObjects/JSError.swift b/Sources/JavaScriptKit/BasicObjects/JSError.swift
index 937581d4b..290838626 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSError.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSError.swift
@@ -8,7 +8,11 @@ public final class JSError: Error, JSBridgedClass {
     private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Error.function })
 
     /// The underlying JavaScript `Error` object.
-    public let jsObject: JSObject
+    ///
+    /// NOTE: This property must be accessed from the thread that
+    ///       the thrown `Error` object was created on. Otherwise,
+    ///       it will result in a runtime assertion failure.
+    public nonisolated(unsafe) let jsObject: JSObject
 
     /// Creates a new instance of the JavaScript `Error` class with a given message.
     public init(message: String) {

From 30f78ff7ebba29a7baca06a213918f13bfd6ff2b Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 4 Mar 2025 01:49:27 +0000
Subject: [PATCH 03/19] Concurrency: Update Package.swift tools version to 6.0

---
 Package.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Package.swift b/Package.swift
index f21a95cb5..4d4634b88 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.8
+// swift-tools-version:6.0
 
 import PackageDescription
 

From 2642df9275f0b87cd6838960f8cfee9f0e53c5fa Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 4 Mar 2025 01:56:16 +0000
Subject: [PATCH 04/19] Concurrency: Replace `swjs_thread_local_closures` with
 `LazyThreadLocal`

---
 .../FundamentalObjects/JSClosure.swift        | 22 +++++++------------
 Sources/_CJavaScriptKit/_CJavaScriptKit.c     |  2 --
 .../_CJavaScriptKit/include/_CJavaScriptKit.h |  5 -----
 3 files changed, 8 insertions(+), 21 deletions(-)

diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
index 5d367ba38..dafd4ce38 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
@@ -26,7 +26,7 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
         }
 
         // 3. Retain the given body in static storage by `funcRef`.
-        JSClosure.sharedClosures[hostFuncRef] = (self, {
+        JSClosure.sharedClosures.wrappedValue[hostFuncRef] = (self, {
             defer { self.release() }
             return body($0)
         })
@@ -42,7 +42,7 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
     /// Release this function resource.
     /// After calling `release`, calling this function from JavaScript will fail.
     public func release() {
-        JSClosure.sharedClosures[hostFuncRef] = nil
+        JSClosure.sharedClosures.wrappedValue[hostFuncRef] = nil
     }
 }
 
@@ -74,14 +74,8 @@ public class JSClosure: JSFunction, JSClosureProtocol {
     }
 
     // Note: Retain the closure object itself also to avoid funcRef conflicts
-    fileprivate static var sharedClosures: SharedJSClosure {
-        if let swjs_thread_local_closures {
-            return Unmanaged<SharedJSClosure>.fromOpaque(swjs_thread_local_closures).takeUnretainedValue()
-        } else {
-            let shared = SharedJSClosure()
-            swjs_thread_local_closures = Unmanaged.passRetained(shared).toOpaque()
-            return shared
-        }
+    fileprivate static let sharedClosures = LazyThreadLocal {
+        SharedJSClosure()
     }
 
     private var hostFuncRef: JavaScriptHostFuncRef = 0
@@ -110,7 +104,7 @@ public class JSClosure: JSFunction, JSClosureProtocol {
         }
 
         // 3. Retain the given body in static storage by `funcRef`.
-        Self.sharedClosures[hostFuncRef] = (self, body)
+        Self.sharedClosures.wrappedValue[hostFuncRef] = (self, body)
     }
 
     #if compiler(>=5.5) && !hasFeature(Embedded)
@@ -192,7 +186,7 @@ func _call_host_function_impl(
     _ argv: UnsafePointer<RawJSValue>, _ argc: Int32,
     _ callbackFuncRef: JavaScriptObjectRef
 ) -> Bool {
-    guard let (_, hostFunc) = JSClosure.sharedClosures[hostFuncRef] else {
+    guard let (_, hostFunc) = JSClosure.sharedClosures.wrappedValue[hostFuncRef] else {
         return true
     }
     let arguments = UnsafeBufferPointer(start: argv, count: Int(argc)).map { $0.jsValue}
@@ -232,7 +226,7 @@ extension JSClosure {
 
 @_cdecl("_free_host_function_impl")
 func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) {
-    JSClosure.sharedClosures[hostFuncRef] = nil
+    JSClosure.sharedClosures.wrappedValue[hostFuncRef] = nil
 }
 #endif
 
@@ -251,4 +245,4 @@ public func _swjs_call_host_function(
 public func _swjs_free_host_function(_ hostFuncRef: JavaScriptHostFuncRef) {
     _free_host_function_impl(hostFuncRef)
 }
-#endif
\ No newline at end of file
+#endif
diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c
index 424e9081b..ea8b5b43d 100644
--- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c
+++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c
@@ -61,5 +61,3 @@ int swjs_library_features(void) {
 }
 #endif
 #endif
-
-_Thread_local void *swjs_thread_local_closures;
diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
index aa0b978a2..5cb6e6037 100644
--- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
+++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
@@ -308,9 +308,4 @@ IMPORT_JS_FUNCTION(swjs_terminate_worker_thread, void, (int tid))
 
 IMPORT_JS_FUNCTION(swjs_get_worker_thread_id, int, (void))
 
-/// MARK: - thread local storage
-
-// TODO: Rewrite closure system without global storage
-extern _Thread_local void * _Nullable swjs_thread_local_closures;
-
 #endif /* _CJavaScriptKit_h */

From daa820960939fceef1aa243af9a1ac84dc724712 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 06:29:04 +0000
Subject: [PATCH 05/19] Concurrency: Remove `Error` conformance from `JSError`

`Error` protocol now requires `Sendable` conformance, which is not
possible for `JSError` because `JSObject` is not `Sendable`.
---
 Sources/JavaScriptKit/BasicObjects/JSError.swift | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/Sources/JavaScriptKit/BasicObjects/JSError.swift b/Sources/JavaScriptKit/BasicObjects/JSError.swift
index 290838626..0f87d3c67 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSError.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSError.swift
@@ -2,17 +2,13 @@
  class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that
  exposes its properties in a type-safe way.
  */
-public final class JSError: Error, JSBridgedClass {
+public final class JSError: JSBridgedClass {
     /// The constructor function used to create new JavaScript `Error` objects.
     public static var constructor: JSFunction? { _constructor.wrappedValue }
     private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Error.function })
 
     /// The underlying JavaScript `Error` object.
-    ///
-    /// NOTE: This property must be accessed from the thread that
-    ///       the thrown `Error` object was created on. Otherwise,
-    ///       it will result in a runtime assertion failure.
-    public nonisolated(unsafe) let jsObject: JSObject
+    public let jsObject: JSObject
 
     /// Creates a new instance of the JavaScript `Error` class with a given message.
     public init(message: String) {

From 9f0197dc8f5c65ebe180712bbd753002cbb1c135 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 06:31:22 +0000
Subject: [PATCH 06/19] Concurrency: Isolate global executor installation by
 MainActor

---
 .../JavaScriptEventLoop/JavaScriptEventLoop.swift  |  4 ++--
 .../WebWorkerTaskExecutor.swift                    |  9 +++++----
 .../JavaScriptEventLoopTestSupport.swift           |  4 +++-
 .../include/_CJavaScriptEventLoop.h                | 14 ++++++++------
 4 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
index 765746bb1..af8738ef8 100644
--- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
+++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
@@ -102,14 +102,14 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
         return eventLoop
     }
 
-    private static var didInstallGlobalExecutor = false
+    @MainActor private static var didInstallGlobalExecutor = false
 
     /// Set JavaScript event loop based executor to be the global executor
     /// Note that this should be called before any of the jobs are created.
     /// This installation step will be unnecessary after custom executor are
     /// introduced officially. See also [a draft proposal for custom 
     /// executors](https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#the-default-global-concurrent-executor)
-    public static func installGlobalExecutor() {
+    @MainActor public static func installGlobalExecutor() {
         guard !didInstallGlobalExecutor else { return }
 
         #if compiler(>=5.9)
diff --git a/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift b/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift
index 5110f60db..ac4769a82 100644
--- a/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift
+++ b/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift
@@ -426,14 +426,15 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
 
     // MARK: Global Executor hack
 
-    private static var _mainThread: pthread_t?
-    private static var _swift_task_enqueueGlobal_hook_original: UnsafeMutableRawPointer?
-    private static var _swift_task_enqueueGlobalWithDelay_hook_original: UnsafeMutableRawPointer?
-    private static var _swift_task_enqueueGlobalWithDeadline_hook_original: UnsafeMutableRawPointer?
+    @MainActor private static var _mainThread: pthread_t?
+    @MainActor private static var _swift_task_enqueueGlobal_hook_original: UnsafeMutableRawPointer?
+    @MainActor private static var _swift_task_enqueueGlobalWithDelay_hook_original: UnsafeMutableRawPointer?
+    @MainActor private static var _swift_task_enqueueGlobalWithDeadline_hook_original: UnsafeMutableRawPointer?
 
     /// Install a global executor that forwards jobs from Web Worker threads to the main thread.
     ///
     /// This function must be called once before using the Web Worker task executor.
+    @MainActor
     public static func installGlobalExecutor() {
         #if canImport(wasi_pthread) && compiler(>=6.1) && _runtime(_multithreaded)
         // Ensure this function is called only once.
diff --git a/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift b/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift
index 64e6776d4..4c441f3c4 100644
--- a/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift
+++ b/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift
@@ -25,7 +25,9 @@ import JavaScriptEventLoop
 @available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
 @_cdecl("swift_javascriptkit_activate_js_executor_impl")
 func swift_javascriptkit_activate_js_executor_impl() {
-    JavaScriptEventLoop.installGlobalExecutor()
+    MainActor.assumeIsolated {
+        JavaScriptEventLoop.installGlobalExecutor()
+    }
 }
 
 #endif
diff --git a/Sources/_CJavaScriptEventLoop/include/_CJavaScriptEventLoop.h b/Sources/_CJavaScriptEventLoop/include/_CJavaScriptEventLoop.h
index 4f1b9470c..08efcb948 100644
--- a/Sources/_CJavaScriptEventLoop/include/_CJavaScriptEventLoop.h
+++ b/Sources/_CJavaScriptEventLoop/include/_CJavaScriptEventLoop.h
@@ -9,6 +9,8 @@
 
 #define SWIFT_EXPORT_FROM(LIBRARY) __attribute__((__visibility__("default")))
 
+#define SWIFT_NONISOLATED_UNSAFE __attribute__((swift_attr("nonisolated(unsafe)")))
+
 /// A schedulable unit
 /// Note that this type layout is a part of public ABI, so we expect this field layout won't break in the future versions.
 /// Current implementation refers the `swift-5.5-RELEASE` implementation.
@@ -27,13 +29,13 @@ typedef SWIFT_CC(swift) void (*swift_task_enqueueGlobal_original)(
     Job *_Nonnull job);
 
 SWIFT_EXPORT_FROM(swift_Concurrency)
-extern void *_Nullable swift_task_enqueueGlobal_hook;
+extern void *_Nullable swift_task_enqueueGlobal_hook SWIFT_NONISOLATED_UNSAFE;
 
 /// A hook to take over global enqueuing with delay.
 typedef SWIFT_CC(swift) void (*swift_task_enqueueGlobalWithDelay_original)(
     unsigned long long delay, Job *_Nonnull job);
 SWIFT_EXPORT_FROM(swift_Concurrency)
-extern void *_Nullable swift_task_enqueueGlobalWithDelay_hook;
+extern void *_Nullable swift_task_enqueueGlobalWithDelay_hook SWIFT_NONISOLATED_UNSAFE;
 
 typedef SWIFT_CC(swift) void (*swift_task_enqueueGlobalWithDeadline_original)(
     long long sec,
@@ -42,13 +44,13 @@ typedef SWIFT_CC(swift) void (*swift_task_enqueueGlobalWithDeadline_original)(
     long long tnsec,
     int clock, Job *_Nonnull job);
 SWIFT_EXPORT_FROM(swift_Concurrency)
-extern void *_Nullable swift_task_enqueueGlobalWithDeadline_hook;
+extern void *_Nullable swift_task_enqueueGlobalWithDeadline_hook SWIFT_NONISOLATED_UNSAFE;
 
 /// A hook to take over main executor enqueueing.
 typedef SWIFT_CC(swift) void (*swift_task_enqueueMainExecutor_original)(
     Job *_Nonnull job);
 SWIFT_EXPORT_FROM(swift_Concurrency)
-extern void *_Nullable swift_task_enqueueMainExecutor_hook;
+extern void *_Nullable swift_task_enqueueMainExecutor_hook SWIFT_NONISOLATED_UNSAFE;
 
 /// A hook to override the entrypoint to the main runloop used to drive the
 /// concurrency runtime and drain the main queue. This function must not return.
@@ -59,13 +61,13 @@ typedef SWIFT_CC(swift) void (*swift_task_asyncMainDrainQueue_original)();
 typedef SWIFT_CC(swift) void (*swift_task_asyncMainDrainQueue_override)(
     swift_task_asyncMainDrainQueue_original _Nullable original);
 SWIFT_EXPORT_FROM(swift_Concurrency)
-extern void *_Nullable swift_task_asyncMainDrainQueue_hook;
+extern void *_Nullable swift_task_asyncMainDrainQueue_hook SWIFT_NONISOLATED_UNSAFE;
 
 
 /// MARK: - thread local storage
 
 extern _Thread_local void * _Nullable swjs_thread_local_event_loop;
 
-extern _Thread_local void * _Nullable swjs_thread_local_task_executor_worker;
+extern _Thread_local void * _Nullable swjs_thread_local_task_executor_worker SWIFT_NONISOLATED_UNSAFE;
 
 #endif

From fa77908b7a9b5d6ac914bc886ee282ebb2403611 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:01:06 +0000
Subject: [PATCH 07/19] Concurrency: Remove `@Sendable` requirement from
 scheduling primitives

They are accessed from a single thread, so there is no need to enforce
`@Sendable` requirement on them. And also the following code is not
working with `@Sendable` requirement because the captured `JSPromise`
is not `Sendable`.

```
let promise = JSPromise(resolver: { resolver -> Void in
    resolver(.success(.undefined))
})
let setTimeout = JSObject.global.setTimeout.function!
let eventLoop = JavaScriptEventLoop(
    queueTask: { job in
        // TODO(katei): Should prefer `queueMicrotask` if available?
        // We should measure if there is performance advantage.
        promise.then { _ in
            job()
            return JSValue.undefined
        }
    },
    setTimeout: { delay, job in
        setTimeout(JSOneshotClosure { _ in
            job()
            return JSValue.undefined
        }, delay)
    }
)
```
---
 Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
index af8738ef8..867fb070a 100644
--- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
+++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
@@ -40,17 +40,17 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
 
     /// A function that queues a given closure as a microtask into JavaScript event loop.
     /// See also: https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
-    public var queueMicrotask: @Sendable (@escaping () -> Void) -> Void
+    public var queueMicrotask: (@escaping () -> Void) -> Void
     /// A function that invokes a given closure after a specified number of milliseconds.
-    public var setTimeout: @Sendable (Double, @escaping () -> Void) -> Void
+    public var setTimeout: (Double, @escaping () -> Void) -> Void
 
     /// A mutable state to manage internal job queue
     /// Note that this should be guarded atomically when supporting multi-threaded environment.
     var queueState = QueueState()
 
     private init(
-        queueTask: @Sendable @escaping (@escaping () -> Void) -> Void,
-        setTimeout: @Sendable @escaping (Double, @escaping () -> Void) -> Void
+        queueTask: @escaping (@escaping () -> Void) -> Void,
+        setTimeout: @escaping (Double, @escaping () -> Void) -> Void
     ) {
         self.queueMicrotask = queueTask
         self.setTimeout = setTimeout

From 97aad009327a645d2296b43160da4ce9f3f6b933 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:07:39 +0000
Subject: [PATCH 08/19] Concurrency: Fix sendability errors around
 `JSClosure.async`

---
 .../BasicObjects/JSPromise.swift              | 14 +++----
 .../FundamentalObjects/JSClosure.swift        | 40 +++++++++++++------
 2 files changed, 34 insertions(+), 20 deletions(-)

diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
index a41a3e1ca..0580c23bb 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
@@ -90,7 +90,7 @@ public final class JSPromise: JSBridgedClass {
         /// Schedules the `success` closure to be invoked on successful completion of `self`.
         @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
         @discardableResult
-        public func then(success: @escaping (JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
+        public func then(success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
             let closure = JSOneshotClosure.async {
                 try await success($0[0]).jsValue
             }
@@ -101,8 +101,8 @@ public final class JSPromise: JSBridgedClass {
     /// Schedules the `success` closure to be invoked on successful completion of `self`.
     @discardableResult
     public func then(
-        success: @escaping (JSValue) -> ConvertibleToJSValue,
-        failure: @escaping (JSValue) -> ConvertibleToJSValue
+        success: @escaping (sending JSValue) -> ConvertibleToJSValue,
+        failure: @escaping (sending JSValue) -> ConvertibleToJSValue
     ) -> JSPromise {
         let successClosure = JSOneshotClosure {
             success($0[0]).jsValue
@@ -117,8 +117,8 @@ public final class JSPromise: JSBridgedClass {
         /// Schedules the `success` closure to be invoked on successful completion of `self`.
         @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
         @discardableResult
-        public func then(success: @escaping (JSValue) async throws -> ConvertibleToJSValue,
-                         failure: @escaping (JSValue) async throws -> ConvertibleToJSValue) -> JSPromise
+        public func then(success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue,
+                         failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise
         {
             let successClosure = JSOneshotClosure.async {
                 try await success($0[0]).jsValue
@@ -132,7 +132,7 @@ public final class JSPromise: JSBridgedClass {
 
     /// Schedules the `failure` closure to be invoked on rejected completion of `self`.
     @discardableResult
-    public func `catch`(failure: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise {
+    public func `catch`(failure: @escaping (sending JSValue) -> ConvertibleToJSValue) -> JSPromise {
         let closure = JSOneshotClosure {
             failure($0[0]).jsValue
         }
@@ -143,7 +143,7 @@ public final class JSPromise: JSBridgedClass {
         /// Schedules the `failure` closure to be invoked on rejected completion of `self`.
         @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
         @discardableResult
-        public func `catch`(failure: @escaping (JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
+        public func `catch`(failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
             let closure = JSOneshotClosure.async {
                 try await failure($0[0]).jsValue
             }
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
index dafd4ce38..81f2540b6 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
@@ -15,7 +15,7 @@ public protocol JSClosureProtocol: JSValueCompatible {
 public class JSOneshotClosure: JSObject, JSClosureProtocol {
     private var hostFuncRef: JavaScriptHostFuncRef = 0
 
-    public init(_ body: @escaping ([JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) {
+    public init(_ body: @escaping (sending [JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) {
         // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
         super.init(id: 0)
 
@@ -34,7 +34,7 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
 
     #if compiler(>=5.5) && !hasFeature(Embedded)
     @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
-    public static func async(_ body: @escaping ([JSValue]) async throws -> JSValue) -> JSOneshotClosure {
+    public static func async(_ body: sending @escaping (sending [JSValue]) async throws -> JSValue) -> JSOneshotClosure {
         JSOneshotClosure(makeAsyncClosure(body))
     }
     #endif
@@ -64,10 +64,10 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
 public class JSClosure: JSFunction, JSClosureProtocol {
 
     class SharedJSClosure {
-        private var storage: [JavaScriptHostFuncRef: (object: JSObject, body: ([JSValue]) -> JSValue)] = [:]
+        private var storage: [JavaScriptHostFuncRef: (object: JSObject, body: (sending [JSValue]) -> JSValue)] = [:]
         init() {}
 
-        subscript(_ key: JavaScriptHostFuncRef) -> (object: JSObject, body: ([JSValue]) -> JSValue)? {
+        subscript(_ key: JavaScriptHostFuncRef) -> (object: JSObject, body: (sending [JSValue]) -> JSValue)? {
             get { storage[key] }
             set { storage[key] = newValue }
         }
@@ -93,7 +93,7 @@ public class JSClosure: JSFunction, JSClosureProtocol {
         })
     }
 
-    public init(_ body: @escaping ([JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) {
+    public init(_ body: @escaping (sending [JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) {
         // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
         super.init(id: 0)
 
@@ -109,7 +109,7 @@ public class JSClosure: JSFunction, JSClosureProtocol {
 
     #if compiler(>=5.5) && !hasFeature(Embedded)
     @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
-    public static func async(_ body: @escaping ([JSValue]) async throws -> JSValue) -> JSClosure {
+    public static func async(_ body: @Sendable @escaping (sending [JSValue]) async throws -> JSValue) -> JSClosure {
         JSClosure(makeAsyncClosure(body))
     }
     #endif
@@ -125,18 +125,29 @@ public class JSClosure: JSFunction, JSClosureProtocol {
 
 #if compiler(>=5.5) && !hasFeature(Embedded)
 @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
-private func makeAsyncClosure(_ body: @escaping ([JSValue]) async throws -> JSValue) -> (([JSValue]) -> JSValue) {
+private func makeAsyncClosure(
+    _ body: sending @escaping (sending [JSValue]) async throws -> JSValue
+) -> ((sending [JSValue]) -> JSValue) {
     { arguments in
         JSPromise { resolver in
+            // NOTE: The context is fully transferred to the unstructured task
+            // isolation but the compiler can't prove it yet, so we need to
+            // use `@unchecked Sendable` to make it compile with the Swift 6 mode.
+            struct Context: @unchecked Sendable {
+                let resolver: (JSPromise.Result) -> Void
+                let arguments: [JSValue]
+                let body: (sending [JSValue]) async throws -> JSValue
+            }
+            let context = Context(resolver: resolver, arguments: arguments, body: body)
             Task {
                 do {
-                    let result = try await body(arguments)
-                    resolver(.success(result))
+                    let result = try await context.body(context.arguments)
+                    context.resolver(.success(result))
                 } catch {
                     if let jsError = error as? JSError {
-                        resolver(.failure(jsError.jsValue))
+                        context.resolver(.failure(jsError.jsValue))
                     } else {
-                        resolver(.failure(JSError(message: String(describing: error)).jsValue))
+                        context.resolver(.failure(JSError(message: String(describing: error)).jsValue))
                     }
                 }
             }
@@ -183,13 +194,16 @@ private func makeAsyncClosure(_ body: @escaping ([JSValue]) async throws -> JSVa
 @_cdecl("_call_host_function_impl")
 func _call_host_function_impl(
     _ hostFuncRef: JavaScriptHostFuncRef,
-    _ argv: UnsafePointer<RawJSValue>, _ argc: Int32,
+    _ argv: sending UnsafePointer<RawJSValue>, _ argc: Int32,
     _ callbackFuncRef: JavaScriptObjectRef
 ) -> Bool {
     guard let (_, hostFunc) = JSClosure.sharedClosures.wrappedValue[hostFuncRef] else {
         return true
     }
-    let arguments = UnsafeBufferPointer(start: argv, count: Int(argc)).map { $0.jsValue}
+    var arguments: [JSValue] = []
+    for i in 0..<Int(argc) {
+        arguments.append(argv[i].jsValue)
+    }
     let result = hostFunc(arguments)
     let callbackFuncRef = JSFunction(id: callbackFuncRef)
     _ = callbackFuncRef(result)

From 75b8cd3c2935f28d3e5bc21e28157ac4fff32211 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:17:37 +0000
Subject: [PATCH 09/19] Concurrency: Introduce `JSException` and remove `Error`
 conformance from `JSValue`

This is a breaking change. It introduces a new `JSException` type to represent
exceptions thrown from JavaScript code. This change is necessary to remove `Sendable`
conformance from `JSValue`, which is derived from `Error` conformance.
---
 .../JavaScriptEventLoop.swift                 |  6 ++--
 .../BasicObjects/JSPromise.swift              | 10 +++++-
 .../FundamentalObjects/JSClosure.swift        |  4 +--
 .../JSThrowingFunction.swift                  |  6 ++--
 Sources/JavaScriptKit/JSException.swift       | 34 +++++++++++++++++++
 Sources/JavaScriptKit/JSValue.swift           |  2 --
 6 files changed, 51 insertions(+), 11 deletions(-)
 create mode 100644 Sources/JavaScriptKit/JSException.swift

diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
index 867fb070a..b9e89a375 100644
--- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
+++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
@@ -218,7 +218,7 @@ public extension JSPromise {
                         return JSValue.undefined
                     },
                     failure: {
-                        continuation.resume(throwing: $0)
+                        continuation.resume(throwing: JSException($0))
                         return JSValue.undefined
                     }
                 )
@@ -227,7 +227,7 @@ public extension JSPromise {
     }
 
     /// Wait for the promise to complete, returning its result or exception as a Result.
-    var result: Result<JSValue, JSValue> {
+    var result: Swift.Result<JSValue, JSException> {
         get async {
             await withUnsafeContinuation { [self] continuation in
                 self.then(
@@ -236,7 +236,7 @@ public extension JSPromise {
                         return JSValue.undefined
                     },
                     failure: {
-                        continuation.resume(returning: .failure($0))
+                        continuation.resume(returning: .failure(JSException($0)))
                         return JSValue.undefined
                     }
                 )
diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
index 0580c23bb..1aec5f4af 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
@@ -31,6 +31,14 @@ public final class JSPromise: JSBridgedClass {
         return Self(jsObject)
     }
 
+    /// The result of a promise.
+    public enum Result {
+        /// The promise resolved with a value.
+        case success(JSValue)
+        /// The promise rejected with a value.
+        case failure(JSValue)
+    }
+
     /// Creates a new `JSPromise` instance from a given `resolver` closure.
     /// The closure is passed a completion handler. Passing a successful
     /// `Result` to the completion handler will cause the promise to resolve
@@ -38,7 +46,7 @@ public final class JSPromise: JSBridgedClass {
     /// promise to reject with the corresponding value.
     /// Calling the completion handler more than once will have no effect
     /// (per the JavaScript specification).
-    public convenience init(resolver: @escaping (@escaping (Result<JSValue, JSValue>) -> Void) -> Void) {
+    public convenience init(resolver: @escaping (@escaping (Result) -> Void) -> Void) {
         let closure = JSOneshotClosure { arguments in
             // The arguments are always coming from the `Promise` constructor, so we should be
             // safe to assume their type here
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
index 81f2540b6..8c42d2ac4 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
@@ -144,8 +144,8 @@ private func makeAsyncClosure(
                     let result = try await context.body(context.arguments)
                     context.resolver(.success(result))
                 } catch {
-                    if let jsError = error as? JSError {
-                        context.resolver(.failure(jsError.jsValue))
+                    if let jsError = error as? JSException {
+                        context.resolver(.failure(jsError.thrownValue))
                     } else {
                         context.resolver(.failure(JSError(message: String(describing: error)).jsValue))
                     }
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift
index 8b4fc7cde..17b61090f 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift
@@ -37,7 +37,7 @@ public class JSThrowingFunction {
     /// - Parameter arguments: Arguments to be passed to this constructor function.
     /// - Returns: A new instance of this constructor.
     public func new(arguments: [ConvertibleToJSValue]) throws -> JSObject {
-        try arguments.withRawJSValues { rawValues -> Result<JSObject, JSValue> in
+        try arguments.withRawJSValues { rawValues -> Result<JSObject, JSException> in
             rawValues.withUnsafeBufferPointer { bufferPointer in
                 let argv = bufferPointer.baseAddress
                 let argc = bufferPointer.count
@@ -52,7 +52,7 @@ public class JSThrowingFunction {
                 let exceptionKind = JavaScriptValueKindAndFlags(bitPattern: exceptionRawKind)
                 if exceptionKind.isException {
                     let exception = RawJSValue(kind: exceptionKind.kind, payload1: exceptionPayload1, payload2: exceptionPayload2)
-                    return .failure(exception.jsValue)
+                    return .failure(JSException(exception.jsValue))
                 }
                 return .success(JSObject(id: resultObj))
             }
@@ -92,7 +92,7 @@ private func invokeJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSV
         }
     }
     if isException {
-        throw result
+        throw JSException(result)
     }
     return result
 }
diff --git a/Sources/JavaScriptKit/JSException.swift b/Sources/JavaScriptKit/JSException.swift
new file mode 100644
index 000000000..7f1959c70
--- /dev/null
+++ b/Sources/JavaScriptKit/JSException.swift
@@ -0,0 +1,34 @@
+/// `JSException` is a wrapper that handles exceptions thrown during JavaScript execution as Swift
+/// `Error` objects.
+/// When a JavaScript function throws an exception, it's wrapped as a `JSException` and propagated
+/// through Swift's error handling mechanism.
+///
+/// Example:
+/// ```swift
+/// do {
+///     try jsFunction.throws()
+/// } catch let error as JSException {
+///     // Access the value thrown from JavaScript
+///     let jsErrorValue = error.thrownValue
+/// }
+/// ```
+public struct JSException: Error {
+    /// The value thrown from JavaScript.
+    /// This can be any JavaScript value (error object, string, number, etc.).
+    public var thrownValue: JSValue {
+      return _thrownValue
+    }
+
+    /// The actual JavaScript value that was thrown.
+    ///
+    /// Marked as `nonisolated(unsafe)` to satisfy `Sendable` requirement
+    /// from `Error` protocol.
+    private nonisolated(unsafe) let _thrownValue: JSValue
+
+    /// Initializes a new JSException instance with a value thrown from JavaScript.
+    ///
+    /// Only available within the package.
+    package init(_ thrownValue: JSValue) {
+        self._thrownValue = thrownValue
+    }
+}
diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift
index ed44f50ea..1efffe484 100644
--- a/Sources/JavaScriptKit/JSValue.swift
+++ b/Sources/JavaScriptKit/JSValue.swift
@@ -124,8 +124,6 @@ public extension JSValue {
     }
 }
 
-extension JSValue: Swift.Error {}
-
 public extension JSValue {
     func fromJSValue<Type>() -> Type? where Type: ConstructibleFromJSValue {
         return Type.construct(from: self)

From d1781a8c596bb14819a80116bc8d13870e316145 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:21:52 +0000
Subject: [PATCH 10/19] CI: Remove Xcode 15.2 (Swift 5.9) from the matrix

---
 .github/workflows/test.yml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index daac3c50f..f87d3c5f5 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -52,8 +52,6 @@ jobs:
     strategy:
       matrix:
         include:
-          - os: macos-14
-            xcode: Xcode_15.2
           - os: macos-15
             xcode: Xcode_16
     runs-on: ${{ matrix.os }}

From 39c207b4e45ad92137ef149fe9ea83c92e9cad14 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:42:53 +0000
Subject: [PATCH 11/19] Fix `JAVASCRIPTKIT_WITHOUT_WEAKREFS` build

---
 Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
index 8c42d2ac4..c1f0361da 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
@@ -221,7 +221,7 @@ func _call_host_function_impl(
 extension JSClosure {
     public func release() {
         isReleased = true
-        Self.sharedClosures[hostFuncRef] = nil
+        Self.sharedClosures.wrappedValue[hostFuncRef] = nil
     }
 }
 

From 0fc7f41c573c3ad25d4367bf591d3c0008bcc303 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:43:19 +0000
Subject: [PATCH 12/19] Concurrency: Use `JSPromise.Result` instead of
 `Swift.Result` for `JSPromise.result`

To reduce burden type casting, it's better to remove the wrapper from
the API.
---
 Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
index b9e89a375..c0141cd63 100644
--- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
+++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
@@ -227,7 +227,7 @@ public extension JSPromise {
     }
 
     /// Wait for the promise to complete, returning its result or exception as a Result.
-    var result: Swift.Result<JSValue, JSException> {
+    var result: JSPromise.Result {
         get async {
             await withUnsafeContinuation { [self] continuation in
                 self.then(
@@ -236,7 +236,7 @@ public extension JSPromise {
                         return JSValue.undefined
                     },
                     failure: {
-                        continuation.resume(returning: .failure(JSException($0)))
+                        continuation.resume(returning: .failure($0))
                         return JSValue.undefined
                     }
                 )

From 899fa637f04d34728401cba2984073e95b802c20 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:44:36 +0000
Subject: [PATCH 13/19] Add `Equatable` conformances to new types

---
 Sources/JavaScriptKit/BasicObjects/JSPromise.swift | 2 +-
 Sources/JavaScriptKit/JSException.swift            | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
index 1aec5f4af..cfe32d515 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift
@@ -32,7 +32,7 @@ public final class JSPromise: JSBridgedClass {
     }
 
     /// The result of a promise.
-    public enum Result {
+    public enum Result: Equatable {
         /// The promise resolved with a value.
         case success(JSValue)
         /// The promise rejected with a value.
diff --git a/Sources/JavaScriptKit/JSException.swift b/Sources/JavaScriptKit/JSException.swift
index 7f1959c70..393ae9615 100644
--- a/Sources/JavaScriptKit/JSException.swift
+++ b/Sources/JavaScriptKit/JSException.swift
@@ -12,7 +12,7 @@
 ///     let jsErrorValue = error.thrownValue
 /// }
 /// ```
-public struct JSException: Error {
+public struct JSException: Error, Equatable {
     /// The value thrown from JavaScript.
     /// This can be any JavaScript value (error object, string, number, etc.).
     public var thrownValue: JSValue {

From 042e26e8740fb084e52c58f3f34867b2795f25a4 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:45:20 +0000
Subject: [PATCH 14/19] Concurency: Remove `@MainActor` requirement from
 `JSEL.installGlobalExecutor`

The installation of the global executor should be done before any
job scheduling, so it should be able to be called at top-level immediately
executed code.
---
 Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
index c0141cd63..07eec2cd2 100644
--- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
+++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
@@ -109,7 +109,13 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
     /// This installation step will be unnecessary after custom executor are
     /// introduced officially. See also [a draft proposal for custom 
     /// executors](https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#the-default-global-concurrent-executor)
-    @MainActor public static func installGlobalExecutor() {
+    public static func installGlobalExecutor() {
+        MainActor.assumeIsolated {
+            Self.installGlobalExecutorIsolated()
+        }
+    }
+
+    @MainActor private static func installGlobalExecutorIsolated() {
         guard !didInstallGlobalExecutor else { return }
 
         #if compiler(>=5.9)

From 22572338eb7eed5624f7fcf76975dfa6f5c0d3e6 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:47:16 +0000
Subject: [PATCH 15/19] Concurrency: Adjust test cases for new exception
 handling

---
 .../Sources/ConcurrencyTests/main.swift       |  4 +-
 .../Sources/PrimaryTests/UnitTestUtils.swift  |  2 +-
 .../Sources/PrimaryTests/main.swift           | 37 +++++++++----------
 3 files changed, 20 insertions(+), 23 deletions(-)

diff --git a/IntegrationTests/TestSuites/Sources/ConcurrencyTests/main.swift b/IntegrationTests/TestSuites/Sources/ConcurrencyTests/main.swift
index ece58b317..1f0764e14 100644
--- a/IntegrationTests/TestSuites/Sources/ConcurrencyTests/main.swift
+++ b/IntegrationTests/TestSuites/Sources/ConcurrencyTests/main.swift
@@ -48,7 +48,7 @@ func entrypoint() async throws {
             resolve(.failure(.number(3)))
         })
         let error = try await expectAsyncThrow(await p.value)
-        let jsValue = try expectCast(error, to: JSValue.self)
+        let jsValue = try expectCast(error, to: JSException.self).thrownValue
         try expectEqual(jsValue, 3)
         try await expectEqual(p.result, .failure(.number(3)))
     }
@@ -157,7 +157,7 @@ func entrypoint() async throws {
             )
         }
         let promise2 = promise.then { _ in
-            throw JSError(message: "should not succeed")
+            throw MessageError("Should not be called", file: #file, line: #line, column: #column)
         } failure: { err in
             return err
         }
diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift
index c4f9a9fb1..0d51c6ff5 100644
--- a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift
+++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift
@@ -110,7 +110,7 @@ func expectThrow<T>(_ body: @autoclosure () throws -> T, file: StaticString = #f
     throw MessageError("Expect to throw an exception", file: file, line: line, column: column)
 }
 
-func wrapUnsafeThrowableFunction(_ body: @escaping () -> Void, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Error {
+func wrapUnsafeThrowableFunction(_ body: @escaping () -> Void, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSValue {
     JSObject.global.callThrowingClosure.function!(JSClosure { _ in 
             body() 
             return .undefined
diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
index 67a51aa2e..12cc91cc9 100644
--- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
+++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
@@ -263,8 +263,8 @@ try test("Closure Lifetime") {
         let c1Line = #line + 1
         let c1 = JSClosure { $0[0] }
         c1.release()
-        let error = try expectThrow(try evalClosure.throws(c1, JSValue.number(42.0))) as! JSValue
-        try expect("Error message should contains definition location", error.description.hasSuffix("PrimaryTests/main.swift:\(c1Line)"))
+        let error = try expectThrow(try evalClosure.throws(c1, JSValue.number(42.0))) as! JSException
+        try expect("Error message should contains definition location", error.thrownValue.description.hasSuffix("PrimaryTests/main.swift:\(c1Line)"))
     }
 #endif
 
@@ -275,8 +275,8 @@ try test("Closure Lifetime") {
 
     do {
         let c1 = JSClosure { _ in fatalError("Crash while closure evaluation") }
-        let error = try expectThrow(try evalClosure.throws(c1)) as! JSValue
-        try expectEqual(error.description, "RuntimeError: unreachable")
+        let error = try expectThrow(try evalClosure.throws(c1)) as! JSException
+        try expectEqual(error.thrownValue.description, "RuntimeError: unreachable")
     }
 }
 
@@ -770,32 +770,32 @@ try test("Exception") {
 
     // MARK: Throwing method calls
     let error1 = try expectThrow(try prop_9.object!.throwing.func1!())
-    try expectEqual(error1 is JSValue, true)
-    let errorObject = JSError(from: error1 as! JSValue)
+    try expectEqual(error1 is JSException, true)
+    let errorObject = JSError(from: (error1 as! JSException).thrownValue)
     try expectNotNil(errorObject)
 
     let error2 = try expectThrow(try prop_9.object!.throwing.func2!())
-    try expectEqual(error2 is JSValue, true)
-    let errorString = try expectString(error2 as! JSValue)
+    try expectEqual(error2 is JSException, true)
+    let errorString = try expectString((error2 as! JSException).thrownValue)
     try expectEqual(errorString, "String Error")
 
     let error3 = try expectThrow(try prop_9.object!.throwing.func3!())
-    try expectEqual(error3 is JSValue, true)
-    let errorNumber = try expectNumber(error3 as! JSValue)
+    try expectEqual(error3 is JSException, true)
+    let errorNumber = try expectNumber((error3 as! JSException).thrownValue)
     try expectEqual(errorNumber, 3.0)
 
     // MARK: Simple function calls
     let error4 = try expectThrow(try prop_9.func1.function!.throws())
-    try expectEqual(error4 is JSValue, true)
-    let errorObject2 = JSError(from: error4 as! JSValue)
+    try expectEqual(error4 is JSException, true)
+    let errorObject2 = JSError(from: (error4 as! JSException).thrownValue)
     try expectNotNil(errorObject2)
 
     // MARK: Throwing constructor call
     let Animal = JSObject.global.Animal.function!
     _ = try Animal.throws.new("Tama", 3, true)
     let ageError = try expectThrow(try Animal.throws.new("Tama", -3, true))
-    try expectEqual(ageError is JSValue, true)
-    let errorObject3 = JSError(from: ageError as! JSValue)
+    try expectEqual(ageError is JSException, true)
+    let errorObject3 = JSError(from: (ageError as! JSException).thrownValue)
     try expectNotNil(errorObject3)
 }
 
@@ -824,18 +824,15 @@ try test("Unhandled Exception") {
 
     // MARK: Throwing method calls
     let error1 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func1!() }
-    try expectEqual(error1 is JSValue, true)
-    let errorObject = JSError(from: error1 as! JSValue)
+    let errorObject = JSError(from: error1)
     try expectNotNil(errorObject)
 
     let error2 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func2!() }
-    try expectEqual(error2 is JSValue, true)
-    let errorString = try expectString(error2 as! JSValue)
+    let errorString = try expectString(error2)
     try expectEqual(errorString, "String Error")
 
     let error3 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func3!() }
-    try expectEqual(error3 is JSValue, true)
-    let errorNumber = try expectNumber(error3 as! JSValue)
+    let errorNumber = try expectNumber(error3)
     try expectEqual(errorNumber, 3.0)
 }
 

From 0c43cbfd67ae8bf0969da51c9d15d181cbe13f7f Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 07:58:09 +0000
Subject: [PATCH 16/19] CI: Update Swift toolchain to 2025-02-26-a

Our new code htis assertion in 2024-10-30-a, but it's fixed in 2025-02-26-a.
---
 .github/workflows/test.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f87d3c5f5..1c8dae632 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -15,11 +15,11 @@ jobs:
             wasi-backend: Node
           - os: ubuntu-22.04
             toolchain:
-              download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a-ubuntu22.04.tar.gz
+              download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a-ubuntu22.04.tar.gz
             wasi-backend: Node
           - os: ubuntu-22.04
             toolchain:
-              download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a-ubuntu22.04.tar.gz
+              download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a-ubuntu22.04.tar.gz
             wasi-backend: Node
 
     runs-on: ${{ matrix.entry.os }}
@@ -69,7 +69,7 @@ jobs:
         entry:
           - os: ubuntu-22.04
             toolchain:
-              download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a-ubuntu22.04.tar.gz
+              download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a-ubuntu22.04.tar.gz
     steps:
       - uses: actions/checkout@v4
       - uses: ./.github/actions/install-swift

From 7a7acb44ea71c58a9ccdb2a6e6f95059d8e624d1 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 08:02:02 +0000
Subject: [PATCH 17/19] Concurrency: Remove unnecessary `sending` keyword

---
 Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
index c1f0361da..c075c63e5 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
@@ -194,7 +194,7 @@ private func makeAsyncClosure(
 @_cdecl("_call_host_function_impl")
 func _call_host_function_impl(
     _ hostFuncRef: JavaScriptHostFuncRef,
-    _ argv: sending UnsafePointer<RawJSValue>, _ argc: Int32,
+    _ argv: UnsafePointer<RawJSValue>, _ argc: Int32,
     _ callbackFuncRef: JavaScriptObjectRef
 ) -> Bool {
     guard let (_, hostFunc) = JSClosure.sharedClosures.wrappedValue[hostFuncRef] else {

From 18ad4e3be8465167af62172b67d64da2fdaab3e2 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 08:13:18 +0000
Subject: [PATCH 18/19] Swift 6.1 and later uses .xctest for XCTest bundle

---
 Makefile | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 1b653315c..88f4e0795 100644
--- a/Makefile
+++ b/Makefile
@@ -21,11 +21,18 @@ test:
 	    CONFIGURATION=release SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS)" $(MAKE) test && \
 	    CONFIGURATION=release SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS) -Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" $(MAKE) test
 
+TEST_RUNNER := node --experimental-wasi-unstable-preview1 scripts/test-harness.mjs
 .PHONY: unittest
 unittest:
 	@echo Running unit tests
 	swift build --build-tests -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor -Xlinker --export-if-defined=main -Xlinker --export-if-defined=__main_argc_argv --static-swift-stdlib -Xswiftc -static-stdlib $(SWIFT_BUILD_FLAGS)
-	node --experimental-wasi-unstable-preview1 scripts/test-harness.mjs ./.build/debug/JavaScriptKitPackageTests.wasm
+# Swift 6.1 and later uses .xctest for XCTest bundle but earliers used .wasm
+# See https://github.com/swiftlang/swift-package-manager/pull/8254
+	if [ -f .build/debug/JavaScriptKitPackageTests.xctest ]; then \
+		$(TEST_RUNNER) .build/debug/JavaScriptKitPackageTests.xctest; \
+	else \
+		$(TEST_RUNNER) .build/debug/JavaScriptKitPackageTests.wasm; \
+	fi
 
 .PHONY: benchmark_setup
 benchmark_setup:

From 3f3b494adf034ec72b24c577f3bd3a11d7ae8a2b Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Wed, 5 Mar 2025 08:34:53 +0000
Subject: [PATCH 19/19] Concurrency: Explicitly mark `Sendable` conformance as
 unavailable for `JSValue`

---
 Sources/JavaScriptKit/JSValue.swift | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift
index 1efffe484..2562daac8 100644
--- a/Sources/JavaScriptKit/JSValue.swift
+++ b/Sources/JavaScriptKit/JSValue.swift
@@ -100,6 +100,13 @@ public enum JSValue: Equatable {
     }
 }
 
+/// JSValue is intentionally not `Sendable` because accessing a JSValue living in a different
+/// thread is invalid. Although there are some cases where Swift allows sending a non-Sendable
+/// values to other isolation domains, not conforming `Sendable` is still useful to prevent
+/// accidental misuse.
+@available(*, unavailable)
+extension JSValue: Sendable {}
+
 public extension JSValue {
 #if !hasFeature(Embedded)
     /// An unsafe convenience method of `JSObject.subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?`