@@ -110,6 +110,16 @@ import WASILibc
110
110
@available ( macOS 15 . 0 , iOS 18 . 0 , watchOS 11 . 0 , tvOS 18 . 0 , visionOS 2 . 0 , * ) // For `Atomic` and `TaskExecutor` types
111
111
public final class WebWorkerTaskExecutor : TaskExecutor {
112
112
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
+
113
123
/// A job worker dedicated to a single Web Worker thread.
114
124
///
115
125
/// ## Lifetime
@@ -348,20 +358,30 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
348
358
}
349
359
}
350
360
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
+ }
351
369
// Start worker threads via pthread_create.
352
370
for worker in workers {
353
371
// NOTE: The context must be allocated on the heap because
354
372
// `pthread_create` on WASI does not guarantee the thread is started
355
373
// immediately. The context must be retained until the thread is started.
356
374
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 ( )
358
378
let ret = pthread_create (
359
379
nil ,
360
380
nil ,
361
381
{ ptr in
362
382
// Cast to a optional pointer to absorb nullability variations between platforms.
363
383
let ptr : UnsafeMutableRawPointer ? = ptr
364
- let context = Unmanaged < Context > . fromOpaque ( ptr!) . takeRetainedValue ( )
384
+ let context = Unmanaged < Context > . fromOpaque ( ptr!) . takeUnretainedValue ( )
365
385
context. worker. start ( executor: context. executor)
366
386
// The worker is started. Throw JS exception to unwind the call stack without
367
387
// reaching the `pthread_exit`, which is called immediately after this block.
@@ -370,7 +390,10 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
370
390
} ,
371
391
ptr
372
392
)
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
+ }
374
397
}
375
398
// Wait until all worker threads are started and wire up messaging channels
376
399
// between the main thread and workers to notify job enqueuing events each other.
@@ -380,7 +403,9 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
380
403
var tid : pid_t
381
404
repeat {
382
405
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
+ )
384
409
}
385
410
tid = worker. tid. load ( ordering: . sequentiallyConsistent)
386
411
try await clock. sleep ( for: checkInterval)
0 commit comments