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.
551 lines
29 KiB
551 lines
29 KiB
/* Контракты 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])); |
|
}
|
|
|