fix(pglite): flush output left after ReadyForQuery before the next request

PGlite's interactive_one discards an incoming request when output from
the previous frame is still pending ('flush after frame'), which showed
up as replies without ReadyForQuery, first after handshakes and then in
bursts mid-scan, each one breaking a pooled connection and the scanner
transaction on it. After a complete reply the bridge now runs one empty
tick and appends anything the backend still had, and logs the message
tags of any reply that is still incomplete.
This commit is contained in:
Deluan 2026-09-05 01:37:39 -04:00
commit d3a1395896

View file

@ -472,7 +472,8 @@ func (pg *PGlite) handleConn(conn net.Conn, ioBase string) {
} else if packet[0] != 'X' { // Terminate gets no reply by design
fixed := ensureReadyForQuery(replies, trapErr)
if len(fixed) != len(replies) {
fmt.Fprintf(pg.cfg.Stderr, "# bridge: incomplete reply to %s (trap=%v); synthesized error+ReadyForQuery\n", wireTags(packet), trapErr != nil)
fmt.Fprintf(pg.cfg.Stderr, "# bridge: incomplete reply to %s: got %s (%d bytes, trap=%v); synthesized error+ReadyForQuery\n",
wireTags(packet), wireTags(bytes.Join(replies, nil)), len(bytes.Join(replies, nil)), trapErr != nil)
}
replies = fixed
}
@ -560,7 +561,16 @@ func (pg *PGlite) forwardWire(packet []byte, outFile string) ([][]byte, error) {
break
}
if endsWithReadyForQuery(replies) {
break // a complete response; skip the empty probe tick
// One empty tick: output the backend still holds after ReadyForQuery would otherwise
// make it discard the next request ("flush after frame" in interactive_one.c).
if _, err := pg.fnInteractiveOne.Call(ctx); err == nil {
var extra [][]byte
if pg.collectReply(outFile, &extra) {
fmt.Fprintf(pg.cfg.Stderr, "# bridge: %d bytes after ReadyForQuery (%s)\n", len(bytes.Join(extra, nil)), wireTags(bytes.Join(extra, nil)))
replies = append(replies, extra...)
}
}
break
}
}
return replies, nil