Skip to content

fix(db): give DbClient a close() so test teardown releases the handle - #327

Open
lovepixel-git wants to merge 1 commit into
CoreBunch:mainfrom
lovepixel-git:fix/db-client-close
Open

fix(db): give DbClient a close() so test teardown releases the handle#327
lovepixel-git wants to merge 1 commit into
CoreBunch:mainfrom
lovepixel-git:fix/db-client-close

Conversation

@lovepixel-git

Copy link
Copy Markdown

Refs #284. Addresses the DbClient/SQLite half of that report, which accounts for the bulk of the Windows failures. The path-as-URL bugs and the rest are separate changes, as the reporter proposed.

Root cause

DbClient exposed no way to release its backing resource, so a file-backed SQLite database stayed open for the life of the process. Test teardown then removes the temp directory holding that file. POSIX tolerates unlinking an open file; Windows returns EBUSY. src/__tests__/helpers/createTestDb.ts is imported by 26 test files, which is where most of the reported failures come from.

The helper's own comments already described the defect:

bun:sqlite doesn't expose a close() method on our DbClient interface; on macOS/Linux the file can still be deleted while the handle is open

and the Postgres branch carried a matching TODO: extend DbClient with a close() method to properly terminate the Postgres connection pool. Both are resolved here rather than restated.

CI is ubuntu-latest on every job, so nothing upstream exercises this.

Change

close(): Promise<void> on the interface, implemented by both adapters. SQLite closes the database, which also releases the WAL and SHM siblings created by PRAGMA journal_mode = WAL. Postgres closes the pool.

Teardown closes before unlinking at all five sites that open a file-backed database: the shared helper, plus pluginScheduler, postTypeBuiltInFields, importEndpointGuidance and createDbClient. Every other test database in the repo is :memory:, so that is the complete set.

Two decisions worth your call

Required, not optional. I made close() required rather than close?(). CONTRIBUTING says to update the source of truth and all callers in the same change rather than add a compat path, and the Postgres TODO explicitly asked for pool termination, so implementing both adapters seemed closer to intent than a SQLite-only optional method. Happy to switch it to optional if you would rather keep the interface minimal.

Transaction-scoped clients. wrapSql backs both the owning client and the tx handed to a .transaction() callback. Closing the pool from inside a transaction would tear the connection out from under the enclosing sql.begin(), so I gated it: only the client returned by createPostgresClient closes the pool, and close() on a tx client is a documented no-op. Nothing calls it there today, but the shared wrapper made it reachable, so I would rather it be deliberate than latent.

createFakeDb also gains a two-line no-op close. The as DbClient fakes typecheck through assertion widening, so without it they would satisfy the type while throwing at runtime.

Tests

New: src/__tests__/db/dbClientClose.test.ts, four cases. The EBUSY symptom is not portable, so these assert the invariant instead: after close() the handle is released and later queries reject, close() is idempotent, the directory is removable once closed, and createTestDb's cleanup() closes before removing. That reproduces the regression cross-platform with no Windows machine.

  • bunx tsc -b exit 0
  • bun run lint clean
  • bun test 6534 pass, 1 fail

The one failure is pre-existing

Bundle size budgets > ContentPage-*.js stays under 87.9 kB, at 90043 B against a 90000 B budget. Not from this change: I stashed the whole diff, rebuilt clean main, and got the byte-identical 90043. It only appears once dist/ exists, so a run on a fresh clone will not show it. Flagging rather than folding it in, since it is a separate problem.

Overlap with #194

That PR contains a version of this change inside an 89-file feature branch, currently CONFLICTING and untouched since 2026-07-12. This is the focused version, mirroring how #192/#193/#202 landed separately. Glad to close this if you would rather take it there.

`DbClient` had no way to release its backing resource, so a file-backed
SQLite database stayed open for the life of the process. Test teardown
removes the temp directory holding that file, and Windows refuses to
unlink a file whose handle is still open, so the suite fails there with
EBUSY. `createTestDb` is imported by 26 test files, which accounts for
the bulk of the failures in CoreBunch#284.

The helper's own comments already documented the gap: createTestDb said
"bun:sqlite doesn't expose a close() method on our DbClient interface;
on macOS/Linux the file can still be deleted while the handle is open",
and its Postgres branch carried a TODO asking for pool termination.
Both are now resolved rather than restated.

`close()` is required rather than optional, per the pre-1.0 rule against
compat shims: the interface is the source of truth and both adapters
implement it in the same change. SQLite closes the database, releasing
the WAL and SHM siblings. Postgres closes the pool, but only on the
client that owns it — the transaction-scoped client handed to a
`.transaction()` callback borrows the pool, so closing it there would
tear the connection out from under the enclosing `sql.begin()`.

Teardown now closes before unlinking at all five sites that open a
file-backed database: the shared helper plus pluginScheduler,
postTypeBuiltInFields, importEndpointGuidance and createDbClient. Every
other test database in the repo is `:memory:`. `createFakeDb` gains a
no-op `close`, so the `as DbClient` fakes satisfy the interface at
runtime and not only through the type assertion.

The EBUSY symptom is not portable, so the new tests assert the invariant
instead: after close() the handle is released, later queries reject, and
the directory is removable. That reproduces cross-platform without a
Windows machine.

Refs CoreBunch#284
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.

1 participant