From 44b0d1671a16cd2700eb7983b511a185bdb1b791 Mon Sep 17 00:00:00 2001
From: Magnus Hagander <magnus@hagander.net>
Date: Thu, 8 Jul 2010 10:20:14 +0000
Subject: [PATCH] Add support for TCP keepalives on Windows, both for backend
 and the new libpq support.

---
 doc/src/sgml/config.sgml          | 50 ++++++++++++-------
 doc/src/sgml/libpq.sgml           | 28 ++++++-----
 src/backend/libpq/pqcomm.c        | 81 ++++++++++++++++++++++++++++---
 src/interfaces/libpq/fe-connect.c | 55 ++++++++++++++++++++-
 4 files changed, 175 insertions(+), 39 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e54b09545d0..2fbe6a8c24b 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.293 2010/07/06 22:55:26 rhaas Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.294 2010/07/08 10:20:13 mha Exp $ -->
 
 <chapter Id="runtime-config">
   <title>Server Configuration</title>
@@ -523,12 +523,17 @@ SET ENABLE_SEQSCAN TO OFF;
       </indexterm>
       <listitem>
        <para>
-        On systems that support the <symbol>TCP_KEEPIDLE</symbol> or
-        <symbol>TCP_KEEPALIVE</> socket option, specifies the
-        number of seconds between sending keepalives on an otherwise idle
-        connection. A value of zero uses the system default. If neither of
-        these socket options is supported, this parameter must be zero. This
-        parameter is ignored for connections made via a Unix-domain socket.
+        Specifies the number of seconds before sending a keepalive packet on an otherwise idle
+        connection.  A value of 0 uses the system default.  This parameter is supported
+        only on systems that support the <symbol>TCP_KEEPIDLE</> or <symbol>TCP_KEEPALIVE</>
+        symbols, and on Windows; on other systems, it must be zero. This parameter is
+        ignored for connections made via a Unix-domain socket.
+         <note>
+          <para>
+           On Windows, a value of 0 will set this parameter to 2 hours,
+           since Windows does not provide a way to read the default value.
+          </para>
+         </note>
        </para>
       </listitem>
      </varlistentry>
@@ -540,11 +545,17 @@ SET ENABLE_SEQSCAN TO OFF;
       </indexterm>
       <listitem>
        <para>
-        On systems that support the <symbol>TCP_KEEPINTVL</symbol> socket option, specifies how
-        long, in seconds, to wait for a response to a keepalive before
-        retransmitting. A value of zero uses the system default. If <symbol>TCP_KEEPINTVL</symbol>
-        is not supported, this parameter must be zero. This parameter is ignored
-        for connections made via a Unix-domain socket.
+        Specifies the number of seconds between sending keepalives on an otherwise idle
+        connection.  A value of 0 uses the system default.  This parameter is supported
+        only on systems that support the <symbol>TCP_KEEPINTVL</>
+        symbol, and on Windows; on other systems, it must be zero. This parameter is
+        ignored for connections made via a Unix-domain socket.
+         <note>
+          <para>
+           On Windows, a value of 0 will set this parameter to 1 second,
+           since Windows does not provide a way to read the default value.
+          </para>
+         </note>
        </para>
       </listitem>
      </varlistentry>
@@ -556,11 +567,16 @@ SET ENABLE_SEQSCAN TO OFF;
       </indexterm>
       <listitem>
        <para>
-        On systems that support the <symbol>TCP_KEEPCNT</symbol> socket option, specifies how
-        many keepalives can be lost before the connection is considered dead.
-        A value of zero uses the system default. If <symbol>TCP_KEEPCNT</symbol> is not
-        supported, this parameter must be zero. This parameter is ignored
-        for connections made via a Unix-domain socket.
+        Specifies the number of keepalive packets to send on an otherwise idle
+        connection.  A value of 0 uses the system default.  This parameter is supported
+        only on systems that support the <symbol>TCP_KEEPCNT</>
+        symbol; on other systems, it must be zero. This parameter is
+        ignored for connections made via a Unix-domain socket.
+         <note>
+          <para>
+           This parameter is not supported on Windows, and must be zero.
+          </para>
+         </note>
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index f70541bb7f1..9e047b110d7 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.312 2010/07/06 21:14:25 rhaas Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.313 2010/07/08 10:20:14 mha Exp $ -->
 
 <chapter id="libpq">
  <title><application>libpq</application> - C Library</title>
