# u_async — Центральный цикл событий (event loop) ## 1. Назначение 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-сокет). На Linux используется epoll, на FreeBSD/Windows — poll/select. Платформенная абстракция прозрачна для вызывающего кода. ## 2. Как пользоваться ### Правила - **Один UASYNC на поток.** Нельзя создать несколько экземпляров и вызывать их из разных потоков. - **Никаких sleep/usleep.** Если поток заблокирован на sleep, цикл событий стоит. Все ожидания — только через таймеры `uasync_set_timeout`. - **Закрытие из callback.** Можно вызывать `uasync_destroy` или `uasync_remove_socket` прямо из callback сокета/таймера. Модуль использует локальные копии указателей перед вызовом callback и генерационные счётчики (gen) для защиты от stale epoll-событий. - **Указатель `user_arg`** передаётся во все callback и позволяет передать контекстную структуру (например, `UTUN_INSTANCE`). ### Типовой сценарий ```c // 1. Создать uasync struct UASYNC* ua = uasync_create(); if (!ua) { /* ошибка */ } // 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); // 3. Зарегистрировать таймер (timebase = 0.1 мс, т.е. timeout_tb=100 = 10 мс) void* t_id = uasync_set_timeout(ua, 100, my_ctx, my_timer_cb, "my_timer"); // 4. Отменить таймер (обязательно обнулить дескриптор!) uasync_cancel_timeout(ua, t_id); t_id = NULL; // 5. Отложенное выполнение в следующей итерации void* soon_id = uasync_call_soon(ua, my_ctx, deferred_cb); // 6. Динамически включить/отключить мониторинг записи на сокете uasync_set_socket_write(ua, sock_id, 1); // включить uasync_set_socket_write(ua, sock_id, 0); // отключить // 7. Запустить главный цикл (блокирующий, выход через uasync_stop) uasync_mainloop(ua); // 8. Или один шаг с таймаутом (timebase, -1 = бесконечно) uasync_poll(ua, 500); // ждать до 50 мс или первого события // 9. Завершение uasync_destroy(ua, 1); // close_fds=1 — закрыть все сокеты ``` ### Таймеры: правильный паттерн ```c void* reconnect_timer = NULL; // дескриптор всегда обнуляем // При запуске: if (!reconnect_timer) { reconnect_timer = uasync_set_timeout(ua, 5000, ctx, reconnect_cb, "reconnect"); } // В callback или при отмене: void reconnect_cb(void* arg) { reconnect_timer = NULL; // сработал — обнулили // ... } // При ручной отмене: if (reconnect_timer) { uasync_cancel_timeout(ua, reconnect_timer); reconnect_timer = NULL; } ``` ### Межпоточное взаимодействие (uasync_post) ```c // Из другого потока: void notify_main(void* arg) { UTUN_INSTANCE* u = arg; // работаем в главном потоке — можно вызывать любые функции uasync, трогать сокеты и т.д. uasync_set_timeout(u->ua, 10, u, handle_work, "work"); } void thread_func(UTUN_INSTANCE* u) { // … uasync_post(u->ua, notify_main, u); } // Для гарантии видимости памяти из другого потока перед uasync_post: uasync_memsync(u->ua); ``` ### Получение времени ```c uint64_t now_tb = get_time_tb(); // timebase 0.1 мс (монотонные часы) uint64_t now_us = get_time_us(); // микросекунды (для burst-измерений) ``` ## 3. API ### Жизненный цикл | Функция | Описание | |---------|----------| | `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` = ждать следующего таймера (или бесконечно, если нет таймеров). | ### Таймеры (timebase = 0.1 мс) | Функция | Описание | |---------|----------| | `uasync_set_timeout(ua, timeout_tb, arg, cb, name)` | Запланировать однократный таймер. Возвращает дескриптор `void*` для отмены. `name` — до 15 символов, используется в логах. | | `uasync_cancel_timeout(ua, t_id)` | Отменить таймер по дескриптору. После отмены дескриптор нужно обнулить — повторный cancel даст ошибку. | ### Немедленное выполнение (FIFO) | Функция | Описание | |---------|----------| | `uasync_call_soon(ua, arg, cb)` | Запланировать callback на ближайшую итерацию цикла. Используется, когда нужно отложить выполнение на «сразу после текущих событий». Узел выделяется из того же `timeout_pool`. | | `uasync_call_soon_cancel(ua, t_id)` | Отменить — просто зануляет callback, узел будет освобождён при обработке очереди (O(1)). | ### Сокеты | Функция | Описание | |---------|----------| | `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)` | Найти дескриптор сокета по fd (возвращает актуальный указатель даже после realloc). | ### Межпоточное взаимодействие | Функция | Описание | |---------|----------| | `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` или кастомных механизмах. | ### Время | Функция | Описание | |---------|----------| | `get_time_tb()` | Монотонное время в timebase (0.1 мс). `clock_gettime(CLOCK_MONOTONIC)` на POSIX, `QueryPerformanceCounter` на Windows. | | `get_time_us()` | Монотонное время в микросекундах (для burst-измерений производительности). | ### Диагностика | Функция | Описание | |---------|----------| | `uasync_get_stats(ua, ...)` | Получить счётчики аллокаций/освобождений таймеров и сокетов. | | `uasync_print_resources(ua, prefix)` | Вывести в лог все активные таймеры (имя, оставшееся время) и сокеты. Вызывается также в `uasync_destroy` перед очисткой. | ### Внутренние структуры - **`struct timeout_node`** — узел таймера: name, arg, callback, expiration_ms, heap_index. Используется и для таймеров в heap, и для FIFO-очереди immediate (через поле `next`). Выделяется из `timeout_pool`. - **`struct socket_node`** — узел сокета: fd/sock, тип (FD/SOCK), колбэки, user_data, флаги active/enable_read/enable_write, gen (защита от stale epoll-событий после переиспользования fd). - **`struct socket_array`** — массив сокетов: O(1) доступ по fd через `fd_to_index[]`, обход активных через `active_indices[]`, динамическое расширение. - **`struct posted_task`** — задача из другого потока: callback + arg + next, защищена `posted_lock`.