Before deciding we can use a socket for statistics collection, test to
authorTom Lane
Mon, 22 Mar 2004 23:55:29 +0000 (23:55 +0000)
committerTom Lane
Mon, 22 Mar 2004 23:55:29 +0000 (23:55 +0000)
ensure that it actually passes data.  This catches cases such as a kernel
packet filter rule that makes the socket useless.

Andrew Dunstan

src/backend/postmaster/pgstat.c

index 15cff4d144bb7d2ac29c23766e9e88ae8bf38be8..74ba693be076b4d72a5f799cc8d3895eaf83a635 100644 (file)
@@ -13,7 +13,7 @@
  *
  * Copyright (c) 2001-2003, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.61 2004/03/15 16:21:37 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.62 2004/03/22 23:55:29 tgl Exp $
  * ----------
  */
 #include "postgres.h"
@@ -191,6 +191,12 @@ pgstat_init(void)
               *addr,
                hints;
    int         ret;
+   fd_set      rset;
+   struct timeval tv;
+   char        test_byte;
+   int         sel_res;
+
+#define TESTBYTEVAL ((char) 199)
 
    /*
     * Force start of collector daemon if something to collect
@@ -308,6 +314,85 @@ pgstat_init(void)
            continue;
        }
 
+       /*
+        * Try to send and receive a one-byte test message on the socket.
+        * This is to catch situations where the socket can be created but
+        * will not actually pass data (for instance, because kernel packet
+        * filtering rules prevent it).
+        */
+       test_byte = TESTBYTEVAL;
+       if (send(pgStatSock, &test_byte, 1, 0) != 1)
+       {
+           ereport(LOG,
+                   (errcode_for_socket_access(),
+                    errmsg("could not send test message on socket for statistics collector: %m")));
+           closesocket(pgStatSock);
+           pgStatSock = -1;
+           continue;
+       }
+
+       /*
+        * There could possibly be a little delay before the message can be
+        * received.  We arbitrarily allow up to half a second before deciding
+        * it's broken.
+        */
+       for (;;)                /* need a loop to handle EINTR */
+       {
+           FD_ZERO(&rset);
+           FD_SET(pgStatSock, &rset);
+           tv.tv_sec = 0;
+           tv.tv_usec = 500000;
+           sel_res = select(pgStatSock+1, &rset, NULL, NULL, &tv);
+           if (sel_res >= 0 || errno != EINTR)
+               break;
+       }
+       if (sel_res < 0)
+       {
+           ereport(LOG,
+                   (errcode_for_socket_access(),
+                    errmsg("select() failed in statistics collector: %m")));
+           closesocket(pgStatSock);
+           pgStatSock = -1;
+           continue;
+       }
+       if (sel_res == 0 || !FD_ISSET(pgStatSock, &rset))
+       {
+           /*
+            * This is the case we actually think is likely, so take pains to
+            * give a specific message for it.
+            *
+            * errno will not be set meaningfully here, so don't use it.
+            */
+           ereport(LOG,
+                   (ERRCODE_CONNECTION_FAILURE,
+                    errmsg("test message did not get through on socket for statistics collector")));
+           closesocket(pgStatSock);
+           pgStatSock = -1;
+           continue;
+       }
+
+       test_byte++;            /* just make sure variable is changed */
+
+       if (recv(pgStatSock, &test_byte, 1, 0) != 1)
+       {
+           ereport(LOG,
+                   (errcode_for_socket_access(),
+                    errmsg("could not receive test message on socket for statistics collector: %m")));
+           closesocket(pgStatSock);
+           pgStatSock = -1;
+           continue;
+       }
+
+       if (test_byte != TESTBYTEVAL) /* strictly paranoia ... */
+       {
+           ereport(LOG,
+                   (ERRCODE_INTERNAL_ERROR,
+                    errmsg("incorrect test message transmission on socket for statistics collector")));
+           closesocket(pgStatSock);
+           pgStatSock = -1;
+           continue;
+       }
+
        /* If we get here, we have a working socket */
        break;
    }