Document uasync scheduling, ownership and lifecycle contracts
This commit is contained in:
1 parent
dffcd19d21
commit
d23a96a08e
2 files changed
+150
-73
No files matched your search
+75
-36
@@ -1,7 +1,8 @@
|
||||
/* Цикл событий одного потока: сокеты (epoll/poll/select), таймеры и FIFO call_soon.
|
||||
* create → регистрация callbacks → poll/mainloop → destroy вне callbacks после остановки владельцев.
|
||||
* Таймауты задаются в timebase 0.1мс; arg принадлежит вызывающему, освобождает его callback/владелец.
|
||||
* API сокетов/таймеров используется в потоке loop. Из другого потока — post/post_reserved и wakeup;
|
||||
* Таймауты задаются в timebase 0.1мс, положительный абсолютный срок округляется вверх до миллисекунды.
|
||||
* arg принадлежит вызывающему, освобождает его callback/владелец; отмена и destroy arg не освобождают.
|
||||
* API сокетов/таймеров используется в потоке loop. Из другого потока — post/post_reserved, wakeup и stop;
|
||||
* публикация не продлевает время жизни ua/arg, их освобождение нужно согласовать с остановкой producers. */
|
||||
|
||||
#ifndef UASYNC_H
|
||||
@@ -43,12 +44,12 @@ struct timeout_node; // Forward declaration
|
||||
* До первого замера valid=0; window_us растёт до секунды. При задержке замера
|
||||
* вклад граничного интервала в среднее оценивается пропорционально времени. */
|
||||
struct uasync_load {
|
||||
double current_percent;
|
||||
double average_percent;
|
||||
uint64_t sample_time_us;
|
||||
uint64_t interval_us;
|
||||
uint64_t window_us;
|
||||
int valid;
|
||||
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 {
|
||||
@@ -73,17 +74,17 @@ struct UASYNC {
|
||||
size_t timer_free_count;
|
||||
size_t socket_alloc_count;
|
||||
size_t socket_free_count;
|
||||
// Wakeup pipe for interrupting poll
|
||||
int wakeup_pipe[2]; // [0] read, [1] write
|
||||
// Wakeup: eventfd на Linux, pipe на остальных POSIX, UDP-сокеты на Windows
|
||||
int wakeup_pipe[2]; // [0] чтение, [1] запись; для eventfd значения совпадают
|
||||
int wakeup_initialized;
|
||||
// Cached pollfd array for optimized poll operations (fallback for non-Linux)
|
||||
// Кеш 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 if using epoll, 0 if using poll
|
||||
int use_epoll; // 1 = epoll; 0 = poll на POSIX или select на Windows
|
||||
struct posted_task* posted_tasks_head;
|
||||
struct posted_task* posted_tasks_tail;
|
||||
|
||||
@@ -92,8 +93,8 @@ struct UASYNC {
|
||||
#else
|
||||
pthread_mutex_t posted_lock;
|
||||
#endif
|
||||
volatile sig_atomic_t stop;
|
||||
volatile sig_atomic_t running; // 0=shutdown mode (API rejected), 1=full init done
|
||||
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 периодов + граничные интервалы при задержках
|
||||
@@ -106,82 +107,120 @@ struct UASYNC {
|
||||
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);
|
||||
}
|
||||
|
||||
// Instance API - основной API для работы с uasync
|
||||
// Создаёт готовый к регистрации и poll экземпляр с running=0, stop=0; NULL при ошибке инициализации.
|
||||
struct UASYNC* uasync_create(void);
|
||||
// Уничтожение только вне callbacks, после возврата из poll/mainloop.
|
||||
// Уничтожение вне 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);
|
||||
|
||||
// текущее время (timebase 0.1ms)
|
||||
// Время в единицах 0.1мс: Linux CLOCK_BOOTTIME (включает suspend), прочие POSIX CLOCK_MONOTONIC, Windows QPC.
|
||||
uint64_t get_time_tb(void);
|
||||
// текущее время в микросекундах (для burst-измерений)
|
||||
// Время в микросекундах в той же шкале; календарные часы ОС на неё не влияют.
|
||||
uint64_t get_time_us(void);
|
||||
|
||||
// Timeouts, timebase = 0.1 mS
|
||||
// Как работать с таймаутами если таймаут может быть использован несколько раз:
|
||||
// 1. заводим дескриптор таймаута и обнуляем
|
||||
// 2. перед активацией таймаута проверяем дескриптор (t_id) на null
|
||||
// 3. при cancel или срабатывании дескриптор обнуляем
|
||||
// 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);
|
||||
|
||||
// Immediate execution in next mainloop (FIFO order)
|
||||
// Добавляет 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);
|
||||
|
||||
// Sockets - for regular file descriptors (pipe, file)
|
||||
// Регистрирует 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);
|
||||
// Sockets - for socket_t (cross-platform sockets)
|
||||
// То же для 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);
|
||||
|
||||
// Single iteration of event loop with timeout (timebase units)
|
||||
// Один проход в потоке владельца: 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);
|
||||
|
||||
// Цикл до uasync_stop (возвращается после остановки).
|
||||
// Повторяет poll(ua, -1), пока stop == 0; уже установленный stop не сбрасывает и сразу возвращается.
|
||||
void uasync_mainloop(struct UASYNC* ua);
|
||||
// Атомарно устанавливает stop=1 и пытается разбудить ожидание; допустим из другого потока при живом ua.
|
||||
// Не прерывает текущий poll/callback и оставшуюся обработку событий. running не изменяет.
|
||||
void uasync_stop(struct UASYNC* ua);
|
||||
|
||||
// Debug statistics
|
||||
// Копирует накопительные счётчики выделения/освобождения; любой выходной указатель может быть 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);
|
||||
|
||||
// Handle — кодированный индекс; сохраняется при расширении массива сокетов.
|
||||
// Находит регистрацию по fd: 0 и актуальный handle, либо -1. socket_id обязателен.
|
||||
// При допустимых аргументах и отсутствии регистрации пишет NULL; при неверных аргументах выход не меняет.
|
||||
int uasync_lookup_socket(struct UASYNC* ua, int fd, void** socket_id);
|
||||
|
||||
// Print all resources (timers, sockets) for debugging
|
||||
// Выводит счётчики, записи heap таймеров (включая отменённые) и активные сокеты с заданным prefix.
|
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix);
|
||||
|
||||
// Wakeup mechanism for interrupting poll
|
||||
// Пробуждение: POSIX пишет uint64_t(1) в eventfd/pipe, Windows посылает байт в UDP-сокет; 0 / -1.
|
||||
// Допустим из другого потока; ua и внутренний endpoint должны оставаться живы.
|
||||
int uasync_wakeup(struct UASYNC* ua);
|
||||
int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers)
|
||||
// Заимствованный endpoint записи или -1 при NULL/неинициализированном wakeup. Не закрывать самостоятельно.
|
||||
// Linux: eventfd (тот же fd для чтения/записи; писать uint64_t), прочие POSIX: pipe, Windows: UDP-сокет.
|
||||
int uasync_get_wakeup_fd(struct UASYNC* ua);
|
||||
|
||||
// сообщить async (из другого thread) чтобы он вызвал callback с аргументом
|
||||
// Потокобезопасно публикует 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 без аллокаций. uasync освобождает
|
||||
* task после callback. Производитель больше не обращается к task после передачи.
|
||||
* Отмена — в потоке владельца вне callbacks, после завершения производителя;
|
||||
* ERR_OK означает, что task удалён из очереди и освобождён. */
|
||||
/* Потокобезопасно передаёт заранее выделенный через 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);
|
||||
|
||||
|
||||
|
||||
+75
-37
@@ -5,9 +5,9 @@
|
||||
u_async — центральный планировщик проекта uTun. Каждый поток, которому нужна асинхронная обработка сокетов, таймеров и отложенных задач, создаёт свой экземпляр `struct UASYNC` и запускает `uasync_mainloop()` / `uasync_poll()`. Модуль объединяет:
|
||||
|
||||
- **Сокеты** — мониторинг fd/socket на чтение/запись/ошибки через Linux epoll (предпочтительно) с fallback на poll/select.
|
||||
- **Таймеры** — однократные таймеры с точностью 0.1 мс, реализованные на базе `timeout_heap` (min-heap) и `memory_pool`.
|
||||
- **Немедленное выполнение** — FIFO-очередь `uasync_call_soon` для отложенного запуска callback в ближайшей итерации цикла.
|
||||
- **Межпоточную связь** — `uasync_post` позволяет другому потоку безопасно запланировать callback в главном потоке (wakeup через pipe/UDP-сокет).
|
||||
- **Таймеры** — однократные таймеры на базе `timeout_heap` (min-heap) и `memory_pool`. Аргумент задаётся в единицах 0.1 мс; положительный абсолютный срок округляется вверх до миллисекунды.
|
||||
- **Отложенное выполнение** — FIFO-очередь `uasync_call_soon`. Callback может выполниться в текущем `uasync_poll`, при следующем проходе обработки этой очереди.
|
||||
- **Межпоточную связь** — `uasync_post` / `uasync_post_reserved` публикуют callback для потока, обслуживающего экземпляр (wakeup через eventfd/pipe/UDP-сокет).
|
||||
|
||||
На Linux используется epoll, на FreeBSD/Windows — poll/select. Платформенная абстракция прозрачна для вызывающего кода.
|
||||
|
||||
@@ -15,22 +15,24 @@ u_async — центральный планировщик проекта uTun.
|
||||
|
||||
### Правила
|
||||
|
||||
- **Один UASYNC на поток.** Нельзя создать несколько экземпляров и вызывать их из разных потоков.
|
||||
- **Один обслуживающий поток на экземпляр.** Независимые экземпляры могут работать в разных потоках. Сокеты, таймеры и обработка очередей конкретного экземпляра принадлежат его потоку. Из другого потока разрешены `uasync_post`, `uasync_post_reserved`, `uasync_wakeup` и `uasync_stop` при согласованном времени жизни экземпляра.
|
||||
- **Никаких sleep/usleep.** Если поток заблокирован на sleep, цикл событий стоит. Все ожидания — только через таймеры `uasync_set_timeout`.
|
||||
- **Закрытие из callback.** Можно удалять сокет и освобождать его пользовательский контекст из callback. Перед следующим callback модуль повторно проверяет регистрацию, поколение и разрешённые события. Для завершения цикла вызывайте `uasync_stop`; `uasync_destroy` выполняйте после возврата из `uasync_poll` / `uasync_mainloop`, вне callbacks.
|
||||
- **Указатель `user_arg`** передаётся во все callback и позволяет передать контекстную структуру (например, `UTUN_INSTANCE`).
|
||||
- **Владение контекстом.** `user_arg` не копируется и автоматически не освобождается. При отмене или уничтожении ожидающей задачи её callback не вызывается; контекст освобождает владелец. Перед `destroy` завершите producers, которые могут обращаться к экземпляру или публиковать задачи.
|
||||
- **Статусы `running` и `stop` независимы.** После `create` оба равны 0, API уже доступен. `mark_running` / `mark_stopped` только выставляют статус владельца; цикл и регистрацию задач не меняют. `stop` устанавливает отдельный флаг выхода из `mainloop`, который автоматически не сбрасывается.
|
||||
|
||||
### Типовой сценарий
|
||||
|
||||
```c
|
||||
// 1. Создать uasync
|
||||
struct UASYNC* ua = uasync_create();
|
||||
if (!ua) { /* ошибка */ }
|
||||
if (!ua) return;
|
||||
|
||||
// 2. Зарегистрировать сокет
|
||||
void* sock_id = uasync_add_socket(ua, fd, my_read_cb, NULL, NULL, my_ctx);
|
||||
// или для socket_t:
|
||||
void* sock_id = uasync_add_socket_t(ua, sock, my_read_cb_sock, my_write_cb_sock, NULL, my_ctx);
|
||||
void* sock_id = uasync_add_socket(ua, fd, my_read_cb, NULL, NULL, "my_fd", my_ctx);
|
||||
// Вместо предыдущей строки для socket_t:
|
||||
// void* sock_id = uasync_add_socket_t(ua, sock, my_read_cb_sock, my_write_cb_sock, NULL, "my_socket", my_ctx);
|
||||
|
||||
// 3. Зарегистрировать таймер (timebase = 0.1 мс, т.е. timeout_tb=100 = 10 мс)
|
||||
void* t_id = uasync_set_timeout(ua, 100, my_ctx, my_timer_cb, "my_timer");
|
||||
@@ -39,7 +41,7 @@ void* t_id = uasync_set_timeout(ua, 100, my_ctx, my_timer_cb, "my_timer");
|
||||
uasync_cancel_timeout(ua, t_id);
|
||||
t_id = NULL;
|
||||
|
||||
// 5. Отложенное выполнение в следующей итерации
|
||||
// 5. Отложенное выполнение при обработке FIFO call_soon
|
||||
void* soon_id = uasync_call_soon(ua, my_ctx, deferred_cb);
|
||||
|
||||
// 6. Динамически включить/отключить мониторинг записи на сокете
|
||||
@@ -49,11 +51,11 @@ uasync_set_socket_write(ua, sock_id, 0); // отключить
|
||||
// 7. Запустить главный цикл (блокирующий, выход через uasync_stop)
|
||||
uasync_mainloop(ua);
|
||||
|
||||
// 8. Или один шаг с таймаутом (timebase, -1 = бесконечно)
|
||||
uasync_poll(ua, 500); // ждать до 50 мс или первого события
|
||||
// 8. Вместо mainloop можно вызывать отдельные шаги:
|
||||
// uasync_poll(ua, 500); // системное ожидание до 50 мс, время callbacks этим не ограничено
|
||||
|
||||
// 9. Завершение
|
||||
uasync_destroy(ua, 1); // close_fds=1 — закрыть все сокеты
|
||||
uasync_destroy(ua, 1); // закрыть оставшиеся зарегистрированные fd и внутренние ресурсы
|
||||
```
|
||||
|
||||
### Таймеры: правильный паттерн
|
||||
@@ -85,7 +87,7 @@ if (reconnect_timer) {
|
||||
// Из другого потока:
|
||||
void notify_main(void* arg) {
|
||||
UTUN_INSTANCE* u = arg;
|
||||
// работаем в главном потоке — можно вызывать любые функции uasync, трогать сокеты и т.д.
|
||||
// Работаем в потоке, обслуживающем u->ua: здесь можно регистрировать таймеры и сокеты.
|
||||
uasync_set_timeout(u->ua, 10, u, handle_work, "work");
|
||||
}
|
||||
|
||||
@@ -94,10 +96,21 @@ void thread_func(UTUN_INSTANCE* u) {
|
||||
uasync_post(u->ua, notify_main, u);
|
||||
}
|
||||
|
||||
// Для гарантии видимости памяти из другого потока перед uasync_post:
|
||||
uasync_memsync(u->ua);
|
||||
// Отдельный uasync_memsync перед post не требуется: публикация и получение задачи используют posted_lock.
|
||||
```
|
||||
|
||||
Публикация не продлевает время жизни `ua` или `arg` и не проверяет `running` / `stop`.
|
||||
Обычный `uasync_post` выделяет `posted_task`; при OOM пишет ошибку в лог и не ставит задачу, статуса возврата нет.
|
||||
Для завершения worker без выделения памяти заранее выделите `posted_task` через `u_malloc`, заполните `callback` и `arg`
|
||||
и один раз передайте в `uasync_post_reserved`. После передачи производитель к задаче не обращается.
|
||||
Задачу освобождает uasync после исполнения, успешной отмены или при уничтожении без вызова callback; `arg` не освобождается.
|
||||
Ошибка wakeup оставляет уже опубликованную задачу в очереди; обе функции публикации не возвращают эту ошибку.
|
||||
|
||||
Отмена через `uasync_cancel_post` выполняется в потоке владельца вне callbacks после завершения производителя.
|
||||
`ERR_OK` означает, что задача найдена в ожидающей очереди и освобождена; `ERR_FAIL` — не найдена.
|
||||
Порция, уже взятая на исполнение, в этой очереди не находится. `uasync_memsync` лишь захватывает и отпускает
|
||||
`posted_lock`; он не ждёт исполнения задач и не заменяет согласование времени жизни контекста.
|
||||
|
||||
### Получение времени
|
||||
|
||||
```c
|
||||
@@ -111,52 +124,75 @@ uint64_t now_us = get_time_us(); // микросекунды (для burst-и
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `uasync_create()` | Создать экземпляр: аллоцирует структуру, `socket_array`, `timeout_heap`, `memory_pool`, epoll/poll, wakeup pipe/сокет. |
|
||||
| `uasync_destroy(ua, close_fds)` | Уничтожить экземпляр. При `close_fds=1` закрывает все зарегистрированные fd. Перед уничтожением выводит диагностику ресурсов и проверяет на утечки (abort при несовпадении аллокаций). |
|
||||
| `uasync_stop(ua)` | Установить флаг `stop = 1` — на следующей итерации `uasync_mainloop` выйдет. |
|
||||
| `uasync_mainloop(ua)` | Бесконечный цикл `while(!stop) uasync_poll(ua, -1)`. |
|
||||
| `uasync_poll(ua, timeout_tb)` | Одна итерация: обработать сокеты + таймеры. `timeout_tb` = максимальное ожидание в timebase; `-1` = ждать следующего таймера (или бесконечно, если нет таймеров). |
|
||||
| `uasync_create()` | Создать готовый к регистрации и опросу экземпляр с `running=0`, `stop=0`. Возвращает указатель или `NULL` при ошибке инициализации. |
|
||||
| `uasync_destroy(ua, close_fds)` | Уничтожить вне callbacks после возврата из poll/mainloop и завершения producers. При `close_fds != 0` закрывает оставшиеся зарегистрированные fd; внутренние ресурсы закрывает всегда. Ожидающие задачи и таймеры удаляет без callbacks и без освобождения `arg`. Проверяет баланс внутренних аллокаций (abort при несовпадении). `NULL` допустим. |
|
||||
| `uasync_mark_running(ua)` / `uasync_mark_stopped(ua)` | Атомарно выставить `running=1/0`. Не меняют `stop`, не управляют циклом и не ограничивают API. |
|
||||
| `uasync_is_running(ua)` | Атомарно прочитать статус владельца; для `NULL` возвращает 0. |
|
||||
| `uasync_stop(ua)` | Атомарно выставить `stop=1` и попытаться разбудить ожидание. Допустим из другого потока при живом экземпляре. Текущий poll, оставшуюся обработку событий и callbacks не прерывает; `running` не меняет. |
|
||||
| `uasync_mainloop(ua)` | Цикл `while(!stop) uasync_poll(ua, -1)`. При уже установленном `stop` сразу возвращается, флаг не сбрасывает. |
|
||||
| `uasync_poll(ua, timeout_tb)` | Один проход обработки событий. Отрицательное значение снимает пользовательский предел системного ожидания; 0 запрещает блокирующее ожидание; положительное задаёт предел в 0.1 мс с округлением вверх до миллисекунд, сокращаемый таймерами. Время callbacks этим пределом не ограничено. После stop ожидание неблокирующее. |
|
||||
|
||||
### Таймеры (timebase = 0.1 мс)
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `uasync_set_timeout(ua, timeout_tb, arg, cb, name)` | Запланировать однократный таймер. Возвращает дескриптор `void*` для отмены. `name` — до 15 символов, используется в логах. |
|
||||
| `uasync_cancel_timeout(ua, t_id)` | Отменить таймер по дескриптору. После отмены дескриптор нужно обнулить — повторный cancel даст ошибку. |
|
||||
| `uasync_set_timeout(ua, timeout_tb, arg, cb, name)` | Однократный таймер: `timeout_tb >= 0`, callback обязателен. Возвращает handle или `NULL` при неверных аргументах/ошибке ресурсов. `arg` и строка `name` сохраняются по указателю; ограничения имени в 15 символов нет. |
|
||||
| `uasync_cancel_timeout(ua, t_id)` | Отменить ещё живой таймер этого экземпляра: `ERR_OK` / `ERR_FAIL`. При успехе узел помечается отменённым; освобождение отложено до очистки heap или destroy. После успеха обнулить handle. |
|
||||
|
||||
Для положительного `timeout_tb` абсолютный срок в миллисекундах равен
|
||||
`ceil((now_us + timeout_tb * 100) / 1000)`, где `now_us` взят при регистрации.
|
||||
При нулевом интервале срок равен `floor(now_us / 1000)` и таймер готов к ближайшей обработке heap.
|
||||
Callback синхронно из `set_timeout` не вызывается; его исполнение может задержаться из-за ожидания или других callbacks.
|
||||
Строка `name` должна оставаться доступной до освобождения узла, включая отложенную очистку после отмены.
|
||||
В начале callback сохранённый handle нужно обнулить. После исполнения, освобождения или destroy старый указатель
|
||||
использовать нельзя: безопасная ошибка при повторной отмене не гарантируется.
|
||||
|
||||
### Немедленное выполнение (FIFO)
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `uasync_call_soon(ua, arg, cb)` | Запланировать callback на ближайшую итерацию цикла. Используется, когда нужно отложить выполнение на «сразу после текущих событий». Узел выделяется из того же `timeout_pool`. |
|
||||
| `uasync_call_soon_cancel(ua, t_id)` | Отменить — просто зануляет callback, узел будет освобождён при обработке очереди (O(1)). |
|
||||
| `uasync_call_soon(ua, arg, cb)` | Добавить callback в FIFO для отложенного исполнения. Возвращает handle или `NULL` при неверных аргументах/ошибке ресурсов; узел выделяется из `timeout_pool`. |
|
||||
| `uasync_call_soon_cancel(ua, t_id)` | Занулить callback ещё живого узла этого экземпляра за O(1); узел освобождается при обработке очереди/destroy. `ERR_OK` при отмене, `ERR_FAIL` при `NULL`/чужом экземпляре. После успеха обнулить handle. |
|
||||
| `uasync_drain_immediate(ua)` | Синхронно обработать все порции call_soon до опустошения, включая новые callbacks. Не обрабатывает posted tasks, сокеты или heap таймеров. Постоянный самоперезапуск callback может не дать функции вернуться. |
|
||||
|
||||
Один проход обработки FIFO берёт исходную порцию. Обычный успешный `poll` обрабатывает FIFO в начале,
|
||||
затем после ожидания и обработки I/O/posted tasks, перед проверкой heap таймеров.
|
||||
Поэтому callback из обработчика сокета или из первой порции call_soon может выполниться в том же `poll`.
|
||||
Ошибка ожидания может прервать проход до второй обработки FIFO. Гарантии «следующая итерация poll/mainloop» нет.
|
||||
При повторной отмене call_soon до обработки узла возможен `ERR_OK`; после освобождения узла старый handle недействителен.
|
||||
|
||||
### Сокеты
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `uasync_add_socket(ua, fd, r_cb, w_cb, e_cb, arg)` | Добавить fd (pipe, file). `r_cb/w_cb/e_cb` могут быть NULL. Возвращает дескриптор `void*`. |
|
||||
| `uasync_add_socket_t(ua, sock, r_cb, w_cb, e_cb, arg)` | Добавить `socket_t` (кросс-платформенный сокет). |
|
||||
| `uasync_remove_socket(ua, s_id)` | Удалить сокет по дескриптору. Помечает слот неактивным, не освобождает индексную ячейку (защита от stale epoll-событий через gen). |
|
||||
| `uasync_remove_socket_t(ua, sock)` | Удалить по значению `socket_t`. |
|
||||
| `uasync_set_socket_read(ua, s_id, enable)` | Динамически включить/отключить мониторинг чтения (EPOLL_CTL_MOD). |
|
||||
| `uasync_set_socket_write(ua, s_id, enable)` | Динамически включить/отключить мониторинг записи. |
|
||||
| `uasync_lookup_socket(ua, fd, &s_id)` | Найти handle сокета по fd (кодированный индекс, устойчивый к расширению массива). |
|
||||
| `uasync_add_socket(ua, fd, r_cb, w_cb, e_cb, name, arg)` | Зарегистрировать fd. Callbacks могут быть `NULL`; `name` и `arg` заимствуются. Возвращает handle либо `NULL` при неверном fd, дублирующей регистрации или ошибке ресурсов/backend. |
|
||||
| `uasync_add_socket_t(ua, sock, r_cb, w_cb, e_cb, name, arg)` | То же для `socket_t`; callbacks получают значение сокета. |
|
||||
| `uasync_remove_socket(ua, s_id)` | Удалить регистрацию по актуальному handle, не закрывая fd и не освобождая `arg`. Слот становится доступным для повторного использования. `ERR_OK` / `ERR_FAIL`. |
|
||||
| `uasync_remove_socket_t(ua, sock)` | Удалить регистрацию типа SOCK по значению `socket_t`, не закрывая сокет. `ERR_OK` / `ERR_FAIL`. |
|
||||
| `uasync_set_socket_read(ua, s_id, enable)` | По актуальному handle включить (`enable != 0`) / отключить (`0`) мониторинг чтения для заданного callback. `ERR_OK` при успехе, `ERR_FAIL` при неверном handle/ошибке backend. |
|
||||
| `uasync_set_socket_write(ua, s_id, enable)` | То же для мониторинга записи. |
|
||||
| `uasync_lookup_socket(ua, fd, &s_id)` | 0 и актуальный handle, либо -1. При допустимых аргументах и отсутствии регистрации записывает `NULL`; при неверных аргументах выход не меняет. |
|
||||
|
||||
Handle кодирует индекс без поколения и сохраняется при расширении массива. После удаления его нужно обнулить:
|
||||
новая регистрация может получить то же числовое значение. Поколения защищают внутреннюю обработку событий,
|
||||
но не делают безопасными вызовы API со старым handle. Строка `name` должна жить до удаления регистрации.
|
||||
|
||||
### Межпоточное взаимодействие
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `uasync_post(ua, cb, arg)` | **Потокобезопасно.** Запланировать callback в главном потоке. Выделяет `posted_task`, добавляет в связный список под мьютексом, будит главный поток через `uasync_wakeup`. |
|
||||
| `uasync_memsync(ua)` | **Потокобезопасно.** Барьер памяти (lock/unlock `posted_lock`) для гарантии видимости данных, записанных из другого потока перед `uasync_post`. |
|
||||
| `uasync_wakeup(ua)` | Разбудить `poll`/`epoll_wait` записью байта в wakeup pipe (POSIX) или send в UDP-сокет (Windows). Можно вызывать из обработчика сигналов. |
|
||||
| `uasync_get_wakeup_fd(ua)` | Получить write-fd wakeup pipe для использования в `signalfd` или кастомных механизмах. |
|
||||
| `uasync_post(ua, cb, arg)` | **Потокобезопасно.** Выделить и опубликовать задачу для потока экземпляра, попытаться разбудить его. При OOM задача не публикуется, ошибка логируется; статуса возврата нет. |
|
||||
| `uasync_post_reserved(ua, task)` | **Потокобезопасно.** Передать заранее выделенный через `u_malloc` task с заполненными callback/arg без новых аллокаций. `ua` и `task` обязательны. Владение task переходит uasync. |
|
||||
| `uasync_cancel_post(ua, task)` | В потоке владельца вне callbacks после завершения производителя: `ERR_OK` при удалении и освобождении ожидающего task, `ERR_FAIL`, если он не найден. `ua` и `task` обязательны. |
|
||||
| `uasync_memsync(ua)` | Lock/unlock `posted_lock` живого экземпляра. Не ждёт исполнения задач; публикация через post уже использует этот мьютекс. |
|
||||
| `uasync_wakeup(ua)` | 0 при успешной записи, -1 при ошибке/неинициализированном wakeup. POSIX: запись `uint64_t(1)` в eventfd/pipe; Windows: send одного байта в UDP-сокет. Экземпляр должен оставаться живым. |
|
||||
| `uasync_get_wakeup_fd(ua)` | Заимствованный endpoint записи, либо -1 при `NULL`/неинициализированном wakeup. Linux: eventfd, тот же fd для чтения и записи (писать `uint64_t`); прочие POSIX: pipe; Windows: UDP-сокет. Самостоятельно не закрывать. |
|
||||
|
||||
### Время
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `get_time_tb()` | Монотонное время в timebase (0.1 мс). `clock_gettime(CLOCK_MONOTONIC)` на POSIX, `QueryPerformanceCounter` на Windows. |
|
||||
| `get_time_tb()` | Время в единицах 0.1 мс: Linux `CLOCK_BOOTTIME` (включает suspend), остальные POSIX `CLOCK_MONOTONIC`, Windows `QueryPerformanceCounter`. Изменение календарных часов на шкалу не влияет. |
|
||||
| `get_time_us()` | Монотонное время в микросекундах (для burst-измерений производительности). |
|
||||
|
||||
### Диагностика
|
||||
@@ -200,6 +236,8 @@ GUI опрашивает ядро каждые 333 мс, не более одн
|
||||
- **`struct socket_array`** — массив сокетов: O(1) доступ по fd через `fd_to_index[]`, обход активных через `active_indices[]`, динамическое расширение.
|
||||
- **`struct posted_task`** — задача из другого потока: callback + arg + next, защищена `posted_lock`.
|
||||
|
||||
Таймеры используют монотонное время: коррекция календарных часов не меняет срок срабатывания. Положительные интервалы округляются вверх до миллисекунд; нулевой интервал готов немедленно. Каждый проход обрабатывает исходную порцию `call_soon`, затем проверяет таймеры и сокеты; новые callbacks остаются на следующий проход. `uasync_drain_immediate` явно обрабатывает все порции до опустошения очереди.
|
||||
Таймеры используют шкалу `get_time_us`; правила округления абсолютного срока и порядок обработки
|
||||
call_soon описаны в разделах API выше. Сокеты и posted tasks обрабатываются после ожидания перед heap таймеров;
|
||||
ошибка ожидания может завершить проход раньше. Установка `stop` не прерывает эти обработчики.
|
||||
|
||||
Для fallback `poll` массив дескрипторов резервируется при создании экземпляра и до регистрации нового сокета. Отказ памяти возвращает `NULL` из `uasync_create` / `uasync_add_socket*`; существующие регистрации сохраняются. Перестроение массива в `uasync_poll` не выделяет память. Расширение из callback сохраняет события текущего прохода, новый сокет опрашивается со следующего прохода.
|
||||
Reference in new issue
Block a user