From f87cf8bfbcf943b21fd84c8b12b00d92f5367b1d Mon Sep 17 00:00:00 2001
From: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Date: Thu, 9 Feb 2023 11:58:35 +0000
Subject: [PATCH v31 4/4] add kqueue support for PQconnCheck and PQcanConnCheck

---
 doc/src/sgml/libpq.sgml        | 16 +++----
 doc/src/sgml/postgres-fdw.sgml | 34 +++++++-------
 src/interfaces/libpq/fe-misc.c | 84 ++++++++++++++++++++++++++++++++--
 3 files changed, 106 insertions(+), 28 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index f49b6e7452..2abc2ed508 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2692,14 +2692,14 @@ int PQconnCheck(PGconn *conn);
 
       <para>
        Unlike <xref linkend="libpq-PQstatus"/>, this function checks socket
-       health. This check is performed by polling the socket. This function is
-       currently available only on systems that support the non-standard
-       <symbol>POLLRDHUP</symbol> extension to the <symbol>poll</symbol> system
-       call, including Linux. <xref linkend="libpq-PQconnCheck"/> returns
-       greater than zero if the remote peer seems to be closed, returns
-       <literal>0</literal> if the socket is valid, and returns
-       <literal>-1</literal> if the connection has been already invalid or
-       an error is error occurred.
+       health. This check is performed by polling the socket. This check is
+       performed by polling the socket. This option relies on kernel events
+       exposed by Linux, macOS, illumos and the BSD family of operating
+       systems, and is not currently available on other systems
+       <xref linkend="libpq-PQconnCheck"/> returns greater than zero if the
+       remote peer seems to be closed, returns <literal>0</literal> if the
+       socket is valid, and returns <literal>-1</literal> if the connection
+       has been already invalid or an error is error occurred.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e1abdb38cb..75ca74a16e 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -835,13 +835,13 @@ postgres=# SELECT postgres_fdw_disconnect_all();
       <filename>postgres_fdw</filename> from the local session to the foreign
       server with the given name. This check is performed by polling the socket
       and allows long-running transactions to be aborted sooner if the kernel
-      reports that the connection is closed. This function is currently
-      available only on systems that support the non-standard <symbol>POLLRDHUP</symbol>
-      extension to the <symbol>poll</symbol> system call, including Linux. This
-      returns <literal>true</literal> if any of the following condition is
-      satisfied: 1) existing connection is not closed by the remote peer, 2)
-      there is no connection for specified server yet, and 3) the checking is
-      not supported on this platform. <literal>false</literal>
+      reports that the connection is closed. This check is performed by polling
+      the socket. This option relies on kernel events exposed by Linux, macOS,
+      illumos and the BSD family of operating systems, and is not currently
+      available on other systems. This returns <literal>true</literal> if any
+      of the following condition is satisfied: 1) existing connection is not
+      closed by the remote peer, 2) there is no connection for specified server
+      yet, and 3) the checking is not supported on this platform. <literal>false</literal>
       is returned if the local session seems to be disconnected from other
       servers. <literal>NULL</literal> is returned if the local session does
       not have a connection cache. Example usage of the function:
@@ -862,16 +862,16 @@ postgres=# SELECT postgres_fdw_verify_connection_states('loopback1');
       by <filename>postgres_fdw</filename> from the local session to the
       foreign servers. This check is performed by polling the socket and allows
       long-running transactions to be aborted sooner if the kernel reports
-      that the connection is closed. This function is currently available only
-      on systems that support the non-standard <symbol>POLLRDHUP</symbol>
-      extension to the <symbol>poll</symbol> system call, including Linux. This
-      returns <literal>true</literal> if any of the following condition is
-      satisfied: 1) all connections are not closed by the remote peer, 2)
-      there are no connections yet, and 3) the checking is
-      not supported on this platform. <literal>false</literal> is returned if
-      the local session seems to be disconnected from at least one remote
-      server. <literal>NULL</literal> is returned if the local session does
-      not have a connection cache. Example usage of the function:
+      that the connection is closed. This check is performed by polling the
+      socket. This option relies on kernel events exposed by Linux, macOS,
+      illumos and the BSD family of operating systems, and is not currently
+      available on other systems. This returns <literal>true</literal> if any
+      of the following condition is satisfied: 1) all connections are not
+      closed by the remote peer, 2) there are no connections yet, and 3) the
+      checking is not supported on this platform. <literal>false</literal> is
+      returned if the local session seems to be disconnected from at least one
+      remote server. <literal>NULL</literal> is returned if the local session
+      does not have a connection cache. Example usage of the function:
 <screen>
 postgres=# SELECT postgres_fdw_verify_connection_states_all();
  postgres_fdw_verify_connection_states_all
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 3bcd760cd7..caa33cd2e4 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -45,6 +45,18 @@
 #include <poll.h>
 #endif
 
