From 2fc19daeec561d18fff2c470d01751e64f47daac Mon Sep 17 00:00:00 2001 From: pfg Date: Fri, 14 Mar 2025 19:02:22 -0700 Subject: [PATCH] Update spawn docs to add timeout and resourceUsage (#18204) --- docs/api/spawn.md | 183 ++++++++++++++++++++++++++++-------- packages/bun-types/bun.d.ts | 38 +++++++- 2 files changed, 182 insertions(+), 39 deletions(-) diff --git a/docs/api/spawn.md b/docs/api/spawn.md index 4219e016c6..77bd57233b 100644 --- a/docs/api/spawn.md +++ b/docs/api/spawn.md @@ -77,6 +77,16 @@ console.log(text); // "const input = "hello world".repeat(400); ..." --- +- `ReadableStream` +- Use a readable stream as input. + +--- + +- `Blob` +- Use a blob as input. + +--- + - `number` - Read from the file with a given file descriptor. @@ -129,13 +139,13 @@ Configure the output stream by passing one of the following values to `stdout/st --- -- `Bun.file()` -- Write to the specified file. +- `"ignore"` +- Discard the output. --- -- `null` -- Write to `/dev/null`. +- `Bun.file()` +- Write to the specified file. --- @@ -174,7 +184,8 @@ const proc = Bun.spawn(["bun", "--version"]); proc.kill(); proc.killed; // true -proc.kill(); // specify an exit code +proc.kill(15); // specify a signal code +proc.kill("SIGTERM"); // specify a signal name ``` The parent `bun` process will not terminate until all child processes have exited. Use `proc.unref()` to detach the child process from the parent. @@ -184,6 +195,64 @@ const proc = Bun.spawn(["bun", "--version"]); proc.unref(); ``` +## Resource usage + +You can get information about the process's resource usage after it has exited: + +```ts +const proc = Bun.spawn(["bun", "--version"]); +await proc.exited; + +const usage = proc.resourceUsage(); +console.log(`Max memory used: ${usage.maxRSS} bytes`); +console.log(`CPU time (user): ${usage.cpuTime.user} µs`); +console.log(`CPU time (system): ${usage.cpuTime.system} µs`); +``` + +## Using AbortSignal + +You can abort a subprocess using an `AbortSignal`: + +```ts +const controller = new AbortController(); +const { signal } = controller; + +const proc = Bun.spawn({ + cmd: ["sleep", "100"], + signal, +}); + +// Later, to abort the process: +controller.abort(); +``` + +## Using timeout and killSignal + +You can set a timeout for a subprocess to automatically terminate after a specific duration: + +```ts +// Kill the process after 5 seconds +const proc = Bun.spawn({ + cmd: ["sleep", "10"], + timeout: 5000, // 5 seconds in milliseconds +}); + +await proc.exited; // Will resolve after 5 seconds +``` + +By default, timed-out processes are killed with the `SIGTERM` signal. You can specify a different signal with the `killSignal` option: + +```ts +// Kill the process with SIGKILL after 5 seconds +const proc = Bun.spawn({ + cmd: ["sleep", "10"], + timeout: 5000, + killSignal: "SIGKILL", // Can be string name or signal number +}); +``` + +The `killSignal` option also controls which signal is sent when an AbortSignal is aborted. + ## Inter-process communication (IPC) Bun supports direct inter-process communication channel between two `bun` processes. To receive messages from a spawned Bun subprocess, specify an `ipc` handler. @@ -233,11 +302,17 @@ process.send("Hello from child as string"); process.send({ message: "Hello from child as object" }); ``` -The `ipcMode` option controls the underlying communication format between the two processes: +The `serialization` option controls the underlying communication format between the two processes: - `advanced`: (default) Messages are serialized using the JSC `serialize` API, which supports cloning [everything `structuredClone` supports](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This does not support transferring ownership of objects. - `json`: Messages are serialized using `JSON.stringify` and `JSON.parse`, which does not support as many object types as `advanced` does. +To disconnect the IPC channel from the parent process, call: + +```ts +childProc.disconnect(); +``` + ### IPC between Bun & Node.js To use IPC between a `bun` process and a Node.js process, set `serialization: "json"` in `Bun.spawn`. This is because Node.js and Bun use different JavaScript engines with different object serialization formats. @@ -310,7 +385,7 @@ spawnSync echo hi 1.47 ms/iter (1.14 ms … 2.64 ms) 1.57 ms 2.37 ms ## Reference -A simple reference of the Spawn API and types are shown below. The real types have complex generics to strongly type the `Subprocess` streams with the options passed to `Bun.spawn` and `Bun.spawnSync`. For full details, find these types as defined [bun.d.ts](https://github.com/oven-sh/bun/blob/main/packages/bun-types/bun.d.ts). +A reference of the Spawn API and types are shown below. The real types have complex generics to strongly type the `Subprocess` streams with the options passed to `Bun.spawn` and `Bun.spawnSync`. For full details, find these types as defined [bun.d.ts](https://github.com/oven-sh/bun/blob/main/packages/bun-types/bun.d.ts). ```ts interface Bun { @@ -329,16 +404,25 @@ interface Bun { namespace SpawnOptions { interface OptionsObject { cwd?: string; - env?: Record; - stdin?: SpawnOptions.Readable; - stdout?: SpawnOptions.Writable; - stderr?: SpawnOptions.Writable; - onExit?: ( - proc: Subprocess, + env?: Record; + stdio?: [Writable, Readable, Readable]; + stdin?: Writable; + stdout?: Readable; + stderr?: Readable; + onExit?( + subprocess: Subprocess, exitCode: number | null, - signalCode: string | null, - error: Error | null, - ) => void; + signalCode: number | null, + error?: ErrorLike, + ): void | Promise; + ipc?(message: any, subprocess: Subprocess): void; + serialization?: "json" | "advanced"; + windowsHide?: boolean; + windowsVerbatimArguments?: boolean; + argv0?: string; + signal?: AbortSignal; + timeout?: number; + killSignal?: string | number; } type Readable = @@ -366,39 +450,62 @@ namespace SpawnOptions { | Request; } -interface Subprocess { +interface Subprocess extends AsyncDisposable { + readonly stdin: FileSink | number | undefined; + readonly stdout: ReadableStream | number | undefined; + readonly stderr: ReadableStream | number | undefined; + readonly readable: ReadableStream | number | undefined; readonly pid: number; - // the exact stream types here are derived from the generic parameters - readonly stdin: number | ReadableStream | FileSink | undefined; - readonly stdout: number | ReadableStream | undefined; - readonly stderr: number | ReadableStream | undefined; - readonly exited: Promise; - - readonly exitCode: number | undefined; - readonly signalCode: Signal | null; + readonly exitCode: number | null; + readonly signalCode: NodeJS.Signals | null; readonly killed: boolean; + kill(exitCode?: number | NodeJS.Signals): void; ref(): void; unref(): void; - kill(code?: number): void; + + send(message: any): void; + disconnect(): void; + resourceUsage(): ResourceUsage | undefined; } -interface SyncSubprocess { - readonly pid: number; - readonly success: boolean; - // the exact buffer types here are derived from the generic parameters - readonly stdout: Buffer | undefined; - readonly stderr: Buffer | undefined; +interface SyncSubprocess { + stdout: Buffer | undefined; + stderr: Buffer | undefined; + exitCode: number; + success: boolean; + resourceUsage: ResourceUsage; + signalCode?: string; + exitedDueToTimeout?: true; + pid: number; } -type ReadableSubprocess = Subprocess; -type WritableSubprocess = Subprocess<"pipe", any, any>; -type PipedSubprocess = Subprocess<"pipe", "pipe", "pipe">; -type NullSubprocess = Subprocess; +interface ResourceUsage { + contextSwitches: { + voluntary: number; + involuntary: number; + }; -type ReadableSyncSubprocess = SyncSubprocess<"pipe", "pipe">; -type NullSyncSubprocess = SyncSubprocess; + cpuTime: { + user: number; + system: number; + total: number; + }; + maxRSS: number; + + messages: { + sent: number; + received: number; + }; + ops: { + in: number; + out: number; + }; + shmSize: number; + signalCount: number; + swapCount: number; +} type Signal = | "SIGABRT" diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 4bb2cf96e5..af184e9ae3 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -6699,7 +6699,8 @@ declare module "bun" { * This is useful for aborting a subprocess when some other part of the * program is aborted, such as a `fetch` response. * - * Internally, this works by calling `subprocess.kill(1)`. + * If the signal is aborted, the process will be killed with the signal + * specified by `killSignal` (defaults to SIGTERM). * * @example * ```ts @@ -6718,6 +6719,41 @@ declare module "bun" { * ``` */ signal?: AbortSignal; + + /** + * The maximum amount of time the process is allowed to run in milliseconds. + * + * If the timeout is reached, the process will be killed with the signal + * specified by `killSignal` (defaults to SIGTERM). + * + * @example + * ```ts + * // Kill the process after 5 seconds + * const subprocess = Bun.spawn({ + * cmd: ["sleep", "10"], + * timeout: 5000, + * }); + * await subprocess.exited; // Will resolve after 5 seconds + * ``` + */ + timeout?: number; + + /** + * The signal to use when killing the process after a timeout or when the AbortSignal is aborted. + * + * @default "SIGTERM" (signal 15) + * + * @example + * ```ts + * // Kill the process with SIGKILL after 5 seconds + * const subprocess = Bun.spawn({ + * cmd: ["sleep", "10"], + * timeout: 5000, + * killSignal: "SIGKILL", + * }); + * ``` + */ + killSignal?: string | number; } type OptionsToSubprocess =