Skip to content

Commit 71e16e7

Browse files
Throw error if the worker thread creation fails
use-sites still can fallback to other task executors, so it should be a recoverable error rather than a fatal error.
1 parent 8603096 commit 71e16e7

File tree

1 file changed

+29
-4
lines changed

1 file changed

+29
-4
lines changed

Diff for: Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift

+29-4
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ import WASILibc
110110
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) // For `Atomic` and `TaskExecutor` types
111111
public final class WebWorkerTaskExecutor: TaskExecutor {
112112

113+
/// An error that occurs when spawning a worker thread fails.
114+
public struct SpawnError: Error {
115+
/// The reason for the error.
116+
public let reason: String
117+
118+
internal init(reason: String) {
119+
self.reason = reason
120+
}
121+
}
122+
113123
/// A job worker dedicated to a single Web Worker thread.
114124
///
115125
/// ## Lifetime
@@ -348,20 +358,30 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
348358
}
349359
}
350360
trace("Executor.start")
361+
362+
// Hold over-retained contexts until all worker threads are started.
363+
var contexts: [Unmanaged<Context>] = []
364+
defer {
365+
for context in contexts {
366+
context.release()
367+
}
368+
}
351369
// Start worker threads via pthread_create.
352370
for worker in workers {
353371
// NOTE: The context must be allocated on the heap because
354372
// `pthread_create` on WASI does not guarantee the thread is started
355373
// immediately. The context must be retained until the thread is started.
356374
let context = Context(executor: self, worker: worker)
357-
let ptr = Unmanaged.passRetained(context).toOpaque()
375+
let unmanagedContext = Unmanaged.passRetained(context)
376+
contexts.append(unmanagedContext)
377+
let ptr = unmanagedContext.toOpaque()
358378
let ret = pthread_create(
359379
nil,
360380
nil,
361381
{ ptr in
362382
// Cast to a optional pointer to absorb nullability variations between platforms.
363383
let ptr: UnsafeMutableRawPointer? = ptr
364-
let context = Unmanaged<Context>.fromOpaque(ptr!).takeRetainedValue()
384+
let context = Unmanaged<Context>.fromOpaque(ptr!).takeUnretainedValue()
365385
context.worker.start(executor: context.executor)
366386
// The worker is started. Throw JS exception to unwind the call stack without
367387
// reaching the `pthread_exit`, which is called immediately after this block.
@@ -370,7 +390,10 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
370390
},
371391
ptr
372392
)
373-
precondition(ret == 0, "Failed to create a thread")
393+
guard ret == 0 else {
394+
let strerror = String(cString: strerror(ret))
395+
throw SpawnError(reason: "Failed to create a thread (\(ret): \(strerror))")
396+
}
374397
}
375398
// Wait until all worker threads are started and wire up messaging channels
376399
// between the main thread and workers to notify job enqueuing events each other.
@@ -380,7 +403,9 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
380403
var tid: pid_t
381404
repeat {
382405
if workerInitStarted.duration(to: .now) > timeout {
383-
fatalError("Worker thread initialization timeout exceeded (\(timeout))")
406+
throw SpawnError(
407+
reason: "Worker thread initialization timeout exceeded (\(timeout))"
408+
)
384409
}
385410
tid = worker.tid.load(ordering: .sequentiallyConsistent)
386411
try await clock.sleep(for: checkInterval)

0 commit comments

Comments
 (0)