Files
utun2/tests/test_uasync_socket_race.c
T
Evgeny bfa3e666d4 fix test_uasync_socket_race: increase listen() backlog from 32 to 1024
Root cause: accept queue overflow (backlog=32) caused kernel SYN drops.
Children waited ~1s for SYN retransmit timeout (5 gaps × 1s = 5s out of 7.2s).
With backlog=1024 the timeout drops from ~7.2s to ~0.065s (110× faster).
2026-07-16 19:22:06 +03:00

171 lines
6.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// test_uasync_socket_race.c — тест гонки fd-reuse в epoll при быстром accept/close/accept
// 4 дочерних процесса, каждый 200 connect+send+close. Сервер на uasync+tcp_io.
// Проверяет что нет use-after-free при двойном epoll-событии (ERROR→free tc, WRITE→stale).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#endif
#include <fcntl.h>
#include "../lib/platform_compat.h"
#include "../lib/tcp_io.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define CHILDREN 4
#define ITER_PER_CHILD 200
#define TIMEOUT_MS 30000
static struct UASYNC* g_ua = NULL;
static int g_ok = 0, g_done = 0;
static int g_conn_count = 0, g_read_count = 0, g_err_count = 0;
#ifndef _WIN32
static pid_t g_children[CHILDREN];
static int g_port = 0;
static void* g_mon_id = NULL;
static void on_read_cb(struct ll_queue* q, void* arg) {
struct tcp_conn* tc = (struct tcp_conn*)arg;
struct ll_entry* e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
if (e->len == 3 && memcmp(e->dgram, "OK\n", 3) == 0) g_read_count++;
memory_pool_free(tc->data_pool, e->dgram); queue_entry_free(e);
queue_resume_callback(q);
}
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
(void)err; (void)arg;
g_err_count++;
tcp_conn_destroy(tc);
}
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
(void)arg;
tcp_conn_push_close(tc);
}
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
(void)arg;
tcp_conn_destroy(tc);
}
static void on_accept_cb(int fd, void* arg) {
(void)arg;
struct sockaddr_in addr; socklen_t alen = sizeof(addr);
int csock = accept(fd, (struct sockaddr*)&addr, &alen);
if (csock < 0) return;
if (csock == 0) { int r = open("/dev/null", O_RDONLY); if (r > 0 && r != 0) { dup2(r, 0); close(r); } return; }
socket_set_nonblocking(csock);
struct tcp_conn* tc = tcp_conn_create(g_ua, csock, 512, 512, 4, 0, 0, on_fin_cb, on_error_cb, NULL);
if (!tc) { socket_close_wrapper(csock); return; }
tc->on_closed = on_closed_cb;
queue_set_callback(tc->read_queue, on_read_cb, tc);
g_conn_count++;
}
static int child_main(int port, int id, int count) {
(void)id;
for (int i = 0; i < count; i++) {
int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) return 1;
struct sockaddr_in a; memset(&a, 0, sizeof(a));
a.sin_family = AF_INET; a.sin_port = htons((uint16_t)port);
inet_pton(AF_INET, "127.0.0.1", &a.sin_addr);
if (connect(s, (struct sockaddr*)&a, sizeof(a)) < 0) { close(s); return 1; }
if (send(s, "OK\n", 3, 0) != 3) { close(s); return 1; }
close(s);
}
return 0;
}
static void monitor(void* arg) {
(void)arg;
int alive = 0;
for (int i = 0; i < CHILDREN; i++) {
if (g_children[i] <= 0) continue;
int status; pid_t r = waitpid(g_children[i], &status, WNOHANG);
if (r == 0) { alive++; continue; }
if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { g_done = -1; return; }
g_children[i] = 0;
}
if (alive == 0) { g_ok = 1; g_done = 1; }
if (!g_done) g_mon_id = uasync_set_timeout(g_ua, 500, NULL, monitor, "mon");
}
static void test_timeout(void* arg) {
(void)arg;
printf("[FAIL] timeout conn=%d read=%d err=%d\n", g_conn_count, g_read_count, g_err_count);
g_done = -1;
}
#endif /* !_WIN32 */
int main(void) {
printf("=== test_uasync_socket_race ===\n"); fflush(stdout);
#ifdef _WIN32
printf("[SKIP] test_uasync_socket_race — fork() not available on Windows\n");
return 0;
#else
debug_config_init();
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) debug_set_category_level(i, DEBUG_LEVEL_NONE);
srand((unsigned)getpid());
int fd0 = open("/dev/null", O_RDONLY);
if (fd0 == 0) { } else if (fd0 > 0) { dup2(fd0, 0); close(fd0); } else { close(0); open("/dev/null", O_RDONLY); }
g_port = 25000 + (rand() % 10000);
g_ua = uasync_create();
if (!g_ua) { printf("[FAIL] uasync_create\n"); return 1; }
int lsock = socket(AF_INET, SOCK_STREAM, 0);
if (lsock < 0) { printf("[FAIL] socket\n"); goto cleanup; }
socket_set_reuseaddr(lsock, 1);
socket_set_nonblocking(lsock);
struct sockaddr_in la; memset(&la, 0, sizeof(la));
la.sin_family = AF_INET; la.sin_port = htons((uint16_t)g_port);
la.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(lsock, (struct sockaddr*)&la, sizeof(la)) < 0) { printf("[FAIL] bind: %s\n", strerror(errno)); goto cleanup; }
if (listen(lsock, 1024) < 0) { printf("[FAIL] listen: %s\n", strerror(errno)); goto cleanup; }
uasync_add_socket(g_ua, lsock, on_accept_cb, NULL, NULL, NULL);
for (int i = 0; i < CHILDREN; i++) {
pid_t pid = fork();
if (pid < 0) { printf("[FAIL] fork\n"); goto cleanup; }
if (pid == 0) { _exit(child_main(g_port, i, ITER_PER_CHILD)); }
g_children[i] = pid;
}
printf(" %d children × %d iterations on port %d\n", CHILDREN, ITER_PER_CHILD, g_port); fflush(stdout);
g_mon_id = uasync_set_timeout(g_ua, 100, NULL, monitor, "mon");
void* to_id = uasync_set_timeout(g_ua, TIMEOUT_MS * 10, NULL, test_timeout, "to");
while (!g_done) uasync_poll(g_ua, 50);
if (to_id) uasync_cancel_timeout(g_ua, to_id);
int expected = CHILDREN * ITER_PER_CHILD;
if (g_ok && g_conn_count == expected && g_read_count == expected) {
printf("[PASS] test_uasync_socket_race — %d/%d/%d conn/read/err\n", g_conn_count, g_read_count, g_err_count);
} else {
printf("[FAIL] test_uasync_socket_race — expected %d, got %d/%d/%d\n", expected, g_conn_count, g_read_count, g_err_count);
g_ok = 0;
}
cleanup:
for (int i = 0; i < CHILDREN; i++) if (g_children[i] > 0) { kill(g_children[i], SIGKILL); waitpid(g_children[i], NULL, 0); }
if (g_mon_id) uasync_cancel_timeout(g_ua, g_mon_id);
if (g_ua) uasync_destroy(g_ua, 0);
return g_ok ? 0 : 1;
#endif
}