Set socket options in child process after forking
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Tue, 12 Mar 2024 11:42:28 +0000 (13:42 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Tue, 12 Mar 2024 11:42:28 +0000 (13:42 +0200)
Try to minimize the work done in the postmaster process for each
accepted connection, so that postmaster can quickly proceed with its
duties. These function calls are very fast so this doesn't make any
measurable performance difference in practice, but it's nice to have
all the socket options initialization code in one place for sake of
readability too. This also paves the way for an upcoming commit that
will move the initialization of the Port struct to the child process.

Discussion: https://www.postgresql.org/message-id/7a59b073-5b5b-151e-7ed3-8b01ff7ce9ef@iki.fi

src/backend/libpq/pqcomm.c

index d9e49ca7028bf22d43e5a84fecfaeee34b14aa5d..c548d38c4df27a833368b46fd7cc4d887444cc9f 100644 (file)
@@ -171,9 +171,102 @@ WaitEventSet *FeBeWaitSet;
 void
 pq_init(void)
 {
+   Port       *port = MyProcPort;
    int         socket_pos PG_USED_FOR_ASSERTS_ONLY;
    int         latch_pos PG_USED_FOR_ASSERTS_ONLY;
 
+   /* fill in the server (local) address */
+   port->laddr.salen = sizeof(port->laddr.addr);
+   if (getsockname(port->sock,
+                   (struct sockaddr *) &port->laddr.addr,
+                   &port->laddr.salen) < 0)
+   {
+       ereport(FATAL,
+               (errmsg("%s() failed: %m", "getsockname")));
+   }
+
+   /* select NODELAY and KEEPALIVE options if it's a TCP connection */
+   if (port->laddr.addr.ss_family != AF_UNIX)
+   {
+       int         on;
+#ifdef WIN32
+       int         oldopt;
+       int         optlen;
+       int         newopt;
+#endif
+
+#ifdef TCP_NODELAY
+       on = 1;
+       if (setsockopt(port->sock, IPPROTO_TCP, TCP_NODELAY,
+                      (char *) &on, sizeof(on)) < 0)
+       {
+           ereport(FATAL,
+                   (errmsg("%s(%s) failed: %m", "setsockopt", "TCP_NODELAY")));
+       }
+#endif
+       on = 1;
+       if (setsockopt(port->sock, SOL_SOCKET, SO_KEEPALIVE,
+                      (char *) &on, sizeof(on)) < 0)
+       {
+           ereport(FATAL,
+                   (errmsg("%s(%s) failed: %m", "setsockopt", "SO_KEEPALIVE")));
+       }
+
+#ifdef WIN32
+
+       /*
+        * This is a Win32 socket optimization.  The OS send buffer should be
+        * large enough to send the whole Postgres send buffer in one go, or
+        * performance suffers.  The Postgres send buffer can be enlarged if a
+        * very large message needs to be sent, but we won't attempt to
+        * enlarge the OS buffer if that happens, so somewhat arbitrarily
+        * ensure that the OS buffer is at least PQ_SEND_BUFFER_SIZE * 4.
+        * (That's 32kB with the current default).
+        *
+        * The default OS buffer size used to be 8kB in earlier Windows
+        * versions, but was raised to 64kB in Windows 2012.  So it shouldn't
+        * be necessary to change it in later versions anymore.  Changing it
+        * unnecessarily can even reduce performance, because setting
+        * SO_SNDBUF in the application disables the "dynamic send buffering"
+        * feature that was introduced in Windows 7.  So before fiddling with
+        * SO_SNDBUF, check if the current buffer size is already large enough
+        * and only increase it if necessary.
+        *
+        * See https://support.microsoft.com/kb/823764/EN-US/ and
+        * https://msdn.microsoft.com/en-us/library/bb736549%28v=vs.85%29.aspx
+        */
+       optlen = sizeof(oldopt);
+       if (getsockopt(port->sock, SOL_SOCKET, SO_SNDBUF, (char *) &oldopt,
+                      &optlen) < 0)
+       {
+           ereport(FATAL,
+                   (errmsg("%s(%s) failed: %m", "getsockopt", "SO_SNDBUF")));
+       }
+       newopt = PQ_SEND_BUFFER_SIZE * 4;
+       if (oldopt < newopt)
+       {
+           if (setsockopt(port->sock, SOL_SOCKET, SO_SNDBUF, (char *) &newopt,
+                          sizeof(newopt)) < 0)
+           {
+               ereport(FATAL,
+                       (errmsg("%s(%s) failed: %m", "setsockopt", "SO_SNDBUF")));
+           }
+       }
+#endif
+
+       /*
+        * Also apply the current keepalive parameters.  If we fail to set a
+        * parameter, don't error out, because these aren't universally
+        * supported.  (Note: you might think we need to reset the GUC
+        * variables to 0 in such a case, but it's not necessary because the
+        * show hooks for these variables report the truth anyway.)
+        */
+       (void) pq_setkeepalivesidle(tcp_keepalives_idle, port);
+       (void) pq_setkeepalivesinterval(tcp_keepalives_interval, port);
+       (void) pq_setkeepalivescount(tcp_keepalives_count, port);
+       (void) pq_settcpusertimeout(tcp_user_timeout, port);
+   }
+
    /* initialize state variables */
    PqSendBufferSize = PQ_SEND_BUFFER_SIZE;
    PqSendBuffer = MemoryContextAlloc(TopMemoryContext, PqSendBufferSize);
@@ -191,7 +284,7 @@ pq_init(void)
     * writes.
     */
 #ifndef WIN32
-   if (!pg_set_noblock(MyProcPort->sock))
+   if (!pg_set_noblock(port->sock))
        ereport(FATAL,
                (errmsg("could not set socket to nonblocking mode: %m")));
 #endif
@@ -199,13 +292,13 @@ pq_init(void)
 #ifndef WIN32
 
    /* Don't give the socket to any subprograms we execute. */
-   if (fcntl(MyProcPort->sock, F_SETFD, FD_CLOEXEC) < 0)
+   if (fcntl(port->sock, F_SETFD, FD_CLOEXEC) < 0)
        elog(FATAL, "fcntl(F_SETFD) failed on socket: %m");
 #endif
 
    FeBeWaitSet = CreateWaitEventSet(NULL, FeBeWaitSetNEvents);
    socket_pos = AddWaitEventToSet(FeBeWaitSet, WL_SOCKET_WRITEABLE,
-                                  MyProcPort->sock, NULL, NULL);
+                                  port->sock, NULL, NULL);
    latch_pos = AddWaitEventToSet(FeBeWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
                                  MyLatch, NULL);
    AddWaitEventToSet(FeBeWaitSet, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
@@ -713,103 +806,6 @@ StreamConnection(pgsocket server_fd, Port *port)
        return STATUS_ERROR;
    }
 
-   /* fill in the server (local) address */
-   port->laddr.salen = sizeof(port->laddr.addr);
-   if (getsockname(port->sock,
-                   (struct sockaddr *) &port->laddr.addr,
-                   &port->laddr.salen) < 0)
-   {
-       ereport(LOG,
-               (errmsg("%s() failed: %m", "getsockname")));
-       return STATUS_ERROR;
-   }
-
-   /* select NODELAY and KEEPALIVE options if it's a TCP connection */
-   if (port->laddr.addr.ss_family != AF_UNIX)
-   {
-       int         on;
-#ifdef WIN32
-       int         oldopt;
-       int         optlen;
-       int         newopt;
-#endif
-
-#ifdef TCP_NODELAY
-       on = 1;
-       if (setsockopt(port->sock, IPPROTO_TCP, TCP_NODELAY,
-                      (char *) &on, sizeof(on)) < 0)
-       {
-           ereport(LOG,
-                   (errmsg("%s(%s) failed: %m", "setsockopt", "TCP_NODELAY")));
-           return STATUS_ERROR;
-       }
-#endif
-       on = 1;
-       if (setsockopt(port->sock, SOL_SOCKET, SO_KEEPALIVE,
-                      (char *) &on, sizeof(on)) < 0)
-       {
-           ereport(LOG,
-                   (errmsg("%s(%s) failed: %m", "setsockopt", "SO_KEEPALIVE")));
-           return STATUS_ERROR;
-       }
-
-#ifdef WIN32
-
-       /*
-        * This is a Win32 socket optimization.  The OS send buffer should be
-        * large enough to send the whole Postgres send buffer in one go, or
-        * performance suffers.  The Postgres send buffer can be enlarged if a
-        * very large message needs to be sent, but we won't attempt to
-        * enlarge the OS buffer if that happens, so somewhat arbitrarily
-        * ensure that the OS buffer is at least PQ_SEND_BUFFER_SIZE * 4.
-        * (That's 32kB with the current default).
-        *
-        * The default OS buffer size used to be 8kB in earlier Windows
-        * versions, but was raised to 64kB in Windows 2012.  So it shouldn't
-        * be necessary to change it in later versions anymore.  Changing it
-        * unnecessarily can even reduce performance, because setting
-        * SO_SNDBUF in the application disables the "dynamic send buffering"
-        * feature that was introduced in Windows 7.  So before fiddling with
-        * SO_SNDBUF, check if the current buffer size is already large enough
-        * and only increase it if necessary.
-        *
-        * See https://support.microsoft.com/kb/823764/EN-US/ and
-        * https://msdn.microsoft.com/en-us/library/bb736549%28v=vs.85%29.aspx
-        */
-       optlen = sizeof(oldopt);
-       if (getsockopt(port->sock, SOL_SOCKET, SO_SNDBUF, (char *) &oldopt,
-                      &optlen) < 0)
-       {
-           ereport(LOG,
-                   (errmsg("%s(%s) failed: %m", "getsockopt", "SO_SNDBUF")));
-           return STATUS_ERROR;
-       }
-       newopt = PQ_SEND_BUFFER_SIZE * 4;
-       if (oldopt < newopt)
-       {
-           if (setsockopt(port->sock, SOL_SOCKET, SO_SNDBUF, (char *) &newopt,
-                          sizeof(newopt)) < 0)
-           {
-               ereport(LOG,
-                       (errmsg("%s(%s) failed: %m", "setsockopt", "SO_SNDBUF")));
-               return STATUS_ERROR;
-           }
-       }
-#endif
-
-       /*
-        * Also apply the current keepalive parameters.  If we fail to set a
-        * parameter, don't error out, because these aren't universally
-        * supported.  (Note: you might think we need to reset the GUC
-        * variables to 0 in such a case, but it's not necessary because the
-        * show hooks for these variables report the truth anyway.)
-        */
-       (void) pq_setkeepalivesidle(tcp_keepalives_idle, port);
-       (void) pq_setkeepalivesinterval(tcp_keepalives_interval, port);
-       (void) pq_setkeepalivescount(tcp_keepalives_count, port);
-       (void) pq_settcpusertimeout(tcp_user_timeout, port);
-   }
-
    return STATUS_OK;
 }