Repository navigation
Conversation
…y-detached handler A handler's socket close listener defers handleClose() with setImmediate. If stop() detaches that handler first and the caller then closes the database, the deferred handleClose() runs detach() again, which calls db.isInTransaction() on the closed database before it checks whether the handler is still attached. handleClose() doesn't await detach(), so the TypeError is an unhandled rejection. Return from detach() before touching the queue or the database when the handler has no socket.
This branch has not been deployed
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.
Fixes #1172
Problem
If a client's socket closes in the same event-loop turn as
await server.stop()followed byawait db.close(), the server later throws an unhandledTypeError: Cannot read properties of undefined (reading '_IsTransactionBlock'). The socket'scloselistener defershandleClose()withsetImmediate.stop()detaches the same handler and resolves, anddb.close()finishes. Then the deferredhandleClose()callsdetach()again.detach()callsclearTransactionIfNeeded(), and sodb.isInTransaction(), before it checks whether the handler is still attached.handleClose()doesn't awaitdetach(), so the error is an unhandled rejection.The README example and the
pglite-serverCLI exit the process right afterdb.close(), so they never run the deferred callback. Test suites that start and stop a server per file do run it.Change
detach()returns before it touches the query queue or the database when the handler has no socket. The existing check after theawaitstays, because a second detach can finish while the first one waits for aROLLBACK.Tests
tests/stop-then-close.test.ts. It starts thestop()anddb.close()shutdown from the server-side socket'scloseevent, which makes the race deterministic, and asserts that no unhandled rejection follows. It fails onmainwith the_IsTransactionBlockTypeError and passes with this change, 5 out of 5 repeated runs.tsc,eslint ./src ./tests --max-warnings 0andprettier --check ./src ./testsare clean.@electric-sql/pglite@0.5.13. In that setup, 7 tests inindex.test.ts,query-with-node-pg.test.ts,query-with-postgres-js.test.tsandextended-query-error.test.tsfail onmainboth with and without this change. They need workspace builds I didn't have. All the other tests pass, includingconnection-cleanup.test.ts.This change was made with AI assistance. The tests above were run locally before opening this PR.