v01-0001-Abort-connection-when-sync-is-sent-after-a-COPY.patch
application/octet-stream
Filename: v01-0001-Abort-connection-when-sync-is-sent-after-a-COPY.patch
Type: application/octet-stream
Part: 0
From f968080ba2d92c942f4fd44aa266d29cbf008348 Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Date: Tue, 3 Jun 2025 10:31:26 +0200
Subject: Abort connection when sync is sent after a COPY
When the backend reads COPY data, it ignores all Sync messages. With
psql pipelining, it's possible to manually send Sync with \sendpipeline
which leaves the frontend in an unrecoverable state as the backend won't
send the necessary ReadyForQuery.
It could be possible to artificially reduce the piped_syncs and
requested_results, but libpq's state will still have queued Sync in its
command queue. And the only way to consume those without directly
calling pqCommandQueueAdvance is to process ReadyForQuery messages that
won't be sent since the backend ignored the Syncs.
Thus, this patch abort the connection if we detect excessive sync after
a COPY in a pipeline to avoid staying in an inconsistent protocol state.
---
src/bin/psql/common.c | 15 +++++++++++++++
src/bin/psql/t/001_basic.pl | 14 ++++++++++++--
2 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 3e4e444f3fd..ed745270b6c 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1867,6 +1867,21 @@ ExecQueryAndProcessResults(const char *query,
{
FILE *copy_stream = NULL;
+ if (pset.piped_syncs > 1)
+ {
+ /*
+ * When reading COPY data, the backend ignores SYNC messages
+ * and won't send a matching ReadyForQuery response. Even if
+ * we adjust piped_syncs and requested_results, it is not
+ * possible to salvage this as the SYNC will still be in
+ * libpq's command queue and we would be stuck in a busy
+ * pipeline state. Thus, we abort the connection to avoid this
+ * state.
+ */
+ pg_log_info("\\syncpipeline after COPY is not supported, aborting connection");
+ exit(EXIT_BADCONN);
+ }
+
/*
* For COPY OUT, direct the output to the default place (probably
* a pager pipe) for \watch, or to pset.copyStream for \copy,
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 4050f9a5e3e..d8fcf0575e0 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -513,15 +513,25 @@ SELECT 'val1' \\bind \\sendpipeline
qr/server closed the connection unexpectedly/,
'protocol sync loss in pipeline: bind COPY, SELECT, sync and getresult');
-# This time, test without the \getresults.
+# This time, test without the \getresults and \syncpipeline.
psql_fails_like(
$node,
qq{\\startpipeline
COPY psql_pipeline FROM STDIN;
SELECT 'val1';
-\\syncpipeline
\\endpipeline},
qr/server closed the connection unexpectedly/,
'protocol sync loss in pipeline: COPY, SELECT and sync');
+# Test sending a sync after a COPY, this will abort the connection from the
+# frontend
+psql_fails_like(
+ $node,
+ qq{\\startpipeline
+COPY psql_pipeline FROM STDIN;
+\\syncpipeline
+\\endpipeline},
+ qr/\\syncpipeline after COPY is not supported, aborting connection/,
+ 'sending sync after COPY');
+
done_testing();
--
2.49.0