Incremental Update: Patch v2 Posted, Critical Race Condition Identified
Major Development: Protocol Extension Approach (Not Version Bump)
Nathan Bossart posted a concrete patch (v2) implementing the notification mechanism as a protocol extension rather than a protocol version bump. This is architecturally significant — it leverages the protocol extension infrastructure added in commit ae65f6066d (2018), meaning it works with any PostgreSQL version since then without requiring a full protocol version negotiation. However, older v10 instances (pre-2018 builds) would error on the extension, raising pg_upgrade compatibility concerns for v20 (which will support v10 as minimum source version).
Design Decisions Crystallized in v2
- Callback-only API — Nathan explored a polling/retrieval model but abandoned it due to complexity (duplicate reports, filtering by statement name, clearing state). The patch uses only the callback mechanism for now.
- Named statements only — Unnamed prepared statements do not generate notifications. No clear use-case identified.
- No tests included — Testing deferred to the libpq LO interface revamp patch.
- New connection parameter added in a follow-up revision to allow disabling the feature in libpq (addressing the pg_upgrade concern).
Bug Found: Empty Notification on DISCARD ALL with No Statements
Zsolt Parragi identified that DropAllPreparedStatements() would send a deallocation notification (with empty string meaning "all") even when no statements were actually removed — specifically when prepared_queries is initialized but empty. Nathan acknowledged and fixed this in a subsequent revision.
Critical Showstopper Identified: Async Race Condition
Nathan identified a fundamental timing problem that he himself calls "a pretty obvious problem" and potentially a "showstopper":
Scenario: A user calls PQsendQuery(conn, "DISCARD ALL") asynchronously, then immediately calls an lo_* function (which would use prepared statements internally). The deallocation notification callback hasn't fired yet (because the result hasn't been consumed), so libpq still believes its internal prepared statements exist. The LO function will fail.
This is not merely a race condition in the network sense — it's a logical ordering problem inherent to asynchronous libpq usage. The callback fires when results are consumed, but nothing forces consumption before the next operation.
Implications for the Larger Effort
Nathan explicitly questions whether moving libpq's LO interface to prepared statements makes sense at all given this hole. If this approach fails, the consequences are:
- The fast-path protocol (
PQfn()) may need to be kept indefinitely, OR - A performance regression must be accepted (using simple query protocol instead)
He frames this as a fundamental limitation: "there's not a great way to use prepared statements internally in libpq."