On Wed Feb 25, 2026 at 4:08 PM CET, Zsolt Parragi wrote:
+ /*
+ * Only signal regular backends, since those need to notify
+ * their clients using a GoAway message.
+ */
+ if (bp->bkend_type == B_BACKEND)

This condition is slightly different to how SignalChildren works, is
that intentional? I don't think it causes any practical difference,
and I don't see an easy way to reuse SignalChildren for this, but
maybe it could still follow the same pattern.

Changed it to be consistent now, and resolved a rebase conflict.

Since the pytest framework seems unlikely to be included in PG19, have
you considered a different test implementation, to have at least some
minimal coverage?

I now included some basic support for GoAway in psql and added a perl
test based on that.
From 720a92a23ffb17bce2095b3701e85938a254b8a5 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Sun, 19 Oct 2025 00:30:48 +0200
Subject: [PATCH v7] Add GoAway protocol message for graceful but fast server
 shutdown/switchover

This commit introduces a new GoAway backend-to-frontend protocol
message (byte 'g') that the server can send to the client to politely
request that client to disconnect/reconnect when convenient. This message is
advisory only - the connection remains fully functional and clients may
continue executing queries and starting new transactions. "When
convenient" is obviously not very well defined, but the primary target
clients are clients that maintain a connection pool. Such clients should
disconnect/reconnect a connection in the pool when there's no user of
that connection. This is similar to how such clients often currently
remove a connection from the pool after the connection hits a maximum
lifetime of e.g. 1 hour.

This new message is used by Postgres during the already existing "smart"
shutdown procedure (i.e. when postmaster receives SIGTERM). When
Postgres is in "smart" shutdown mode existing clients can continue to
run queries as usual but new connection attempts are rejected. This mode
is primarily useful when triggering a switchover of a read replica. A
load balancer can route new connections only to the new read replica,
while the old load balancer keeps serving the existing connections until
they disconnect. The problem is that this draining of connections could
often take a long time. Even when clients only run very short
queries/transactions because the session can be kept open much longer
(many connection pools use 1 hour max lifetime of a connection by default).
With the introduction of the GoAway message Postgres now sends this
message to all connected clients when it enters smart shutdown mode.
If these clients respond to the message by reconnecting/disconnecting
earlier than their maximum connection lifetime the draining can complete
much quicker. Similar benefits to switchover duration can be achieved
for other applications or proxies implementing the Postgres protocol,
like when switching over a cluster of PgBouncer machines to a newer
version.

Applications/clients that use libpq can periodically check the result of
PQgoAwayReceived() at an inactive time to see whether they are asked to
reconnect.
---
 doc/src/sgml/libpq.sgml              | 52 +++++++++++++++++++++
 doc/src/sgml/protocol.sgml           | 70 ++++++++++++++++++++++++++--
 src/backend/postmaster/postmaster.c  | 26 +++++++++++
 src/backend/storage/ipc/procsignal.c |  3 ++
 src/backend/tcop/backend_startup.c   | 21 +++++++--
 src/backend/tcop/postgres.c          | 36 ++++++++++++++
 src/bin/psql/common.c                |  7 +++
 src/bin/psql/meson.build             |  1 +
 src/bin/psql/t/040_goaway.pl         | 46 ++++++++++++++++++
 src/include/libpq/libpq-be.h         |  5 ++
 src/include/libpq/protocol.h         |  1 +
 src/include/storage/procsignal.h     |  3 +-
 src/include/tcop/tcopprot.h          |  1 +
 src/interfaces/libpq/exports.txt     |  1 +
 src/interfaces/libpq/fe-connect.c    |  1 +
 src/interfaces/libpq/fe-exec.c       | 27 +++++++++++
 src/interfaces/libpq/fe-protocol3.c  | 39 ++++++++++++++--
 src/interfaces/libpq/libpq-fe.h      |  3 ++
 src/interfaces/libpq/libpq-int.h     |  1 +
 19 files changed, 331 insertions(+), 13 deletions(-)
 create mode 100644 src/bin/psql/t/040_goaway.pl

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6db823808fc..b304bca765c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2248,6 +2248,14 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         equivalent to the latest protocol version supported by the libpq
         version being used, which is currently <literal>3.2</literal>.
        </para>
