Close the connection when fetching array types fails - #1231
Open
Nitjsefnie wants to merge 2 commits into
Open
Nitjsefnie wants to merge 2 commits into
Nitjsefnie wants to merge 2 commits into
Conversation
fetchArrayTypes() awaited the types query and only then filled typeArrayMap. The types query's ReadyForQuery resolves it and, in the same synchronous call, goes on to execute(initial), so a new connection's first query was built while the map was still empty and an sql.array() parameter was bound as its element type, e.g. "malformed array literal" with an array cast. Fill the map inside the query's resolve, like fetchState() does, so it is populated before the initial query is built. Fixes porsager#789 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReadyForQuery discarded the promise returned by fetchArrayTypes(), so when the types query failed, e.g. a role that cannot read pg_type or a statement_timeout cancelling it, its rejection went unhandled and terminated the process. errored() had already rejected the waiting query, and the connection then went on to onopen() without array types, so the next sql.array() query on it was bound as its element type. A reserve waiting on the connection was even handed that connection. Settle the failure synchronously in the types query's reject instead: it marks the types as still needed and rejects a waiting reserve with the same error. ReadyForQuery then closes the connection rather than opening it, so queued queries go to a new connection that fetches the types again, and the awaited query is caught since nothing is left for it to report. Fixes porsager#1192 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A failed array-type lookup on a new connection left a rejected promise that no one handled, because
ReadyForQuerydiscarded the promise returned byfetchArrayTypes(). The Node process then exited. The connection was also handed to the pool with no array types. With this change, the failure is settled on the connection itself: the waiting query or reserve is rejected with the server's error, and the connection is closed, so the next connection fetches its types again.Related Issues and Pull Requests
Fixes #1192
Depends on #1230
Changes
src/connection.jsfetchArrayTypes(reserve): wraps the types query'sreject, matching theresolvewrapper in Store fetched array types before running the first query #1230. On failure it setsneedsTypesagain and rejects a waiting reserve.ReadyForQuerypasses the reserve in because it removes the reserve frominitialbefore the fetch starts, soerrored()never reaches it. The query is awaited through.catch(), so no rejection is left without a handler.ReadyForQuery: a connection that still needs its types is closed (if (needsTypes) return terminate()) instead of being opened. This happens synchronously, beforeonopen()can give the connection to queued work.errored()still rejects the waiting query with the database error, as before.tests/index.js: awithoutArrayTypes(fn)helper connects as a role that cannot readpg_catalog.pg_type, throughpsql, like the other superuser steps. Itsfinallyrestores the grant first. It recordsunhandledRejectionfor the duration of the test, except on Deno, which has no such hook and aborts on an unhandled rejection. Three cases use the helper:42501without an unhandled rejection, and a latersql.arrayquery must succeed once the grant is restored;max: 1client must both reject with42501;sql.reserve()must reject with42501.cjs/,cf/,deno/: regenerated withnpm run build, keeping only this change's hunks. A fresh build also rewrites output from411429eand9afd16dthat was never regenerated; that output is left out of this PR.Testing
.catch()leaves an unhandled42501;22P02;.catch()continuation makes the second queued query fail withCONNECTION_DESTROYED;permission denied for table pg_type, and passes with the fix.npm testsuite (esm, cjs, deno) passed, with all 268 tests, on Node 24 against PostgreSQL 17, set up like CI. On a heavily loaded host, some full runs failed in the listen tests and in a 500 ms timing test. Store fetched array types before running the first query #1230 without this change failed the same way on the same host, and the listen client runs withfetch_types: false, which never reaches this code.Follow-ups / Known Limitations
fetchArrayTypes(). Once Store fetched array types before running the first query #1230 merges, only the second commit remains.fetchState(), which runs thetarget_session_attrsquery, still wraps onlyresolve. So a failing state query still ends in an unhandled rejection. That path runs only on servers that do not reportin_hot_standbyat startup; it reproduced on PostgreSQL 13.Footer
Generated by Claude Opus 5.5 (brief, implementation, review)