Skip to content

Close Collection before process.exit() to prevent Windows libuv assertion crash#5

Open
HeyItsGilbert wants to merge 3 commits into
callumalpass:mainfrom
HeyItsGilbert:winFileLock
Open

Close Collection before process.exit() to prevent Windows libuv assertion crash#5
HeyItsGilbert wants to merge 3 commits into
callumalpass:mainfrom
HeyItsGilbert:winFileLock

Conversation

@HeyItsGilbert

@HeyItsGilbert HeyItsGilbert commented Mar 1, 2026

Copy link
Copy Markdown

Problem

On Windows, running any command that opens a Collection (e.g. mdbase types create reviews) triggers a libuv assertion failure on exit:

Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76

This happens because Collection.open() initializes an in-memory sql.js/WASM SQLite database with prepared statements. When process.exit() is called without first closing the collection, Node tears down the event loop while these handles are still active. On Linux/macOS this is silently ignored; on Windows, libuv's stricter handle lifecycle checking triggers a fatal assertion.

Root Cause

Every command that calls Collection.open() was calling process.exit(N) directly without first calling collection.close(). The close() method flushes the SQLite cache to disk, frees prepared statements, and closes the database — all necessary for a clean shutdown.

Only collections files had proper cleanup via try/finally with await collection.close().

Solution

  1. Added closeAndExit() utility in utils.ts — a helper that gracefully closes a Collection (if provided) then calls process.exit().

  2. Replaced ~70 bare process.exit() calls across all 16 command files that open a Collection:

    • types.ts (the reported crash site)
    • create.ts
    • update.ts
    • read.ts
    • delete.ts
    • rename.ts
    • query.ts
    • validate.ts
    • lint.ts
    • fmt.ts
    • diff.ts
    • stats.ts
    • export.ts
    • import.ts
    • graph.ts
    • schema.ts
    • base.ts
    • watch.ts (cleanup handler)
  3. process.exit() calls before any Collection is opened (e.g. in init, collections, early argument validation) are left unchanged — they have no handles to clean up.

Secondary benefit

Beyond fixing the Windows crash, this also ensures the SQLite cache is properly flushed to disk on every exit path. Previously, cache updates from mutations (create, update, delete, rename, createType, import, lint --fix, fmt) were silently lost because the in-memory database was never written back.

How closeAndExit works

export async function closeAndExit(
  collection: { close(): Promise<void> } | null | undefined,
  code: number,
): Promise<never> {
  if (collection) {
    try {
      await collection.close();
    } catch {
      // Ignore cleanup errors — we're exiting anyway.
    }
  }
  process.exit(code);
}

Note

Tests that spawn the CLI via execFileSync may need timeout adjustments since collection.close() adds a small amount of async work before exit. The existing 30s testTimeout in vitest.config.ts should be sufficient.

Fixes #3

* Updated multiple command files to use `closeAndExit` for graceful shutdown.
* This change ensures that resources are properly released before exiting, preventing potential assertion failures on Windows.
* Affected files include: base.ts, create.ts, delete.ts, diff.ts, export.ts, fmt.ts, graph.ts, import.ts, lint.ts, query.ts, read.ts, rename.ts, schema.ts, stats.ts, types.ts, update.ts, validate.ts, watch.ts.
* Updated `parseFields` function in `create.ts`, `query.ts`, and `update.ts` to use `await closeAndExit` instead of `process.exit`.
* This change improves the handling of process termination by allowing for cleanup operations.
@HeyItsGilbert
HeyItsGilbert marked this pull request as ready for review March 1, 2026 15:44
@HeyItsGilbert
HeyItsGilbert marked this pull request as draft March 1, 2026 15:45
@HeyItsGilbert
HeyItsGilbert marked this pull request as ready for review March 1, 2026 15:45
@HeyItsGilbert
HeyItsGilbert marked this pull request as draft March 1, 2026 16:20
- Replace direct `process.exit()` call with a delayed exit to allow libuv close-callbacks to complete.
- Ensure `closeAndExit` returns a `Promise<never>` to prevent further execution after calling.
@HeyItsGilbert
HeyItsGilbert marked this pull request as ready for review March 1, 2026 20:20
@HeyItsGilbert

Copy link
Copy Markdown
Author

Pulled from Codespace to my local machine (and had to add the latest commit) but here's a screenshot of it working on my windows laptop now.
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fresh install fails on type creation

1 participant