+
+       <para>
+        When using protocol version <literal>3.2</literal> or higher,
+        <application>libpq</application> requests all supported
+        <link linkend="protocol-extensions">protocol extensions</link>
+        from the server, such as <literal>goaway</literal>
+        which enables <xref linkend="libpq-PQgoAwayReceived"/>.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -3004,6 +3012,50 @@ int PQserverVersion(const PGconn *conn);
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-PQgoAwayReceived">
+     <term><function>PQgoAwayReceived</function><indexterm><primary>PQgoAwayReceived</primary></indexterm></term>
+     <listitem>
+      <para>
+       Returns true if the server has sent a <literal>GoAway</literal> message,
+       requesting the client to disconnect when convenient.
+
+<synopsis>
+int PQgoAwayReceived(const PGconn *conn);
+</synopsis>
+      </para>
+
+      <para>
+       The <literal>GoAway</literal> message is sent by the server during a
+       smart shutdown to politely request that clients disconnect. This is
+       advisory only - the connection remains fully functional and queries
+       can continue to be executed. Applications can choose to honor the request
+       by calling this function periodically and disconnect gracefully when
+       possible, such as after completing the current transaction.
+      </para>
+
+      <para>
+       This message is only sent to clients that request the
+       <literal>goaway</literal> protocol extension in the startup message,
+       which <application>libpq</application> does when using protocol version
+       3.2 or higher (see <xref linkend="libpq-connect-max-protocol-version"/>).
+       The function returns 1 if the <literal>GoAway</literal> message was
+       received, 0 otherwise.
+      </para>
+
+      <para>
+       <function>PQgoAwayReceived</function> does not actually read data from the
+       server; it just returns messages previously absorbed by another
+       <application>libpq</application> function. So normally you would first
+       call <xref linkend="libpq-PQconsumeInput"/>, then check
+       <function>PQgoAwayReceived</function>.  You can use
+       <function>select()</function> to wait for data to arrive from the
+       server, thereby using no <acronym>CPU</acronym> power unless there is
+       something to do.  (See <xref linkend="libpq-PQsocket"/> to obtain the file
+       descriptor number to use with <function>select()</function>.)
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-PQerrorMessage">
      <term>
       <function>PQerrorMessage</function><indexterm><primary>PQerrorMessage</primary></indexterm>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 49f81676712..eec13b29439 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -346,8 +346,14 @@
 
      <tbody>
       <row>
-       <entry namest="last" align="center" valign="middle">
-        <emphasis>(No supported protocol extensions are currently defined.)</emphasis>
+       <entry><literal>goaway</literal></entry>
+       <entry><literal>1</literal></entry>
+       <entry>PostgreSQL 19</entry>
+       <entry>
+        Enables the server to send
+        <link linkend="protocol-message-formats-GoAway">GoAway</link>
+        messages to request the client to disconnect when convenient.
+        See <xref linkend="protocol-async"/> for more details.
        </entry>
       </row>
      </tbody>
@@ -710,6 +716,17 @@
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry>
+      <term>GoAway</term>
+      <listitem>
+       <para>
+        The server requests the client politely to close the connection at its
+        earliest convenient moment. See <xref linkend="protocol-async"/> for
+        more details.
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
 
@@ -1458,7 +1475,7 @@ SELCT 1/0;<!-- this typo is intentional -->
    </para>
 
    <para>
-    It is possible for NoticeResponse and ParameterStatus messages to be
+    It is possible for NoticeResponse, ParameterStatus and GoAway messages to be
     interspersed between CopyData messages; frontends must handle these cases,
     and should be prepared for other asynchronous message types as well (see
     <xref linkend="protocol-async"/>).  Otherwise, any message type other than
@@ -1569,6 +1586,28 @@ SELCT 1/0;<!-- this typo is intentional -->
     parameters that it does not understand or care about.
    </para>
 
