db-postgres: connect() rejects its `initializing` promise with no argument, producing an unhandledRejection with reason `undefined`
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 76/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- node.js, postgresql, typescript
Direzione di ricerca
Leggi packages/db-postgres/src/index.ts e packages/db-postgres/src/connect.ts, poi controlla il tipo di rejectInitializing in packages/drizzle/src/types.ts e il suo utilizzo in packages/drizzle/src/transactions/beginTransaction.ts. L’issue descrive due possibili soluzioni. Segui il percorso degli errori di connessione fino alla promise e il modo in cui beginTransaction gestisce il rifiuto. Il lavoro è concluso quando il rifiuto orfano non segnala più undefined e gli errori di connessione continuano a propagarsi.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Describe the Bug
@payloadcms/db-postgres rejects its internal initializing promise with no argument when connect() fails. Because the only thing that ever awaits that promise is beginTransaction, a failed connect on a request path that opens no transaction leaves the rejection unhandled — and since the reason is undefined, Node attaches no stack to it.
The result is an unhandledRejection with reason === undefined. In an app with Sentry (or any unhandledRejection reporter) wired up, that arrives as an event with no message, no stack and no in-app frames, pointing only at the reporter's own handler. It is effectively undebuggable from the report: nothing in it names Payload, Postgres, or the connection at all.
The two lines, at v3.90.2:
-
packages/db-postgres/src/index.ts#L80-L82— the promise is created and no handler is attached:const initializing = new Promise<void>((res, rej) => { resolveInitializing = res rejectInitializing = rej }) -
packages/db-postgres/src/connect.ts#L109-L111— it is rejected with nothing, and then a separate realErroris thrown:if (typeof this.rejectInitializing === 'function') { this.rejectInitializing() // ← no argument ⇒ rejects with `undefined` } throw new Error(`Error: cannot connect to Postgres: ${err.message}`)
Still present on main (packages/db-postgres/src/connect.ts, this.rejectInitializing() immediately before the throw). The type itself guarantees it can never carry a reason — rejectInitializing: () => void, declared with no parameter, in packages/drizzle/src/types.ts and the db-postgres types.
The only consumer is packages/drizzle/src/transactions/beginTransaction.ts (await this.initializing). packages/drizzle/src/destroy.ts replaces the promise rather than awaiting it. So on any read path that does not begin a transaction, nothing is attached when the rejection lands.
Two suggested fixes, either of which is sufficient:
- Pass the error:
this.rejectInitializing(err), and widen the type to(err?: unknown) => void. The rejection then at least carries a stack for whoever is listening. - Attach a no-op handler where the promise is created, so the orphan can never surface:
initializing.catch(() => {}).connect()already throws the real error separately, andbeginTransaction's ownawaitstill rejects, so nothing is swallowed.
(1) is the more useful of the two; doing both costs nothing.
Link to the code that reproduces this issue
https://github.com/payloadcms/payload/blob/v3.90.2/packages/db-postgres/src/connect.ts#L109-L111
I have not pushed a create-payload-app scaffold for this one, because the defect is visible in the published source above and does not depend on app config: rejectInitializing is typed () => void, it is called with no argument, and the promise it rejects has no handler attached at creation. Happy to put a scaffold together if you'd rather have one to run — say so and I will.
Reproduction Steps
Any postgresAdapter app where connect() fails will do it. Forcing it deterministically:
-
Build the app and start it (
output: standalonein my case, but plainnext startbehaves the same). -
Set
connectionTimeoutMillison the adapter'spoollow enough that the pool's timer expires during the first server render, which blocks the event loop while the CMS config is built. WithconnectionTimeoutMillis: 1through10it fires every time:db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URL, connectionTimeoutMillis: 1 }, }) -
Request any page that reads from the database but opens no transaction (a plain
payload.find()in a server component is enough). -
Observe two things in the output: Payload's own
Error: cannot connect to Postgres: Connection terminated due to connection timeout— correct and useful — and, separately, anunhandledRejectionwhose reason isundefined.
With a listener installed to inspect it:
process.on('unhandledRejection', (reason) => {
console.log('typeof reason:', typeof reason, '| isError:', reason instanceof Error)
})
// → typeof reason: undefined | isError: false
Patching the reject capability of new Promise(executor) to log a stack when called with a non-Error names the call site directly:
!! new Promise(…) reject() with a non-Error reason: undefined
at Object.rejectInitializing (…)
at Object.i [as connect] (…/connect.js)
at async …init (…)
Ruled out below Payload, by reading the installed source, so that the report lands in the right place:
[email protected]can only reject its connect promise with a truthy value —err ? rej(err) : res(client).[email protected]'sClient.end()with no callback returns a promise with no reject path at all.@opentelemetry/[email protected]'shandleConnectResultre-rejects the identical error.
Which area(s) are affected?
area: db-postgres
Environment Info
payload: 3.90.2
@payloadcms/db-postgres: 3.90.2
@payloadcms/drizzle: 3.90.2
pg: 8.20.0
pg-pool: 3.13.0
next: 15.4.11
node: 22.16.0 (also reproduced on 22.23.1)
- Lingua principale
- TypeScript
- Stelle
- 45.3k
- Fork
- 4.3k
- Merge medio
- 2g 9m
- PR unite (30g)
- 288
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di payloadcms/payload
-
Regression: `findByID` with `disableErrors: true` throws `NotFound` for null/empty ids since 3.91.0Forse già presa @Krrishkumar125 l’ha presa 1 giorno fa. Apertaarea: core Bug invalid-reproduction status: needs-triage v3
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
payloadcms/payload#18935 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug: Local API call with req resets req.fallbackLocale to defaultLocaleForse già presa Una pull request collegata a questa issue è aperta o già unita. Apertaarea: core Bug created-by: Contributor
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
payloadcms/payload#18768 ·
I maintainer di solito rispondono entro 1 giorno
-
[plugin-multi-tenant] Infinite redirect loop with custom tenantFieldName and isGlobal collectionsForse già presa Una pull request collegata a questa issue è aperta o già unita. ApertaBug plugin: multi-tenant status: needs-triage v3
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
payloadcms/payload#18690 ·
I maintainer di solito rispondono entro 1 giorno
-
Global update drops overrideAccess, so Local API updateGlobal fails filterOptions validation (3.90 regression)Forse già presa @PINYOPATTANAWASANPORN l’ha presa 9 giorni fa. Apertaarea: core Bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
payloadcms/payload#18674 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
plugin-cloud-storage: unused `find-node-modules` dependency pulls in `braces` (GHSA-vfj7-8cjw-p6xm, no patched version)Forse già presa @MokiMeow l’ha presa 13 giorni fa. ApertaBug plugin: cloud-storage plugin: storage-* status: needs-triage v3
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 75/100
payloadcms/payload#18623 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di payloadcms/payload
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
Automattic/data-liberation-agent#953 ·
I maintainer di solito rispondono entro 1 giorno
-
README に GitHub の組織のバッジを足すApertadocumentation
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
funmary-app/funmary#399 ·
I maintainer di solito rispondono entro 1 giorno
-
area:curator enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
CassioDalla/Scrinalia#150 ·
I maintainer di solito rispondono entro 1 giorno
-
status: needs-triage v1-bug-bash
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
a2ui-project/a2ui#3533 ·
I maintainer di solito rispondono entro 1 giorno
-
backend enhancement priority:P3
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
geolens-io/geolens#3310 ·
I maintainer di solito rispondono entro 1 giorno