Files
utun2/lib/u_async.h
T

231 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Цикл событий одного потока: сокеты (epoll/poll/select), таймеры и FIFO call_soon.
* create → регистрация callbacks → poll/mainloop → destroy вне callbacks после остановки владельцев.
* Таймауты задаются в timebase 0.1мс, положительный абсолютный срок округляется вверх до миллисекунды.
* arg принадлежит вызывающему, освобождает его callback/владелец; отмена и destroy arg не освобождают.
* API сокетов/таймеров используется в потоке loop. Из другого потока — post/post_reserved, wakeup и stop;
* публикация не продлевает время жизни ua/arg, их освобождение нужно согласовать с остановкой producers. */
#ifndef UASYNC_H
#define UASYNC_H
#ifdef __cplusplus
extern "C" {
#endif
#include "platform_compat.h"
#include <stddef.h>
#include <signal.h>
#include "timeout_heap.h"
#include "socket_compat.h"
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket (for pipe/file)
typedef void (*socket_t_callback_t)(socket_t sock, void* user_arg);// передаёт user_arg из uasync_add_socket_t (for sockets)
// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е
// Error type
typedef int err_t;
#define ERR_OK 0
#define ERR_FAIL -1
// Socket node types
#define SOCKET_NODE_TYPE_FD 0 // Regular file descriptor (pipe, file)
#define SOCKET_NODE_TYPE_SOCK 1 // Socket (socket_t)
typedef void (*uasync_post_callback_t)(void* user_arg);
#include "memory_pool.h"
struct timeout_node; // Forward declaration
/* CPU одного потока: 100% = одно ядро. При работе цикла/запросе снимок обновляется раз в 100 мс, без пробуждений.
* До первого замера valid=0; window_us растёт до секунды. При задержке замера
* вклад граничного интервала в среднее оценивается пропорционально времени. */
struct uasync_load {
double current_percent; // CPU потока за последний фактический интервал, 0..100%
double average_percent; // Среднее за накопленное окно до секунды, 0..100%
uint64_t sample_time_us; // Время снимка в шкале get_time_us
uint64_t interval_us; // Длительность последнего интервала
uint64_t window_us; // Длительность учтённого окна среднего
int valid; // 1 = снимок готов; 0 = ещё нет замера или ошибка часов
};
struct uasync_load_interval {
uint64_t start_us, end_us, cpu_ns;
};
struct posted_task {
uasync_post_callback_t callback;
void* arg;
struct posted_task* next;
};
// Uasync instance structure
struct UASYNC {
struct memory_pool* timeout_pool; // Pool for timeout_node allocation
struct timeout_node* immediate_queue_head; // FIFO queue for immediate execution
struct timeout_node* immediate_queue_tail;
TimeoutHeap* timeout_heap; // Heap for timeout management
struct socket_array* sockets; // Array-based socket management
// Debug counters for memory allocation tracking
size_t timer_alloc_count;
size_t timer_free_count;
size_t socket_alloc_count;
size_t socket_free_count;
// Wakeup: eventfd на Linux, pipe на остальных POSIX, UDP-сокеты на Windows
int wakeup_pipe[2]; // [0] чтение, [1] запись; для eventfd значения совпадают
int wakeup_initialized;
// Кеш pollfd для POSIX fallback poll, включая Linux без epoll
struct pollfd* poll_fds; // Pre-allocated pollfd array
int poll_fds_capacity; // Current capacity of poll_fds
int poll_fds_count; // Number of valid entries in poll_fds
int poll_fds_dirty; // 1 if poll_fds needs rebuild
// Epoll support (Linux only)
int epoll_fd; // epoll file descriptor (-1 if not using epoll)
int use_epoll; // 1 = epoll; 0 = poll на POSIX или select на Windows
struct posted_task* posted_tasks_head;
struct posted_task* posted_tasks_tail;
#ifdef _WIN32
CRITICAL_SECTION posted_lock;
#else
pthread_mutex_t posted_lock;
#endif
volatile sig_atomic_t stop; // Флаг выхода из mainloop; stop устанавливает, автоматически не сбрасывается
volatile sig_atomic_t running; // Статус, выставляемый владельцем через mark_*; API его не проверяет
uint64_t last_poll_exit_us;
struct uasync_load load;
struct uasync_load_interval load_history[12]; // 10 периодов + граничные интервалы при задержках
uint64_t load_previous_us, load_previous_cpu_ns, load_next_us, load_log_us;
unsigned int load_head, load_count;
int load_state; // 0=не начат, 1=работает, -1=ошибка часов
};
// Type definitions
typedef struct UASYNC uasync_t;
typedef struct UASYNC UASYNC_t;
// Атомарно читает статус владельца (NULL → 0); состояние stop и работу poll не определяет.
static inline int uasync_is_running(struct UASYNC* ua) {
if (!ua) return 0;
return __atomic_load_n(&ua->running, __ATOMIC_ACQUIRE);
}
// Создаёт готовый к регистрации и poll экземпляр с running=0, stop=0; NULL при ошибке инициализации.
struct UASYNC* uasync_create(void);
// Уничтожение вне callbacks после возврата из poll/mainloop и завершения всех producers; NULL допустим.
// close_fds != 0 закрывает оставшиеся зарегистрированные fd; внутренние wakeup/epoll закрываются всегда.
// Ожидающие таймеры и задачи удаляются без вызова callbacks; user_arg остаётся владельцу.
void uasync_destroy(struct UASYNC* ua, int close_fds);
// Атомарно выставляют running=1/0. Не меняют stop, не запускают/останавливают цикл и не запрещают API.
void uasync_mark_running(struct UASYNC* ua);
void uasync_mark_stopped(struct UASYNC* ua);
// Время в единицах 0.1мс: Linux CLOCK_BOOTTIME (включает suspend), прочие POSIX CLOCK_MONOTONIC, Windows QPC.
uint64_t get_time_tb(void);
// Время в микросекундах в той же шкале; календарные часы ОС на неё не влияют.
uint64_t get_time_us(void);
// Timeouts, timebase = 0.1 mS
// Как работать с таймаутами если таймаут может быть использован несколько раз:
// 1. заводим дескриптор таймаута и обнуляем
// 2. перед активацией таймаута проверяем дескриптор (t_id) на null
// 3. после успешного cancel или в начале callback дескриптор обнуляем
// это обеспечит отсутствие утечек и задвоений таймаутов
// Однократный таймер: timeout_tb >= 0, callback не NULL, user_arg передаётся без копирования.
// При timeout_tb > 0 срок = ceil((now_us + timeout_tb*100)/1000) мс; при 0 готов к обработке сразу.
// Callback вызывается при обработке heap, а не синхронно из set_timeout; задержка исполнения возможна.
// name сохраняется по указателю без ограничения в 15 символов; строка должна жить до освобождения узла.
// Возвращает handle или NULL при неверных аргументах/ошибке выделения ресурсов.
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback, const char* name);
// Только для ещё живого таймера этого ua: ERR_OK при отмене, ERR_FAIL при неверных аргументах/отсутствии в heap.
// Освобождение узла отложено до очистки heap/destroy. После освобождения старый handle использовать нельзя.
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id);
// Добавляет callback(user_arg) в FIFO; handle или NULL при неверных аргументах/ошибке выделения.
// Один проход берёт исходную порцию; poll обрабатывает очередь в начале и после ожидания, перед heap.
// Поэтому callback может выполниться в текущем poll. Синхронно из call_soon он не вызывается.
void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t callback);
// Только для ещё живого узла call_soon этого ua: зануляет callback, освобождение — при обработке/destroy.
// ERR_OK при отмене (включая повторную до обработки узла), ERR_FAIL при NULL/чужом ua.
// После успешной отмены обнулить handle; после исполнения/освобождения обращаться к нему нельзя.
err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
// Регистрирует fd с callbacks чтения/записи/исключения (каждый может быть NULL) и user_arg без копирования.
// name — заимствованная строка для логов, должна жить до удаления регистрации; NULL допустим.
// Возвращает handle или NULL при неверном fd, дублирующей регистрации или ошибке ресурсов/backend.
// Handle кодирует индекс без поколения, устойчив к расширению массива, но недействителен после удаления.
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, const char* name, void* user_arg);
// То же для socket_t; callbacks получают значение sock. Возврат и владение как у uasync_add_socket.
void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t read_cbk, socket_t_callback_t write_cbk, socket_t_callback_t except_cbk, const char* name, void* user_arg);
// Удаляет регистрацию по актуальному handle, не закрывает fd и не освобождает user_arg; ERR_OK / ERR_FAIL.
// Слот и значение handle могут быть переиспользованы новой регистрацией; старый handle нужно обнулить.
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
// Удаляет регистрацию типа SOCK по значению sock; fd остаётся открыт. ERR_OK / ERR_FAIL.
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
// Меняет мониторинг чтения/записи по актуальному handle: enable != 0 включает, 0 отключает.
// Callback должен быть задан при регистрации. ERR_OK при успехе, ERR_FAIL при неверном handle/ошибке backend.
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable);
// Один проход в потоке владельца: call_soon, ожидание/I/O/posted tasks, call_soon и готовые таймеры.
// timeout_tb < 0 — без пользовательского предела ожидания; 0 — без блокирующего ожидания;
// > 0 — предел системного ожидания в 0.1мс с округлением вверх до миллисекунд, сокращаемый таймерами.
// После stop ожидание неблокирующее. Время callbacks не ограничено timeout_tb; ошибка ожидания может прервать проход.
void uasync_poll(struct UASYNC* ua, int timeout_tb);
// Синхронно опустошает только call_soon, включая новые порции из callbacks; постоянный самоперезапуск мешает возврату.
void uasync_drain_immediate(struct UASYNC* ua);
// Повторяет poll(ua, -1), пока stop == 0; уже установленный stop не сбрасывает и сразу возвращается.
void uasync_mainloop(struct UASYNC* ua);
// Атомарно устанавливает stop=1 и пытается разбудить ожидание; допустим из другого потока при живом ua.
// Не прерывает текущий poll/callback и оставшуюся обработку событий. running не изменяет.
void uasync_stop(struct UASYNC* ua);
// Копирует накопительные счётчики выделения/освобождения; любой выходной указатель может быть NULL.
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
// Актуализирует снимок по сроку 100 мс. Только в потоке uasync; наружу передавать копию через события.
// ERR_OK при копировании (в том числе с valid=0), ERR_FAIL при NULL ua/load.
err_t uasync_get_load(struct UASYNC* ua, struct uasync_load* load);
// Находит регистрацию по fd: 0 и актуальный handle, либо -1. socket_id обязателен.
// При допустимых аргументах и отсутствии регистрации пишет NULL; при неверных аргументах выход не меняет.
int uasync_lookup_socket(struct UASYNC* ua, int fd, void** socket_id);
// Выводит счётчики, записи heap таймеров (включая отменённые) и активные сокеты с заданным prefix.
void uasync_print_resources(struct UASYNC* ua, const char* prefix);
// Пробуждение: POSIX пишет uint64_t(1) в eventfd/pipe, Windows посылает байт в UDP-сокет; 0 / -1.
// Допустим из другого потока; ua и внутренний endpoint должны оставаться живы.
int uasync_wakeup(struct UASYNC* ua);
// Заимствованный endpoint записи или -1 при NULL/неинициализированном wakeup. Не закрывать самостоятельно.
// Linux: eventfd (тот же fd для чтения/записи; писать uint64_t), прочие POSIX: pipe, Windows: UDP-сокет.
int uasync_get_wakeup_fd(struct UASYNC* ua);
// Потокобезопасно публикует callback(user_arg) для потока, обслуживающего ua. ua/callback должны быть не NULL.
// При OOM задача не публикуется, ошибка только логируется; статуса возврата нет. user_arg не копируется.
// При destroy ожидающая задача удаляется без callback; user_arg во всех случаях остаётся владельцу.
void uasync_post(struct UASYNC* ua, uasync_post_callback_t callback, void* user_arg);
/* Потокобезопасно передаёт заранее выделенный через u_malloc task без новых аллокаций; ua/task не NULL.
* До передачи задать callback и arg. Производитель после передачи к task не обращается.
* uasync освобождает task после callback, успешной отмены или при destroy без callback; arg не освобождает.
* Публикация не проверяет running/stop. Ошибка wakeup не удаляет уже поставленную задачу и не возвращается. */
void uasync_post_reserved(struct UASYNC* ua, struct posted_task* task);
// Отмена в потоке владельца вне callbacks после завершения производителя; ua/task не NULL.
// ERR_OK — task найден в очереди и освобождён; ERR_FAIL — не найден (в том числе уже взят на исполнение).
err_t uasync_cancel_post(struct UASYNC* ua, struct posted_task* task);
// Lock/unlock posted_lock у живого ua; не ждёт исполнения задач. post уже публикует данные под этим мьютексом.
void uasync_memsync(struct UASYNC* ua);
#ifdef __cplusplus
}
#endif
#endif // UASYNC_H