+   <para>
+    A GoAway message is sent by the server to politely request the client to
+    disconnect when convenient and reconnect when the client needs a connection
+    again. This is advisory only - the connection remains fully functional and
+    queries can continue to be executed. "When convenient" is very vaguely
+    defined on purpose because it depends on the client and application whether
+    such a moment even exists. So clients are allowed to completely ignore this
+    message and disconnect whenever they otherwise would have. An important
+    type of client that can actually honor the request to disconnect early is a
+    client that maintains a connection pool. Such a client can honor the
+    request by disconnecting a connection that has received a GoAway message
+    when it's not in use by a user of the pool. It is allowed for a server to
+    send multiple GoAway messages on the same connection, but any subsequent
+    GoAway messages after the first GoAway have no effect on the client's
+    behavior. The GoAway message is currently sent by Postgres during the
+    "smart" shutdown procedure (i.e. when postmaster receives
+    <systemitem>SIGTERM</systemitem>). The server only sends the GoAway
+    message to clients that request the <literal>goaway</literal> protocol
+    extension by setting <literal>_pq_.goaway</literal> to <literal>1</literal>
+    in the StartupMessage.
+   </para>
+
    <para>
     If a frontend issues a <command>LISTEN</command> command, then the
     backend will send a NotificationResponse message (not to be
@@ -5359,6 +5398,31 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
     </listitem>
    </varlistentry>
 
+   <varlistentry id="protocol-message-formats-GoAway">
+    <term>GoAway (B)</term>
+    <listitem>
+     <variablelist>
+      <varlistentry>
+       <term>Byte1('g')</term>
+       <listitem>
+        <para>
+         Identifies the message as a polite request for the client to disconnect.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32(4)</term>
+       <listitem>
+        <para>
+         Length of message contents in bytes, including self.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="protocol-message-formats-GSSENCRequest">
     <term>GSSENCRequest (F)</term>
     <listitem>
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3fac46c402b..9a7baaba9e4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2134,7 +2134,33 @@ process_pm_shutdown_request(void)
 			 * later state, do not change it.
 			 */
 			if (pmState == PM_RUN || pmState == PM_HOT_STANDBY)
+			{
+				dlist_iter	iter;
+
 				connsAllowed = false;
+
+				/*
+				 * Signal all backends to send a GoAway message to their
+				 * clients, to politely request that they disconnect.
+				 */
+				dlist_foreach(iter, &ActiveChildList)
+				{
+					PMChild    *bp = dlist_container(PMChild, elem, iter.cur);
+
+					/*
+					 * Only signal regular backends, since those are the ones
+					 * that need to notify their clients using a GoAway
+					 * message. Follow the same pattern as SignalChildren to
+					 * correctly distinguish backends from WAL senders.
+					 */
+					if (bp->bkend_type == B_BACKEND &&
+						!IsPostmasterChildWalSender(bp->child_slot))
+					{
+						SendProcSignal(bp->pid, PROCSIG_SMART_SHUTDOWN,
+									   INVALID_PROC_NUMBER);
+					}
+				}
+			}
 			else if (pmState == PM_STARTUP || pmState == PM_RECOVERY)
 			{
 				/* There should be no clients, so proceed to stop children */
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7e017c8d53b..dba32b91865 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -697,6 +697,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
 		HandleLogMemoryContextInterrupt();
 
+	if (CheckProcSignal(PROCSIG_SMART_SHUTDOWN))
+		HandleSmartShutdownInterrupt();
+
 	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
 		HandleParallelApplyMessageInterrupt();
 
diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c
index c517115927c..17ab063bcab 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -779,11 +779,24 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 			{
 				/*
 				 * Any option beginning with _pq_. is reserved for use as a
-				 * protocol-level option, but at present no such options are
-				 * defined.
+				 * protocol-level option.
 				 */
-				unrecognized_protocol_options =
-					lappend(unrecognized_protocol_options, pstrdup(nameptr));
+				if (strcmp(nameptr, "_pq_.goaway") == 0)
+				{
+					/* Client wants to receive GoAway messages. */
+					if (strcmp(valptr, "1") != 0)
+						ereport(FATAL,
+								(errcode(ERRCODE_PROTOCOL_VIOLATION),
+								 errmsg("invalid value for protocol option \"%s\": \"%s\"",
+										nameptr, valptr),
+								 errhint("Valid values are: \"1\".")));
+					port->goaway_negotiated = true;
+				}
+				else
+				{
+					unrecognized_protocol_options =
+						lappend(unrecognized_protocol_options, pstrdup(nameptr));
+				}
 			}
 			else
 			{
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d01a09dd0c4..4dcdb21c98b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -42,8 +42,10 @@
 #include "common/pg_prng.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
+#include "libpq/pqcomm.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
+#include "libpq/protocol.h"
 #include "mb/pg_wchar.h"
 #include "mb/stringinfo_mb.h"
 #include "miscadmin.h"
@@ -92,6 +94,14 @@ const char *debug_query_string; /* client-supplied query string */
 /* Note: whereToSendOutput is initialized for the bootstrap/standalone case */
 CommandDest whereToSendOutput = DestDebug;
 
+/*
+ * Track whether we've been notified of smart shutdown and sent GoAway.
+ * SmartShutdownPending is set by the PROCSIG_SMART_SHUTDOWN signal handler.
+ * GoAwaySent tracks whether we've already sent the GoAway message.
+ */
+static volatile sig_atomic_t SmartShutdownPending = false;
+static bool GoAwaySent = false;
+
 /* flag for logging end of session */
 bool		Log_disconnections = false;
 
@@ -507,6 +517,20 @@ ProcessClientReadInterrupt(bool blocked)
 		/* Check for general interrupts that arrived before/while reading */
 		CHECK_FOR_INTERRUPTS();
 
+		/* Send GoAway message if smart shutdown is pending */
+		if (SmartShutdownPending && !GoAwaySent &&
+			whereToSendOutput == DestRemote &&
+			MyProcPort && MyProcPort->goaway_negotiated)
+		{
+			StringInfoData buf;
+
+			pq_beginmessage(&buf, PqMsg_GoAway);
+			pq_endmessage(&buf);
+			pq_flush();
+
+			GoAwaySent = true;
+		}
+
 		/* Process sinval catchup interrupts, if any */
 		if (catchupInterruptPending)
 			ProcessCatchupInterrupt();
@@ -3066,6 +3090,18 @@ FloatExceptionHandler(SIGNAL_ARGS)
 					   "invalid operation, such as division by zero.")));
 }
 
