Set socket options in child process after forking

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
This commit is contained in:
Heikki Linnakangas 2024-03-12 13:42:28 +02:00
parent f8c5317d00
commit 73f7fb2a4c
1 changed files with 96 additions and 100 deletions

View 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;
}