Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wSpLl-000883-2q for pgsql-bugs@arkaria.postgresql.org; Fri, 29 May 2026 05:01:38 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wSpKj-001Vsh-0U for pgsql-bugs@arkaria.postgresql.org; Fri, 29 May 2026 05:00:33 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wSpKg-001VsY-1n for pgsql-bugs@lists.postgresql.org; Fri, 29 May 2026 05:00:33 +0000 Received: from fout-b8-smtp.messagingengine.com ([202.12.124.151]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wSpKd-000000004Nw-2ZgS for pgsql-bugs@lists.postgresql.org; Fri, 29 May 2026 05:00:29 +0000 Received: from phl-compute-05.internal (phl-compute-05.internal [10.202.2.45]) by mailfout.stl.internal (Postfix) with ESMTP id 9F4A61D00134; Fri, 29 May 2026 01:00:26 -0400 (EDT) Received: from phl-frontend-04 ([10.202.2.163]) by phl-compute-05.internal (MEProxy); Fri, 29 May 2026 01:00:26 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h= cc:cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm3; t=1780030826; x=1780117226; bh=Gg0eWC9ZEl 6KepnEAOQclqPz2Byz5YBXoAVsmrgiuzU=; b=VqPXWdqTo5VJMD1KaZUp3A9eRj VwkPTpCWFhWMCnbdlrGTaXIgOByY/FlzLqKNu9izUz0JDQNKklKGY0fHSBw0cj+y B4HZ7UQwg6DpMdGbafFps12owe1Wm0RGk3bFO+5dNGEL2Xoqumzvjrt9N8DRXgNq b1yC5/NJlycyKU64jo+sSoelzK/6hcEjakC40ASQnAGGXmkUk5HSadb0Qu8H4HzP 87eoLhrk/CmM/fbzfpPYM5flD5NOrzWt9SBShEWDmHAKWUSI1fzn42vJ+edeNkw+ QDyRhl7Jw3dbgFrgCAuz2PZmzRryeAArut5QOVm+WaGdnKNETF9HKxpzde+A== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t= 1780030826; x=1780117226; bh=Gg0eWC9ZEl6KepnEAOQclqPz2Byz5YBXoAV smrgiuzU=; b=LD7MqySCoTFRQZQYqpAMlreHbu7jxCypG7pL9W1hS2kvTLj+Klw sv56a7TkmeMHZaHldZg/MyBXXJif0SE+khGwhXVvBr+dDgHfqV54VYf5DrP0sprB 1lQeJa6aqjy8Yddh3eMtmsPdFUf9EXJxVjujBCBE9USXcnn6bPx+VvXZsq2w2btC /VRVXJ95dduFt+JL+Bk9bOUcIBddSjmjmfMGvqedAS48XP0VJHpGhUpZb6wcCpps MVLCL7rCDwD6uzEmsU51G6+ri7z+wvokCyx6kZKMbpVYvQWI3OhetXreD/5NrTMR AXWPJ4S5m+kIO7lGDWi/2I8PRUdzD9d9Dfw== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: dmFkZTGcSk5WQkhj4p99IvyhnaQzJVR11qkVdEs5J/PqGAMKpUNs2xy6EyTucUd55xV7Ra geQ/H8KgT5F7JKPjq7Adbwaf36/3M6DlgmNScd6wC7mIxtXPDDxaqlWxP/1pfJCaGrLvG9 +OWDerD3Xoyl2FC3jnSS6nBMEscZOpbx/DZzBZEtQ7XvmsCLC8ahQKJ7j4JmswQvD54iuF MFbnlzqhaHrycqH5ACjnZI93ll75wN5o9UytIbb1RzLNEVzbt+lU7tt1WlLZOFFkvr7mQ3 DlCWXnK91JKQ2rKkTvh0hk/NEJLJjUiUEkqFRwSuft0WZIx6TN/mwamXlghIBSVILx/vmJ nzDXtCIlsP6UkTM8xWpaXmGSIWf5ngt/DLDp94KZsgCXNjtVljXKKuGnorllKjyrgbu2jE QgDhtilOx8qyst5r9W9RVUJ6XNhRQPO1LzcL6fEEK4TsZCPCR7p4bDYPNSZAU9fottgBgu aFxf6jDEyxgRd4Xn5Yp9jjLduqMQethGQdKH0KiAnw6tJG1Lf8LedIaXTnh1udURCenMvB tNYdmJ0NCvYkKnDzjtU58IMHG5PBMHseuH6alW7L6njqOwkkelI6jzwuhzZ+x8mZP2YwNn Nm9viE2coF0OERUhNz7JevdG1ys8Br8Pomz6PJGy4XiTs6vZU6GrxrNSHpzQ X-ME-Proxy: Feedback-ID: i0fe9450f:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Fri, 29 May 2026 01:00:24 -0400 (EDT) Date: Fri, 29 May 2026 14:00:20 +0900 From: Michael Paquier To: Alexander Lakhin Cc: pgsql-bugs@lists.postgresql.org Subject: Re: BUG #19494: Error on transaction commit inside pipeline triggers psql's Assert Message-ID: References: <19494-97a86d84fee71c47@postgresql.org> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="ay3rrdrQOan3oHqv" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --ay3rrdrQOan3oHqv Content-Type: multipart/mixed; boundary="T8Uu/sJWZkRv1+Ye" Content-Disposition: inline --T8Uu/sJWZkRv1+Ye Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Fri, May 29, 2026 at 07:00:01AM +0300, Alexander Lakhin wrote: > it's happening upon/after postgres process termination, so PQgetResult() > returns NULL, pset.piped_syncs == 1. I need more time to look deeper and > to come with a reproducer, but maybe you can already see what's wrong. Yeah, I do. Nice catch. See this sequence to reproduce the problem: \startpipeline INSERT INTO psql_pipeline_defer VALUES (1), (1) \bind \sendpipeline \syncpipeline SELECT pg_terminate_backend(pg_backend_pid()) \bind \sendpipeline SELECT 1 \bind \sendpipeline \endpipeline When ending the pipeline the loop consuming the results is stuck, so we could check the connection state. We are going to enter in a freeze of the branches due to beta1 next week, so let's take our time. Please feel to use the v2 attached for your tests. I am also testing it more on my side. -- Michael --T8Uu/sJWZkRv1+Ye Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0001-psql-Fix-issues-with-deferred-errors-in-pipelines.patch Content-Transfer-Encoding: quoted-printable =46rom c7e13a8826f25b3c99fe554f7e2c52a56bbea626 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Fri, 29 May 2026 13:58:29 +0900 Subject: [PATCH v2] psql: Fix issues with deferred errors in pipelines When an error is raised while processing a Sync message in a pipeline, e like a deferred constraint violation, the error was not associated with the piped command and was not counted in available_results. This caused assertion failures in discardAbortedPipelineResults(), keeping an incorrect state at pipeline exit, because the code assumed that the number of available and requested results would always be positive, expecting all the counters to be 0 at the end of a pipeline. This commit switches discardAbortedPipelineResults() and ExecQueryAndProcessResults() to take a softer approach when consuming and draining the results after an error since the error generated by a Sync are not tracked in the result counters (well, we could perhaps do that, but I am not convinced that this is worth the complication). The reporter has shown one problematic assertion failure. While investigating more this issue I have bumped into two more. All these cases are covered by the regression tests added in this commit, plus some bonuses. Reported-by: Alexander Lakhin Author: Michael Paquier Discussion: https://postgr.es/m/19494-97a86d84fee71c47@postgresql.org Backpatch-through: 18 --- src/bin/psql/common.c | 51 ++++++-- src/test/regress/expected/psql_pipeline.out | 124 ++++++++++++++++++++ src/test/regress/sql/psql_pipeline.sql | 63 ++++++++++ 3 files changed, 227 insertions(+), 11 deletions(-) diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 1a4e2ea0da82..c021eb97552a 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -1499,11 +1499,19 @@ discardAbortedPipelineResults(void) } else if (res =3D=3D NULL) { - /* A query was processed, decrement the counters */ - Assert(pset.available_results > 0); - Assert(pset.requested_results > 0); - pset.available_results--; - pset.requested_results--; + /* + * A query was processed, decrement the counters. + * + * It is possible to get here with available_results =3D=3D 0 when an + * error is generated by the Sync message processing itself. Such + * errors are not counted in available_results because they are + * not associated with a piped command. In that case, skip the + * counter decrements and continue to find the Sync result. + */ + if (pset.available_results > 0) + pset.available_results--; + if (pset.requested_results > 0) + pset.requested_results--; } =20 if (pset.requested_results =3D=3D 0) @@ -2175,14 +2183,35 @@ ExecQueryAndProcessResults(const char *query, =20 if (end_pipeline) { - /* after a pipeline is processed, pipeline piped_syncs should be 0 */ - Assert(pset.piped_syncs =3D=3D 0); - /* all commands have been processed */ - Assert(pset.piped_commands =3D=3D 0); - /* all results were read */ - Assert(pset.available_results =3D=3D 0); + /* + * Reset available/requested results. Normally these are already 0, + * but an error generated by a Sync processing itself can leave some + * of them behind. Consume them before exiting pipeline mode. + */ + while (pset.piped_syncs > 0) + { + PGresult *remaining =3D PQgetResult(pset.db); + + if (remaining =3D=3D NULL) + { + if (!ConnectionUp()) + break; + continue; + } + if (PQresultStatus(remaining) =3D=3D PGRES_PIPELINE_SYNC) + pset.piped_syncs--; + PQclear(remaining); + } + pset.piped_syncs =3D 0; + pset.piped_commands =3D 0; + pset.available_results =3D 0; + pset.requested_results =3D 0; + + if (PQpipelineStatus(pset.db) !=3D PQ_PIPELINE_OFF) + PQexitPipelineMode(pset.db); } Assert(pset.requested_results =3D=3D 0); + SetPipelineVariables(); =20 /* may need this to recover from conn loss during COPY */ diff --git a/src/test/regress/expected/psql_pipeline.out b/src/test/regress= /expected/psql_pipeline.out index a0816fb10b68..a931d63cafe7 100644 --- a/src/test/regress/expected/psql_pipeline.out +++ b/src/test/regress/expected/psql_pipeline.out @@ -764,5 +764,129 @@ VACUUM psql_pipeline \bind \sendpipeline 1 (1 row) =20 +-- Deferred constraint violation at commit time in a pipeline. +CREATE TABLE psql_pipeline_defer (a INTEGER PRIMARY KEY DEFERRABLE INITIAL= LY DEFERRED); +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) RETURNING * \bind 1 \sen= dpipeline +\endpipeline + a=20 +--- + 1 + 1 +(2 rows) + +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +-- Same with \syncpipeline and commands after the failing sync. +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind 'after_sync_1' \sendpipeline +\endpipeline +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. + ?column? =20 +-------------- + after_sync_1 +(1 row) + +-- More patterns with more \syncpipeline, more commands and \getresults +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind 'after_sync_1' \sendpipeline +\getresults +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +SELECT $1 \bind 'after_sync_2' \sendpipeline +\endpipeline + ?column? =20 +-------------- + after_sync_1 +(1 row) + + ?column? =20 +-------------- + after_sync_2 +(1 row) + +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +\getresults +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +SELECT $1 \bind 'after_sync_1' \sendpipeline +\getresults +SELECT $1 \bind 'after_sync_2' \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +SELECT $1 \bind 'after_sync_3' \sendpipeline +SELECT $1 \bind 'after_sync_4' \sendpipeline +SELECT $1 \bind 'after_sync_5' \sendpipeline +\endpipeline + ?column? =20 +-------------- + after_sync_1 +(1 row) + + ?column? =20 +-------------- + after_sync_2 +(1 row) + + ?column? =20 +-------------- + after_sync_3 +(1 row) + + ?column? =20 +-------------- + after_sync_4 +(1 row) + + ?column? =20 +-------------- + after_sync_5 +(1 row) + +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +-- Deferred error combined with a regular command error after the sync. +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind \sendpipeline +SELECT $1 \bind 'after_error' \sendpipeline +\endpipeline +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +ERROR: bind message supplies 0 parameters, but prepared statement "" requ= ires 1 +-- Empty sync segment followed by a deferred error. +\startpipeline +\syncpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\endpipeline +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +-- Deferred error with \getresults reading results one at a time. +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +SELECT $1 \bind 'partial' \sendpipeline +\syncpipeline +\getresults 1 +\getresults 1 + ?column?=20 +---------- + partial +(1 row) + +\getresults +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +\endpipeline +DROP TABLE psql_pipeline_defer; -- Clean up DROP TABLE psql_pipeline; diff --git a/src/test/regress/sql/psql_pipeline.sql b/src/test/regress/sql/= psql_pipeline.sql index 6788dceee2e9..468ef1d090b6 100644 --- a/src/test/regress/sql/psql_pipeline.sql +++ b/src/test/regress/sql/psql_pipeline.sql @@ -438,5 +438,68 @@ SELECT 1 \bind \sendpipeline VACUUM psql_pipeline \bind \sendpipeline \endpipeline =20 +-- Deferred constraint violation at commit time in a pipeline. +CREATE TABLE psql_pipeline_defer (a INTEGER PRIMARY KEY DEFERRABLE INITIAL= LY DEFERRED); +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) RETURNING * \bind 1 \sen= dpipeline +\endpipeline + +-- Same with \syncpipeline and commands after the failing sync. +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind 'after_sync_1' \sendpipeline +\endpipeline + +-- More patterns with more \syncpipeline, more commands and \getresults +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind 'after_sync_1' \sendpipeline +\getresults +SELECT $1 \bind 'after_sync_2' \sendpipeline +\endpipeline +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +\getresults +SELECT $1 \bind 'after_sync_1' \sendpipeline +\getresults +SELECT $1 \bind 'after_sync_2' \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +SELECT $1 \bind 'after_sync_3' \sendpipeline +SELECT $1 \bind 'after_sync_4' \sendpipeline +SELECT $1 \bind 'after_sync_5' \sendpipeline +\endpipeline + +-- Deferred error combined with a regular command error after the sync. +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind \sendpipeline +SELECT $1 \bind 'after_error' \sendpipeline +\endpipeline + +-- Empty sync segment followed by a deferred error. +\startpipeline +\syncpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\endpipeline + +-- Deferred error with \getresults reading results one at a time. +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +SELECT $1 \bind 'partial' \sendpipeline +\syncpipeline +\getresults 1 +\getresults 1 +\getresults +\endpipeline + +DROP TABLE psql_pipeline_defer; + -- Clean up DROP TABLE psql_pipeline; --=20 2.54.0 --T8Uu/sJWZkRv1+Ye-- --ay3rrdrQOan3oHqv Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmoZHWQACgkQnvQgOdby QH2xexAAqtVI7XzloIQiG/Go3qPqb611F+1xrrLZqE/ky6ZBUEbeA3d8zyHrHvA9 XTH6xzUD2L2p+pFX/x7JBDPxuRzncqMPzHwjjmPRkJt4VwMVykyq11DIdKmpIXmm 82yQjz4nZcE8rfthgMmpR2nSAw1bXJ1sdik0BoRbZ+GMUz8rA4rO8xbAz+OcUGcF iqEU4ipf1rKasetcetk5dxiS6/uBug3aKWZxx91zuEhXcAJ01G8LgYxv8sSD7TDq 39V74SwEeBVqf7xhMdiOFuDj5Y3Aj5QPRpe3ImF2ihGCUgYkqxCmKR6GsL9MrMHB hUccbpgcAxNZ8HvLk9BiWn+bgywdWyJ/8xvr/+Q3H9IaO8srKCxZ7IxaTTVZ0lL8 T6kI3leM7UuBPG3zQAQYXgPXeTFtWYhiqCh3+nYH11n7awKF37eqMXKEuVDfP/qx W0EX/P9U07iG1+OJeWZ8y/WQDyCD9B2UVgiQFEh/i2UKeu7+clLZgFFWEdhPgRvD p0n7RHm44f0yC+R74v4FD2DMP3c2a3OmZa/n9SVWX4GK94VFz5Sxot27sw1NOhcM aNOO2gT8i7pRkMn+0veFDxAeJoDg51dJjrQLxvuj2zOWkAROhW2WCQeChUztCKdo eF7LomWwqo7mRlpFg9w5uo/aP0nGLBT8C82bAUopJ7IPXfcmw18= =9OMr -----END PGP SIGNATURE----- --ay3rrdrQOan3oHqv--