Skip to content

Unify the installGlobalExecutor process for JavaScriptEventLoop and WebWorkerTaskExecutor #351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,18 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
}

private func unsafeEnqueue(_ job: UnownedJob) {
#if canImport(wasi_pthread) && compiler(>=6.1) && _runtime(_multithreaded)
guard swjs_get_worker_thread_id_cached() == SWJS_MAIN_THREAD_ID else {
// Notify the main thread to execute the job when a job is
// enqueued from a Web Worker thread but without an executor preference.
// This is usually the case when hopping back to the main thread
// at the end of a task.
let jobBitPattern = unsafeBitCast(job, to: UInt.self)
swjs_send_job_to_main_thread(jobBitPattern)
return
}
// If the current thread is the main thread, do nothing special.
#endif
insertJobQueue(job: job)
}

Expand Down
74 changes: 2 additions & 72 deletions Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -602,78 +602,8 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
internal func dumpStats() {}
#endif

// MARK: Global Executor hack

@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?

/// Installs a global executor that forwards jobs from Web Worker threads to the main thread.
///
/// This method sets up the necessary hooks to ensure proper task scheduling between
/// the main thread and worker threads. It must be called once (typically at application
/// startup) before using any `WebWorkerTaskExecutor` instances.
///
/// ## Example
///
/// ```swift
/// // At application startup
/// WebWorkerTaskExecutor.installGlobalExecutor()
///
/// // Later, create and use executor instances
/// let executor = try await WebWorkerTaskExecutor(numberOfThreads: 4)
/// ```
///
/// - Important: This method must be called from the main thread.
public static func installGlobalExecutor() {
MainActor.assumeIsolated {
installGlobalExecutorIsolated()
}
}

@MainActor
static func installGlobalExecutorIsolated() {
#if canImport(wasi_pthread) && compiler(>=6.1) && _runtime(_multithreaded)
// Ensure this function is called only once.
guard _mainThread == nil else { return }

_mainThread = pthread_self()
assert(swjs_get_worker_thread_id() == -1, "\(#function) must be called on the main thread")

_swift_task_enqueueGlobal_hook_original = swift_task_enqueueGlobal_hook

typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original)
-> Void
let swift_task_enqueueGlobal_hook_impl: swift_task_enqueueGlobal_hook_Fn = { job, base in
WebWorkerTaskExecutor.traceStatsIncrement(\.enqueueGlobal)
// Enter this block only if the current Task has no executor preference.
if pthread_equal(pthread_self(), WebWorkerTaskExecutor._mainThread) != 0 {
// If the current thread is the main thread, delegate the job
// execution to the original hook of JavaScriptEventLoop.
let original = unsafeBitCast(
WebWorkerTaskExecutor._swift_task_enqueueGlobal_hook_original,
to: swift_task_enqueueGlobal_hook_Fn.self
)
original(job, base)
} else {
// Notify the main thread to execute the job when a job is
// enqueued from a Web Worker thread but without an executor preference.
// This is usually the case when hopping back to the main thread
// at the end of a task.
WebWorkerTaskExecutor.traceStatsIncrement(\.sendJobToMainThread)
let jobBitPattern = unsafeBitCast(job, to: UInt.self)
swjs_send_job_to_main_thread(jobBitPattern)
}
}
swift_task_enqueueGlobal_hook = unsafeBitCast(
swift_task_enqueueGlobal_hook_impl,
to: UnsafeMutableRawPointer?.self
)
#else
fatalError("Unsupported platform")
#endif
}
@available(*, deprecated, message: "Not needed anymore, just use `JavaScriptEventLoop.installGlobalExecutor()`.")
public static func installGlobalExecutor() {}
}

/// Enqueue a job scheduled from a Web Worker thread to the main thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ import JavaScriptEventLoop
func swift_javascriptkit_activate_js_executor_impl() {
MainActor.assumeIsolated {
JavaScriptEventLoop.installGlobalExecutor()
#if canImport(wasi_pthread) && compiler(>=6.1) && _runtime(_multithreaded)
if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) {
WebWorkerTaskExecutor.installGlobalExecutor()
}
#endif
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ IMPORT_JS_FUNCTION(swjs_get_worker_thread_id, int, (void))

IMPORT_JS_FUNCTION(swjs_create_object, JavaScriptObjectRef, (void))

#define SWJS_MAIN_THREAD_ID -1

int swjs_get_worker_thread_id_cached(void);

/// Requests sending a JavaScript object to another worker thread.
Expand Down