@@ -298,10 +298,11 @@
           <para>
            Controls the number of seconds of inactivity after which TCP should
            send a keepalive message to the server.  A value of zero uses the
-           system default.  This parameter is ignored if the neither the
-           <symbol>TCP_KEEPIDLE</> nor the <symbol>TCP_KEEPALIVE</> socket
-           options are supported, for connections made via a Unix-domain
-           socket, or if keepalives are disabled.
+           system default. This parameter is ignored for connections made via a
+           Unix-domain socket, or if keepalives are disabled. It is only supported
+           on systems where the <symbol>TCP_KEEPIDLE</> or <symbol>TCP_KEEPALIVE</>
+           socket option is available, and on Windows; on other systems, it has no
+           effect.
           </para>
          </listitem>
         </varlistentry>
@@ -312,10 +313,11 @@
           <para>
            Controls the number of seconds after which a TCP keepalive message
            that is not acknowledged by the server should be retransmitted.  A
-           value of zero uses the system default.  This parameter is ignored if
-           the <symbol>TCP_KEEPINTVL</> socket option is not supported, for
-           connections made via a Unix-domain socket, or if keepalives are
-           disabled.
+           value of zero uses the system default. This parameter is ignored for
+           connections made via a Unix-domain socket, or if keepalives are disabled.
+           It is only supported on systems where the <symbol>TCP_KEEPINTVL</>
+           socket option is available, and on Windows; on other systems, it has no
+           effect.
           </para>
          </listitem>
         </varlistentry>
@@ -326,10 +328,10 @@
           <para>
            Controls the number of TCP keepalives that can be lost before the
            client's connection to the server is considered dead.  A value of
-           zero uses the system default.  This parameter is ignored if the
-           <symbol>TCP_KEEPCNT</> socket option is not supported, for
-           connections made via a Unix-domain socket, or if keepalives are
-           disabled.
+           zero uses the system default. This parameter is ignored for
+           connections made via a Unix-domain socket, or if keepalives are disabled.
+           It is only supported on systems where the <symbol>TCP_KEEPINTVL</>
+           socket option is available; on other systems, it has no effect.
           </para>
          </listitem>
         </varlistentry>
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 46577f379ac..b5be4baa444 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -30,7 +30,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/libpq/pqcomm.c,v 1.210 2010/07/06 21:14:25 rhaas Exp $
+ *	$PostgreSQL: pgsql/src/backend/libpq/pqcomm.c,v 1.211 2010/07/08 10:20:12 mha Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -83,6 +83,9 @@
 #ifdef HAVE_UTIME_H
 #include <utime.h>
 #endif
+#ifdef WIN32
+#include <mstcpip.h>
+#endif
 
 #include "libpq/ip.h"
 #include "libpq/libpq.h"
@@ -1314,10 +1317,55 @@ pq_endcopyout(bool errorAbort)
  * Support for TCP Keepalive parameters
  */
 
