pydantic/monty · error · Error

pool is closed

Error message

pool is closed

What it means

`WorkerPool.checkout()` refuses to borrow a worker once the pool has been closed, because closing shuts down worker dispatch and no new session can be served. This is a fail-fast guard so you get a clear synchronous error rather than hangs or crashes inside `WorkerTransport.create`.

Source

Thrown at crates/monty-js/ts/worker/pool.ts:112

  private constructor(
    private readonly factory: WorkerFactory,
    private readonly maxWorkers: number,
    private readonly maxCheckouts: number | undefined,
  ) {}

  /** Creates the pool and prewarms `minWorkers` idle workers. */
  static async create(factory: WorkerFactory, options: WorkerPoolOptions = {}): Promise<WorkerPool> {
    const max = Math.max(1, options.maxWorkers ?? 4)
    const min = Math.min(Math.max(0, options.minWorkers ?? 1), max)
    const pool = new WorkerPool(factory, max, options.maxCheckoutsPerWorker)
    const warm = await Promise.all(Array.from({ length: min }, () => pool.spawn()))
    pool.idle.push(...warm)
    return pool
  }

  /** Borrows a worker and returns a session bound to it. */
  async checkout(config: WorkerSessionConfig = {}): Promise<MontySession> {
    if (this.closed) throw new Error('pool is closed')
    const slot = await this.acquire()
    let transport: WorkerTransport
    try {
      transport = await WorkerTransport.create(slot.worker.dispatch, config)
    } catch (err) {
      this.discard(slot)
      throw err
    }
    transport.onFinish = (reusable) => this.release(slot, reusable)
    return new MontySession(transport as unknown as SessionNative)
  }

  /** Terminates every worker and rejects anyone still waiting. */
  async close(): Promise<void> {
    this.closed = true
    for (const waiter of this.waiters.splice(0)) waiter.reject(new Error('pool is closed'))
    for (const slot of this.idle.splice(0)) {
      slot.worker.terminate()

View on GitHub (pinned to adc986b362)

Solutions

  1. Create a fresh pool with `createWorkerPool(modules)` (or `Monty.create()`) and call `checkout()` on that instead of the closed one.
  2. Move `checkout()` calls inside the lifetime of the pool (`await using pool = ...; ... checkout within that scope`).
  3. Track pool lifecycle in application code: set a flag or null out the reference when `close()` is awaited so late callers can be rerouted to a new pool.
  4. In long-running services, keep one pool per application lifetime and never close it per-request.

Example fix

// before
await using pool = await createWorkerPool(modules)
await pool.close()
const session = await pool.checkout() // throws

// after
await using pool = await createWorkerPool(modules)
const session = await pool.checkout()
await session.feedRun(code)
// pool closes automatically at scope end
Defensive patterns

Strategy: validation

Validate before calling

function ensureOpen(pool: { closed: boolean }) {
  if (pool.closed) throw new Error('attempt to checkout a closed pool')
}

Type guard

function isUsable(pool: WorkerPool | null | undefined): pool is WorkerPool {
  return pool != null && !pool.closed
}

Try / catch

try {
  session = await pool.checkout(config)
} catch (err) {
  if (err instanceof Error && err.message === 'pool is closed') {
    pool = await createWorkerPool(modules)
    session = await pool.checkout(config)
  } else throw err
}

Prevention

When it happens

Trigger: Calling `pool.checkout(config)` after `await pool.close()` (or after `await using` disposal of the pool) has completed.

Common situations: Reusing a pool object captured in a longer-lived variable after an `await using` scope ends; closing a pool in a shutdown handler while background tasks still hold a reference; accidentally closing in a test fixture teardown that runs before remaining assertions.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/58c889386d0c4096. Report an issue: GitHub.