> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# pool

- **Type:**

```ts
export type RstestPoolType = 'forks' | 'threads' | 'vmForks' | 'vmThreads';

export type RstestPoolOptions = {
  /** Worker pool used to run tests. */
  type?: RstestPoolType;
  /** Maximum number or percentage of workers to run tests in. */
  maxWorkers?: number | string;
  /**
   * Memory threshold used to recycle a worker after it finishes a test file.
   * This is a worker-recycling threshold, not a hard process RSS limit.
   * Values in `(0, 1]` are fractions of system memory; larger numbers are bytes,
   * and strings may use `%`, `MB`, `MiB`, `GB`, or `GiB` suffixes.
   * Uses RSS for `forks` with `isolate: false`, and V8 heap for VM pools.
   * Ignored by `threads` and isolated `forks`.
   * @default undefined (`system memory / maxWorkers` for VM pools)
   */
  memoryLimit?: number | string;
  /** Pass additional Node.js arguments to workers. */
  execArgv?: string[];
};

export type RstestConfig = {
  /** Worker pool used to run tests. */
  pool?: RstestPoolType | RstestPoolOptions;
};
```

- **Default:**

```ts
const defaultPool = {
  type: 'forks',
  // maxWorkers is computed from CPU count and command mode
};
```

- **CLI:** `--pool <type>`, `--pool.type <type>`, `--pool.maxWorkers <value>`, `--pool.memoryLimit <limit>`, `--pool.execArgv <arg>`

Configure the worker pool Rstest uses to run tests, including isolation, concurrency, and memory recycling thresholds.

## Choose a pool type

Start with the default `forks` pool. It introduces the fewest execution restrictions of the four pools and offers broader compatibility with Node.js APIs and native addons. The default [`isolate: true`](/config/test/isolate.md) also gives each test file a separate process, preventing process-level state from persisting between files and containing native crashes within the child process.

If performance does not meet your expectations, first identify where the time is spent. The `rstest-debugging` skill can help; see [Profiling](/guide/debug/profiling.md) for installation and analysis guidance. If worker startup or module loading is the bottleneck, consider these options:

| Scenario                                                                                    | Pool to try | Check before switching                                                               |
| ------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------ |
| Short test files spend a large share of their time starting workers                         | `threads`   | Tests and dependencies support worker-thread API and native-addon restrictions       |
| Many files repeatedly start workers and load and compile dependencies                       | `vmThreads` | Tests support worker-thread and cross-realm restrictions                             |
| Worker reuse is desirable, but you also need process APIs or easier memory-pressure control | `vmForks`   | Tests tolerate process state persisting within a worker and cross-realm restrictions |

VM pools reuse workers and compilation resources, which can benefit suites with many files or large setup dependencies. Setup files, test environment creation, and module evaluation still run for every file, however. Switching to a VM pool will not eliminate database initialization or network requests performed in setup. Use your project's runtime and memory measurements to make the final choice.

## Pool types

`forks` and `threads` run tests in child processes and worker threads, respectively. `vmForks` and `vmThreads` reuse those workers while creating a fresh `vm.Context` for each file.

