Skip to content

프로세스 생성 (Bun.spawn())

문자열 배열로 명령을 제공합니다. Bun.spawn() 의 결과는 Bun.Subprocess 객체입니다.

ts
const proc = Bun.spawn(["bun", "--version"]);
console.log(await proc.exited); // 0

Bun.spawn 의 두 번째 인수는 서브프로세스를 구성하는 데 사용할 수 있는 매개변수 객체입니다.

ts
const proc = Bun.spawn(["bun", "--version"], {
  cwd: "./path/to/subdir", // 작업 디렉토리 지정
  env: { ...process.env, FOO: "bar" }, // 환경 변수 지정
  onExit(proc, exitCode, signalCode, error) {
    // 종료 핸들러
  },
});

proc.pid; // 서브프로세스의 프로세스 ID

입력 스트림

기본적으로 서브프로세스의 입력 스트림은 정의되지 않으며 stdin 매개변수로 구성할 수 있습니다.

ts
const proc = Bun.spawn(["cat"], {
  stdin: await fetch("https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js"),
});

const text = await proc.stdout.text();
console.log(text); // "const input = "hello world".repeat(400); ..."
설명
null기본값. 서브프로세스에 입력 제공 안 함
"pipe"빠른 증분 쓰기를 위한 FileSink 반환
"inherit"부모 프로세스의 stdin 상속
Bun.file()지정된 파일에서 읽기
TypedArray | DataView이진 버퍼 입력 사용
Response응답 body 를 입력으로 사용
Request요청 body 를 입력으로 사용
ReadableStream읽기 가능한 스트림 입력 사용
Blobblob 입력 사용
number주어진 파일 디스크립터로 파일에서 읽기

"pipe" 옵션을 사용하면 부모 프로세스에서 서브프로세스의 입력 스트림에 증분적으로 쓸 수 있습니다.

ts
const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // 쓰기를 위한 FileSink 반환
});

// 문자열 데이터 인큐
proc.stdin.write("hello");

// 이진 데이터 인큐
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));

// 버퍼된 데이터 전송
proc.stdin.flush();

// 입력 스트림 닫기
proc.stdin.end();

ReadableStreamstdin 에 전달하면 JavaScript ReadableStream 의 데이터를 서브프로세스 입력으로 직접 파이프할 수 있습니다.

ts
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello from ");
    controller.enqueue("ReadableStream!");
    controller.close();
  },
});

const proc = Bun.spawn(["cat"], {
  stdin: stream,
  stdout: "pipe",
});

const output = await proc.stdout.text();
console.log(output); // "Hello from ReadableStream!"

출력 스트림

stdoutstderr 속성을 통해 서브프로세스에서 결과를 읽을 수 있습니다. 기본적으로 이들은 ReadableStream 의 인스턴스입니다.

ts
const proc = Bun.spawn(["bun", "--version"]);
const text = await proc.stdout.text();
console.log(text); // => "1.3.3\n"

다음 값 중 하나를 stdout/stderr 에 전달하여 출력 스트림을 구성합니다.

설명
"pipe"stdout 의 기본값. 출력을 반환된 Subprocess 객체의 ReadableStream 으로 파이프
"inherit"stderr 의 기본값. 부모 프로세스에서 상속
"ignore"출력 폐기
Bun.file()지정된 파일에 쓰기
number주어진 파일 디스크립터로 파일에 쓰기

종료 처리

onExit 콜백을 사용하여 프로세스가 종료되거나 강제 종료될 때 리스닝합니다.

ts
const proc = Bun.spawn(["bun", "--version"], {
  onExit(proc, exitCode, signalCode, error) {
    // 종료 핸들러
  },
});

편의상 exited 속성은 프로세스가 종료될 때 해결되는 Promise 입니다.

ts
const proc = Bun.spawn(["bun", "--version"]);

await proc.exited; // 프로세스 종료 시 해결
proc.killed; // 불리언 — 프로세스가 강제 종료되었는가?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...

프로세스를 종료하려면:

ts
const proc = Bun.spawn(["bun", "--version"]);
proc.kill();
proc.killed; // true

proc.kill(15); // 시그널 코드 지정
proc.kill("SIGTERM"); // 시그널 이름 지정

부모 bun 프로세스는 모든 자식 프로세스가 종료될 때까지 종료되지 않습니다. proc.unref() 를 사용하여 자식 프로세스를 부모에서 분리합니다.

ts
const proc = Bun.spawn(["bun", "--version"]);
proc.unref();

리소스 사용량

