fix(lib): netio TCP connections run with TCP_NODELAY (bug-1890)
With Nagle on, the small TLS record that ends a burst -- a packet header, the last short chunk of an HA snapshot, a reply -- waited for the peer's delayed ACK, ~40 ms on Linux: seen on 5 of 40 snapshot transfers. Set TCP_NODELAY on every netio TCP socket, outbound (socket_create) and accepted (socket_accept). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3061df1401
commit
8b561547dd
4 changed files with 119 additions and 0 deletions
|
|
@ -221,6 +221,7 @@ libxce_test_src = ['testsuite/test.c',
|
||||||
'testsuite/test_netio_bounded_connect.c',
|
'testsuite/test_netio_bounded_connect.c',
|
||||||
'testsuite/test_netio_batch_dispatch_uaf.c',
|
'testsuite/test_netio_batch_dispatch_uaf.c',
|
||||||
'testsuite/test_netio_err_queue.c',
|
'testsuite/test_netio_err_queue.c',
|
||||||
|
'testsuite/test_netio_nodelay.c',
|
||||||
'testsuite/test_request_send_propagation.c',
|
'testsuite/test_request_send_propagation.c',
|
||||||
'testsuite/test_raft.c',
|
'testsuite/test_raft.c',
|
||||||
'testsuite/test_wal_wire.c',
|
'testsuite/test_wal_wire.c',
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
|
@ -230,6 +232,17 @@ static int set_fd_nonblocking(int fd)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* bug-1890: every netio connection carries small request/reply messages and
|
||||||
|
* TLS records; with Nagle on, the small record that ends a burst waits for
|
||||||
|
* the peer's delayed ACK (~40 ms on Linux). Best effort: a socket that
|
||||||
|
* cannot take the option still works, only slower. */
|
||||||
|
static void socket_set_nodelay(int sock)
|
||||||
|
{
|
||||||
|
int on = 1;
|
||||||
|
|
||||||
|
(void)setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
|
||||||
|
}
|
||||||
|
|
||||||
static int socket_create(int domain, int type, int port, int backlog)
|
static int socket_create(int domain, int type, int port, int backlog)
|
||||||
{
|
{
|
||||||
int sock;
|
int sock;
|
||||||
|
|
@ -255,6 +268,10 @@ static int socket_create(int domain, int type, int port, int backlog)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((domain == AF_INET) && (type == SOCK_STREAM)) {
|
||||||
|
socket_set_nodelay(sock);
|
||||||
|
}
|
||||||
|
|
||||||
if (port > 0) {
|
if (port > 0) {
|
||||||
struct sockaddr_in sin;
|
struct sockaddr_in sin;
|
||||||
|
|
||||||
|
|
@ -417,6 +434,7 @@ static int socket_accept(int sock)
|
||||||
}
|
}
|
||||||
|
|
||||||
fcntl(s, F_SETFD, (fcntl(s, F_GETFD) | FD_CLOEXEC));
|
fcntl(s, F_SETFD, (fcntl(s, F_GETFD) | FD_CLOEXEC));
|
||||||
|
socket_set_nodelay(s);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ extern void test_netio_close_propagation(void);
|
||||||
extern void test_netio_bounded_connect(void);
|
extern void test_netio_bounded_connect(void);
|
||||||
extern void test_netio_batch_dispatch_uaf(void);
|
extern void test_netio_batch_dispatch_uaf(void);
|
||||||
extern void test_netio_err_queue(void);
|
extern void test_netio_err_queue(void);
|
||||||
|
extern void test_netio_nodelay(void);
|
||||||
extern void test_request_send_propagation(void);
|
extern void test_request_send_propagation(void);
|
||||||
extern void test_raft(void);
|
extern void test_raft(void);
|
||||||
extern void test_wal_wire(void);
|
extern void test_wal_wire(void);
|
||||||
|
|
@ -178,6 +179,7 @@ int main(int argc, char* argv[])
|
||||||
TEST(test_netio_bounded_connect);
|
TEST(test_netio_bounded_connect);
|
||||||
TEST(test_netio_batch_dispatch_uaf);
|
TEST(test_netio_batch_dispatch_uaf);
|
||||||
TEST(test_netio_err_queue);
|
TEST(test_netio_err_queue);
|
||||||
|
TEST(test_netio_nodelay);
|
||||||
TEST(test_request_send_propagation);
|
TEST(test_request_send_propagation);
|
||||||
TEST(test_raft);
|
TEST(test_raft);
|
||||||
TEST(test_wal_wire);
|
TEST(test_wal_wire);
|
||||||
|
|
|
||||||
98
src/lib/testsuite/test_netio_nodelay.c
Normal file
98
src/lib/testsuite/test_netio_nodelay.c
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* test_netio_nodelay.c — bug-1890: every netio TCP connection must run with
|
||||||
|
* TCP_NODELAY.
|
||||||
|
*
|
||||||
|
* With Nagle on, the small TLS record that ends a burst (a packet header, the
|
||||||
|
* last short chunk of an HA snapshot, a reply) is held back until the peer
|
||||||
|
* ACKs the data already in flight, and the peer delays that ACK (~40 ms on
|
||||||
|
* Linux). Observed on 5 of 40 HA snapshot transfers: the primary had written
|
||||||
|
* every chunk, the standby read the last ones ~40 ms later.
|
||||||
|
*
|
||||||
|
* Both ends are covered: outbound sockets (endpoint_init with no port, as
|
||||||
|
* ha.c/xcectx.c/client.c create them) and inbound ones (endpoint_accept on a
|
||||||
|
* listening endpoint, as cluster.c's handle_connection does).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <errno.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <unity/unity.h>
|
||||||
|
|
||||||
|
#include "netio.h"
|
||||||
|
|
||||||
|
static int nodelay_of(int fd)
|
||||||
|
{
|
||||||
|
int on = -1;
|
||||||
|
socklen_t len = sizeof(on);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, &len));
|
||||||
|
return on;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_outbound_socket_has_nodelay(void)
|
||||||
|
{
|
||||||
|
endpoint_t* ep = endpoint_get();
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(ep);
|
||||||
|
TEST_ASSERT_TRUE(endpoint_init(ep, AF_INET, SOCK_STREAM, 0, 0) >= 0);
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_EQUAL_MESSAGE(0, nodelay_of(ep->fd),
|
||||||
|
"bug-1890: outbound netio socket runs with Nagle on");
|
||||||
|
|
||||||
|
endpoint_unref(ep);
|
||||||
|
printf("test_outbound_socket_has_nodelay: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_accepted_socket_has_nodelay(void)
|
||||||
|
{
|
||||||
|
iomgr_t iomgr;
|
||||||
|
endpoint_t* ls = endpoint_get();
|
||||||
|
endpoint_t* ep2 = endpoint_get();
|
||||||
|
int cfd;
|
||||||
|
int port;
|
||||||
|
int rc = -1;
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(ls);
|
||||||
|
TEST_ASSERT_NOT_NULL(ep2);
|
||||||
|
|
||||||
|
/* endpoint_accept only needs an SSL_CTX to build the session object. */
|
||||||
|
memset(&iomgr, 0, sizeof(iomgr));
|
||||||
|
iomgr.ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||||
|
TEST_ASSERT_NOT_NULL(iomgr.ssl_ctx);
|
||||||
|
|
||||||
|
for (port = 29870; (port < 29970) && (rc < 0); port++) {
|
||||||
|
rc = endpoint_init(ls, AF_INET, SOCK_STREAM, port, 4);
|
||||||
|
}
|
||||||
|
TEST_ASSERT_TRUE_MESSAGE(rc >= 0, "no free port for the listener");
|
||||||
|
port--;
|
||||||
|
ls->iomgr = &iomgr;
|
||||||
|
|
||||||
|
cfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
TEST_ASSERT_TRUE(cfd >= 0);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, socket_connect(cfd, "127.0.0.1", port, 1000));
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(endpoint_accept(ls, ep2) >= 0);
|
||||||
|
TEST_ASSERT_NOT_EQUAL_MESSAGE(0, nodelay_of(ep2->fd),
|
||||||
|
"bug-1890: accepted netio socket runs with Nagle on");
|
||||||
|
|
||||||
|
close(cfd);
|
||||||
|
endpoint_unref(ep2);
|
||||||
|
endpoint_unref(ls);
|
||||||
|
SSL_CTX_free(iomgr.ssl_ctx);
|
||||||
|
printf("test_accepted_socket_has_nodelay: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_netio_nodelay(void)
|
||||||
|
{
|
||||||
|
printf("\n>> netio TCP_NODELAY tests (bug-1890)\n\n");
|
||||||
|
|
||||||
|
RUN_TEST(test_outbound_socket_has_nodelay);
|
||||||
|
RUN_TEST(test_accepted_socket_has_nodelay);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue