Skip to content

Trailing extended request deadlocks after pipeline splitting #1508

Description

@apkipa

PgDog version

v0.1.57 (main 7594279; session and transaction modes)

Description

A legal extended-protocol pipeline containing two executed queries, a portal Describe, and one Sync completes directly but stalls through PgDog after the first two results. split_extended() inserts Flush after each Execute, then emits the trailing portal Describe and original Sync as separate requests. PostgreSQL does not send the Describe response until Flush or Sync (pq_flush()), but client_messages() waits for that response before it processes the separated Sync. The wait survives frontend close, leaving the backend checked out in both tested pool modes; repeated requests can exhaust the affected pool.

Reproduce

Install psycopg[binary] and run:

#!/usr/bin/env python3
import time

from psycopg.pq import ExecStatus, PGconn


TIMEOUT = 3.0


def run(label: str, dsn: str) -> None:
    conn = PGconn.connect(dsn.encode())
    try:
        if conn.status != 0:
            raise RuntimeError(conn.error_message.decode(errors="replace"))

        conn.enter_pipeline_mode()
        conn.send_query_params(b"SELECT 1", None)
        conn.send_query_params(b"SELECT 2", None)
        conn.send_describe_portal(b"")
        conn.pipeline_sync()

        statuses = []
        deadline = time.monotonic() + TIMEOUT
        while time.monotonic() < deadline:
            conn.consume_input()
            if not conn.is_busy():
                result = conn.get_result()
                if result is None:
                    if statuses and statuses[-1] == "PIPELINE_SYNC":
                        break
                    time.sleep(0.01)
                    continue
                statuses.append(ExecStatus(result.status).name)
            else:
                time.sleep(0.01)
            if statuses and statuses[-1] == "PIPELINE_SYNC":
                break

        print(f"{label}: statuses={statuses}")
    finally:
        try:
            conn.finish()
        except Exception:
            pass


run("direct", DIRECT_DSN)
run("pgdog", PGDOG_DSN)

Expected behavior

Both endpoints should print:

direct: statuses=['TUPLES_OK', 'TUPLES_OK', 'COMMAND_OK', 'PIPELINE_SYNC']
pgdog: statuses=['TUPLES_OK', 'TUPLES_OK', 'COMMAND_OK', 'PIPELINE_SYNC']

Actual behavior

The reproducer prints:

direct: statuses=['TUPLES_OK', 'TUPLES_OK', 'COMMAND_OK', 'PIPELINE_SYNC']
pgdog: statuses=['TUPLES_OK', 'TUPLES_OK']

The trailing Describe response and pipeline synchronization result never arrive.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

acceptedThe issue is added to our backlog.

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions