Сохрани накопленные исправления аудио, async и диагностики

This commit is contained in:
evgeny committed 2026-10-02 01:49:32 +03:00
1 parent b1e3193899
commit bdcf8b5634
59 files changed
+4611 -1441

No files matched your search

+18 -1
View File
@@ -118,7 +118,8 @@ check_PROGRAMS = \
# Linux-only: test_auto_socket_dynamic требует root + dummy-интерфейсы + iproute2/netlink.
if OS_LINUX
check_PROGRAMS += test_auto_socket_dynamic test_standby test_standby_transport
check_PROGRAMS += test_auto_socket_dynamic test_standby test_standby_transport test_android_udp_log test_pool_corruption_log
check_PROGRAMS += test_uasync_regressions test_ll_queue_regressions
endif
# Silero VAD: только при включённом ONNX Runtime (--with-silero-vad)
@@ -161,6 +162,22 @@ test_standby_transport_SOURCES = test_standby_transport.c $(standby_test_sources
test_standby_transport_CPPFLAGS = $(standby_test_cppflags)
test_standby_transport_LDADD = $(LIBUTUN) $(COMMON_LIBS)
test_standby_transport_LDFLAGS = -Wl,--wrap=etcp_encrypt_send -Wl,--wrap=uasync_set_timeout
test_android_udp_log_SOURCES = test_android_udp_log.c ../tools/chatgui-android/jni_bridge/android_udp_log.c
test_android_udp_log_LDADD = $(COMMON_LIBS)
test_android_udp_log_LDFLAGS = -Wl,--wrap=uasync_set_timeout -Wl,--wrap=uasync_cancel_timeout
test_pool_corruption_log_SOURCES = test_pool_corruption_log.c
test_pool_corruption_log_LDADD = $(COMMON_LIBS)
# Linux/ELF: faults внедряются линкером, тестовых веток в библиотеках нет.
async_regression_sources = async_regression.c async_regression.h
async_regression_ldflags = -Wl,--wrap=u_malloc_impl -Wl,--wrap=u_calloc_impl -Wl,--wrap=u_realloc_impl \
-Wl,--wrap=gettimeofday -Wl,--wrap=epoll_wait -Wl,--wrap=epoll_ctl -Wl,--wrap=eventfd -Wl,--wrap=epoll_create1
test_uasync_regressions_SOURCES = test_uasync_regressions.c $(async_regression_sources)
test_uasync_regressions_LDADD = $(COMMON_LIBS)
test_uasync_regressions_LDFLAGS = $(async_regression_ldflags)
test_ll_queue_regressions_SOURCES = test_ll_queue_regressions.c $(async_regression_sources)
test_ll_queue_regressions_LDADD = $(COMMON_LIBS)
test_ll_queue_regressions_LDFLAGS = $(async_regression_ldflags)
endif
# Test definitions
+169
View File
@@ -0,0 +1,169 @@
#define _GNU_SOURCE
#include "async_regression.h"
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/eventfd.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static int alloc_mask, alloc_nth, alloc_hit, alloc_persistent;
static const char* alloc_scope;
static int64_t clock_shift_ms;
static struct epoll_event pending_events[64];
static int pending_count, pending_active, fail_ctl, fail_eventfd, fail_epoll_create;
void regression_fault_arm(int mask, const char* scope, int nth) {
alloc_mask = mask; alloc_scope = scope; alloc_nth = nth; alloc_hit = 0; alloc_persistent = nth == 0;
}
int regression_fault_hit(void) { return alloc_hit; }
void regression_fault_reset(void) { alloc_mask = 0; }
void regression_clock_shift(int64_t milliseconds) { clock_shift_ms = milliseconds; }
void regression_epoll_ctl_fail(int operation) { fail_ctl = operation; }
void regression_eventfd_fail(void) { fail_eventfd = 1; }
void regression_epoll_create_fail(void) { fail_epoll_create = 1; }
static int fail_allocation(int kind, const char* location) {
if (!(alloc_mask & kind) || (alloc_scope && !strstr(location, alloc_scope))) return 0;
// nth=0: устойчивый отказ всех подходящих выделений до reset.
if (!alloc_persistent && --alloc_nth != 0) return 0;
alloc_hit = 1;
fprintf(stderr, "[INJECT] allocation failure: kind=%d at %s\n", kind, location);
return 1;
}
void* __real_u_malloc_impl(uint32_t size, const char* location);
void* __real_u_calloc_impl(uint32_t count, uint32_t size, const char* location);
void* __real_u_realloc_impl(void* ptr, uint32_t size, const char* location);
void* __wrap_u_malloc_impl(uint32_t size, const char* location) {
return fail_allocation(FAULT_MALLOC, location) ? NULL : __real_u_malloc_impl(size, location);
}
void* __wrap_u_calloc_impl(uint32_t count, uint32_t size, const char* location) {
return fail_allocation(FAULT_CALLOC, location) ? NULL : __real_u_calloc_impl(count, size, location);
}
void* __wrap_u_realloc_impl(void* ptr, uint32_t size, const char* location) {
return fail_allocation(FAULT_REALLOC, location) ? NULL : __real_u_realloc_impl(ptr, size, location);
}
int __real_gettimeofday(struct timeval* tv, void* tz);
int __wrap_gettimeofday(struct timeval* tv, void* tz) {
int rc = __real_gettimeofday(tv, tz);
if (rc == 0 && clock_shift_ms) {
int64_t us = (int64_t)tv->tv_sec * 1000000 + tv->tv_usec + clock_shift_ms * 1000;
tv->tv_sec = us / 1000000; tv->tv_usec = us % 1000000;
}
return rc;
}
void regression_epoll_events(const struct epoll_event* events, int count) {
if (count < 0 || count > 64) abort();
memcpy(pending_events, events, count * sizeof(*events));
pending_count = count; pending_active = 1;
}
int __real_epoll_wait(int fd, struct epoll_event* events, int maxevents, int timeout);
int __wrap_epoll_wait(int fd, struct epoll_event* events, int maxevents, int timeout) {
if (!pending_active) return __real_epoll_wait(fd, events, maxevents, timeout);
if (pending_count > maxevents) abort();
memcpy(events, pending_events, pending_count * sizeof(*events));
pending_active = 0;
return pending_count;
}
int __real_epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
int __wrap_epoll_ctl(int epfd, int op, int fd, struct epoll_event* event) {
if (fail_ctl == op) { fail_ctl = 0; errno = ENOMEM; return -1; }
return __real_epoll_ctl(epfd, op, fd, event);
}
int __real_eventfd(unsigned int initval, int flags);
int __wrap_eventfd(unsigned int initval, int flags) {
if (fail_eventfd) { fail_eventfd = 0; errno = EMFILE; return -1; }
return __real_eventfd(initval, flags);
}
int __real_epoll_create1(int flags);
int __wrap_epoll_create1(int flags) {
if (fail_epoll_create) { fail_epoll_create = 0; errno = EMFILE; return -1; }
return __real_epoll_create1(flags);
}
uint64_t regression_time_ms(void) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) abort();
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
void regression_log_init(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) debug_set_category_level(i, DEBUG_LEVEL_WARN);
if (getenv("UTUN_TEST_DEBUG")) {
debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_SOCKET, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_TIMERS, DEBUG_LEVEL_DEBUG);
}
}
static int open_fds(void) {
DIR* dir = opendir("/proc/self/fd");
if (!dir) return -1;
int count = 0;
struct dirent* entry;
while ((entry = readdir(dir))) if (entry->d_name[0] != '.') count++;
closedir(dir);
return count;
}
int regression_run(int argc, char** argv, const struct regression_case* cases, size_t count) {
if (argc > 2) { fprintf(stderr, "Usage: %s [case-name-substring]\n", argv[0]); return 1; }
setvbuf(stdout, NULL, _IONBF, 0);
int passed = 0, failed = 0, skipped = 0;
for (size_t i = 0; i < count; i++) {
if (argc == 2 && !strstr(cases[i].name, argv[1])) continue;
printf("[RUN] %s\n", cases[i].name);
int pipefd[2];
if (pipe(pipefd) < 0) { perror("regression pipe"); return 1; }
pid_t pid = fork();
if (pid < 0) { perror("regression fork"); close(pipefd[0]); close(pipefd[1]); return 1; }
if (pid == 0) {
close(pipefd[0]);
struct rlimit core = {0, 0}; setrlimit(RLIMIT_CORE, &core);
regression_log_init();
size_t allocations = u_get_allocated_count();
int fds = open_fds();
int rc = cases[i].run(cases[i].param);
if (rc == 0 && (allocations != u_get_allocated_count() || fds != open_fds())) {
fprintf(stderr, "[FAIL] resource leak: allocations=%zu->%zu fds=%d->%d\n",
allocations, u_get_allocated_count(), fds, open_fds());
u_report_unfreed_blocks(); rc = 1;
}
close(pipefd[1]);
_exit(rc);
}
close(pipefd[1]);
struct pollfd p = { .fd = pipefd[0], .events = POLLIN };
uint64_t deadline = regression_time_ms() + 2000;
int ready;
do {
uint64_t now = regression_time_ms();
int remaining = deadline > now ? (int)(deadline - now) : 0;
ready = poll(&p, 1, remaining);
} while (ready < 0 && errno == EINTR && regression_time_ms() < deadline);
int timed_out = ready <= 0;
if (timed_out && kill(pid, SIGKILL) < 0 && errno != ESRCH) perror("regression kill");
close(pipefd[0]);
int status;
while (waitpid(pid, &status, 0) < 0) if (errno != EINTR) { perror("regression waitpid"); return 1; }
if (timed_out) { printf("[FAIL] %s: timeout (2000ms)\n", cases[i].name); failed++; }
else if (WIFSIGNALED(status)) { printf("[FAIL] %s: signal %d\n", cases[i].name, WTERMSIG(status)); failed++; }
else if (WEXITSTATUS(status) == 77) { printf("[SKIP] %s\n", cases[i].name); skipped++; }
else if (WEXITSTATUS(status)) { printf("[FAIL] %s: exit %d\n", cases[i].name, WEXITSTATUS(status)); failed++; }
else { printf("[PASS] %s\n", cases[i].name); passed++; }
}
printf("Test Results: %d passed, %d failed, %d skipped\n", passed, failed, skipped);
if (!passed && !failed && !skipped) { fprintf(stderr, "No matching cases\n"); return 1; }
return failed ? 1 : 0;
}
+41
View File
@@ -0,0 +1,41 @@
/* Изоляция регрессий: падение/зависание одного случая не скрывает остальные. */
#ifndef ASYNC_REGRESSION_H
#define ASYNC_REGRESSION_H
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include <stdio.h>
#include <sys/epoll.h>
#define CHECK(cond) do { if (!(cond)) { \
fprintf(stderr, "[FAIL] %s:%d: %s\n", __FILE__, __LINE__, #cond); return 1; \
} } while (0)
#define CHECK_EQ(actual, expected) do { \
long long a_ = (long long)(actual), e_ = (long long)(expected); \
if (a_ != e_) { fprintf(stderr, "[FAIL] %s:%d: %s=%lld expected=%lld\n", \
__FILE__, __LINE__, #actual, a_, e_); return 1; } \
} while (0)
struct regression_case {
const char* name;
int (*run)(int param);
int param;
};
int regression_run(int argc, char** argv, const struct regression_case* cases, size_t count);
uint64_t regression_time_ms(void);
void regression_log_init(void);
enum regression_alloc { FAULT_MALLOC = 1, FAULT_CALLOC = 2, FAULT_REALLOC = 4, FAULT_ALLOC = 7 };
// nth=0 — устойчивый отказ, nth>0 — отказ только соответствующего выделения.
void regression_fault_arm(int mask, const char* scope, int nth);
int regression_fault_hit(void);
void regression_fault_reset(void);
void regression_clock_shift(int64_t milliseconds);
void regression_epoll_events(const struct epoll_event* events, int count);
void regression_epoll_ctl_fail(int operation);
void regression_eventfd_fail(void);
void regression_epoll_create_fail(void);
#endif
+134
View File
@@ -0,0 +1,134 @@
/* Реальный Android UDP-логгер: несколько producers, смена адреса, restart и
* reentrant debug hook. Все операции с таймерами обязаны идти в owner thread. */
#include "../tools/chatgui-android/jni_bridge/android_udp_log.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include <assert.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static pthread_t owner;
static int fail_timer;
static unsigned timer_sets, timer_cancels;
void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*);
err_t __real_uasync_cancel_timeout(struct UASYNC*, void*);
void* __wrap_uasync_set_timeout(struct UASYNC* ua, int tb, void* arg, timeout_callback_t cb, const char* name) {
assert(pthread_equal(owner, pthread_self()));
timer_sets++;
if (fail_timer) {
fail_timer = 0;
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "injected flush timer allocation failure");
return NULL;
}
return __real_uasync_set_timeout(ua, tb, arg, cb, name);
}
err_t __wrap_uasync_cancel_timeout(struct UASYNC* ua, void* timer) {
assert(pthread_equal(owner, pthread_self()));
timer_cancels++;
return __real_uasync_cancel_timeout(ua, timer);
}
struct producer { unsigned id; int port; };
static void* produce(void* arg) {
struct producer* p = arg;
char line[32]; memset(line, 'a' + p->id, sizeof(line)); line[31] = '\n';
for (unsigned i = 0; i < 1000; i++) {
udp_log_write(line, sizeof(line));
if (!p->id && i % 250 == 0) udp_log_set_target("127.0.0.1", p->port);
if (i % 32 == 0) usleep(500);
}
return NULL;
}
static unsigned receive_lines(int fd) {
char buf[65536];
unsigned count = 0;
ssize_t len;
while ((len = recv(fd, buf, sizeof(buf), MSG_DONTWAIT)) > 0) {
assert(len % 32 == 0);
for (ssize_t off = 0; off < len; off += 32) {
assert(buf[off] >= 'a' && buf[off] <= 'd' && buf[off + 31] == '\n');
for (int i = 1; i < 31; i++) assert(buf[off + i] == buf[off]);
count++;
}
}
return count;
}
static void forward_log(int level, const char* category, const char* message) {
(void)category;
udp_log_write(message, (int)strlen(message));
if (level == DEBUG_LEVEL_ERROR) udp_log_flush();
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
owner = pthread_self();
int fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); assert(fd >= 0);
int buffer_size = 1024 * 1024;
assert(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof(buffer_size)) == 0);
struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
assert(bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0);
socklen_t addr_len = sizeof(addr);
assert(getsockname(fd, (struct sockaddr*)&addr, &addr_len) == 0);
int port = ntohs(addr.sin_port);
struct UASYNC* ua = uasync_create(); assert(ua);
udp_log_set_target("127.0.0.1", port); udp_log_attach(ua);
pthread_t threads[4]; struct producer producers[4] = {0};
for (unsigned i = 0; i < 4; i++) {
producers[i].id = i; producers[i].port = port;
assert(pthread_create(&threads[i], NULL, produce, &producers[i]) == 0);
}
unsigned received = 0;
uint64_t deadline = get_time_tb() + 30000;
while (received < 4000 && get_time_tb() < deadline) {
uasync_poll(ua, 10);
received += receive_lines(fd);
}
for (int i = 0; i < 4; i++) assert(pthread_join(threads[i], NULL) == 0);
assert(received == 4000);
assert(timer_sets && ua->timer_alloc_count - ua->timer_free_count == 1);
puts("PASS: 4000 records from 4 threads, target changes and timed flush without torn records");
/* Команда в очереди перед detach не должна обращаться к уничтоженному ua. */
udp_log_set_target("127.0.0.1", port);
udp_log_detach(ua); uasync_poll(ua, 0);
assert(ua->timer_alloc_count == ua->timer_free_count);
uasync_destroy(ua, 0);
unsigned detached_sets = timer_sets;
udp_log_write("detached\n", 9);
udp_log_set_target("127.0.0.1", port);
assert(timer_sets == detached_sets);
udp_log_stop();
char error_log[4096];
while (recv(fd, error_log, sizeof(error_log), MSG_DONTWAIT) > 0) {}
/* Ошибка установки таймера логируется через тот же hook, без deadlock. */
ua = uasync_create(); assert(ua);
debug_set_log_hook(forward_log);
udp_log_set_target("127.0.0.1", port); fail_timer = 1; udp_log_attach(ua);
ssize_t error_len = recv(fd, error_log, sizeof(error_log) - 1, MSG_DONTWAIT);
assert(error_len > 0); error_log[error_len] = 0;
assert(strstr(error_log, "injected flush timer allocation failure"));
udp_log_set_target("127.0.0.1", port); uasync_poll(ua, 0);
assert(ua->timer_alloc_count - ua->timer_free_count == 1);
udp_log_write("x", INT_MAX); /* Отказ до чтения данных, без переполнения длины. */
udp_log_set_target(NULL, 0); uasync_poll(ua, 0);
assert(ua->timer_alloc_count == ua->timer_free_count);
udp_log_set_target("127.0.0.1", port); uasync_poll(ua, 0);
assert(ua->timer_alloc_count - ua->timer_free_count == 1);
udp_log_detach(ua); udp_log_stop(); uasync_poll(ua, 0);
assert(ua->timer_alloc_count == ua->timer_free_count && timer_cancels);
debug_set_log_hook(NULL);
uasync_destroy(ua, 0); close(fd);
puts("PASS: detach/restart, disable/re-enable, oversized log and reentrant error hook");
return 0;
}
+6 -2
View File
@@ -502,9 +502,13 @@ int main(int argc, char** argv) {
if (t.inst[IDX_A]) utun_instance_destroy(t.inst[IDX_A]);
if (t.inst[IDX_B]) utun_instance_destroy(t.inst[IDX_B]);
uasync_poll(ua, 0); // завершить отложенное освобождение CM и роутера
uasync_drain_immediate(ua); // завершить все порции отложенного освобождения CM и роутера
uasync_poll(ua, 0); // удалить отменённые таймеры из heap
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
fprintf(stderr, "headless teardown left live timers or sockets\n"); ok = 0;
fprintf(stderr, "headless teardown left live timers or sockets: timers=%zu/%zu sockets=%zu/%zu\n",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_INFO);
uasync_print_resources(ua, "HEADLESS_TEARDOWN_FAILED"); ok = 0;
}
uasync_destroy(ua, 0);
+402
View File
@@ -0,0 +1,402 @@
/* Проверяем очередь независимой моделью и контрактами callbacks/владения. */
#include "async_regression.h"
#include <stddef.h>
#include <string.h>
static void count_cb(struct ll_queue* q, void* arg) { (void)q; ++*(int*)arg; }
static struct ll_entry* new_entry(int value, int len) {
struct ll_entry* e = queue_entry_new(sizeof(value));
if (e) { memcpy(e->data, &value, sizeof(value)); e->len = len; }
return e;
}
static int entry_value(struct ll_entry* e) { int value; memcpy(&value, e->data, sizeof(value)); return value; }
static void dispose(struct ll_queue* q) {
queue_set_callback(q, NULL, NULL); queue_set_on_get(q, NULL, NULL); queue_set_empty_callback(q, NULL, NULL);
struct ll_entry* e;
while ((e = queue_data_get(q))) { queue_dgram_free(e); queue_entry_free(e); }
queue_free(q);
}
static int test_empty_notification(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "empty notification"); CHECK(q);
int calls = 0;
if (param == 0 || param == 1) queue_set_empty_callback(q, count_cb, &calls);
struct ll_entry* e = new_entry(1, 12); CHECK(e); CHECK_EQ(queue_data_put(q, e), 0);
if (param == 0) uasync_drain_immediate(ua); /* Очередь успела заполниться до вызова. */
if (param >= 2) queue_set_empty_callback(q, count_cb, &calls);
if (param == 3) { CHECK_EQ(queue_remove_data(q, e), 0); queue_entry_free(e); }
else queue_entry_free(queue_data_get(q));
CHECK_EQ(calls, 0); uasync_drain_immediate(ua); CHECK_EQ(calls, 1);
uasync_drain_immediate(ua); CHECK_EQ(calls, 1);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_empty_cancel_replace(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "empty cancellation"); CHECK(q);
int old = 0, current = 0;
queue_set_empty_callback(q, count_cb, &old);
queue_set_empty_callback(q, param ? count_cb : NULL, &current);
uasync_drain_immediate(ua); CHECK_EQ(old, 0); CHECK_EQ(current, param ? 1 : 0);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_empty_free(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "empty before free"); CHECK(q); int calls = 0;
if (param) CHECK_EQ(queue_data_put(q, new_entry(1, 0)), 0);
queue_set_empty_callback(q, count_cb, &calls);
if (param) queue_entry_free(queue_data_get(q));
queue_free(q); uasync_drain_immediate(ua); CHECK_EQ(calls, 0);
uasync_destroy(ua, 0); return 0;
}
static int test_on_get(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "on get"); CHECK(q); int calls = 0;
queue_set_on_get(q, count_cb, &calls);
for (int i = 0; i < 4; i++) CHECK_EQ(queue_data_put(q, new_entry(i, i + 1)), 0);
for (int i = 0; i < 4; i++) queue_entry_free(queue_data_get(q));
CHECK_EQ(calls, 0);
if (param) queue_set_on_get(q, NULL, NULL);
uasync_drain_immediate(ua); CHECK_EQ(calls, param ? 0 : 4);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_on_get_free(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "on get teardown"); CHECK(q); int calls = 0;
queue_set_on_get(q, count_cb, &calls);
for (int i = 0; i < param + 1; i++) CHECK_EQ(queue_data_put(q, new_entry(i, 1)), 0);
for (int i = 0; i < param + 1; i++) queue_entry_free(queue_data_get(q));
queue_set_on_get(q, NULL, NULL); queue_free(q);
uasync_drain_immediate(ua); CHECK_EQ(calls, 0); /* ASAN проверяет даже чтение cb=NULL из freed q. */
uasync_destroy(ua, 0); return 0;
}
typedef int (*put_fn)(struct ll_queue*, struct ll_entry*);
static put_fn putters[] = {queue_data_put, queue_data_put_first, queue_data_put_with_index, queue_data_put_first_with_index};
static int test_limits(int param) {
int which = param & 3, limit = param & 4 ? 0 : 1;
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, which >= 2 ? 1 : 0, 0, which >= 2 ? sizeof(int) : 0, "limit"); CHECK(q);
queue_set_size_limit(q, limit);
if (limit) CHECK_EQ(putters[which](q, new_entry(1, 30)), 0);
CHECK_EQ(putters[which](q, new_entry(2, 40)), -1);
CHECK_EQ(q->count, limit); CHECK_EQ(q->total_bytes, limit ? 30 : 0);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_remove_invalid(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 1, 0, sizeof(int), "invalid remove"); CHECK(q);
struct ll_entry* a = new_entry(1, 100), *b = new_entry(2, 200); CHECK(a && b);
CHECK_EQ(queue_data_put_with_index(q, a), 0);
if (param == 1) {
CHECK_EQ(queue_data_put_with_index(q, b), 0); CHECK_EQ(queue_remove_data(q, b), 0);
}
struct ll_queue* other = NULL;
if (param == 2) {
other = queue_new(ua, 1, 0, sizeof(int), "other queue"); CHECK(other);
CHECK_EQ(queue_data_put_with_index(other, b), 0);
}
CHECK_EQ(queue_remove_data(q, b), -1);
CHECK_EQ(q->count, 1); CHECK_EQ(q->total_bytes, 100); CHECK(q->head == a && q->tail == a);
CHECK_EQ(queue_check_consistency(q), 0);
if (other) dispose(other); else queue_entry_free(b);
dispose(q); uasync_destroy(ua, 0); return 0;
}
struct consume_test { int calls, depth, max_depth, error, action; };
static void consume_cb(struct ll_queue* q, void* arg) {
struct consume_test* t = arg;
if (++t->depth > t->max_depth) t->max_depth = t->depth;
struct ll_entry* e = queue_data_get(q);
if (!e) t->error = 1; else { t->calls++; queue_entry_free(e); }
if (t->action != 1) queue_resume_callback(q);
t->depth--;
}
static int test_consume(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "consumer"); CHECK(q);
struct consume_test t = {0}; t.action = param & 2 ? 1 : 0;
queue_set_callback(q, consume_cb, &t); queue_set_callback_defer(q, param & 1);
for (int i = 0; i < 8; i++) CHECK_EQ(queue_data_put(q, new_entry(i, 1)), 0);
if (param & 1) CHECK_EQ(t.calls, 0);
uasync_drain_immediate(ua);
if (t.action) {
CHECK_EQ(t.calls, 1); CHECK_EQ(q->count, 7);
t.action = 0; queue_resume_callback(q); uasync_drain_immediate(ua);
}
CHECK_EQ(t.calls, 8); CHECK_EQ(t.error, 0); CHECK_EQ(t.max_depth, 1); CHECK_EQ(q->count, 0);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_consume_free_pending(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "consumer teardown"); CHECK(q);
struct consume_test t = {0}; queue_set_callback(q, consume_cb, &t); queue_set_callback_defer(q, 1);
CHECK_EQ(queue_data_put(q, new_entry(1, 1)), 0);
if (param) queue_set_callback(q, NULL, NULL);
dispose(q); uasync_drain_immediate(ua); CHECK_EQ(t.calls, 0);
uasync_destroy(ua, 0); return 0;
}
static int test_waiter_cancel(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "waiter cancellation"); CHECK(q);
queue_set_waiter_defer(q, 1); int calls = 0; struct queue_waiter_handle h = {0};
if (param) CHECK_EQ(queue_data_put(q, new_entry(1, 1)), 0);
CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &calls), param ? 0 : 1);
if (param == 2) queue_entry_free(queue_data_get(q));
queue_waiter_cancel(q, &h); CHECK(!h.internal && !h.call_soon_id);
uasync_drain_immediate(ua); CHECK_EQ(calls, 0);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_waiter_bytes(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "waiter bytes"); CHECK(q);
int calls = 0; struct queue_waiter_handle h = {0}; queue_set_waiter_defer(q, param);
queue_set_threshold(q, 10, 20);
CHECK_EQ(queue_data_put(q, new_entry(1, 20)), 0); CHECK_EQ(queue_data_put(q, new_entry(2, 30)), 0);
CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &calls), 0);
queue_entry_free(queue_data_get(q)); uasync_drain_immediate(ua); CHECK_EQ(calls, 0);
queue_entry_free(queue_data_get(q)); uasync_drain_immediate(ua); CHECK_EQ(calls, 1);
CHECK(!h.internal && !h.call_soon_id);
queue_waiter_cancel(q, &h); dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_waiter_teardown(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "waiter teardown"); CHECK(q);
struct queue_waiter_handle h = {0}; int calls = 0; queue_set_waiter_defer(q, 1);
if (param) CHECK_EQ(queue_data_put(q, new_entry(1, 1)), 0);
queue_waiter_wait(q, &h, count_cb, &calls);
if (param == 2) queue_entry_free(queue_data_get(q));
queue_waiter_cancel(q, &h); dispose(q); uasync_drain_immediate(ua); CHECK_EQ(calls, 0);
CHECK(!h.internal && !h.call_soon_id); uasync_destroy(ua, 0); return 0;
}
/* Teardown без ручной отмены и повторное использование handle до dispatch. */
static int test_waiter_auto_teardown(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "automatic teardown"); CHECK(q);
queue_set_waiter_defer(q, 1); struct queue_waiter_handle h[8] = {0}; int calls = 0;
if (param) CHECK_EQ(queue_data_put(q, new_entry(1, 1)), 0);
for (int i = 0; i < 8; i++) CHECK_EQ(queue_waiter_wait(q, &h[i], count_cb, &calls), param ? 0 : 1);
if (param == 2) queue_entry_free(queue_data_get(q));
dispose(q);
for (int i = 0; i < 8; i++) CHECK(!h[i].internal && !h[i].call_soon_id && !h[i].defer_q);
uasync_drain_immediate(ua); CHECK_EQ(calls, 0); uasync_destroy(ua, 0); return 0;
}
static int test_waiter_rearm(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "rearm"); CHECK(q);
struct queue_waiter_handle h = {0}; int old = 0, current = 0;
queue_set_waiter_defer(q, 1); CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &old), 1);
if (param) CHECK_EQ(queue_data_put(q, new_entry(1, 1)), 0);
CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &current), param ? 0 : 1);
if (param) queue_entry_free(queue_data_get(q));
uasync_drain_immediate(ua); CHECK_EQ(old, 0); CHECK_EQ(current, 1);
CHECK(!h.internal && !h.call_soon_id && !h.defer_q);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static void free_queue_cb(struct ll_queue* q, void* arg) { ++*(int*)arg; dispose(q); }
static int test_callback_free_queue(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
int which = param < 8 ? param & 3 : 0, deferred = param & 4;
struct ll_queue* q = queue_new(ua, which >= 2 ? 1 : 0, 0, which >= 2 ? sizeof(int) : 0, "callback teardown"); CHECK(q);
int calls = 0;
if (param < 8) {
queue_set_callback(q, free_queue_cb, &calls); queue_set_callback_defer(q, deferred);
CHECK_EQ(putters[which](q, new_entry(1, 10)), 0);
} else if (param == 8) queue_set_empty_callback(q, free_queue_cb, &calls);
else if (param == 9) {
for (int i = 0; i < 4; i++) CHECK_EQ(queue_data_put(q, new_entry(i, 1)), 0);
queue_set_on_get(q, free_queue_cb, &calls);
for (int i = 0; i < 4; i++) queue_entry_free(queue_data_get(q));
} else {
struct queue_waiter_handle h = {0}; queue_set_waiter_defer(q, param == 11);
CHECK_EQ(queue_data_put(q, new_entry(1, 1)), 0);
CHECK_EQ(queue_waiter_wait(q, &h, free_queue_cb, &calls), 0);
queue_entry_free(queue_data_get(q)); uasync_drain_immediate(ua);
CHECK(!h.internal && !h.call_soon_id && !h.defer_q);
}
uasync_drain_immediate(ua); CHECK_EQ(calls, 1); uasync_destroy(ua, 0); return 0;
}
/* Связный список/хеш сравниваются с простым массивом, без использования internals поиска. */
static int test_model(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, param == 0 ? 0 : param == 1 ? 1 : 32, 0,
param ? sizeof(int) : 0, "model"); CHECK(q);
struct ll_entry* model[64]; int count = 0; uint32_t random = 0x73519321u;
for (int step = 0; step < 10000; step++) {
random = random * 1664525u + 1013904223u;
int op = random >> 28;
if (count == 0 || (count < 64 && op < 8)) {
int value = (random >> 8) % 16, len = random % 1000;
struct ll_entry* e = new_entry(value, len); CHECK(e);
int first = op & 1;
CHECK_EQ(putters[(param ? 2 : 0) + first](q, e), 0);
if (first) memmove(model + 1, model, count * sizeof(*model));
model[first ? 0 : count] = e; count++;
} else {
int pos = op & 1 ? random % count : 0;
struct ll_entry* e = model[pos];
if (op & 1) CHECK_EQ(queue_remove_data(q, e), 0); else CHECK(queue_data_get(q) == e);
memmove(model + pos, model + pos + 1, (count - pos - 1) * sizeof(*model)); count--;
CHECK(!e->next && !e->prev && !e->hash_next); queue_entry_free(e);
}
size_t bytes = 0;
struct ll_entry* actual = q->head;
for (int i = 0; i < count; i++) {
CHECK(actual == model[i]); CHECK(actual->prev == (i ? model[i - 1] : NULL));
bytes += model[i]->len; actual = actual->next;
}
CHECK(!actual); CHECK_EQ(q->count, count); CHECK_EQ(q->total_bytes, bytes);
CHECK(q->tail == (count ? model[count - 1] : NULL)); CHECK_EQ(queue_check_consistency(q), 0);
if (param && step % 31 == 0) for (int key = 0; key < 16; key++) {
int expected = 0, found = 0;
for (int i = 0; i < count; i++) if (entry_value(model[i]) == key) expected++;
struct ll_entry* e = queue_find_data_by_index(q, &key);
while (e) {
CHECK_EQ(entry_value(e), key); int present = 0;
for (int i = 0; i < count; i++) if (model[i] == e) present++;
CHECK_EQ(present, 1); CHECK(++found <= expected);
e = queue_find_next_by_index(q, &key, e);
}
CHECK_EQ(found, expected);
}
}
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_snapshot_bytes(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "length snapshot"); CHECK(q);
struct ll_entry* e = new_entry(1, 100); CHECK(e); CHECK_EQ(queue_data_put(q, e), 0);
e->len = 1; CHECK_EQ(q->total_bytes, 100);
if (param) CHECK_EQ(queue_remove_data(q, e), 0); else CHECK(queue_data_get(q) == e);
CHECK_EQ(q->total_bytes, 0); queue_entry_free(e);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_index_bounds(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 1, param & 2 ? 3 : 0, param & 2 ? 4 : 0, "invalid index"); CHECK(q);
CHECK_EQ(putters[2 + (param & 1)](q, new_entry(1, 30)), -1);
CHECK_EQ(q->count, 0); CHECK_EQ(q->total_bytes, 0);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_binary_index(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 1, 3, 7, "binary index"); CHECK(q);
const unsigned char key[] = {0, 1, 0xff, 0x80, 2, 0, 3};
struct ll_entry* e = queue_entry_new(10); CHECK(e); memcpy(e->data + 3, key, sizeof(key));
CHECK_EQ(putters[2 + param](q, e), 0); CHECK(queue_find_data_by_index(q, key) == e);
CHECK_EQ(queue_remove_data(q, e), 0); CHECK(!queue_find_data_by_index(q, key)); queue_entry_free(e);
dispose(q); uasync_destroy(ua, 0); return 0;
}
static int custom_frees;
static void custom_free(uint8_t* ptr) { custom_frees++; u_free(ptr); }
static int test_dgram_ownership(int param) {
struct ll_entry* e = queue_entry_new(0); CHECK(e); e->len = 10;
struct memory_pool* pool = NULL;
if (param == 1) {
pool = memory_pool_init(32, "dgram ownership"); CHECK(pool);
e->dgram_pool = pool; e->dgram = memory_pool_alloc(pool);
} else {
e->dgram = u_malloc(32); if (param == 2) e->dgram_free_fn = custom_free;
}
CHECK(e->dgram); queue_dgram_free(e); queue_dgram_free(e);
CHECK(!e->dgram); CHECK_EQ(e->len, 0); CHECK_EQ(custom_frees, param == 2 ? 1 : 0);
queue_entry_free(e); if (pool) memory_pool_destroy(pool); return 0;
}
static int test_creation_oom(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
regression_fault_arm(FAULT_CALLOC, "ll_queue.c:", param);
CHECK(!queue_new(ua, 8, 0, sizeof(int), "creation oom"));
CHECK(regression_fault_hit()); regression_fault_reset(); uasync_destroy(ua, 0); return 0;
}
static int test_entry_oom(int param) {
regression_fault_arm(FAULT_MALLOC, "ll_queue.c:", param ? 2 : 1);
CHECK(!(param ? ll_alloc_lldgram(32) : queue_entry_new(8)));
CHECK(regression_fault_hit()); regression_fault_reset(); return 0;
}
static int test_waiter_oom(int param) {
(void)param;
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "waiter oom"); CHECK(q);
CHECK_EQ(queue_data_put(q, new_entry(1, 1)), 0); int calls = 0; struct queue_waiter_handle h = {0};
regression_fault_arm(FAULT_MALLOC, "ll_queue.c:", 1);
CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &calls), -1);
CHECK(regression_fault_hit()); regression_fault_reset(); CHECK(!h.internal);
CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &calls), 0);
queue_entry_free(queue_data_get(q)); CHECK_EQ(calls, 1);
queue_waiter_cancel(q, &h); dispose(q); uasync_destroy(ua, 0); return 0;
}
static int test_waiter_schedule_oom(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct ll_queue* q = queue_new(ua, 0, 0, 0, "waiter dispatch oom"); CHECK(q);
queue_set_waiter_defer(q, 1); struct queue_waiter_handle h = {0}; int calls = 0;
if (param) {
queue_set_threshold(q, 1, 0);
CHECK_EQ(queue_data_put(q, new_entry(1, 1)), 0); CHECK_EQ(queue_data_put(q, new_entry(2, 1)), 0);
CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &calls), 0);
}
regression_fault_arm(FAULT_CALLOC, "u_async.c:", 1);
if (param) queue_entry_free(queue_data_get(q));
else CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &calls), -1);
CHECK(regression_fault_hit()); regression_fault_reset();
if (param) { CHECK(h.internal); queue_entry_free(queue_data_get(q)); }
else { CHECK(!h.internal && !h.call_soon_id); CHECK_EQ(queue_waiter_wait(q, &h, count_cb, &calls), 1); }
uasync_drain_immediate(ua); CHECK_EQ(calls, 1);
CHECK(!h.internal && !h.call_soon_id && !h.defer_q);
dispose(q); uasync_destroy(ua, 0); return 0;
}
#define CASE(name, fn, p) {name, fn, p}
int main(int argc, char** argv) {
const struct regression_case cases[] = {
CASE("empty/refill-before-dispatch", test_empty_notification, 0),
CASE("empty/drain-before-dispatch", test_empty_notification, 1),
CASE("empty/drain-get", test_empty_notification, 2), CASE("empty/drain-remove", test_empty_notification, 3),
CASE("empty/cancel", test_empty_cancel_replace, 0), CASE("empty/replace", test_empty_cancel_replace, 1),
CASE("empty/free-pending-empty", test_empty_free, 0), CASE("empty/free-pending-get", test_empty_free, 1),
CASE("on-get/each-entry", test_on_get, 0), CASE("on-get/cancel", test_on_get, 1),
CASE("on-get/free-one-pending", test_on_get_free, 0), CASE("on-get/free-many-pending", test_on_get_free, 3),
CASE("limit/put", test_limits, 0), CASE("limit/put-first", test_limits, 1),
CASE("limit/put-index", test_limits, 2), CASE("limit/put-first-index", test_limits, 3),
CASE("limit/zero-put", test_limits, 4), CASE("limit/zero-put-first", test_limits, 5),
CASE("limit/zero-put-index", test_limits, 6), CASE("limit/zero-put-first-index", test_limits, 7),
CASE("remove/not-in-any-queue", test_remove_invalid, 0), CASE("remove/already-removed", test_remove_invalid, 1),
CASE("remove/in-another-queue", test_remove_invalid, 2),
CASE("consumer/immediate", test_consume, 0), CASE("consumer/deferred", test_consume, 1),
CASE("consumer/suspend-resume", test_consume, 2), CASE("consumer/deferred-suspend-resume", test_consume, 3),
CASE("consumer/free-pending", test_consume_free_pending, 0), CASE("consumer/cancel-pending", test_consume_free_pending, 1),
CASE("waiter/cancel-free-queue", test_waiter_cancel, 0), CASE("waiter/cancel-busy-queue", test_waiter_cancel, 1),
CASE("waiter/cancel-released-queue", test_waiter_cancel, 2),
CASE("waiter/byte-threshold-immediate", test_waiter_bytes, 0), CASE("waiter/byte-threshold-deferred", test_waiter_bytes, 1),
CASE("waiter/teardown-free", test_waiter_teardown, 0), CASE("waiter/teardown-busy", test_waiter_teardown, 1),
CASE("waiter/teardown-released", test_waiter_teardown, 2),
CASE("waiter/auto-free-pending", test_waiter_auto_teardown, 0),
CASE("waiter/auto-free-busy", test_waiter_auto_teardown, 1),
CASE("waiter/auto-free-released", test_waiter_auto_teardown, 2),
CASE("waiter/rearm-pending", test_waiter_rearm, 0), CASE("waiter/rearm-busy", test_waiter_rearm, 1),
CASE("callback/free-put", test_callback_free_queue, 0), CASE("callback/free-put-first", test_callback_free_queue, 1),
CASE("callback/free-put-index", test_callback_free_queue, 2), CASE("callback/free-put-first-index", test_callback_free_queue, 3),
CASE("callback/free-deferred-put", test_callback_free_queue, 4),
CASE("callback/free-deferred-put-first", test_callback_free_queue, 5),
CASE("callback/free-deferred-put-index", test_callback_free_queue, 6),
CASE("callback/free-deferred-put-first-index", test_callback_free_queue, 7),
CASE("callback/free-empty", test_callback_free_queue, 8), CASE("callback/free-on-get", test_callback_free_queue, 9),
CASE("callback/free-waiter", test_callback_free_queue, 10), CASE("callback/free-deferred-waiter", test_callback_free_queue, 11),
CASE("model/no-index-10000", test_model, 0), CASE("model/collisions-10000", test_model, 1),
CASE("model/hash-10000", test_model, 2),
CASE("bytes/snapshot-get", test_snapshot_bytes, 0), CASE("bytes/snapshot-remove", test_snapshot_bytes, 1),
CASE("index/zero-put", test_index_bounds, 0), CASE("index/zero-put-first", test_index_bounds, 1),
CASE("index/overflow-put", test_index_bounds, 2), CASE("index/overflow-put-first", test_index_bounds, 3),
CASE("index/binary-offset-put", test_binary_index, 0), CASE("index/binary-offset-put-first", test_binary_index, 1),
CASE("ownership/dgram-malloc", test_dgram_ownership, 0), CASE("ownership/dgram-pool", test_dgram_ownership, 1),
CASE("ownership/dgram-custom", test_dgram_ownership, 2),
CASE("oom/waiter-immediate-dispatch-retry", test_waiter_schedule_oom, 0),
CASE("oom/waiter-released-dispatch-retry", test_waiter_schedule_oom, 1),
CASE("oom/queue-object", test_creation_oom, 1), CASE("oom/hash-table", test_creation_oom, 2),
CASE("oom/entry", test_entry_oom, 0), CASE("oom/dgram", test_entry_oom, 1), CASE("oom/waiter-retry", test_waiter_oom, 0),
};
return regression_run(argc, argv, cases, sizeof(cases) / sizeof(cases[0]));
}
+95
View File
@@ -0,0 +1,95 @@
/* Проверяем лог до намеренной остановки; зависший child завершает parent. */
#include "../lib/memory_pool.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include <assert.h>
#include <signal.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static int log_fd;
static struct UASYNC* child_ua;
static void* child_timer;
static void capture_log(int level, const char* category, const char* message) {
(void)level; (void)category;
size_t len = strlen(message);
assert(write(log_fd, message, len) == (ssize_t)len);
}
static void free_executing_timer(void* arg) {
(void)arg;
memory_pool_free_impl(child_ua->timeout_pool, child_timer, "first free inside callback");
}
static void damage(int mode) {
if (mode == 2) {
child_ua = uasync_create(); assert(child_ua);
child_timer = uasync_set_timeout(child_ua, 0, NULL, free_executing_timer, "corrupt_timer_test"); assert(child_timer);
for (int i = 0; i < 10; i++) uasync_poll(child_ua, 10);
} else {
struct memory_pool* pool = memory_pool_init(16, "corrupt_pool_test"); assert(pool);
void* obj = memory_pool_alloc_impl(pool, "allocation test location"); assert(obj);
if (mode == 0) {
memory_pool_free_impl(pool, obj, "first free test location");
memory_pool_free_impl(pool, obj, "repeated free test location");
} else {
/* Битые pointers в хвосте нельзя разыменовывать для вывода строки. */
memset((char*)obj + pool->object_size, 0xa5, 4 + 2 * sizeof(const char*));
memory_pool_free_impl(pool, obj, "overflow free test location");
}
}
_exit(1); /* Остановка обязательна. */
}
static void check_log_before_halt(int mode) {
int pipe_fd[2]; assert(pipe(pipe_fd) == 0);
pid_t pid = fork(); assert(pid >= 0);
if (!pid) {
close(pipe_fd[0]); log_fd = pipe_fd[1];
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_enable_console(0);
debug_set_log_hook(capture_log);
damage(mode);
}
close(pipe_fd[1]);
char output[8192] = {0}; size_t used = 0;
uint64_t deadline = get_time_tb() + 30000;
while (!strstr(output, "stopping thread permanently") && get_time_tb() < deadline) {
struct pollfd p = { .fd = pipe_fd[0], .events = POLLIN };
if (poll(&p, 1, 100) <= 0) continue;
ssize_t len = read(pipe_fd[0], output + used, sizeof(output) - used - 1);
if (len <= 0) break;
used += len; output[used] = 0;
if (used == sizeof(output) - 1) break;
}
/* Если вместо остановки произошёл abort/exit, pipe получит HUP. */
struct pollfd stopped = { .fd = pipe_fd[0], .events = POLLIN };
poll(&stopped, 1, 100);
int status = 0;
int still_running = waitpid(pid, &status, WNOHANG) == 0;
if (still_running) { assert(kill(pid, SIGKILL) == 0); assert(waitpid(pid, &status, 0) == pid); }
close(pipe_fd[0]);
fprintf(stderr, "%s", output);
assert(still_running && strstr(output, "stopping thread permanently"));
if (mode == 1) {
assert(strstr(output, "BUFFER OVERFLOW") && strstr(output, "overflow free test location"));
} else {
assert(strstr(output, "DOUBLE FREE") && strstr(output, "first_free=") && strstr(output, "repeated_free="));
if (mode == 0) {
assert(strstr(output, "allocation test location") && strstr(output, "first free test location"));
assert(strstr(output, "repeated free test location"));
} else {
assert(strstr(output, "timer freed inside callback") && strstr(output, "corrupt_timer_test"));
assert(strstr(output, "first free inside callback"));
}
}
}
int main(void) {
for (int i = 0; i < 3; i++) check_log_before_halt(i);
puts("PASS: double free, damaged canary/metadata and timer callback log before permanent halt");
return 0;
}
+3 -4
View File
@@ -98,11 +98,10 @@ static int test_delay_line(void) {
speex_aec_feed_playback(a, pcm + 480, 480);
CHECK(speex_aec_delay_fill(a) == 1, "fill=%d after 960 (expect 1)", speex_aec_delay_fill(a));
/* захват чанками по 480 → 0, потом кадр */
/* Частичный захват отклоняется без изменения линии задержки. */
n = speex_aec_process_capture(a, in, 480, out);
CHECK(n == 0, "capture 480 should accumulate (returned %d)", n);
n = speex_aec_process_capture(a, in + 480, 480, out);
CHECK(n == FS, "capture 960 should emit frame (returned %d)", n);
CHECK(n == 0, "partial capture must be rejected (returned %d)", n);
CHECK(speex_aec_delay_fill(a) == 1, "invalid capture changed delay line");
/* докормить рендер до глубины 3 */
speex_aec_feed_playback(a, pcm, FS);
+33 -23
View File
@@ -107,6 +107,7 @@ typedef struct {
/* Timer callback for testing */
static void test_timer_callback(void* arg) {
test_context_t* ctx = (test_context_t*)arg;
ctx->timer_id = NULL;
ctx->callback_count++;
test_stats.timer_callbacks++;
@@ -177,13 +178,12 @@ static void test_timer_cancellation_races(void) {
uasync_t* ua = uasync_create();
ASSERT_NOT_NULL(ua, "Failed to create uasync instance");
test_context_t ctx = {0};
ctx.expected_count = 2;
test_context_t ctx[3] = {{0}};
/* Create timers that will be cancelled at different stages */
void* timer1 = uasync_set_timeout(ua, 5, &ctx, test_timer_callback, "test_t1"); /* 0.5ms */
void* timer2 = uasync_set_timeout(ua, 50, &ctx, test_timer_callback, "test_t2"); /* 5ms */
void* timer3 = uasync_set_timeout(ua, 100, &ctx, test_timer_callback, "test_t3"); /* 10ms */
void* timer1 = ctx[0].timer_id = uasync_set_timeout(ua, 5, &ctx[0], test_timer_callback, "test_t1");
void* timer2 = ctx[1].timer_id = uasync_set_timeout(ua, 5000, &ctx[1], test_timer_callback, "test_t2");
void* timer3 = ctx[2].timer_id = uasync_set_timeout(ua, 100, &ctx[2], test_timer_callback, "test_t3");
ASSERT_NOT_NULL(timer1, "Failed to set timer 1");
ASSERT_NOT_NULL(timer2, "Failed to set timer 2");
@@ -192,30 +192,34 @@ static void test_timer_cancellation_races(void) {
/* Cancel timer1 immediately (before it fires) */
err_t cancel_result = uasync_cancel_timeout(ua, timer1);
ASSERT_EQ(cancel_result, ERR_OK, "Failed to cancel timer 1");
ctx[0].timer_id = NULL;
test_stats.timer_cancellations++;
/* Poll briefly - timer1 should not fire, others should */
uasync_poll(ua, 10); /* 1ms */
/* Cancel timer2 while it might be firing */
cancel_result = uasync_cancel_timeout(ua, timer2);
/* Result could be ERR_OK or ERR_FAIL depending on timing */
/* После callback handle уже недействителен: отменяем только pending таймер. */
if (ctx[1].timer_id) {
ASSERT_EQ(uasync_cancel_timeout(ua, ctx[1].timer_id), ERR_OK, "Failed to cancel pending timer 2");
ctx[1].timer_id = NULL;
}
/* Continue polling */
int poll_count = 0;
while (ctx.callback_count < 2 && poll_count < 50) {
while (ctx[2].callback_count < 1 && poll_count < 50) {
uasync_poll(ua, 10);
poll_count++;
}
/* Verify we got expected callbacks (timer3 + possibly timer2) */
ASSERT_TRUE(ctx.callback_count >= 1, "Too few timers fired");
ASSERT_TRUE(ctx.callback_count <= 2, "Too many timers fired");
ASSERT_EQ(ctx[0].callback_count, 0, "Cancelled timer fired");
ASSERT_EQ(ctx[1].callback_count, 0, "Cancelled timer 2 fired");
ASSERT_EQ(ctx[2].callback_count, 1, "Timer 3 must fire exactly once");
/* Cleanup remaining timer */
if (timer3) {
uasync_cancel_timeout(ua, timer3);
timer3 = NULL;
if (ctx[2].timer_id) {
uasync_cancel_timeout(ua, ctx[2].timer_id);
ctx[2].timer_id = NULL;
}
uasync_destroy(ua, 0);
@@ -413,9 +417,10 @@ static void test_error_handling(void) {
void* error_timer = uasync_set_timeout(ua, 5, &ctx, test_error_callback, "test_err");
ASSERT_NOT_NULL(error_timer, "Failed to set error timer");
uasync_poll(ua, 10);
for (int i = 0; i < 20 && !ctx.callback_count; i++) uasync_poll(ua, 10);
/* Verify error was recorded */
/* Проверяем вызов именно этого callback, с учётом округления таймеров вверх. */
ASSERT_EQ(ctx.callback_count, 1, "Error callback must fire exactly once");
ASSERT_TRUE(test_stats.race_condition_errors > 0, "Error wasn't recorded");
/* Cleanup */
@@ -483,6 +488,7 @@ static void test_concurrent_operations(void) {
contexts[i] = individual_ctx;
timers[i] = uasync_set_timeout(ua, (i + 1) * 5, individual_ctx, test_timer_callback, "test_stress");
ASSERT_NOT_NULL(timers[i], "Failed to set timer");
individual_ctx->timer_id = timers[i];
}
/* Stress test: poll while operations are happening */
@@ -490,8 +496,9 @@ static void test_concurrent_operations(void) {
/* Cancel some timers randomly */
if (cycle % 7 == 0) {
int idx = cycle % 10;
if (timers[idx]) {
uasync_cancel_timeout(ua, timers[idx]);
if (contexts[idx]->timer_id) {
ASSERT_EQ(uasync_cancel_timeout(ua, contexts[idx]->timer_id), ERR_OK, "Failed to cancel pending stress timer");
contexts[idx]->timer_id = NULL;
timers[idx] = NULL;
}
}
@@ -520,9 +527,7 @@ static void test_concurrent_operations(void) {
}
for (int i = 0; i < 10; i++) {
if (contexts[i] && contexts[i]->callback_count == 0 && timers[i]) {
uasync_cancel_timeout(ua, timers[i]);
}
if (contexts[i] && contexts[i]->timer_id) uasync_cancel_timeout(ua, contexts[i]->timer_id);
timers[i] = NULL;
if (contexts[i]) { u_free(contexts[i]); contexts[i] = NULL; }
}
@@ -641,6 +646,8 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_SYS, "=== lib Comprehensive Unit Tests ===");
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Testing race conditions, memory management, and error handling");
size_t allocated_before = u_get_allocated_count();
/* Run all tests */
test_basic_timers();
test_timer_cancellation_races();
@@ -665,7 +672,10 @@ int main(void) {
/* Memory leak detection */
DEBUG_INFO(DEBUG_CATEGORY_SYS, "=== Memory Leak Detection ===");
DEBUG_INFO(DEBUG_CATEGORY_SYS, "No memory leaks detected during testing");
if (u_get_allocated_count() != allocated_before) {
TEST_FAIL("Memory allocation count did not return to baseline");
u_report_unfreed_blocks();
} else DEBUG_INFO(DEBUG_CATEGORY_SYS, "No memory leaks detected during testing");
return (test_stats.tests_failed > 0) ? 1 : 0;
}
}
+14 -3
View File
@@ -18,6 +18,7 @@ typedef struct {
uint64_t fire_time; /* время срабатывания */
int fired; /* флаг что уже сработал */
void* timer_id; /* ID таймера в uasync */
void* context; /* Pending контекст принадлежит тесту. */
} timer_data_t;
typedef struct {
@@ -35,6 +36,8 @@ static int test_failed = 0;
/* Callback для таймера */
static void timer_callback(void* arg) {
timer_ctx_t* ctx = (timer_ctx_t*)arg;
ctx->timer_data[ctx->timer_number].timer_id = NULL;
ctx->timer_data[ctx->timer_number].context = NULL;
/* Записываем время срабатывания */
ctx->timer_data[ctx->timer_number].fire_time = get_time_tb();
@@ -44,6 +47,7 @@ static void timer_callback(void* arg) {
/* Если stop_flag установлен - НЕ перезапускаем */
if (*ctx->stop_flag == 1) {
(*ctx->active_count)--;
u_free(ctx);
return;
}
@@ -62,6 +66,7 @@ static void timer_callback(void* arg) {
void* new_timer = uasync_set_timeout(ctx->ua, random_delay, new_ctx, timer_callback, "test_timer");
if (new_timer) {
ctx->timer_data[ctx->timer_number].timer_id = new_timer;
ctx->timer_data[ctx->timer_number].context = new_ctx;
} else {
/* Не удалось создать - уменьшаем active_count */
(*ctx->active_count)--;
@@ -70,6 +75,7 @@ static void timer_callback(void* arg) {
} else {
(*ctx->active_count)--;
}
u_free(ctx);
}
/* Callback для стоп-таймера */
@@ -84,7 +90,7 @@ int main(void) {
debug_set_categories(DEBUG_CATEGORY_ALL);
DEBUG_INFO(DEBUG_CATEGORY_TEST, "=== UASYNC Timeout Test ===");
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Timer count: %d, random delay 0-10ms, stop after 10s", TIMER_COUNT);
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Timer count: %d, random delay 0-10ms, stop after 1s", TIMER_COUNT);
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Each timer restarts with new random delay until stop");
srand(42);
@@ -132,12 +138,13 @@ int main(void) {
}
timer_data[i].timer_id = timer_id;
timer_data[i].context = ctx;
active_count++;
}
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Created %d timers, active_count=%d", TIMER_COUNT, active_count);
/* Создаём стоп-таймер на 10 секунд */
/* Создаём стоп-таймер на 1 секунду */
timer_ctx_t* stop_ctx = u_malloc(sizeof(timer_ctx_t));
if (!stop_ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate stop context");
@@ -160,7 +167,7 @@ int main(void) {
return 1;
}
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Stop timer set for 10 seconds");
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Stop timer set for 1 second");
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Running event loop...");
/* Ждём пока active_count станет 0 (все таймеры отработают после stop) */
@@ -192,6 +199,10 @@ int main(void) {
uasync_cancel_timeout(ua, stop_timer);
}
u_free(stop_ctx);
for (int i = 0; i < TIMER_COUNT; i++) {
if (timer_data[i].timer_id) uasync_cancel_timeout(ua, timer_data[i].timer_id);
u_free(timer_data[i].context);
}
uasync_destroy(ua, 0);
+551
View File
@@ -0,0 +1,551 @@
/* Контракты event loop: обе ветки опроса, жизненный цикл, fault injection. */
#define _GNU_SOURCE
#include "async_regression.h"
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
struct socket_test {
struct UASYNC* ua;
void* id;
int fd[2];
int reads, writes, errors, callback_error, action;
};
static void count_cb(void* arg) { ++*(int*)arg; }
static void read_cb(int fd, void* arg) {
struct socket_test* t = arg;
char byte;
if (read(fd, &byte, 1) < 0 && errno != EAGAIN) t->callback_error = 1;
t->reads++;
if (t->action == 1) {
if (uasync_remove_socket(t->ua, t->id) != ERR_OK) t->callback_error = 1;
t->id = NULL;
} else if (t->action == 2) {
if (uasync_set_socket_write(t->ua, t->id, 0) != ERR_OK) t->callback_error = 1;
}
}
static void write_cb(int fd, void* arg) { (void)fd; ((struct socket_test*)arg)->writes++; }
static void error_cb(int fd, void* arg) { (void)fd; ((struct socket_test*)arg)->errors++; }
static int socket_setup(struct socket_test* t, int param, int want_read, int want_write, int want_error) {
memset(t, 0, sizeof(*t));
if (param & 1) regression_epoll_create_fail();
t->ua = uasync_create(); CHECK(t->ua);
CHECK_EQ(t->ua->use_epoll, !(param & 1));
CHECK_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, t->fd), 0);
CHECK_EQ(socket_set_nonblocking(t->fd[0]), 0);
if (param & 2) {
t->id = uasync_add_socket_t(t->ua, t->fd[0], want_read ? read_cb : NULL,
want_write ? write_cb : NULL, want_error ? error_cb : NULL, "regression", t);
} else {
t->id = uasync_add_socket(t->ua, t->fd[0], want_read ? read_cb : NULL,
want_write ? write_cb : NULL, want_error ? error_cb : NULL, "regression", t);
}
CHECK(t->id);
return 0;
}
static void socket_cleanup(struct socket_test* t) {
if (t->id) uasync_remove_socket(t->ua, t->id);
close(t->fd[0]); if (t->fd[1] >= 0) close(t->fd[1]);
uasync_destroy(t->ua, 0);
}
static int test_socket_read_write(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 1, 1), 0);
CHECK_EQ(write(t.fd[1], "x", 1), 1);
uasync_poll(t.ua, 0);
CHECK_EQ(t.reads, 1); CHECK_EQ(t.writes, 1); CHECK_EQ(t.errors, 0); CHECK_EQ(t.callback_error, 0);
socket_cleanup(&t); return 0;
}
static int test_remove_in_read(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param & 3, 1, !(param & 4), 1), 0);
t.action = 1;
CHECK_EQ(write(t.fd[1], "x", 1), 1);
if (param & 4) { close(t.fd[1]); t.fd[1] = -1; }
uasync_poll(t.ua, 0);
CHECK_EQ(t.reads, 1); CHECK_EQ(t.writes, 0); CHECK_EQ(t.errors, 0); CHECK_EQ(t.callback_error, 0);
socket_cleanup(&t); return 0;
}
static int test_disable_in_read(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 1, 0), 0);
t.action = 2; CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0);
CHECK_EQ(t.reads, 1); CHECK_EQ(t.writes, 0); CHECK_EQ(t.callback_error, 0);
socket_cleanup(&t); return 0;
}
static int test_socket_toggle(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 1, 0), 0);
CHECK_EQ(uasync_set_socket_read(t.ua, t.id, 0), ERR_OK);
CHECK_EQ(uasync_set_socket_write(t.ua, t.id, 0), ERR_OK);
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0);
CHECK_EQ(t.reads, 0); CHECK_EQ(t.writes, 0);
CHECK_EQ(uasync_set_socket_read(t.ua, t.id, 1), ERR_OK); uasync_poll(t.ua, 0);
CHECK_EQ(t.reads, 1); CHECK_EQ(t.writes, 0);
CHECK_EQ(uasync_set_socket_write(t.ua, t.id, 1), ERR_OK); uasync_poll(t.ua, 0);
CHECK_EQ(t.writes, 1);
socket_cleanup(&t); return 0;
}
static int test_fd_mapping(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 0, 0), 0);
CHECK_EQ(uasync_remove_socket(t.ua, t.id), ERR_OK); t.id = NULL;
void* other = uasync_add_socket_t(t.ua, t.fd[1], read_cb, NULL, NULL, "other fd", &t); CHECK(other);
if (param & 4) {
CHECK_EQ(uasync_remove_socket_t(t.ua, t.fd[0]), ERR_FAIL);
CHECK_EQ(uasync_set_socket_read(t.ua, other, 0), ERR_OK);
} else {
t.id = uasync_add_socket(t.ua, t.fd[0], read_cb, NULL, NULL, "readded fd", &t); CHECK(t.id);
}
CHECK_EQ(uasync_remove_socket(t.ua, other), ERR_OK);
socket_cleanup(&t); return 0;
}
static int test_lookup(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param & 3, 1, 0, 0), 0);
if (param & 4) {
CHECK_EQ(uasync_remove_socket(t.ua, t.id), ERR_OK); t.id = NULL;
int high = fcntl(t.fd[0], F_DUPFD, FD_SETSIZE + 16); CHECK(high >= FD_SETSIZE);
close(t.fd[0]); t.fd[0] = high;
t.id = uasync_add_socket(t.ua, high, read_cb, NULL, NULL, "high fd", &t); CHECK(t.id);
}
void* found = NULL; CHECK_EQ(uasync_lookup_socket(t.ua, t.fd[0], &found), 0);
CHECK(found == t.id);
CHECK_EQ(uasync_set_socket_read(t.ua, found, 0), ERR_OK);
CHECK_EQ(uasync_set_socket_write(t.ua, found, 0), ERR_OK);
CHECK_EQ(uasync_remove_socket(t.ua, found), ERR_OK); t.id = NULL;
socket_cleanup(&t); return 0;
}
static int test_array_growth(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 0, 0), 0);
int high = fcntl(t.fd[0], F_DUPFD, 128); CHECK(high >= 128);
void* h = uasync_add_socket(t.ua, high, NULL, NULL, NULL, "grow", NULL); CHECK(h);
CHECK_EQ(uasync_remove_socket(t.ua, h), ERR_OK); close(high);
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0); CHECK_EQ(t.reads, 1);
CHECK_EQ(uasync_set_socket_read(t.ua, t.id, 0), ERR_OK);
socket_cleanup(&t); return 0;
}
static int test_duplicate_fd(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 0, 0), 0);
CHECK(!uasync_add_socket_t(t.ua, t.fd[0], read_cb, NULL, NULL, "duplicate", &t));
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0); CHECK_EQ(t.reads, 1);
socket_cleanup(&t); return 0;
}
static int test_stale_batch(int param) {
(void)param;
struct socket_test t; CHECK_EQ(socket_setup(&t, 0, 0, 1, 0), 0);
struct epoll_event event;
CHECK_EQ(epoll_wait(t.ua->epoll_fd, &event, 1, 0), 1);
CHECK_EQ(uasync_remove_socket(t.ua, t.id), ERR_OK);
t.id = uasync_add_socket(t.ua, t.fd[0], NULL, write_cb, NULL, "new generation", &t); CHECK(t.id);
regression_epoll_events(&event, 1); uasync_poll(t.ua, 0); CHECK_EQ(t.writes, 0);
uasync_poll(t.ua, 0); CHECK_EQ(t.writes, 1);
socket_cleanup(&t); return 0;
}
struct replace_test { struct UASYNC* ua; int fd[4]; void* id[2]; int old_calls, new_calls, error; };
static void replacement_cb(int fd, void* arg) { (void)fd; ((struct replace_test*)arg)->new_calls++; }
static void replace_other_cb(int fd, void* arg) {
struct replace_test* t = arg;
if (++t->old_calls != 1) return;
int other = fd == t->fd[0] ? 1 : 0;
if (uasync_remove_socket(t->ua, t->id[other]) != ERR_OK) t->error = 1;
t->id[other] = uasync_add_socket(t->ua, t->fd[other * 2], NULL, replacement_cb, NULL, "replacement", t);
if (!t->id[other]) t->error = 1;
}
static int test_replace_other_in_batch(int param) {
struct replace_test t = {0};
if (param) regression_epoll_create_fail();
t.ua = uasync_create(); CHECK(t.ua);
for (int i = 0; i < 2; i++) {
CHECK_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, t.fd + i * 2), 0);
t.id[i] = uasync_add_socket(t.ua, t.fd[i * 2], NULL, replace_other_cb, NULL, "original", &t); CHECK(t.id[i]);
}
uasync_poll(t.ua, 0); CHECK_EQ(t.error, 0); CHECK_EQ(t.old_calls, 1); CHECK_EQ(t.new_calls, 0);
uasync_poll(t.ua, 0); CHECK_EQ(t.new_calls, 1);
for (int i = 0; i < 2; i++) CHECK_EQ(uasync_remove_socket(t.ua, t.id[i]), ERR_OK);
for (int i = 0; i < 4; i++) close(t.fd[i]);
uasync_destroy(t.ua, 0); return 0;
}
static int test_priority_event(int param) {
(void)param;
struct socket_test t; CHECK_EQ(socket_setup(&t, 0, 0, 1, 1), 0);
struct epoll_event event; CHECK_EQ(epoll_wait(t.ua->epoll_fd, &event, 1, 0), 1);
event.events = EPOLLPRI; regression_epoll_events(&event, 1); uasync_poll(t.ua, 0);
CHECK_EQ(t.errors, 1);
socket_cleanup(&t); return 0;
}
struct free_context { struct UASYNC* ua; void* id; int* writes; };
static void free_read_cb(int fd, void* arg) {
struct free_context* c = arg; (void)fd;
uasync_remove_socket(c->ua, c->id); u_free(c);
}
static void freed_write_cb(int fd, void* arg) { (void)fd; ++*((struct free_context*)arg)->writes; }
static int test_freed_context(int param) {
(void)param;
struct UASYNC* ua = uasync_create(); CHECK(ua);
int fd[2], writes = 0; CHECK_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, fd), 0);
struct free_context* c = u_calloc(1, sizeof(*c)); CHECK(c); c->ua = ua; c->writes = &writes;
c->id = uasync_add_socket(ua, fd[0], free_read_cb, freed_write_cb, NULL, "free context", c); CHECK(c->id);
CHECK_EQ(write(fd[1], "x", 1), 1); uasync_poll(ua, 0); CHECK_EQ(writes, 0);
close(fd[0]); close(fd[1]); uasync_destroy(ua, 0); return 0;
}
static int test_due_timer(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua); int count = 0;
CHECK(uasync_set_timeout(ua, 0, &count, count_cb, "due"));
if (param) regression_clock_shift(-60000);
uint64_t start = regression_time_ms(); uasync_poll(ua, param ? 0 : 10000);
uint64_t elapsed = regression_time_ms() - start;
regression_clock_shift(0);
fprintf(stderr, "[OBSERVE] due timer: elapsed=%llums callbacks=%d\n", (unsigned long long)elapsed, count);
CHECK_EQ(count, 1); CHECK(elapsed < 500);
uasync_destroy(ua, 0); return 0;
}
static int test_forward_clock(int param) {
(void)param;
struct UASYNC* ua = uasync_create(); CHECK(ua); int count = 0;
void* id = uasync_set_timeout(ua, 100000, &count, count_cb, "ten seconds"); CHECK(id);
regression_clock_shift(60000); uasync_poll(ua, 0); regression_clock_shift(0);
CHECK_EQ(count, 0); CHECK_EQ(uasync_cancel_timeout(ua, id), ERR_OK);
uasync_destroy(ua, 0); return 0;
}
struct timer_item { struct UASYNC* ua; void* id; int* count; int* sequence; int value; };
static void timer_cb(void* arg) {
struct timer_item* t = arg; t->id = NULL;
if (t->sequence) t->sequence[*t->count] = t->value;
++*t->count;
}
static int test_timer_cancel(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct timer_item items[96] = {0}; int count = 0;
for (int i = 0; i < 96; i++) {
items[i].ua = ua; items[i].count = &count;
items[i].id = uasync_set_timeout(ua, param ? 100000 : 0, &items[i], timer_cb, "cancel heap growth");
CHECK(items[i].id);
}
for (int i = 0; i < 96; i += 2) {
CHECK_EQ(uasync_cancel_timeout(ua, items[i].id), ERR_OK); items[i].id = NULL;
}
uasync_poll(ua, 0); CHECK_EQ(count, param ? 0 : 48);
for (int i = 0; i < 96; i++) if (items[i].id) {
CHECK_EQ(uasync_cancel_timeout(ua, items[i].id), ERR_OK); items[i].id = NULL;
}
uasync_poll(ua, 0);
size_t alloc, freed; uasync_get_stats(ua, &alloc, &freed, NULL, NULL); CHECK_EQ(alloc, freed);
uasync_destroy(ua, 0); return 0;
}
static int test_soon_fifo(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct timer_item items[96] = {0}; int sequence[96], count = 0;
for (int i = 0; i < 96; i++) {
items[i].value = i; items[i].count = &count; items[i].sequence = sequence;
items[i].id = uasync_call_soon(ua, &items[i], timer_cb); CHECK(items[i].id);
}
if (param) for (int i = 0; i < 96; i += 2) {
CHECK_EQ(uasync_call_soon_cancel(ua, items[i].id), ERR_OK); items[i].id = NULL;
}
uasync_drain_immediate(ua); CHECK_EQ(count, param ? 48 : 96);
for (int i = 0; i < count; i++) CHECK_EQ(sequence[i], param ? i * 2 + 1 : i);
uasync_destroy(ua, 0); return 0;
}
struct chain_test { struct UASYNC* ua; int runs, timer_runs, observed; };
static void chain_cb(void* arg) {
struct chain_test* t = arg;
if (++t->runs == 1000) t->observed = t->timer_runs;
if (t->runs < 1000) uasync_call_soon(t->ua, t, chain_cb);
}
static int test_soon_fairness(int param) {
(void)param;
struct chain_test t = {0}; t.ua = uasync_create(); CHECK(t.ua);
CHECK(uasync_set_timeout(t.ua, 0, &t.timer_runs, count_cb, "fairness"));
CHECK(uasync_call_soon(t.ua, &t, chain_cb));
for (int i = 0; i < 2000 && t.runs < 1000; i++) uasync_poll(t.ua, 0);
CHECK_EQ(t.runs, 1000); CHECK_EQ(t.observed, 1);
uasync_destroy(t.ua, 0); return 0;
}
static void stop_cb(void* arg) { uasync_stop(arg); }
static int test_stop_soon(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 0, 0), 0);
CHECK(uasync_call_soon(t.ua, t.ua, stop_cb)); uasync_mainloop(t.ua);
CHECK_EQ(t.ua->stop, 1); socket_cleanup(&t); return 0;
}
static int test_stopped_poll_cleanup(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua); int calls = 0;
void* id = uasync_set_timeout(ua, 100000, &calls, count_cb, "cancel before stopped poll"); CHECK(id);
CHECK_EQ(uasync_cancel_timeout(ua, id), ERR_OK);
if (param) CHECK(uasync_call_soon(ua, ua, stop_cb)); else uasync_stop(ua);
uasync_poll(ua, -1); CHECK_EQ(calls, 0); CHECK_EQ(ua->timer_alloc_count, ua->timer_free_count);
uasync_destroy(ua, 0); return 0;
}
static int test_post_empty(int param) {
if (param) regression_epoll_create_fail();
struct UASYNC* ua = uasync_create(); CHECK(ua); int count = 0;
uasync_post(ua, count_cb, &count); uasync_poll(ua, -1); CHECK_EQ(count, 1);
uasync_destroy(ua, 0); return 0;
}
static void* worker_post(void* arg) { uasync_post(arg, stop_cb, arg); return NULL; }
static int test_worker_wakeup(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 0, 0), 0);
pthread_t worker; CHECK_EQ(pthread_create(&worker, NULL, worker_post, t.ua), 0);
uasync_mainloop(t.ua); CHECK_EQ(pthread_join(worker, NULL), 0);
CHECK_EQ(t.ua->stop, 1); socket_cleanup(&t); return 0;
}
static void* worker_stop(void* arg) { usleep(20000); uasync_stop(arg); return NULL; }
static int test_worker_stop(int param) {
if (param) regression_epoll_create_fail();
struct UASYNC* ua = uasync_create(); CHECK(ua); pthread_t worker;
CHECK_EQ(pthread_create(&worker, NULL, worker_stop, ua), 0);
uasync_mainloop(ua); CHECK_EQ(pthread_join(worker, NULL), 0);
CHECK_EQ(ua->stop, 1); uasync_destroy(ua, 0); return 0;
}
static int test_reserved_post_cancel(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua); int count = 0;
struct posted_task* tasks[3];
for (int i = 0; i < 3; i++) {
tasks[i] = u_calloc(1, sizeof(*tasks[i])); CHECK(tasks[i]);
tasks[i]->callback = count_cb; tasks[i]->arg = &count; uasync_post_reserved(ua, tasks[i]);
}
CHECK_EQ(uasync_cancel_post(ua, tasks[param]), ERR_OK);
uasync_poll(ua, 0); CHECK_EQ(count, 2);
uasync_destroy(ua, 0); return 0;
}
static int test_socket_growth_oom(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, 0, 1, 0, 0), 0);
int high = fcntl(t.fd[0], F_DUPFD, 128); CHECK(high >= 128);
regression_fault_arm(FAULT_ALLOC, "u_async.c:", param);
void* id = uasync_add_socket(t.ua, high, read_cb, NULL, NULL, "growth failure", &t);
CHECK(regression_fault_hit()); regression_fault_reset(); CHECK(!id); close(high);
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0); CHECK_EQ(t.reads, 1);
socket_cleanup(&t); return 0;
}
static int test_create_oom(int param) {
regression_fault_arm(FAULT_ALLOC, NULL, param);
struct UASYNC* ua = uasync_create();
CHECK(regression_fault_hit()); regression_fault_reset(); CHECK(!ua);
return 0;
}
static int test_eventfd_failure(int param) {
(void)param; regression_eventfd_fail(); CHECK(!uasync_create()); return 0;
}
static int test_schedule_oom(int param) {
struct UASYNC* ua = uasync_create(); CHECK(ua); int count = 0;
regression_fault_arm(FAULT_CALLOC, "u_async.c:", 1);
void* id = param ? uasync_call_soon(ua, &count, count_cb) : uasync_set_timeout(ua, 0, &count, count_cb, "oom");
CHECK(regression_fault_hit()); regression_fault_reset(); CHECK(!id);
uasync_poll(ua, 0); CHECK_EQ(count, 0);
CHECK(uasync_set_timeout(ua, 0, &count, count_cb, "recover")); uasync_poll(ua, 0); CHECK_EQ(count, 1);
uasync_destroy(ua, 0); return 0;
}
static int test_heap_growth_oom(int param) {
(void)param;
struct UASYNC* ua = uasync_create(); CHECK(ua); int count = 0;
void* ids[16];
for (int i = 0; i < 16; i++) { ids[i] = uasync_set_timeout(ua, 100000, &count, count_cb, "heap"); CHECK(ids[i]); }
regression_fault_arm(FAULT_REALLOC, "timeout_heap.c:", 1);
CHECK(!uasync_set_timeout(ua, 0, &count, count_cb, "heap failure"));
CHECK(regression_fault_hit()); regression_fault_reset();
for (int i = 0; i < 16; i++) CHECK_EQ(uasync_cancel_timeout(ua, ids[i]), ERR_OK);
uasync_poll(ua, 0); CHECK_EQ(count, 0);
uasync_destroy(ua, 0); return 0;
}
static int test_epoll_control_failure(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, 0, 1, 1, 0), 0);
regression_epoll_ctl_fail(EPOLL_CTL_MOD);
int result = param ? uasync_set_socket_write(t.ua, t.id, 0) : uasync_set_socket_read(t.ua, t.id, 0);
CHECK_EQ(result, ERR_FAIL);
CHECK_EQ(param ? uasync_set_socket_write(t.ua, t.id, 0) : uasync_set_socket_read(t.ua, t.id, 0), ERR_OK);
socket_cleanup(&t); return 0;
}
static int test_epoll_add_failure(int param) {
(void)param;
struct UASYNC* ua = uasync_create(); CHECK(ua);
int fd[2]; CHECK_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, fd), 0);
regression_epoll_ctl_fail(EPOLL_CTL_ADD);
CHECK(!uasync_add_socket(ua, fd[0], read_cb, NULL, NULL, "failed add", NULL));
void* id = uasync_add_socket(ua, fd[0], NULL, NULL, NULL, "retry add", NULL); CHECK(id);
CHECK_EQ(uasync_remove_socket(ua, id), ERR_OK);
close(fd[0]); close(fd[1]); uasync_destroy(ua, 0); return 0;
}
/* Отказ памяти переносится из poll в create/add; готовые регистрации остаются рабочими. */
static void* poll_add(struct UASYNC* ua, int fd, socket_callback_t read_fn, socket_callback_t write_fn, void* arg, int sock_api) {
if (sock_api) return uasync_add_socket_t(ua, fd, read_fn, write_fn, NULL, "poll reserve", arg);
return uasync_add_socket(ua, fd, read_fn, write_fn, NULL, "poll reserve", arg);
}
static int test_poll_create_oom(int param) {
regression_epoll_create_fail(); regression_fault_arm(FAULT_REALLOC, "u_async.c:", param);
struct UASYNC* ua = uasync_create();
CHECK(regression_fault_hit()); regression_fault_reset(); CHECK(!ua); return 0;
}
static int test_poll_growth_oom(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, 1, 1, 0, 0), 0);
CHECK(t.ua->poll_fds); CHECK_EQ(t.ua->poll_fds_capacity, 16);
int fds[15]; void* ids[15];
for (int i = 0; i < 15; i++) {
fds[i] = dup(t.fd[0]); CHECK(fds[i] >= 0);
if (i < 14) { ids[i] = poll_add(t.ua, fds[i], NULL, NULL, NULL, param); CHECK(ids[i]); }
}
uasync_poll(t.ua, 0); CHECK_EQ(t.ua->poll_fds_count, 16);
struct pollfd* previous = t.ua->poll_fds;
size_t socket_alloc = t.ua->socket_alloc_count, socket_free = t.ua->socket_free_count;
regression_fault_arm(FAULT_REALLOC, "u_async.c:", 0);
for (int i = 0; i < 3; i++) CHECK(!poll_add(t.ua, fds[14], NULL, NULL, NULL, param));
CHECK(regression_fault_hit()); CHECK(t.ua->poll_fds == previous);
CHECK_EQ(t.ua->poll_fds_count, 16); CHECK_EQ(t.ua->poll_fds_capacity, 16);
CHECK_EQ(t.ua->socket_alloc_count, socket_alloc); CHECK_EQ(t.ua->socket_free_count, socket_free);
void* found = NULL; CHECK_EQ(uasync_lookup_socket(t.ua, fds[14], &found), -1); CHECK(!found);
int timers = 0; CHECK(uasync_set_timeout(t.ua, 0, &timers, count_cb, "poll OOM timer"));
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, -1);
CHECK_EQ(t.reads, 1); CHECK_EQ(timers, 1);
regression_fault_reset();
ids[14] = poll_add(t.ua, fds[14], NULL, NULL, NULL, param); CHECK(ids[14]);
CHECK_EQ(t.ua->poll_fds_capacity, 32);
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0); CHECK_EQ(t.reads, 2);
for (int i = 0; i < 15; i++) { CHECK_EQ(uasync_remove_socket(t.ua, ids[i]), ERR_OK); close(fds[i]); }
socket_cleanup(&t); return 0;
}
static int test_poll_no_alloc(int param) {
struct socket_test t; CHECK_EQ(socket_setup(&t, param, 1, 1, 0), 0);
if (!param) CHECK(!t.ua->poll_fds); /* epoll не резервирует ненужный массив. */
int timers = 0; CHECK(uasync_set_timeout(t.ua, 0, &timers, count_cb, "no allocation in poll"));
regression_fault_arm(FAULT_ALLOC, NULL, 0);
for (int i = 0; i < 8; i++) {
CHECK_EQ(uasync_set_socket_read(t.ua, t.id, i & 1), ERR_OK);
CHECK_EQ(uasync_set_socket_write(t.ua, t.id, i & 1), ERR_OK);
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0);
}
CHECK(!regression_fault_hit()); regression_fault_reset(); CHECK_EQ(timers, 1);
CHECK_EQ(t.reads, 4); CHECK_EQ(t.writes, 4);
socket_cleanup(&t); return 0;
}
struct poll_worker { struct UASYNC* ua; struct posted_task* task; };
static void* poll_oom_worker(void* arg) {
struct poll_worker* t = arg; usleep(20000);
if (t->task) uasync_post_reserved(t->ua, t->task); else uasync_stop(t->ua);
return NULL;
}
static int test_poll_oom_wakeup(int param) {
regression_epoll_create_fail(); struct UASYNC* ua = uasync_create(); CHECK(ua);
struct poll_worker t = { .ua = ua };
if (param) { t.task = u_calloc(1, sizeof(*t.task)); CHECK(t.task); t.task->callback = stop_cb; t.task->arg = ua; }
regression_fault_arm(FAULT_ALLOC, NULL, 0); pthread_t worker;
CHECK_EQ(pthread_create(&worker, NULL, poll_oom_worker, &t), 0);
uasync_mainloop(ua); CHECK_EQ(pthread_join(worker, NULL), 0);
CHECK_EQ(ua->stop, 1); CHECK(!regression_fault_hit()); regression_fault_reset();
uasync_destroy(ua, 0); return 0;
}
struct poll_growth {
struct UASYNC* ua; int fd[2], extra[15]; void* id[16];
int reads, writes, new_writes, callback_error, fail, sock_api, count_before;
};
static void poll_old_write_cb(int fd, void* arg) { (void)fd; ((struct poll_growth*)arg)->writes++; }
static void poll_new_write_cb(int fd, void* arg) { (void)fd; ((struct poll_growth*)arg)->new_writes++; }
static void poll_growth_read_cb(int fd, void* arg) {
struct poll_growth* t = arg; char byte;
if (read(fd, &byte, 1) != 1) t->callback_error = 1;
if (t->reads++ != 0) return;
t->id[15] = poll_add(t->ua, t->extra[14], NULL, poll_new_write_cb, t, t->sock_api);
if (!!t->id[15] == t->fail || t->ua->poll_fds_count != t->count_before) t->callback_error = 1;
}
static int test_poll_growth_in_callback(int param) {
regression_epoll_create_fail(); struct poll_growth t = { .fail = param >= 2, .sock_api = param & 1 };
t.ua = uasync_create(); CHECK(t.ua); CHECK_EQ(t.ua->poll_fds_capacity, 16);
CHECK_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, t.fd), 0);
t.id[0] = poll_add(t.ua, t.fd[0], poll_growth_read_cb, poll_old_write_cb, &t, t.sock_api); CHECK(t.id[0]);
for (int i = 0; i < 15; i++) {
t.extra[i] = i == 14 ? fcntl(t.fd[0], F_DUPFD, 128) : dup(t.fd[0]); CHECK(t.extra[i] >= 0);
if (i < 14) {
t.id[i + 1] = poll_add(t.ua, t.extra[i], NULL, i == 0 ? poll_old_write_cb : NULL, &t, t.sock_api);
CHECK(t.id[i + 1]);
}
}
t.count_before = 16;
if (t.fail) regression_fault_arm(param >= 4 ? FAULT_CALLOC : FAULT_REALLOC, "u_async.c:", param >= 4 ? param - 3 : 0);
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0);
CHECK_EQ(t.callback_error, 0); CHECK_EQ(t.reads, 1); CHECK_EQ(t.writes, 2); CHECK_EQ(t.new_writes, 0);
if (t.fail) {
CHECK(regression_fault_hit()); CHECK_EQ(t.ua->poll_fds_capacity, param >= 4 ? 32 : 16); regression_fault_reset();
t.id[15] = poll_add(t.ua, t.extra[14], NULL, poll_new_write_cb, &t, t.sock_api); CHECK(t.id[15]);
}
CHECK_EQ(t.ua->poll_fds_capacity, 32);
CHECK_EQ(write(t.fd[1], "x", 1), 1); uasync_poll(t.ua, 0);
CHECK_EQ(t.callback_error, 0); CHECK_EQ(t.reads, 2); CHECK_EQ(t.writes, 4); CHECK_EQ(t.new_writes, 1);
for (int i = 0; i < 16; i++) CHECK_EQ(uasync_remove_socket(t.ua, t.id[i]), ERR_OK);
for (int i = 0; i < 15; i++) close(t.extra[i]);
close(t.fd[0]); close(t.fd[1]); uasync_destroy(t.ua, 0); return 0;
}
#define CASE(name, fn, p) {name, fn, p}
#define SOCKET_MATRIX(name, fn) \
CASE(name "/epoll/fd", fn, 0), CASE(name "/poll/fd", fn, 1), \
CASE(name "/epoll/socket", fn, 2), CASE(name "/poll/socket", fn, 3)
int main(int argc, char** argv) {
const struct regression_case cases[] = {
SOCKET_MATRIX("socket/read-write", test_socket_read_write),
SOCKET_MATRIX("socket/remove-in-read", test_remove_in_read),
CASE("socket/remove-in-read-hup/epoll", test_remove_in_read, 4),
CASE("socket/remove-in-read-hup/poll", test_remove_in_read, 5),
SOCKET_MATRIX("socket/disable-write-in-read", test_disable_in_read),
SOCKET_MATRIX("socket/toggle", test_socket_toggle),
SOCKET_MATRIX("socket/fd-readd-after-slot-reuse", test_fd_mapping),
CASE("socket/remove-absent-fd/epoll", test_fd_mapping, 4),
CASE("socket/remove-absent-fd/poll", test_fd_mapping, 5),
SOCKET_MATRIX("socket/lookup-handle", test_lookup),
CASE("socket/lookup-high-fd/epoll", test_lookup, 4),
CASE("socket/lookup-high-fd/poll", test_lookup, 5),
SOCKET_MATRIX("socket/handle-after-array-growth", test_array_growth),
SOCKET_MATRIX("socket/duplicate-fd", test_duplicate_fd),
CASE("socket/replace-other-in-batch/epoll", test_replace_other_in_batch, 0),
CASE("socket/replace-other-in-batch/poll", test_replace_other_in_batch, 1),
CASE("socket/stale-epoll-generation", test_stale_batch, 0),
CASE("socket/epoll-priority-event", test_priority_event, 0),
CASE("socket/freed-context-in-read", test_freed_context, 0),
CASE("timer/due-before-long-poll", test_due_timer, 0),
CASE("timer/wall-clock-backwards", test_due_timer, 1),
CASE("timer/wall-clock-forwards", test_forward_clock, 0),
CASE("timer/cancel-expired-heap-growth", test_timer_cancel, 0),
CASE("timer/cancel-future-heap-growth", test_timer_cancel, 1),
CASE("soon/fifo", test_soon_fifo, 0), CASE("soon/fifo-with-cancel", test_soon_fifo, 1),
CASE("soon/timer-fairness", test_soon_fairness, 0),
CASE("stop/from-soon/epoll", test_stop_soon, 0), CASE("stop/from-soon/poll", test_stop_soon, 1),
CASE("stop/from-worker/epoll", test_worker_stop, 0), CASE("stop/from-worker/poll", test_worker_stop, 1),
CASE("stop/poll-cleans-cancelled-timers", test_stopped_poll_cleanup, 0),
CASE("stop/from-soon-cleans-cancelled-timers", test_stopped_poll_cleanup, 1),
CASE("post/empty/epoll", test_post_empty, 0), CASE("post/empty/poll", test_post_empty, 1),
CASE("post/worker-wakeup/epoll", test_worker_wakeup, 0), CASE("post/worker-wakeup/poll", test_worker_wakeup, 1),
CASE("post/cancel-head", test_reserved_post_cancel, 0), CASE("post/cancel-middle", test_reserved_post_cancel, 1),
CASE("post/cancel-tail", test_reserved_post_cancel, 2),
CASE("poll-reserve/create-oom-once", test_poll_create_oom, 1),
CASE("poll-reserve/create-oom-persistent", test_poll_create_oom, 0),
CASE("poll-reserve/add-oom-fd-recovery", test_poll_growth_oom, 0),
CASE("poll-reserve/add-oom-socket-recovery", test_poll_growth_oom, 1),
CASE("poll-reserve/no-alloc-in-epoll", test_poll_no_alloc, 0),
CASE("poll-reserve/no-alloc-in-poll", test_poll_no_alloc, 1),
CASE("poll-reserve/stop-during-oom", test_poll_oom_wakeup, 0),
CASE("poll-reserve/post-during-oom", test_poll_oom_wakeup, 1),
CASE("poll-reserve/callback-grow-fd", test_poll_growth_in_callback, 0),
CASE("poll-reserve/callback-grow-socket", test_poll_growth_in_callback, 1),
CASE("poll-reserve/callback-oom-fd", test_poll_growth_in_callback, 2),
CASE("poll-reserve/callback-oom-socket", test_poll_growth_in_callback, 3),
CASE("poll-reserve/callback-socket-array-oom-1", test_poll_growth_in_callback, 4),
CASE("poll-reserve/callback-socket-array-oom-2", test_poll_growth_in_callback, 5),
CASE("poll-reserve/callback-socket-array-oom-3", test_poll_growth_in_callback, 6),
CASE("poll-reserve/callback-socket-array-oom-4", test_poll_growth_in_callback, 7),
CASE("oom/socket-grow-1", test_socket_growth_oom, 1), CASE("oom/socket-grow-2", test_socket_growth_oom, 2),
CASE("oom/socket-grow-3", test_socket_growth_oom, 3), CASE("oom/socket-grow-4", test_socket_growth_oom, 4),
CASE("oom/create-1", test_create_oom, 1), CASE("oom/create-2", test_create_oom, 2),
CASE("oom/create-3", test_create_oom, 3), CASE("oom/create-4", test_create_oom, 4),
CASE("oom/create-5", test_create_oom, 5), CASE("oom/create-6", test_create_oom, 6),
CASE("oom/create-7", test_create_oom, 7), CASE("oom/create-8", test_create_oom, 8), CASE("oom/create-9", test_create_oom, 9),
CASE("oom/eventfd-create", test_eventfd_failure, 0),
CASE("oom/timer-node", test_schedule_oom, 0), CASE("oom/soon-node", test_schedule_oom, 1),
CASE("oom/heap-growth", test_heap_growth_oom, 0),
CASE("epoll/failed-add-retry", test_epoll_add_failure, 0),
CASE("epoll/failed-disable-read", test_epoll_control_failure, 0),
CASE("epoll/failed-disable-write", test_epoll_control_failure, 1),
};
return regression_run(argc, argv, cases, sizeof(cases) / sizeof(cases[0]));
}
+73 -17
View File
@@ -32,12 +32,38 @@ static int g_conn_count = 0, g_read_count = 0, g_err_count = 0;
static pid_t g_children[CHILDREN];
static int g_port = 0;
static void* g_mon_id = NULL;
static void* g_timeout_id = NULL;
static void* g_listener_id = NULL;
static int g_closed_count;
struct race_conn {
struct race_conn* next;
struct tcp_conn* tc;
int received;
};
static struct race_conn* g_connections;
static void destroy_connection(struct tcp_conn* tc) {
struct race_conn* c = tc->arg;
struct race_conn** p = &g_connections;
while (*p && *p != c) p = &(*p)->next;
if (*p) *p = c->next;
else { printf("[FAIL] closing untracked connection\n"); g_done = -1; }
g_closed_count++;
tcp_conn_destroy(tc); u_free(c);
}
static void on_read_cb(struct ll_queue* q, void* arg) {
struct tcp_conn* tc = (struct tcp_conn*)arg;
struct race_conn* c = tc->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++;
/* TCP вправе разделить три байта между несколькими queue entries. */
if (c->received > 3 || e->len > 3 - c->received || memcmp(e->dgram, "OK\n" + c->received, e->len)) {
printf("[FAIL] unexpected stream data: received=%d len=%u\n", c->received, e->len); g_done = -1;
} else {
c->received += e->len;
if (c->received == 3 && e->len) g_read_count++;
}
memory_pool_free(tc->data_pool, e->dgram); queue_entry_free(e);
queue_resume_callback(q);
}
@@ -45,7 +71,8 @@ static void on_read_cb(struct ll_queue* q, void* arg) {
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
(void)err; (void)arg;
g_err_count++;
tcp_conn_destroy(tc);
printf("[FAIL] connection error=%d\n", err);
g_done = -1; destroy_connection(tc);
}
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
@@ -55,7 +82,7 @@ static void on_fin_cb(struct tcp_conn* tc, void* arg) {
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
(void)arg;
tcp_conn_destroy(tc);
destroy_connection(tc);
}
static void on_accept_cb(int fd, void* arg) {
@@ -63,11 +90,18 @@ static void on_accept_cb(int fd, 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) {
if (errno == EAGAIN || errno == EWOULDBLOCK) return;
if (errno == EINTR) continue;
printf("[FAIL] accept: %s\n", strerror(errno)); g_done = -1; 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; }
if (socket_set_nonblocking(csock) < 0) { close(csock); g_done = -1; return; }
struct race_conn* c = u_calloc(1, sizeof(*c));
if (!c) { printf("[FAIL] allocate connection context\n"); close(csock); g_done = -1; return; }
struct tcp_conn* tc = tcp_conn_create(g_ua, csock, 512, 512, 4, 0, 0, on_fin_cb, on_error_cb, c);
if (!tc) { printf("[FAIL] create connection\n"); u_free(c); close(csock); g_done = -1; return; }
c->tc = tc; c->next = g_connections; g_connections = c;
tc->on_closed = on_closed_cb;
queue_set_callback(tc->read_queue, on_read_cb, tc);
g_conn_count++;
@@ -91,20 +125,31 @@ static int child_main(int port, int id, int count) {
static void monitor(void* arg) {
(void)arg;
g_mon_id = NULL;
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; }
if (r < 0) {
if (errno == EINTR) { alive++; continue; }
printf("[FAIL] waitpid: %s\n", strerror(errno)); g_done = -1; return;
}
g_children[i] = 0;
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
printf("[FAIL] child exit status=%d\n", status); g_done = -1; return;
}
}
if (alive == 0 && g_read_count == CHILDREN * ITER_PER_CHILD && g_closed_count == g_conn_count) { g_ok = 1; g_done = 1; }
if (!g_done) {
g_mon_id = uasync_set_timeout(g_ua, 500, NULL, monitor, "mon");
if (!g_mon_id) { printf("[FAIL] restart monitor timer\n"); g_done = -1; }
}
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;
g_timeout_id = NULL;
printf("[FAIL] timeout conn=%d read=%d err=%d\n", g_conn_count, g_read_count, g_err_count);
g_done = -1;
}
@@ -125,20 +170,25 @@ int main(void) {
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; }
if (lsock < 0) { printf("[FAIL] socket: %s\n", strerror(errno)); 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_family = AF_INET; la.sin_port = 0;
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; }
socklen_t la_len = sizeof(la);
if (getsockname(lsock, (struct sockaddr*)&la, &la_len) < 0) { printf("[FAIL] getsockname: %s\n", strerror(errno)); goto cleanup; }
g_port = ntohs(la.sin_port);
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, "test_listen", NULL);
g_listener_id = uasync_add_socket(g_ua, lsock, on_accept_cb, NULL, NULL, "test_listen", NULL);
if (!g_listener_id) {
printf("[FAIL] register listener\n"); goto cleanup;
}
for (int i = 0; i < CHILDREN; i++) {
pid_t pid = fork();
@@ -149,14 +199,15 @@ int main(void) {
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");
g_timeout_id = uasync_set_timeout(g_ua, TIMEOUT_MS * 10, NULL, test_timeout, "to");
if (!g_mon_id || !g_timeout_id) { printf("[FAIL] create timers\n"); goto cleanup; }
while (!g_done) uasync_poll(g_ua, 50);
if (to_id) uasync_cancel_timeout(g_ua, to_id);
if (g_timeout_id) { uasync_cancel_timeout(g_ua, g_timeout_id); g_timeout_id = NULL; }
int expected = CHILDREN * ITER_PER_CHILD;
if (g_ok && g_conn_count == expected && g_read_count == expected) {
if (g_ok && g_conn_count == expected && g_read_count == expected && g_closed_count == expected && !g_err_count) {
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);
@@ -166,6 +217,11 @@ int main(void) {
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_timeout_id) uasync_cancel_timeout(g_ua, g_timeout_id);
if (g_listener_id) uasync_remove_socket(g_ua, g_listener_id);
if (lsock >= 0) close(lsock);
while (g_connections) destroy_connection(g_connections->tc);
uasync_drain_immediate(g_ua);
if (g_ua) uasync_destroy(g_ua, 0);
return g_ok ? 0 : 1;
#endif