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