Skip to content

Commit c6f4f49

Browse files
Add stackSize parameter to WebWorkerTaskExecutor and WebWorkerDedicatedExecutor
This is useful for extending the stack size of the worker threads as wasi-libc's default stack size is typically 128KB, which may not be sufficient for some workloads.
1 parent d3b26d3 commit c6f4f49

File tree

3 files changed

+27
-4
lines changed

3 files changed

+27
-4
lines changed

Diff for: Sources/JavaScriptEventLoop/WebWorkerDedicatedExecutor.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ public final class WebWorkerDedicatedExecutor: SerialExecutor {
3939
private let underlying: WebWorkerTaskExecutor
4040

4141
/// - Parameters:
42+
/// - stackSize: The stack size for each worker thread. Default is `nil` (use the platform default stack size).
4243
/// - timeout: The maximum time to wait for all worker threads to be started. Default is 3 seconds.
4344
/// - checkInterval: The interval to check if all worker threads are started. Default is 5 microseconds.
4445
/// - Throws: An error if any worker thread fails to initialize within the timeout period.
45-
public init(timeout: Duration = .seconds(3), checkInterval: Duration = .microseconds(5)) async throws {
46+
/// - Note: The default stack size of wasi-libc is typically 128KB.
47+
public init(stackSize: Int? = nil, timeout: Duration = .seconds(3), checkInterval: Duration = .microseconds(5)) async throws {
4648
let underlying = try await WebWorkerTaskExecutor(
4749
numberOfThreads: 1,
50+
stackSize: stackSize,
4851
timeout: timeout,
4952
checkInterval: checkInterval
5053
)

Diff for: Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift

+17-3
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
347347
self.workers = workers
348348
}
349349

350-
func start(timeout: Duration, checkInterval: Duration) async throws {
350+
func start(stackSize: Int?, timeout: Duration, checkInterval: Duration) async throws {
351351
#if canImport(wasi_pthread) && compiler(>=6.1) && _runtime(_multithreaded)
352352
class Context: @unchecked Sendable {
353353
let executor: WebWorkerTaskExecutor.Executor
@@ -375,9 +375,19 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
375375
let unmanagedContext = Unmanaged.passRetained(context)
376376
contexts.append(unmanagedContext)
377377
let ptr = unmanagedContext.toOpaque()
378+
var attr = pthread_attr_t()
379+
pthread_attr_init(&attr)
380+
// Set the stack size if specified.
381+
if let stackSize {
382+
let ret = pthread_attr_setstacksize(&attr, stackSize)
383+
guard ret == 0 else {
384+
let strerror = String(cString: strerror(ret))
385+
throw SpawnError(reason: "Failed to set stack size (\(stackSize)) for thread (\(ret): \(strerror))")
386+
}
387+
}
378388
let ret = pthread_create(
379389
nil,
380-
nil,
390+
&attr,
381391
{ ptr in
382392
// Cast to a optional pointer to absorb nullability variations between platforms.
383393
let ptr: UnsafeMutableRawPointer? = ptr
@@ -390,6 +400,7 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
390400
},
391401
ptr
392402
)
403+
pthread_attr_destroy(&attr)
393404
guard ret == 0 else {
394405
let strerror = String(cString: strerror(ret))
395406
throw SpawnError(reason: "Failed to create a thread (\(ret): \(strerror))")
@@ -467,16 +478,19 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
467478
///
468479
/// - Parameters:
469480
/// - numberOfThreads: The number of Web Worker threads to spawn.
481+
/// - stackSize: The stack size for each worker thread. Default is `nil` (use the platform default stack size).
470482
/// - timeout: The maximum time to wait for all worker threads to be started. Default is 3 seconds.
471483
/// - checkInterval: The interval to check if all worker threads are started. Default is 5 microseconds.
472484
/// - Throws: An error if any worker thread fails to initialize within the timeout period.
485+
/// - Note: The default stack size of wasi-libc is typically 128KB.
473486
public init(
474487
numberOfThreads: Int,
488+
stackSize: Int? = nil,
475489
timeout: Duration = .seconds(3),
476490
checkInterval: Duration = .microseconds(5)
477491
) async throws {
478492
self.executor = Executor(numberOfThreads: numberOfThreads)
479-
try await self.executor.start(timeout: timeout, checkInterval: checkInterval)
493+
try await self.executor.start(stackSize: stackSize, timeout: timeout, checkInterval: checkInterval)
480494
}
481495

482496
/// Terminates all worker threads managed by this executor.

Diff for: Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift

+6
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ final class WebWorkerTaskExecutorTests: XCTestCase {
122122
executor.terminate()
123123
}
124124

125+
func testThreadStackSize() async throws {
126+
// Sanity check for stackSize parameter
127+
let executor = try await WebWorkerTaskExecutor(numberOfThreads: 3, stackSize: 512 * 1024)
128+
executor.terminate()
129+
}
130+
125131
func testTaskGroupRunOnDifferentThreads() async throws {
126132
let executor = try await WebWorkerTaskExecutor(numberOfThreads: 2)
127133

0 commit comments

Comments
 (0)