v2-0002-Use-timeout-socket-options-for-cancel-connections.patch
text/x-diff
Filename: v2-0002-Use-timeout-socket-options-for-cancel-connections.patch
Type: text/x-diff
Part: 1
Patch
Format: unified
Series: patch v2-0002
| File | + | − |
|---|---|---|
| src/interfaces/libpq/fe-connect.c | 159 | 18 |
| src/interfaces/libpq/libpq-int.h | 7 | 0 |
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9e75abd304..a4a559904d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -1944,26 +1944,17 @@ setKeepalivesCount(PGconn *conn)
/*
* Enable keepalives and set the keepalive values on Win32,
* where they are always set in one batch.
+ *
+ * setKeepalivesWin32 is used by PQcancel, and had better be signal safe.
*/
static int
-setKeepalivesWin32(PGconn *conn)
+setKeepalivesWin32(pgsocket sock, int idle, int interval)
{
struct tcp_keepalive ka;
DWORD retsize;
- int idle = 0;
- int interval = 0;
- if (conn->keepalives_idle &&
- !parse_int_param(conn->keepalives_idle, &idle, conn,
- "keepalives_idle"))
- return 0;
if (idle <= 0)
idle = 2 * 60 * 60; /* 2 hours = default */
-
- if (conn->keepalives_interval &&
- !parse_int_param(conn->keepalives_interval, &interval, conn,
- "keepalives_interval"))
- return 0;
if (interval <= 0)
interval = 1; /* 1 second = default */
@@ -1971,7 +1962,7 @@ setKeepalivesWin32(PGconn *conn)
ka.keepalivetime = idle * 1000;
ka.keepaliveinterval = interval * 1000;
- if (WSAIoctl(conn->sock,
+ if (WSAIoctl(sock,
SIO_KEEPALIVE_VALS,
(LPVOID) &ka,
sizeof(ka),
@@ -1981,6 +1972,26 @@ setKeepalivesWin32(PGconn *conn)
NULL,
NULL)
!= 0)
+ return 0;
+ return 1;
+}
+
+static int
+prepKeepalivesWin32(PGconn *conn)
+{
+ int idle = -1;
+ int interval = -1;
+
+ if (conn->keepalives_idle &&
+ !parse_int_param(conn->keepalives_idle, &idle, conn,
+ "keepalives_idle"))
+ return 0;
+ if (conn->keepalives_interval &&
+ !parse_int_param(conn->keepalives_interval, &interval, conn,
+ "keepalives_interval"))
+ return 0;
+
+ if (!setKeepalivesWin32(conn->sock, idle, interval))
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("%s(%s) failed: error code %d\n"),
@@ -2644,7 +2655,7 @@ keep_going: /* We will come back to here until there is
err = 1;
#else /* WIN32 */
#ifdef SIO_KEEPALIVE_VALS
- else if (!setKeepalivesWin32(conn))
+ else if (!prepKeepalivesWin32(conn))
err = 1;
#endif /* SIO_KEEPALIVE_VALS */
#endif /* WIN32 */
@@ -4380,8 +4391,53 @@ PQgetCancel(PGconn *conn)
memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr));
cancel->be_pid = conn->be_pid;
cancel->be_key = conn->be_key;
+ /* We use -1 to indicate an unset connection option */
+ cancel->pgtcp_user_timeout = -1;
+ cancel->keepalives = -1;
+ cancel->keepalives_idle = -1;
+ cancel->keepalives_interval = -1;
+ cancel->keepalives_count = -1;
+ if (conn->pgtcp_user_timeout != NULL)
+ {
+ if (!parse_int_param(conn->pgtcp_user_timeout,
+ &cancel->pgtcp_user_timeout,
+ conn, "tcp_user_timeout"))
+ goto fail;
+ }
+ if (conn->keepalives != NULL)
+ {
+ if (!parse_int_param(conn->keepalives,
+ &cancel->keepalives,
+ conn, "keepalives"))
+ goto fail;
+ }
+ if (conn->keepalives_idle != NULL)
+ {
+ if (!parse_int_param(conn->keepalives_idle,
+ &cancel->keepalives_idle,
+ conn, "keepalives_idle"))
+ goto fail;
+ }
+ if (conn->keepalives_interval != NULL)
+ {
+ if (!parse_int_param(conn->keepalives_interval,
+ &cancel->keepalives_interval,
+ conn, "keepalives_interval"))
+ goto fail;
+ }
+ if (conn->keepalives_count != NULL)
+ {
+ if (!parse_int_param(conn->keepalives_count,
+ &cancel->keepalives_count,
+ conn, "keepalives_count"))
+ goto fail;
+ }
return cancel;
+
+fail:
+ free(cancel);
+ return NULL;
}
/* PQfreeCancel: free a cancel structure */
@@ -4393,6 +4449,23 @@ PQfreeCancel(PGcancel *cancel)
}
+/*
+ * Sets an integer socket option on a TCP socket, if the provided value is
+ * not negative. Returns false if setsockopt fails for some reason.
+ *
+ * CAUTION: This needs to be signal safe, since it's used by PQcancel.
+ */
+static bool
+optional_setsockopt(int fd, int protoid, int optid, int value)
+{
+ if (value < 0)
+ return true;
+ if (setsockopt(fd, protoid, optid, (char *) &value, sizeof(value)) < 0)
+ return false;
+ return true;
+}
+
+
/*
* PQcancel: request query cancel
*
@@ -4443,6 +4516,78 @@ PQcancel(PGcancel *cancel, char *errbuf, int errbufsize)
strlcpy(errbuf, "PQcancel() -- socket() failed: ", errbufsize);
goto cancel_errReturn;
}
+
+ /*
+ * Since this connection will only be used to send a tiny bit of data, we
+ * don't need NODELAY. We also don't set the socket to nonblocking mode,
+ * because the API definition of PQcancel requires the cancel to be sent
+ * in a blocking way.
+ *
+ * We do set socket options related to keepalives and other TCP timeouts.
+ * This ensures that this function does not block indefinitely when
+ * reasonable keepalive and timeout settings have been provided.
+ */
+ if (!IS_AF_UNIX(cancel->raddr.addr.ss_family) &&
+ cancel->keepalives != 0)
+ {
+#ifndef WIN32
+ if (!optional_setsockopt(tmpsock, SOL_SOCKET, SO_KEEPALIVE, 1))
+ {
+ strlcpy(errbuf, "PQcancel() -- setsockopt(SO_KEEPALIVE) failed: ", errbufsize);
+ goto cancel_errReturn;
+ }
+
+#ifdef PG_TCP_KEEPALIVE_IDLE
+ if (!optional_setsockopt(tmpsock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE,
+ cancel->keepalives_idle))
+ {
+ strlcpy(errbuf, "PQcancel() -- setsockopt(" PG_TCP_KEEPALIVE_IDLE_STR ") failed: ", errbufsize);
+ goto cancel_errReturn;
+ }
+#endif
+
+#ifdef TCP_KEEPINTVL
+ if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_KEEPINTVL,
+ cancel->keepalives_interval))
+ {
+ strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_KEEPINTVL) failed: ", errbufsize);
+ goto cancel_errReturn;
+ }
+#endif
+
+#ifdef TCP_KEEPCNT
+ if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_KEEPCNT,
+ cancel->keepalives_count))
+ {
+ strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_KEEPCNT) failed: ", errbufsize);
+ goto cancel_errReturn;
+ }
+#endif
+
+#else /* WIN32 */
+
+#ifdef SIO_KEEPALIVE_VALS
+ if (!setKeepalivesWin32(tmpsock,
+ cancel->keepalives_idle,
+ cancel->keepalives_interval))
+ {
+ strlcpy(errbuf, "PQcancel() -- WSAIoctl(SIO_KEEPALIVE_VALS) failed: ", errbufsize);
+ goto cancel_errReturn;
+ }
+#endif /* SIO_KEEPALIVE_VALS */
+#endif /* WIN32 */
+
+ /* TCP_USER_TIMEOUT works the same way on Unix and Windows */
+#ifdef TCP_USER_TIMEOUT
+ if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_USER_TIMEOUT,
+ cancel->pgtcp_user_timeout))
+ {
+ strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_USER_TIMEOUT) failed: ", errbufsize);
+ goto cancel_errReturn;
+ }
+#endif
+ }
+
retry3:
if (connect(tmpsock, (struct sockaddr *) &cancel->raddr.addr,
cancel->raddr.salen) < 0)
@@ -4454,10 +4599,6 @@ retry3:
goto cancel_errReturn;
}
- /*
- * We needn't set nonblocking I/O or NODELAY options here.
- */
-
/* Create and send the cancel request packet. */
crp.packetlen = pg_hton32((uint32) sizeof(crp));
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index fcce13843e..4290553482 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -583,6 +583,13 @@ struct pg_cancel
SockAddr raddr; /* Remote address */
int be_pid; /* PID of backend --- needed for cancels */
int be_key; /* key of backend --- needed for cancels */
+ int pgtcp_user_timeout; /* tcp user timeout */
+ int keepalives; /* use TCP keepalives? */
+ int keepalives_idle; /* time between TCP keepalives */
+ int keepalives_interval; /* time between TCP keepalive
+ * retransmits */
+ int keepalives_count; /* maximum number of TCP keepalive
+ * retransmits */
};