+/*
+ * Tell the next ProcessClientReadInterrupt() call to send a GoAway message to
+ * the client.  Runs in a SIGUSR1 handler.
+ */
+void
+HandleSmartShutdownInterrupt(void)
+{
+	SmartShutdownPending = true;
+	InterruptPending = true;
+	/* latch will be set by procsignal_sigusr1_handler */
+}
+
 /*
  * Tell the next CHECK_FOR_INTERRUPTS() to process recovery conflicts.  Runs
  * in a SIGUSR1 handler.
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 2eadd391a9c..4e99ed69841 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -739,6 +739,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout,
 static void
 PrintNotifications(void)
 {
+	static bool goaway_reported = false;
 	PGnotify   *notify;
 
 	PQconsumeInput(pset.db);
@@ -755,6 +756,12 @@ PrintNotifications(void)
 		PQfreemem(notify);
 		PQconsumeInput(pset.db);
 	}
+
+	if (!goaway_reported && PQgoAwayReceived(pset.db))
+	{
+		pg_log_info("Server sent GoAway, requesting disconnect when convenient.");
+		goaway_reported = true;
+	}
 }
 
 
diff --git a/src/bin/psql/meson.build b/src/bin/psql/meson.build
index 922b2845267..047b40cc40e 100644
--- a/src/bin/psql/meson.build
+++ b/src/bin/psql/meson.build
@@ -78,6 +78,7 @@ tests += {
       't/010_tab_completion.pl',
       't/020_cancel.pl',
       't/030_pager.pl',
+      't/040_goaway.pl',
     ],
   },
 }
diff --git a/src/bin/psql/t/040_goaway.pl b/src/bin/psql/t/040_goaway.pl
new file mode 100644
index 00000000000..a53653308bf
--- /dev/null
+++ b/src/bin/psql/t/040_goaway.pl
@@ -0,0 +1,46 @@
+
+# Copyright (c) 2021-2026, PostgreSQL Global Development Group
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use Time::HiRes qw(usleep);
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+my $psql = $node->background_psql('postgres');
+
+# Confirm connection works
+my $result = $psql->query_safe("SELECT 'before_goaway'");
+like($result, qr/before_goaway/, 'connection works before smart shutdown');
+
+# Initiate smart shutdown without waiting for it to complete
+$node->command_ok(
+	[ 'pg_ctl', 'stop', '-D', $node->data_dir, '-m', 'smart', '--no-wait' ],
+	'pg_ctl smart shutdown');
+
+# The backend sends GoAway once it processes the smart shutdown signal.
+# Poll with queries until psql reports it.
+my $saw_goaway = 0;
+for (my $i = 0; $i < 100; $i++)
+{
+	my $out = $psql->query("SELECT 'after_goaway'");
+	if ($psql->{stderr} =~ /Server sent GoAway, requesting disconnect when convenient/)
+	{
+		$saw_goaway = 1;
+		# The query should still have succeeded
+		like($out, qr/after_goaway/, 'query still works after GoAway');
+		last;
+	}
+	usleep(50_000);
+}
+ok($saw_goaway, 'psql reported GoAway notice during smart shutdown');
+
+$psql->quit;
+
+done_testing();
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 921b2daa4ff..b8769412c72 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -202,6 +202,11 @@ typedef struct Port
 	void	   *gss;
 #endif
 
+	/*
+	 * Protocol extensions.
+	 */
+	bool		goaway_negotiated;	/* client supports GoAway message */
+
 	/*
 	 * SSL structures.
 	 */
diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h
index eae8f0e7238..fd3c195daa2 100644
--- a/src/include/libpq/protocol.h
+++ b/src/include/libpq/protocol.h
@@ -53,6 +53,7 @@
 #define PqMsg_FunctionCallResponse	'V'
 #define PqMsg_CopyBothResponse		'W'
 #define PqMsg_ReadyForQuery			'Z'
+#define PqMsg_GoAway				'g'
 #define PqMsg_NoData				'n'
 #define PqMsg_PortalSuspended		's'
 #define PqMsg_ParameterDescription	't'
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 348fba53a93..bba38b92f22 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -39,9 +39,10 @@ typedef enum
 	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
 								 * PGPROC->pendingRecoveryConflicts for the
 								 * reason */
+	PROCSIG_SMART_SHUTDOWN,		/* notify backend of smart shutdown */
 } ProcSignalReason;
 
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT + 1)
+#define NUM_PROCSIGNALS (PROCSIG_SMART_SHUTDOWN + 1)
 
 typedef enum
 {
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 5bc5bcfb20d..7447609b3cf 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -74,6 +74,7 @@ extern void die(SIGNAL_ARGS);
 pg_noreturn extern void quickdie(SIGNAL_ARGS);
 extern void StatementCancelHandler(SIGNAL_ARGS);
 pg_noreturn extern void FloatExceptionHandler(SIGNAL_ARGS);
+extern void HandleSmartShutdownInterrupt(void);
 extern void HandleRecoveryConflictInterrupt(void);
 extern void ProcessClientReadInterrupt(bool blocked);
 extern void ProcessClientWriteInterrupt(bool blocked);
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 1e3d5bd5867..f57aae46b65 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -211,3 +211,4 @@ PQdefaultAuthDataHook     208
 PQfullProtocolVersion     209
 appendPQExpBufferVA       210
 PQgetThreadLock           211
+PQgoAwayReceived          212
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db9b4c8edbf..4d81d5f03bb 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -701,6 +701,7 @@ pqDropServerData(PGconn *conn)
 	conn->password_needed = false;
 	conn->gssapi_used = false;
 	conn->write_failed = false;
+	conn->goaway_received = false;
 	free(conn->write_err_msg);
 	conn->write_err_msg = NULL;
 	conn->oauth_want_retry = false;
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 203d388bdbf..bc1d95324ed 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2702,6 +2702,33 @@ PQnotifies(PGconn *conn)
 	return event;
 }
 
+/*
+ * PQgoAwayReceived
+ *	  returns 1 if a GoAway message has been received from the server
+ *	  returns 0 if not
+ *
+ * Note that this function does not read any new data from the socket;
+ * caller should call PQconsumeInput() first if they want to ensure
+ * all available data has been read.
+ */
+int
+PQgoAwayReceived(PGconn *conn)
+{
+	if (!conn)
+		return 0;
+
+	if (conn->goaway_received)
+		return 1;
+
+	/*
+	 * Parse any available data to see if a GoAway message has arrived.
+	 */
+	parseInput(conn);
+
+	return conn->goaway_received ? 1 : 0;
+}
+
+
 /*
  * PQputCopyData - send some data to the backend during COPY IN or COPY BOTH
  *
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 8c1fda5caf0..d09002a22ac 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -134,8 +134,8 @@ pqParseInput3(PGconn *conn)
 		}
 
 		/*
-		 * NOTIFY and NOTICE messages can happen in any state; always process
-		 * them right away.
+		 * NOTIFY and NOTICE and GoAway messages can happen in any state;
+		 * always process them right away.
 		 *
 		 * Most other messages should only be processed while in BUSY state.
 		 * (In particular, in READY state we hold off further parsing until
@@ -159,6 +159,10 @@ pqParseInput3(PGconn *conn)
 			if (pqGetErrorNotice3(conn, false))
 				return;
 		}
+		else if (id == PqMsg_GoAway)
+		{
+			conn->goaway_received = true;
+		}
 		else if (conn->asyncStatus != PGASYNC_BUSY)
 		{
 			/* If not IDLE state, just wait ... */
