This patch introduces support for a `tcp_syn_count` parameter in
libpq, allowing control over the number of SYN retransmissions when
initiating a connection.

The primary goal is to prevent the walreceiver from getting stuck
resending SYNs for an extended period, up to
`net.ipv4.tcp_syn_retries` (127 seconds by default), in the event of
network disruptions.

A specific scenario where this can occur is during a failover in Kubernetes:

* The primary node fails, a standby is promoted, and other standbys
attempt to reconnect to the service representing the new primary.
* The `primary_conninfo` of the standby points to a service, usually
managed via iptables rules.
* If the walreceiver's initial SYN is dropped due to outdated rules,
the connection may remain stranded until the system timeout is
reached.
* As a result, a second standby may reattach after a couple of
minutes. In the case of synchronous replication, this can block the
writes from the application.

In this scenario, `tcp_user_timeout` could close a connection retrying
the SYNs (even though it doesn't seem to do it from the documentation,
it works) the parameter will affect the entire connection.
`connect_timeout`, doesn't work with `PQconnectPoll`, so it won't
prevent the walreceiver from timing out.

Thank you,
Francesco
From 34eb35abdfdc660d90f104544f40331d659718a9 Mon Sep 17 00:00:00 2001
From: Francesco Canovai <francesco.canovai@enterprisedb.com>
Date: Fri, 31 Jan 2025 13:39:51 +0100
Subject: [PATCH] Support TCP_SYNCNT in libpq

Add a tcp_syn_count parameter to libpq.

This will allow the user to define the maximum amount of retransmission
that TCP will send before aborting the attempt to establish a
connection. This can be used only on systems where TCP_SYNCNT is
available.
---
 doc/src/sgml/libpq.sgml           | 15 ++++++++++
 src/interfaces/libpq/fe-connect.c | 49 +++++++++++++++++++++++++++++++
 src/interfaces/libpq/libpq-int.h  |  1 +
 3 files changed, 65 insertions(+)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 8fa0515c6a0..8029819a25e 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1576,6 +1576,21 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-tcp-syn-count" xreflabel="tcp_syn_count">
+      <term><literal>tcp_syn_count</literal></term>
+      <listitem>
+       <para>
+        Controls the maximum number of SYN retransmissions that TCP
+        will send before aborting the attempt to establish a connection.
+        A higher value increases the number of retries before giving up,
+        while a lower value results in faster failure detection.
+        This parameter is only effective on systems where
+        <symbol>TCP_SYNCNT</symbol> is available; on other systems,
+        it has no effect.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-replication" xreflabel="replication">
       <term><literal>replication</literal></term>
       <listitem>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d5051f5e820..889c1c771d4 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -267,6 +267,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"TCP-User-Timeout", "", 10, /* strlen(INT32_MAX) == 10 */
 	offsetof(struct pg_conn, pgtcp_user_timeout)},
 
+	{"tcp_syn_count", NULL, NULL, NULL,
+		"TCP-SYN-Count", "", 10, /* strlen(INT32_MAX) == 10 */
+	offsetof(struct pg_conn, tcp_syn_count)},
+
 	/*
 	 * ssl options are allowed even without client SSL support because the
 	 * client can still handle SSL modes "disable" and "allow". Other
@@ -2627,6 +2631,41 @@ setTCPUserTimeout(PGconn *conn)
 	return 1;
 }
 
+/*
+* Set the maximum number of SYN retransmissions.
+*/
+static int
+setTCPSynCnt(PGconn *conn)
+{
+	int			count;
+
+	if (conn->tcp_syn_count == NULL)
+		return 1;
+
+	if (!pqParseIntParam(conn->tcp_syn_count, &count, conn,
+						 "tcp_syn_count"))
+		return 0;
+
+	if (count < 0)
+		count = 0;
+
+#ifdef TCP_SYNCNT
+	if (setsockopt(conn->sock, IPPROTO_TCP, TCP_SYNCNT,
+				   (char *) &count, sizeof(count)) < 0)
+	{
+		char		sebuf[PG_STRERROR_R_BUFLEN];
+
+		libpq_append_conn_error(conn, "%s(%s) failed: %s",
+								"setsockopt",
+								"TCP_SYNCNT",
+								SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		return 0;
+	}
+#endif
+
+	return 1;
+}
+
 /* ----------
  * pqConnectDBStart -
  *		Begin the process of making a connection to the backend.
@@ -3368,6 +3407,15 @@ keep_going:						/* We will come back to here until there is
 						}
 					}
 
+					if (addr_cur->family != AF_UNIX)
+					{
+						if (!setTCPSynCnt(conn))
+						{
+							conn->try_next_addr = true;
+							goto keep_going;
+						}
+					}
+
 					/*----------
 					 * We have three methods of blocking SIGPIPE during
 					 * send() calls to this socket:
@@ -5008,6 +5056,7 @@ freePGconn(PGconn *conn)
 	free(conn->keepalives_idle);
 	free(conn->keepalives_interval);
 	free(conn->keepalives_count);
+	free(conn->tcp_syn_count);
 	free(conn->sslmode);
 	free(conn->sslnegotiation);
 	free(conn->sslcert);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index f36f7f19d58..d25f440a940 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -408,6 +408,7 @@ struct pg_conn
 										 * retransmits */
 	char	   *keepalives_count;	/* maximum number of TCP keepalive
 									 * retransmits */
+	char	   *tcp_syn_count;		/* maximum number of TCP SYN retransmits */
 	char	   *sslmode;		/* SSL mode (require,prefer,allow,disable) */
 	char	   *sslnegotiation; /* SSL initiation style (postgres,direct) */
 	char	   *sslcompression; /* SSL compression (0 or 1) */
-- 
2.48.1

Reply via email to