@@ -57,7 +57,28 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
57
57
}
58
58
59
59
/// 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 {
61
82
let promise = JSPromise ( resolver: { resolver -> Void in
62
83
resolver ( . success( . undefined) )
63
84
} )
@@ -79,9 +100,13 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
79
100
}
80
101
)
81
102
return eventLoop
82
- } ( )
103
+ }
83
104
84
105
private static var didInstallGlobalExecutor = false
106
+ fileprivate static var _mainThreadEventLoop : JavaScriptEventLoop !
107
+ fileprivate static var mainThreadEventLoop : JavaScriptEventLoop {
108
+ return _mainThreadEventLoop
109
+ }
85
110
86
111
/// Set JavaScript event loop based executor to be the global executor
87
112
/// Note that this should be called before any of the jobs are created.
@@ -91,6 +116,10 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
91
116
public static func installGlobalExecutor( ) {
92
117
guard !didInstallGlobalExecutor else { return }
93
118
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
+
94
123
#if compiler(>=5.9)
95
124
typealias swift_task_asyncMainDrainQueue_hook_Fn = @convention ( thin) ( swift_task_asyncMainDrainQueue_original , swift_task_asyncMainDrainQueue_override ) -> Void
96
125
let swift_task_asyncMainDrainQueue_hook_impl : swift_task_asyncMainDrainQueue_hook_Fn = { _, _ in
@@ -121,10 +150,10 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
121
150
122
151
typealias swift_task_enqueueMainExecutor_hook_Fn = @convention ( thin) ( UnownedJob , swift_task_enqueueMainExecutor_original ) -> Void
123
152
let swift_task_enqueueMainExecutor_hook_impl : swift_task_enqueueMainExecutor_hook_Fn = { job, original in
124
- JavaScriptEventLoop . shared . unsafeEnqueue ( job)
153
+ JavaScriptEventLoop . enqueueMainJob ( job)
125
154
}
126
155
swift_task_enqueueMainExecutor_hook = unsafeBitCast ( swift_task_enqueueMainExecutor_hook_impl, to: UnsafeMutableRawPointer ? . self)
127
-
156
+
128
157
didInstallGlobalExecutor = true
129
158
}
130
159
@@ -159,6 +188,21 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
159
188
public func asUnownedSerialExecutor( ) -> UnownedSerialExecutor {
160
189
return UnownedSerialExecutor ( ordinary: self )
161
190
}
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
+ }
162
206
}
163
207
164
208
#if compiler(>=5.7)
0 commit comments