All pools serialize environment options before sending them to workers, so functions are not supported in those options. See [`testEnvironment.options`](/config/test/test-environment.md#environment-options) for the requirements.

### forks

Creates Node.js child processes with `child_process.fork`. Each worker has separate process memory and process-level state. By default, every file gets a new process; with [`isolate: false`](/config/test/isolate.md), multiple files can reuse the same child process.

With the default isolation setting, each file incurs process startup and initialization costs.


**CLI**

```bash
npx rstest --pool forks
```


**rstest.config.ts**

```ts
import { defineConfig } from '@rstest/core';

export default defineConfig({
  pool: 'forks',
});
```


### threads


[Added in v0.10.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.10.0)

Runs tests through `node:worker_threads`. Each worker has its own V8 isolate and heap, but all threads share one operating-system process. RSS therefore measures memory usage across the entire process.

Threads generally start faster than child processes, but have some Node.js API restrictions. The following differences apply to both `threads` and `vmThreads`:

- `process.chdir()`, `process.abort()`, and methods that change user or group IDs are unavailable; `process.title` cannot be changed.
- OS signals are not delivered through `process.on()`, and `process.exit()` terminates only the worker thread.
- Native addons must support use in worker threads. A native crash can affect the entire process.

See the [Node.js Worker documentation](https://nodejs.org/api/worker_threads.html#class-worker) for the full list of differences.


**CLI**

```bash
npx rstest --pool threads
```


**rstest.config.ts**

```ts
import { defineConfig } from '@rstest/core';

export default defineConfig({
  pool: 'threads',
});
```


### vmForks


[Added in v0.12.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.12.0)

Reuses child processes and creates a fresh `vm.Context` for each test file, reducing repeated process startup. Process APIs such as `process.chdir()` remain available, but files in the same child process can share process-level state.


**CLI**

```bash
npx rstest --pool vmForks
```


**rstest.config.ts**

```ts
import { defineConfig } from '@rstest/core';

export default defineConfig({
  pool: 'vmForks',
});
```


### vmThreads


[Added in v0.12.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.12.0)

Reuses worker threads and creates a fresh `vm.Context` for each test file, reducing repeated thread startup. It provides the same VM isolation as `vmForks`, with the worker-thread restrictions described above.

For the isolation scope and compatibility requirements of both VM pools, see [VM pool behavior](#vm-pool-behavior) at the end of this page.


**CLI**

```bash
npx rstest --pool vmThreads --pool.memoryLimit 256MB
```


**rstest.config.ts**

```ts
import { defineConfig } from '@rstest/core';

export default defineConfig({
  pool: {
    type: 'vmThreads',
    memoryLimit: '256MB',
  },
});
```


## Configure worker concurrency and memory

### `pool.maxWorkers`

`pool.maxWorkers` controls how many test files run at the same time. Lower it to reduce resource conflicts when tests share a database, port, or fixture directory.

The default is computed from the CPU count and the command mode. You can provide a positive integer or a percentage of the available CPU count.

```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';

export default defineConfig({
  pool: {
    maxWorkers: 1,
  },
});
```

You can also pass the value from the CLI:

```bash
npx rstest --pool.maxWorkers 1
```

Common values:

- `1`: run test files one at a time. This is the Rstest equivalent of Vitest's `fileParallelism: false` and Jest's `--runInBand`.
- `50%`: keep parallelism proportional to the available CPU count, which is useful on CI machines with shared capacity.
- A fixed number such as `4`: run at most 4 workers, regardless of the machine's CPU count.

`maxWorkers` is about file-level parallelism. It does not limit `test.concurrent` cases inside a single test file — use [`maxConcurrency`](/config/test/max-concurrency.md) for that.

### `pool.memoryLimit`


[Added in v0.12.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.12.0)

As a VM worker runs successive files, its memory usage can grow. `pool.memoryLimit` controls when to recycle it: after each file, Rstest checks the worker's V8 `heapUsed`. If it reaches the threshold, Rstest recycles the worker and runs subsequent files in a new one.

For `vmForks` and `vmThreads`, the default threshold is `system memory / maxWorkers`.

For `forks` with `isolate: false`, an explicit `memoryLimit` checks the child process's RSS instead. There is no limit by default. With `isolate: true`, fork workers are already discarded after each file, so this option is ignored. Ordinary `threads` also ignore it because RSS measures the entire process, not an individual worker thread.

To set a threshold, use one of these formats:

- A number: values in `(0, 1]` are fractions of system memory; values greater than `1` are bytes.
- A string: supports `%`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, such as `'25%'` or `'256MB'`.

For example, this configuration runs up to 4 VM workers. After each file, a worker whose heap usage reaches `256MB` is recycled:

```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';

export default defineConfig({
  pool: {
    type: 'vmThreads',
    maxWorkers: 4,
    memoryLimit: '256MB',
  },
});
```

You can also set it from the CLI:

```bash
npx rstest --pool vmThreads --pool.memoryLimit 256MB
```

`memoryLimit` triggers recycling only between files. It is not a hard process RSS limit and cannot guarantee that a run avoids OOM.

It also bounds each VM worker's shared cache of immutable assets, external source, resolution results, and compilation data to the smaller of 64 MiB and one quarter of `memoryLimit`. Lowering the threshold can shrink this cache and increase loading and compilation work.

Separately, `vmForks` can use each child process's RSS to defer worker creation under memory pressure. This makes memory pressure easier to control than with the shared process RSS of `vmThreads`. This scheduling mechanism operates independently of `memoryLimit`; child processes still have additional overhead, so actual memory usage is not necessarily lower.

## Pass Node.js flags to workers

Use `pool.execArgv` to pass Node.js startup flags to workers. For example, this configuration enables the `development` condition:

```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';

export default defineConfig({
  pool: {
    execArgv: ['--conditions=development'],
  },
});
```

When debugging, combine it with `maxWorkers: 1` to run files one at a time and make execution easier to follow:

```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';

export default defineConfig({
  pool: {
    maxWorkers: 1,
    execArgv: ['--inspect-brk'],
  },
});
```

## VM pool behavior

These restrictions apply to both `vmForks` and `vmThreads`.

### Isolation and cleanup

Each file gets a fresh VM context, module graph, and test environment. Worker-scoped fixtures are also created and cleaned up per file, regardless of [`isolate`](/config/test/isolate.md). Workers are reused, however, so state outside the VM, such as `process.env` and native-addon state, may persist across files.

Tests must still await or cancel asynchronous work before the file finishes. Teardown clears timers managed by Rstest, but does not cancel arbitrary Promise chains, native I/O, or background tasks.

Pending wrapped `node:timers/promises` and promisified timeout/immediate operations are cancelled with `AbortError`. Cancellation may run user `catch` or `finally` handlers, so teardown does not guarantee that no user code runs afterward.

### Cross-realm assertions

Values from Node.js, workers, or DOM environments may use different constructors and fail `instanceof` checks in the current VM. Use `await` and content assertions, and check an error's `name`, `code`, or `message`.

### Module loading

Test and setup bundles, external JavaScript ESM, and CommonJS can run in the VM. Custom Node.js loaders and external TypeScript execution are unsupported; compile or bundle those files first.

Synchronous `require(esm)` requires Node.js 24.9+, VM graph support, and a dependency graph without top-level await. Otherwise, use dynamic `import()`. Native addons can only be loaded through direct CommonJS `require()`, and their state is outside VM isolation.


Module loading compatibility

VM loading differs from the native Node.js loader in the following ways:
| Loading path                               | Support and limits                                                                                                                                                                                                                                      |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Test/setup bundles and external JavaScript | Execute in the file VM, including static and dynamic imports. Setup and evaluated module state are recreated per file.                                                                                                                                  |
| CommonJS named exports                     | Statically detected own exports, including non-enumerable properties and getters, are read from the original `module.exports` receiver. They are snapshots, not live bindings; `interopDefault` still applies.                                          |
| JSON, WebAssembly, and `data:` URLs        | JSON ESM imports require `type: 'json'`. Asynchronous WebAssembly and JavaScript, JSON, and base64 WebAssembly `data:` URLs are supported. Synchronous Wasm `require()` is unsupported.                                                                 |
| Synchronous `require(esm)`                 | Requires Node.js 24.9+ and VM graph support. Async graphs throw `ERR_REQUIRE_ASYNC_MODULE`; older VM implementations reject ESM require with `ERR_REQUIRE_ESM`.                                                                                         |
| Native addons                              | Direct CommonJS `require()` delegates to Node.js. VM ESM imports of `.node` files are unsupported, and addon state is not VM-isolated.                                                                                                                  |
| CommonJS resolution                        | Node.js `require.resolve()` selects paths and export conditions. Rstest does not choose a fallback entry if the selected entry cannot run in the VM.                                                                                                    |
| `Module._cache`                            | Exposes the file's `require.cache` for inspecting and deleting CommonJS/JSON entries. Replacing or deleting `_cache` itself is unsupported. Synchronous `require(esm)` uses the VM ESM cache without CommonJS cache records or `module.children` links. |
