sql(postgres): release the event loop ref when a request is rejected with nothing sent

Merged
RO
2mo ago
+49-15

Problem

  • Regression from #34732 (73df7bb270). With prepare: false, a script whose last query is rejected while a parameter is encoded never exits, when that query runs from a later tick than the last reply (in a timer, in a request handler).
  • PostgresSQLQuery::do_run takes the connection's event loop ref when it enqueues the request, then calls advance_and_flush(). With prepare: false the batch is encoded inside that advance(). Since #34732 a failed encode leaves nothing to send, so no reply arrives, and only the idle check at the end of on_data releases the ref. Before #34732 the server's close released it.

Fix

  • The idle check of on_data moves to PostgresSQLConnection::update_poll_ref(): unref when the connection is connected, nothing is in flight and the write buffer is empty, else ref.
  • do_run runs it with scopeguard::defer! right after it takes the ref, so every exit path after the ref recomputes it.
  • Verified: new test a script whose last query was rejected exits on its own in postgres-bind-encode-throw.test.ts. Without the fix the prepare: false case times out. Also the test/js/sql/postgres-* files, sql-prepare-false, sql-pool-transaction-isolation, wire-frames, sql.test.ts: 238 tests pass.

Background

  • poll_ref is a KeepAlive: while it is active, the process does not exit. A connection holds it only while a query is in flight, so an idle pool does not keep a script alive.
  • Named statements do not hit this. Their Bind is encoded from on_data, or in do_run before the ref is taken.

Downsides

  • Per do_run of a prepared statement: one more idle check (4 flag and field reads). No allocation, no syscall.