프로세스가 종료된 후 프로세스의 리소스 사용량에 대한 정보를 얻을 수 있습니다.

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`);

AbortSignal 사용

AbortSignal 을 사용하여 서브프로세스를 중단할 수 있습니다.

ts
const controller = new AbortController();
const { signal } = controller;

const proc = Bun.spawn({
  cmd: ["sleep", "100"],
  signal,
});

// 나중에 프로세스 중단:
controller.abort();

timeout 과 killSignal 사용

특정 시간 후에 자동으로 종료되도록 서브프로세스의 타임아웃을 설정할 수 있습니다.

ts
// 5 초 후 프로세스 종료
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // 밀리초 단위의 5 초
});

await proc.exited; // 5 초 후 해결

기본적으로 타임아웃된 프로세스는 SIGTERM 시그널로 종료됩니다. killSignal 옵션으로 다른 시그널을 지정할 수 있습니다.

ts
// 5 초 후 SIGKILL 로 프로세스 종료
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // 문자열 이름 또는 시그널 번호 가능
});

killSignal 옵션은 AbortSignal 이 중단될 때 전송되는 시그널도 제어합니다.

maxBuffer 사용

spawnSync 의 경우 프로세스가 종료되기 전에 출력의 최대 바이트 수를 제한할 수 있습니다.

ts
// 100 바이트 이상의 출력을 생성한 후 'yes' 종료
const result = Bun.spawnSync({
  cmd: ["yes"], // 또는 Windows 에서 ["bun", "exec", "yes"]
  maxBuffer: 100,
});
// 프로세스 종료

프로세스 간 통신 (IPC)

Bun 은 두 bun 프로세스 간 직접 프로세스 간 통신 채널을 지원합니다. 생성된 Bun 서브프로세스에서 메시지를 수신하려면 ipc 핸들러를 지정합니다.

ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * 서브 프로세스에서 받은 메시지
     **/
  },
});

부모 프로세스는 반환된 Subprocess 인스턴스의 .send() 메서드를 사용하여 서브프로세스에 메시지를 보낼 수 있습니다. 보내는 서브프로세스에 대한 참조는 ipc 핸들러의 두 번째 인수로도 사용할 수 있습니다.

ts
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * 서브 프로세스에서 받은 메시지
     **/
    childProc.send("Respond to child");
  },
});

childProc.send("I am your father"); // 부모도 자식에게 메시지 전송 가능

한편 자식 프로세스는 process.send() 를 사용하여 부모에게 메시지를 보내고 process.on("message") 를 사용하여 메시지를 받을 수 있습니다. 이는 Node.js 의 child_process.fork() 에 사용되는 것과 동일한 API 입니다.

ts
process.send("Hello from child as string");
process.send({ message: "Hello from child as object" });

process.on("message", message => {
  // 부모의 메시지 출력
  console.log(message);
});
ts
// 문자열 전송
process.send("Hello from child as string");

// 객체 전송
process.send({ message: "Hello from child as object" });

serialization 옵션은 두 프로세스 간 기본 통신 형식을 제어합니다.

  • advanced: (기본값) 메시지는 JSC serialize API 를 사용하여 직렬화되며, everything structuredClone supports 를 복제할 수 있습니다. 이는 객체 소유권 이전을 지원하지 않습니다.
  • json: 메시지는 JSON.stringifyJSON.parse 를 사용하여 직렬화되며, advanced 보다 많은 객체 타입을 지원하지 않습니다.

부모 프로세스에서 IPC 채널을 연결 해제하려면 다음을 호출합니다.

ts
childProc.disconnect();

Bun 과 Node.js 간 IPC

bun 프로세스와 Node.js 프로세스 간 IPC 를 사용하려면 Bun.spawn 에서 serialization: "json" 을 설정합니다. 이는 Node.js 와 Bun 이 서로 다른 JavaScript 엔진을 사용하며 서로 다른 객체 직렬화 형식을 사용하기 때문입니다.

js
if (typeof Bun !== "undefined") {
  const prefix = `[bun ${process.versions.bun} 🐇]`;
  const node = Bun.spawn({
    cmd: ["node", __filename],
    ipc({ message }) {
      console.log(message);
      node.send({ message: `${prefix} 👋 hey node` });
      node.kill();
    },
    stdio: ["inherit", "inherit", "inherit"],
    serialization: "json",
  });

  node.send({ message: `${prefix} 👋 hey node` });
} else {
  const prefix = `[node ${process.version}]`;
  process.on("message", ({ message }) => {
    console.log(message);
    process.send({ message: `${prefix} 👋 hey bun` });
  });
}

블로킹 API (Bun.spawnSync())

Bun 은 Bun.spawn 의 동기화된 버전인 Bun.spawnSync 를 제공합니다. 이는 Bun.spawn 과 동일한 입력과 매개변수를 지원하는 블로킹 API 입니다. SyncSubprocess 객체를 반환하며, 이는 몇 가지 측면에서 Subprocess 와 다릅니다.

  1. 프로세스가 0 종료 코드로 종료되었는지를 나타내는 success 속성이 있습니다.
  2. stdoutstderr 속성은 ReadableStream 대신 Buffer 의 인스턴스입니다.
  3. stdin 속성이 없습니다. 서브프로세스의 입력 스트림에 증분적으로 쓰려면 Bun.spawn 을 사용하세요.
ts
const proc = Bun.spawnSync(["echo", "hello"]);

console.log(proc.stdout.toString());
// => "hello\n"

경험적 규칙으로, 비동기 Bun.spawn API 는 HTTP 서버와 앱에 더 적합하며, Bun.spawnSync 는 명령줄 도구 구축에 더 적합합니다.


벤치마크

NOTE

⚡️ 내부적으로 `Bun.spawn` 과 `Bun.spawnSync` 는 [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html) 을 사용합니다.

Bun 의 spawnSync 는 Node.js child_process 모듈보다 60% 빠르게 프로세스를 생성합니다.

bash
bun spawn.mjs
txt
cpu: Apple M1 Max
runtime: bun 1.x (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi  888.14 µs/iter    (821.83 µs … 1.2 ms) 905.92 µs      1 ms   1.03 ms
sh
node spawn.node.mjs
txt
cpu: Apple M1 Max
runtime: node v18.9.1 (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi    1.47 ms/iter     (1.14 ms … 2.64 ms)   1.57 ms   2.37 ms   2.52 ms

참조

Spawn API 와 타입의 참조는 아래에 나와 있습니다. 실제 타입은 Bun.spawnBun.spawnSync 에 전달된 옵션으로 Subprocess 스트림을 강력하게 타입 지정하기 위한 복잡한 제네릭을 가지고 있습니다. 전체 세부 정보는 bun.d.ts 에서 정의된 이러한 타입을 찾으세요.

ts
interface Bun {
  spawn(command: string[], options?: SpawnOptions.OptionsObject): Subprocess;
  spawnSync(command: string[], options?: SpawnOptions.OptionsObject): SyncSubprocess;

  spawn(options: { cmd: string[] } & SpawnOptions.OptionsObject): Subprocess;
  spawnSync(options: { cmd: string[] } & SpawnOptions.OptionsObject): SyncSubprocess;
}

namespace SpawnOptions {
  interface OptionsObject {
    cwd?: string;
    env?: Record<string, string | undefined>;
    stdio?: [Writable, Readable, Readable];
    stdin?: Writable;
    stdout?: Readable;
    stderr?: Readable;
    onExit?(
      subprocess: Subprocess,
      exitCode: number | null,
      signalCode: number | null,
      error?: ErrorLike,
    ): void | Promise<void>;
    ipc?(message: any, subprocess: Subprocess): void;
    serialization?: "json" | "advanced";
    windowsHide?: boolean;
    windowsVerbatimArguments?: boolean;
    argv0?: string;
    signal?: AbortSignal;
    timeout?: number;
    killSignal?: string | number;
    maxBuffer?: number;
  }

  type Readable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // "ignore" 와 동일
    | undefined // 기본값 사용
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // "ignore" 와 동일
    | undefined // 기본값 사용
    | BunFile
    | ArrayBufferView
    | number
    | ReadableStream
    | Blob
    | Response
    | Request;
}

interface Subprocess extends AsyncDisposable {
  readonly stdin: FileSink | number | undefined;
  readonly stdout: ReadableStream<Uint8Array> | number | undefined;
  readonly stderr: ReadableStream<Uint8Array> | number | undefined;
  readonly readable: ReadableStream<Uint8Array> | number | undefined;
  readonly pid: number;
  readonly exited: Promise<number>;
  readonly exitCode: number | null;
  readonly signalCode: NodeJS.Signals | null;
  readonly killed: boolean;

  kill(exitCode?: number | NodeJS.Signals): void;
  ref(): void;
  unref(): void;

  send(message: any): void;
  disconnect(): void;
  resourceUsage(): ResourceUsage | undefined;
}

interface SyncSubprocess {
  stdout: Buffer | undefined;
  stderr: Buffer | undefined;
  exitCode: number;
  success: boolean;
  resourceUsage: ResourceUsage;
  signalCode?: string;
  exitedDueToTimeout?: true;
  pid: number;
}

interface ResourceUsage {
  contextSwitches: {
    voluntary: number;
    involuntary: number;
  };

  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"
  | "SIGALRM"
  | "SIGBUS"
  | "SIGCHLD"
  | "SIGCONT"
  | "SIGFPE"
  | "SIGHUP"
  | "SIGILL"
  | "SIGINT"
  | "SIGIO"
  | "SIGIOT"
  | "SIGKILL"
  | "SIGPIPE"
  | "SIGPOLL"
  | "SIGPROF"
  | "SIGPWR"
  | "SIGQUIT"
  | "SIGSEGV"
  | "SIGSTKFLT"
  | "SIGSTOP"
  | "SIGSYS"
  | "SIGTERM"
  | "SIGTRAP"
  | "SIGTSTP"
  | "SIGTTIN"
  | "SIGTTOU"
  | "SIGUNUSED"
  | "SIGURG"
  | "SIGUSR1"
  | "SIGUSR2"
  | "SIGVTALRM"
  | "SIGWINCH"
  | "SIGXCPU"
  | "SIGXFSZ"
  | "SIGBREAK"
  | "SIGLOST"
  | "SIGINFO";

Bun by www.bunjs.com.cn 편집