@@ -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
111111public 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