@@ -57,7 +57,28 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
5757 }
5858
5959 /// A singleton instance of the Executor
60- public static let shared : JavaScriptEventLoop = {
60+ public static var shared : JavaScriptEventLoop {
61+ return _shared
62+ }
63+
64+ #if _runtime(_multithreaded)
65+ // In multi-threaded environment, we have an event loop executor per
66+ // thread (per Web Worker). A job enqueued in one thread should be
67+ // executed in the same thread under this global executor.
68+ private static var _shared : JavaScriptEventLoop {
69+ if let tls = swjs_thread_local_event_loop {
70+ let eventLoop = Unmanaged < JavaScriptEventLoop > . fromOpaque ( tls) . takeUnretainedValue ( )
71+ return eventLoop
72+ }
73+ let eventLoop = create ( )
74+ swjs_thread_local_event_loop = Unmanaged . passRetained ( eventLoop) . toOpaque ( )
75+ return eventLoop
76+ }
77+ #else
78+ private static let _shared : JavaScriptEventLoop = create ( )
79+ #endif
80+
81+ private static func create( ) -> JavaScriptEventLoop {
6182 let promise = JSPromise ( resolver: { resolver -> Void in
6283 resolver ( . success( . undefined) )
6384 } )
@@ -79,9 +100,13 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
79100 }
80101 )
81102 return eventLoop
82- } ( )
103+ }
83104
84105 private static var didInstallGlobalExecutor = false
106+ fileprivate static var _mainThreadEventLoop : JavaScriptEventLoop !
107+ fileprivate static var mainThreadEventLoop : JavaScriptEventLoop {
108+ return _mainThreadEventLoop
109+ }
85110
86111 /// Set JavaScript event loop based executor to be the global executor
87112 /// Note that this should be called before any of the jobs are created.
@@ -91,6 +116,10 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
91116 public static func installGlobalExecutor( ) {
92117 guard !didInstallGlobalExecutor else { return }
93118
119+ // NOTE: We assume that this function is called before any of the jobs are created, so we can safely
120+ // assume that we are in the main thread.
121+ _mainThreadEventLoop = JavaScriptEventLoop . shared
122+
94123 #if compiler(>=5.9)
95124 typealias swift_task_asyncMainDrainQueue_hook_Fn = @convention ( thin) ( swift_task_asyncMainDrainQueue_original , swift_task_asyncMainDrainQueue_override ) -> Void
96125 let swift_task_asyncMainDrainQueue_hook_impl : swift_task_asyncMainDrainQueue_hook_Fn = { _, _ in
@@ -121,10 +150,10 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
121150
122151 typealias swift_task_enqueueMainExecutor_hook_Fn = @convention ( thin) ( UnownedJob , swift_task_enqueueMainExecutor_original ) -> Void
123152 let swift_task_enqueueMainExecutor_hook_impl : swift_task_enqueueMainExecutor_hook_Fn = { job, original in
124- JavaScriptEventLoop . shared . unsafeEnqueue ( job)
153+ JavaScriptEventLoop . enqueueMainJob ( job)
125154 }
126155 swift_task_enqueueMainExecutor_hook = unsafeBitCast ( swift_task_enqueueMainExecutor_hook_impl, to: UnsafeMutableRawPointer ? . self)
127-
156+
128157 didInstallGlobalExecutor = true
129158 }
130159
@@ -159,6 +188,21 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
159188 public func asUnownedSerialExecutor( ) -> UnownedSerialExecutor {
160189 return UnownedSerialExecutor ( ordinary: self )
161190 }
191+
192+ public static func enqueueMainJob( _ job: consuming ExecutorJob ) {
193+ self . enqueueMainJob ( UnownedJob ( job) )
194+ }
195+
196+ static func enqueueMainJob( _ job: UnownedJob ) {
197+ let currentEventLoop = JavaScriptEventLoop . shared
198+ if currentEventLoop === JavaScriptEventLoop . mainThreadEventLoop {
199+ currentEventLoop. unsafeEnqueue ( job)
200+ } else {
201+ // Notify the main thread to execute the job
202+ let jobBitPattern = unsafeBitCast ( job, to: UInt . self)
203+ _ = JSObject . global. postMessage!( jobBitPattern)
204+ }
205+ }
162206}
163207
164208#if compiler(>=5.7)
0 commit comments