@@ -1446,6 +1450,7 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
 	int			num;
 	bool		found_test_protocol_negotiation;
 	bool		expect_test_protocol_negotiation;
+	bool		requested_goaway = conn->pversion >= PG_PROTOCOL(3, 2);
 
 	/*
 	 * During 19beta only, if protocol grease is in use, assume that it's the
@@ -1521,8 +1526,7 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
 	conn->pversion = their_version;
 
 	/*
-	 * Check that all expected unsupported parameters are reported by the
-	 * server.
+	 * Process the list of unsupported protocol extensions.
 	 */
 	found_test_protocol_negotiation = false;
 	expect_test_protocol_negotiation = (conn->max_pversion == PG_PROTOCOL_GREASE);
@@ -1539,12 +1543,23 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
 			goto failure;
 		}
 
-		/* Check if this is the expected test parameter */
+		/*
+		 * Handle protocol extensions that we requested. Extensions that were
+		 * not requested by us are an error.
+		 */
 		if (expect_test_protocol_negotiation &&
 			strcmp(conn->workBuffer.data, "_pq_.test_protocol_negotiation") == 0)
 		{
 			found_test_protocol_negotiation = true;
 		}
+		else if (requested_goaway && strcmp(conn->workBuffer.data, "_pq_.goaway") == 0)
+		{
+			/*
+			 * Server doesn't support GoAway, that's fine. It will simply never
+			 * send the message.
+			 */
+			continue;
+		}
 		else
 		{
 			libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported an unsupported parameter that was not requested (\"%s\")",
@@ -1905,6 +1920,9 @@ getCopyDataMessage(PGconn *conn)
 				if (getParameterStatus(conn))
 					return 0;
 				break;
+			case PqMsg_GoAway:
+				conn->goaway_received = true;
+				break;
 			case PqMsg_CopyData:
 				return msgLength;
 			case PqMsg_CopyDone:
@@ -2398,6 +2416,9 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 				if (getParameterStatus(conn))
 					continue;
 				break;
+			case PqMsg_GoAway:
+				conn->goaway_received = true;
+				break;
 			default:
 				/* The backend violates the protocol. */
 				libpq_append_conn_error(conn, "protocol error: id=0x%x", id);
@@ -2531,6 +2552,14 @@ build_startup_packet(const PGconn *conn, char *packet,
 		}
 	}
 
+	/*
+	 * Request protocol extensions. Only do this for protocol version 3.2 and
+	 * later, to avoid confusing old proxies that don't understand _pq_.*
+	 * options.
+	 */
+	if (conn->pversion >= PG_PROTOCOL(3, 2))
+		ADD_STARTUP_OPTION("_pq_.goaway", "1");
+
 	/* Add trailing terminator */
 	if (packet)
 		packet[packet_len] = '\0';
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index f06e7a972c3..d1a6dd54407 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -68,6 +68,8 @@ extern "C"
 #define LIBPQ_HAS_GET_THREAD_LOCK 1
 /* Indicates presence of the PQAUTHDATA_OAUTH_BEARER_TOKEN_V2 authdata hook */
 #define LIBPQ_HAS_OAUTH_BEARER_TOKEN_V2 1
+/* Indicates presence of PQgoAwayReceived */
+#define LIBPQ_HAS_GOAWAY 1
 
 /*
  * Option flags for PQcopyResult
@@ -419,6 +421,7 @@ extern const char *PQparameterStatus(const PGconn *conn,
 extern int	PQprotocolVersion(const PGconn *conn);
 extern int	PQfullProtocolVersion(const PGconn *conn);
 extern int	PQserverVersion(const PGconn *conn);
+extern int	PQgoAwayReceived(PGconn *conn);
 extern char *PQerrorMessage(const PGconn *conn);
 extern int	PQsocket(const PGconn *conn);
 extern int	PQbackendPID(const PGconn *conn);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index bd7eb59f5f8..2018927d0cb 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -511,6 +511,7 @@ struct pg_conn
 	bool		sigpipe_flag;	/* can we mask SIGPIPE via MSG_NOSIGNAL? */
 	bool		write_failed;	/* have we had a write failure on sock? */
 	char	   *write_err_msg;	/* write error message, or NULL if OOM */
+	bool		goaway_received;	/* true if server sent GoAway message */
 
 	bool		auth_required;	/* require an authentication challenge from
 								 * the server? */

base-commit: f30cebb9542358702ca0f2c4be2cd504a2568606
-- 
2.53.0

Reply via email to