4 changed files with 146 additions and 6 deletions
@ -0,0 +1,100 @@
|
||||
#include "test_clock.h" |
||||
#include "../lib/debug_config.h" |
||||
#include <stdlib.h> |
||||
#include <time.h> |
||||
|
||||
#ifdef __linux__ |
||||
#include <sys/epoll.h> |
||||
#include <poll.h> |
||||
|
||||
static int accelerated; |
||||
static uint64_t offset_us, limit_us, advances, io_events; |
||||
static struct UASYNC* polling; |
||||
static uint64_t step_us; |
||||
|
||||
int __real_clock_gettime(clockid_t id, struct timespec* ts); |
||||
int __wrap_clock_gettime(clockid_t id, struct timespec* ts) { |
||||
int result = __real_clock_gettime(id, ts); |
||||
if (!result && (id == CLOCK_BOOTTIME || id == CLOCK_MONOTONIC)) { |
||||
uint64_t ns = (uint64_t)ts->tv_nsec + __atomic_load_n(&offset_us, __ATOMIC_RELAXED) * 1000; |
||||
ts->tv_sec += ns / 1000000000; ts->tv_nsec = ns % 1000000000; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/* Продвигаем время внутри wait: uasync видит нормальное ожидание, а не искусственный stall между poll. */ |
||||
static void advance_idle(int result, int timeout_ms) { |
||||
if (!polling) return; |
||||
if (result > 0) { io_events += result; return; } |
||||
if (result < 0 || !timeout_ms || polling->immediate_queue_head) return; |
||||
uint64_t now = get_time_us(), end = now + step_us; |
||||
TimeoutEntry next; |
||||
if (!timeout_heap_peek(polling->timeout_heap, &next) && next.expiration * 1000 < end) end = next.expiration * 1000; |
||||
if (limit_us && limit_us < end) end = limit_us; |
||||
if (end <= now) return; |
||||
__atomic_fetch_add(&offset_us, end - now, __ATOMIC_RELAXED); advances++; |
||||
} |
||||
|
||||
int __real_epoll_wait(int fd, struct epoll_event* events, int count, int timeout); |
||||
int __wrap_epoll_wait(int fd, struct epoll_event* events, int count, int timeout) { |
||||
int wait_ms = polling ? (timeout && polling->socket_alloc_count - polling->socket_free_count > 1 ? 1 : 0) : timeout; |
||||
int result = __real_epoll_wait(fd, events, count, wait_ms); |
||||
advance_idle(result, timeout); |
||||
return result; |
||||
} |
||||
|
||||
int __real_poll(struct pollfd* fds, nfds_t count, int timeout); |
||||
int __wrap_poll(struct pollfd* fds, nfds_t count, int timeout) { |
||||
int wait_ms = polling ? (timeout && count > 1 ? 1 : 0) : timeout; |
||||
int result = __real_poll(fds, count, wait_ms); |
||||
advance_idle(result, timeout); |
||||
return result; |
||||
} |
||||
#endif |
||||
|
||||
void test_clock_init(int max_step_tb) { |
||||
if (max_step_tb <= 0) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "test clock: invalid step=%d", max_step_tb); abort(); } |
||||
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO); |
||||
#ifdef __linux__ |
||||
accelerated = getenv("UTUN_TEST_REAL_TIME") == NULL; |
||||
step_us = (uint64_t)max_step_tb * 100; |
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "test clock: mode=%s max_step_tb=%d; native timers and real I/O", |
||||
accelerated ? "accelerated" : "real", max_step_tb); |
||||
#else |
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "test clock: real time on this platform"); |
||||
#endif |
||||
} |
||||
|
||||
void test_clock_poll(struct UASYNC* ua, int timeout_tb) { |
||||
#ifdef __linux__ |
||||
if (accelerated && timeout_tb > 0) { |
||||
if (polling) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "test clock: nested poll is unsupported"); abort(); } |
||||
polling = ua; |
||||
uasync_poll(ua, timeout_tb); |
||||
polling = NULL; |
||||
return; |
||||
} |
||||
#endif |
||||
uasync_poll(ua, timeout_tb); |
||||
} |
||||
|
||||
void test_clock_run_for(struct UASYNC* ua, int duration_tb) { |
||||
if (duration_tb < 0) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "test clock: negative duration=%d", duration_tb); abort(); } |
||||
uint64_t end = get_time_us() + (uint64_t)duration_tb * 100; |
||||
#ifdef __linux__ |
||||
if (limit_us) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "test clock: nested time interval is unsupported"); abort(); } |
||||
limit_us = end; |
||||
#endif |
||||
while (get_time_us() < end) test_clock_poll(ua, 5); |
||||
#ifdef __linux__ |
||||
limit_us = 0; |
||||
#endif |
||||
} |
||||
|
||||
void test_clock_finish(void) { |
||||
#ifdef __linux__ |
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "test clock: completed mode=%s advanced_tb=%llu steps=%llu real_io_events=%llu", |
||||
accelerated ? "accelerated" : "real", (unsigned long long)(offset_us / 100), |
||||
(unsigned long long)advances, (unsigned long long)io_events); |
||||
#endif |
||||
} |
||||
@ -0,0 +1,14 @@
|
||||
#ifndef TEST_CLOCK_H |
||||
#define TEST_CLOCK_H |
||||
|
||||
#include "../lib/u_async.h" |
||||
|
||||
/* Linux: часы продвигаются при пустом I/O до ближайшего таймера, с сохранением реальных сокетов.
|
||||
* UTUN_TEST_REAL_TIME=1 и остальные платформы используют обычные часы/ожидание. |
||||
* init — до создания uasync; finish — после уничтожения. Вызовы только из потока теста, не из callbacks. */ |
||||
void test_clock_init(int max_step_tb); |
||||
void test_clock_poll(struct UASYNC* ua, int timeout_tb); |
||||
void test_clock_run_for(struct UASYNC* ua, int duration_tb); |
||||
void test_clock_finish(void); |
||||
|
||||
#endif |
||||
Loading…
Reference in new issue