+/*
+ * On Windows, we need to set both idle and interval at the same time.
+ * We also cannot reset them to the default (setting to zero will
+ * actually set them to zero, not default), therefor we fallback to
+ * the out-of-the-box default instead.
+ */
+#ifdef WIN32
+static int
+pq_setkeepaliveswin32(Port *port, int idle, int interval)
+{
+	struct tcp_keepalive	ka;
+	DWORD					retsize;
+
+	if (idle <= 0)
+		idle = 2 * 60 * 60; /* default = 2 hours */
+	if (interval <= 0)
+		interval = 1;       /* default = 1 second */
+
+	ka.onoff = 1;
+	ka.keepalivetime = idle * 1000;
+	ka.keepaliveinterval = interval * 1000;
+
+	if (WSAIoctl(port->sock,
+				 SIO_KEEPALIVE_VALS,
+				 (LPVOID) &ka,
+				 sizeof(ka),
+				 NULL,
+				 0,
+				 &retsize,
+				 NULL,
+				 NULL)
+		!= 0)
+	{
+		elog(LOG, "WSAIoctl(SIO_KEEPALIVE_VALS) failed: %ui",
+			 WSAGetLastError());
+		return STATUS_ERROR;
+	}
+	if (port->keepalives_idle != idle)
+		port->keepalives_idle = idle;
+	if (port->keepalives_interval != interval)
+		port->keepalives_interval = interval;
+	return STATUS_OK;
+}
+#endif
+
 int
 pq_getkeepalivesidle(Port *port)
 {
-#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
+#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE) || defined(WIN32)
 	if (port == NULL || IS_AF_UNIX(port->laddr.addr.ss_family))
 		return 0;
 
@@ -1326,6 +1374,7 @@ pq_getkeepalivesidle(Port *port)
 
 	if (port->default_keepalives_idle == 0)
 	{
+#ifndef WIN32
 		ACCEPT_TYPE_ARG3 size = sizeof(port->default_keepalives_idle);
 
 #ifdef TCP_KEEPIDLE
@@ -1344,7 +1393,11 @@ pq_getkeepalivesidle(Port *port)
 			elog(LOG, "getsockopt(TCP_KEEPALIVE) failed: %m");
 			port->default_keepalives_idle = -1; /* don't know */
 		}
-#endif
+#endif /* TCP_KEEPIDLE */
+#else /* WIN32 */
+		/* We can't get the defaults on Windows, so return "don't know" */
+		port->default_keepalives_idle = -1;
+#endif /* WIN32 */
 	}
 
 	return port->default_keepalives_idle;
@@ -1359,10 +1412,11 @@ pq_setkeepalivesidle(int idle, Port *port)
 	if (port == NULL || IS_AF_UNIX(port->laddr.addr.ss_family))
 		return STATUS_OK;
 
-#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
+#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE) || defined(WIN32)
 	if (idle == port->keepalives_idle)
 		return STATUS_OK;
 
