Skip to content

fix(oracle): a failed connect closes the pool it created (#1102) - #1105

Merged
cevheri merged 2 commits into
libredb:mainfrom
sloemo01:fix/1102-oracle-pool-orphan
Sep 24, 2026
Merged

cevheri merged 2 commits into
libredb:mainfrom
sloemo01:fix/1102-oracle-pool-orphan

Conversation

@sloemo01

Copy link
Copy Markdown
Contributor

Resolves #1102.

What this is

OracleProvider.connect() built its pool, then tested it with a borrow. When that borrow failed, the catch rethrew without closing the pool and without clearing this.pool. factory.getOrCreateProvider() never caches a provider whose connect() threw, so nothing held a reference that could close it, and node-oracledb's background creator keeps reaching for poolMin connections with no delay between attempts — a spinning core and a connection flood that outlives the process's usefulness.

The catch now does what PostgreSQL and SQL Server already do:

const failedPool = this.pool;
this.pool = null;
await failedPool?.close(0).catch(() => {});

close(0) is the oracledb equivalent of end(). Clearing this.pool matters as much as closing: left set, the if (this.pool) guard at the top of connect() returns on a retry without dialling and without an error, so a caller that retries reads a silent success. The ticket lists this as a second, smaller defect from the same line, and one test arm covers it directly.

A close() that itself rejects is swallowed, so a cleanup failure never becomes the reason a connect was refused.

Tests, written first

Four arms in tests/integration/db/oracle-provider.test.ts, using the file's existing mockCreatePoolFn / mockPoolCloseFn doubles:

arm before the fix
a getConnection failure closes the pool it just created close called 0 times
the NJS-138 config path closes the pool too close called 0 times
a connect retried after a closed failure creates a new pool second connect() returned early
a close that itself rejects does not replace the connect error close never attempted

All four failed on current main (209 pass, 4 fail), all four pass with the fix (213 pass). The arms count close calls rather than asserting on this.pool alone, because a pool can be unreferenced and still running — which is the defect, and the reason a null check would have gone green on the broken code.

Two of the arms pin things the acceptance criteria ask for explicitly: the NJS-138 path (mapped instanceof DatabaseConfigError throws before the fix's code would have run in a naive ordering) and the error-identity rule that a rejected close must not replace the original connect error.

Docs

docs/providers/oracle.md gains §3.6, recording why a failed connect closes its own pool and the measured cost of not doing it, per the provider triad rule in CLAUDE.md.

Verification

  • bun tests/run-tests.ts tests/integration/db/oracle-provider.test.ts — 213 pass, and 100% line coverage on src/lib/db/providers/sql/oracle.ts (1012/1012, zero uncovered lines) from the lcov this PR produces.
  • bun run format clean, bun run lint 0 errors, bun run typecheck clean.

One thing worth stating plainly rather than burying: the repo's full suite does not go green in this environment, and not because of this change. On a clean origin/main with this branch absent, these files already fail: sqlite-provider (83), sqlite-driver (27), launcher-utils (6), object-edit-declarations (4), result-pagination-capability (4), object-source-declarations (3), object-column-declarations (3), agent-investigation-e2e (2), test-runner-cli (2), monaco-language-ids (1), agent-statement-boundary (1), and mongodb-provider exits without writing a report. The identical list appears with my change applied. Since those abort bun run test:coverage before the lcov merge, I verified the coverage gate for the file this PR touches instead of claiming the global gate passed.

`createPool` resolves before anything is dialled in Thin mode, so a connect that failed on its
test borrow left a live pool behind with no reference to it. `factory.getOrCreateProvider()`
never caches a provider whose `connect()` threw, so no later `disconnect()` could reach that
pool, and node-oracledb's background creator keeps reaching for `poolMin` connections with no
delay between attempts: ~14,000 TCP connects and one full CPU core per second, forever.

The catch now closes the pool with `close(0)` and clears `this.pool` before rethrowing, which
also fixes the silent retry - left set, the `if (this.pool)` guard returned without dialling and
without an error. A close that rejects is swallowed rather than replacing the connect error.
Same contract PostgreSQL and SQL Server already follow.

docs/providers/oracle.md gains the reason and the measured numbers, per the provider triad.
@sloemo01
sloemo01 force-pushed the fix/1102-oracle-pool-orphan branch from b1c6d21 to 81bf137 Compare September 23, 2026 22:44
@codecov

codecov Bot commented Sep 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cevheri cevheri added the bug Something isn't working label Sep 24, 2026

@cevheri cevheri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great, thanks
just one docs error(reference) : docs/providers/oracle.md:1572 #36-privilege-resilient-monitoring

Inserting the new §3.6 moved Privilege-resilient monitoring to §3.7; the reference at line 1587 still named the old anchor, which no longer exists.
@sloemo01

Copy link
Copy Markdown
Contributor Author

Fixed in 3bb16ca.

You are right, and it was a miss on my side: inserting the new section made the monitoring one §3.7, and the reference in §8 kept the old target. #36-privilege-resilient-monitoring no longer matches any heading, so the link went nowhere.

One line, at docs/providers/oracle.md:1587: §3.6 -> §3.7, #36-privilege-resilient-monitoring -> #37-privilege-resilient-monitoring.

Checked the rest of the file while I was in there: every other internal anchor resolves against a real heading, and there are no remaining §3.6 text references. The section itself is unchanged.

@cevheri
cevheri merged commit 3717ec0 into libredb:main Sep 24, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] A failed Oracle connect orphans its pool, which retries forever and pins a CPU core

2 participants