You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
6.2 KiB
172 lines
6.2 KiB
// 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; |
|
while (1) { |
|
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); } continue; } |
|
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); continue; } |
|
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_read_count >= CHILDREN * ITER_PER_CHILD) { 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 |
|
}
|
|
|