+/* We use poll(2) for checking the health of a socket, otherwise kqueue(2) */
+#if (!(defined(HAVE_POLL) && defined(POLLRDHUP)) && \
+	 defined(HAVE_KQUEUE))
+#define CHECK_USE_KQUEUE
+#endif
+
+#if defined(CHECK_USE_KQUEUE)
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#endif
+
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
@@ -57,6 +69,10 @@ static int	pqSocketIsReadableOrWritableOrValid(PGconn *conn, int forRead,
 												int forWrite, time_t end_time);
 static int	pqSocketPoll(int sock, int forRead, int forWrite, time_t end_time);
 
+#if defined(CHECK_USE_KQUEUE)
+static int	pqSocketKqueue(int sock);
+#endif
+
 /*
  * PQlibVersion: return the libpq version number
  */
@@ -1104,9 +1120,12 @@ pqSocketPoll(int sock, int forRead, int forWrite, time_t end_time)
 		if (end_time == 0)
 #if defined(POLLRDHUP)
 			input_fd.events = POLLRDHUP | POLLHUP | POLLNVAL;
+#elif defined(CHECK_USE_KQUEUE)
+			/* Use kqueue(2) instead */
+			return pqSocketKqueue(sock);
 #else
 			return 0;
-#endif /* defined(POLLRDHUP) */
+#endif
 		else
 			return 0;
 	}
@@ -1143,7 +1162,22 @@ pqSocketPoll(int sock, int forRead, int forWrite, time_t end_time)
 	struct timeval *ptr_timeout;
 
 	if (!forRead && !forWrite)
-		return 0;
+	{
+		/*
+		 * Try to check the health if requested
+		 *
+		 * XXX: Is there any systems that cannot use poll(2) but have
+		 * kqueue(2) system call?
+		 */
+		if (end_time == 0)
+#if defined(CHECK_USE_KQUEUE)
+			return pqSocketKqueue(sock);
+#else
+			return 0;
+#endif							/* defined(CHECK_USE_KQUEUE) */
+		else
+			return 0;
+	}
 
 	FD_ZERO(&input_mask);
 	FD_ZERO(&output_mask);
@@ -1175,6 +1209,49 @@ pqSocketPoll(int sock, int forRead, int forWrite, time_t end_time)
 #endif							/* HAVE_POLL */
 }
 
+#if defined(CHECK_USE_KQUEUE)
+static int
+pqSocketKqueue(int sock)
+{
+	struct kevent kev,
+				ret;
+	struct timespec timeout = {0};
+	static int	kq = -1;
+	int			result;
+
+	/* If this function has never called yet, create a kernel event queue */
+	if (kq < 0)
+	{
+		kq = kqueue();
+		if (kq < 0)
+			return -1;
+	}
+
+	/* Prepare kevent structure */
+	EV_SET(&kev, sock, EVFILT_READ, EV_ADD, 0, 0, NULL);
+	if (kevent(kq, &kev, 1, NULL, 0, NULL))
+		return -1;
+
+	/*
+	 * Check the status of socket. Note that we will retry as long as we get
+	 * EINTR.
+	 */
+	do
+		result = kevent(kq, NULL, 0, &ret, 1, &timeout);
+	while (result < 0 && errno == EINTR);
+
+	/* Clean up the queue */
+	EV_SET(&kev, sock, EVFILT_READ, EV_DELETE, 0, 0, NULL);
+	kevent(kq, &kev, 1, NULL, 0, NULL);
+
+	if (result < 0)
+		return -1;
+
+	printf("debug print");
+
+	return ret.flags & EV_EOF;
+}
+#endif							/* defined(CHECK_USE_KQUEUE) */
 
 /*
  * A couple of "miscellaneous" multibyte related functions. They used
@@ -1255,7 +1332,8 @@ PQconnCheck(PGconn *conn)
 int
 PQcanConnCheck(void)
 {
-#if (defined(HAVE_POLL) && defined(POLLRDHUP))
+#if ((defined(HAVE_POLL) && defined(POLLRDHUP)) || \
+	 defined(CHECK_USE_KQUEUE))
 	return true;
 #else
 	return false;
-- 
2.27.0