+#ifndef WIN32
 	if (port->default_keepalives_idle <= 0)
 	{
 		if (pq_getkeepalivesidle(port) < 0)
@@ -1394,21 +1448,23 @@ pq_setkeepalivesidle(int idle, Port *port)
 #endif
 
 	port->keepalives_idle = idle;
-#else
+#else /* WIN32 */
+	return pq_setkeepaliveswin32(port, idle, port->keepalives_interval);
+#endif
+#else /* TCP_KEEPIDLE || WIN32 */
 	if (idle != 0)
 	{
 		elog(LOG, "setting the keepalive idle time is not supported");
 		return STATUS_ERROR;
 	}
 #endif
-
 	return STATUS_OK;
 }
 
 int
 pq_getkeepalivesinterval(Port *port)
 {
-#ifdef TCP_KEEPINTVL
+#if defined(TCP_KEEPINTVL) || defined(WIN32)
 	if (port == NULL || IS_AF_UNIX(port->laddr.addr.ss_family))
 		return 0;
 
@@ -1417,6 +1473,7 @@ pq_getkeepalivesinterval(Port *port)
 
 	if (port->default_keepalives_interval == 0)
 	{
+#ifndef WIN32
 		ACCEPT_TYPE_ARG3 size = sizeof(port->default_keepalives_interval);
 
 		if (getsockopt(port->sock, IPPROTO_TCP, TCP_KEEPINTVL,
@@ -1426,6 +1483,10 @@ pq_getkeepalivesinterval(Port *port)
 			elog(LOG, "getsockopt(TCP_KEEPINTVL) failed: %m");
 			port->default_keepalives_interval = -1;		/* don't know */
 		}
+#else
+		/* We can't get the defaults on Windows, so return "don't know" */
+		port->default_keepalives_interval = -1;
+#endif /* WIN32 */
 	}
 
 	return port->default_keepalives_interval;
@@ -1440,10 +1501,11 @@ pq_setkeepalivesinterval(int interval, Port *port)
 	if (port == NULL || IS_AF_UNIX(port->laddr.addr.ss_family))
 		return STATUS_OK;
 
-#ifdef TCP_KEEPINTVL
+#if defined(TCP_KEEPINTVL) || defined (WIN32)
 	if (interval == port->keepalives_interval)
 		return STATUS_OK;
 
+#ifndef WIN32
 	if (port->default_keepalives_interval <= 0)
 	{
 		if (pq_getkeepalivesinterval(port) < 0)
@@ -1466,6 +1528,9 @@ pq_setkeepalivesinterval(int interval, Port *port)
 	}
 
 	port->keepalives_interval = interval;
+#else /* WIN32 */
+	return pq_setkeepaliveswin32(port, port->keepalives_idle, interval);
+#endif
 #else
 	if (interval != 0)
 	{
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 5bb404fc27e..5f671f32993 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.396 2010/07/06 21:14:25 rhaas Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.397 2010/07/08 10:20:12 mha Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,6 +38,7 @@
 #endif
 #define near
 #include <shlobj.h>
+#include <mstcpip.h>
 #else
 #include <sys/socket.h>
 #include <netdb.h>
@@ -982,6 +983,7 @@ useKeepalives(PGconn *conn)
 	return val != 0 ? 1 : 0;
 }
 
+#ifndef WIN32
 /*
  * Set the keepalive idle timer.
  */
@@ -1090,6 +1092,52 @@ setKeepalivesCount(PGconn *conn)
 	return 1;
 }
 
+#else /* Win32 */
+/*
+ * Enable keepalives and set the keepalive values on Win32,
+ * where they are always set in one batch.
+ */
+static int
+setKeepalivesWin32(PGconn *conn)
+{
+	struct tcp_keepalive 	ka;
+	DWORD					retsize;
+	int						idle = 0;
+	int						interval = 0;
+
+	if (conn->keepalives_idle)
+		idle = atoi(conn->keepalives_idle);
+	if (idle <= 0)
+		idle = 2 * 60 * 60; /* 2 hours = default */
+
+	if (conn->keepalives_interval)
+		interval = atoi(conn->keepalives_interval);
+	if (interval <= 0)
+		interval = 1; /* 1 second = default */
+
+	ka.onoff = 1;
+	ka.keepalivetime = idle * 1000;
+	ka.keepaliveinterval = interval * 1000;
+
+	if (WSAIoctl(conn->sock,
+				 SIO_KEEPALIVE_VALS,
+				 (LPVOID) &ka,
+				 sizeof(ka),
+				 NULL,
+				 0,
+				 &retsize,
+				 NULL,
+				 NULL)
+		!= 0)
+	{
+		appendPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("WSAIoctl(SIO_KEEPALIVE_VALS) failed: %ui\n"),
+						  WSAGetLastError());
+		return 0;
+	}
+	return 1;
+}
+#endif /* WIN32 */
 
 /* ----------
  * connectDBStart -
@@ -1492,6 +1540,7 @@ keep_going:						/* We will come back to here until there is
 						{
 							/* Do nothing */
 						}
+#ifndef WIN32
 						else if (setsockopt(conn->sock,
 											SOL_SOCKET, SO_KEEPALIVE,
 											(char *) &on, sizeof(on)) < 0)
@@ -1505,6 +1554,10 @@ keep_going:						/* We will come back to here until there is
 								 || !setKeepalivesInterval(conn)
 								 || !setKeepalivesCount(conn))
 							err = 1;
+#else /* WIN32 */
+						else if (!setKeepalivesWin32(conn))
+							err = 1;
+#endif /* WIN32 */
 
 						if (err)
 						{
-- 
GitLab