- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
108 lines
6.9 KiB
Markdown
108 lines
6.9 KiB
Markdown
# timeout_heap — Min-heap для управления таймерами
|
||
|
||
## 1. Назначение
|
||
|
||
timeout_heap — легковесная реализация min-heap (двоичная куча с минимумом в корне) для хранения таймеров в порядке возрастания времени срабатывания. Используется модулем `u_async` как внутренняя структура `UASYNC::timeout_heap`.
|
||
|
||
Ключевая особенность — **ленивое удаление (lazy deletion)**: при отмене таймера элемент не удаляется из кучи, а помечается флагом `deleted = 1`, его `expiration` обнуляется, и он всплывает в корень (bubble-up). Реальное удаление происходит при `peek`/`pop` — корень с `deleted == 1` выкидывается, а данные освобождаются через пользовательский `free_callback`. Это даёт O(log n) на отмену без полного удаления из середины кучи.
|
||
|
||
## 2. Как пользоваться
|
||
|
||
Модуль используется только внутри `u_async.c`, пользователь напрямую с ним не работает. Приводится для понимания внутреннего устройства.
|
||
|
||
### Создание и уничтожение
|
||
|
||
```c
|
||
TimeoutHeap* h = timeout_heap_create(16); // начальная ёмкость 16
|
||
timeout_heap_set_free_callback(h, ua, timeout_node_free_callback);
|
||
// ... использование ...
|
||
timeout_heap_destroy(h); // уничтожает все элементы через free_callback
|
||
```
|
||
|
||
### Вставка и извлечение
|
||
|
||
```c
|
||
size_t index;
|
||
timeout_heap_push(h, expiration_ms, my_data, &index);
|
||
// index обновляется при перемещениях элемента в куче
|
||
|
||
TimeoutEntry entry;
|
||
if (timeout_heap_peek(h, &entry) == 0) {
|
||
// entry.expiration — ближайшее время срабатывания
|
||
// entry.data — пользовательские данные
|
||
}
|
||
|
||
if (timeout_heap_pop(h, &entry) == 0) {
|
||
// элемент удалён из кучи, данные нужно освободить вручную или через free_callback
|
||
}
|
||
```
|
||
|
||
### Отмена
|
||
|
||
```c
|
||
// O(n) — линейный поиск по expiration+data:
|
||
timeout_heap_cancel(h, expiration, data);
|
||
|
||
// O(log n) — удаление по индексу (для внешнего отслеживания позиции):
|
||
timeout_heap_cancel_at(h, index, data);
|
||
```
|
||
|
||
### Ленивое удаление: как это работает
|
||
|
||
1. `cancel` / `cancel_at` ставит `deleted = 1`, `expiration = 0`, вызывает `bubble_up` — элемент всплывает в корень.
|
||
2. При следующем `peek`/`pop`: если корень `deleted`, он удаляется из кучи (`remove_root`), данные освобождаются через `free_callback`. Повторяется, пока корень не окажется активным.
|
||
3. O(log n) как на cancel, так и на последующее удаление.
|
||
|
||
## 3. API
|
||
|
||
### Жизненный цикл
|
||
|
||
| Функция | Описание |
|
||
|---------|----------|
|
||
| `timeout_heap_create(capacity)` | Создать кучу с начальной ёмкостью. Расширяется автоматически при заполнении. |
|
||
| `timeout_heap_destroy(h)` | Уничтожить кучу. Все данные (включая помеченные deleted) освобождаются через `free_callback` или `free()`. |
|
||
| `timeout_heap_set_free_callback(h, user_data, cb)` | Установить callback для освобождения данных при удалении deleted-узлов. Если NULL — данные не освобождаются. |
|
||
|
||
### Основные операции
|
||
|
||
| Функция | Описание |
|
||
|---------|----------|
|
||
| `timeout_heap_push(h, exp, data, &index)` | Вставить элемент с временем срабатывания `exp`. `index_ptr` (опционально) отслеживает позицию элемента в куче — обновляется при всех перемещениях. O(log n). |
|
||
| `timeout_heap_peek(h, &entry)` | Посмотреть ближайший **неудалённый** элемент без удаления. Попутно вычищает deleted-корни через `free_callback`. O(log n) в худшем случае. |
|
||
| `timeout_heap_pop(h, &entry)` | Извлечь ближайший неудалённый элемент. Аналогично чистит deleted. O(log n). |
|
||
|
||
### Отмена
|
||
|
||
| Функция | Описание |
|
||
|---------|----------|
|
||
| `timeout_heap_cancel(h, exp, data)` | Отменить по совпадению `expiration + data`. Линейный поиск — O(n). Для массового использования предпочитать `cancel_at`. |
|
||
| `timeout_heap_cancel_at(h, index, data)` | Отменить по индексу (с проверкой data). O(log n) — ленивое удаление с bubble-up. |
|
||
|
||
### Статистика
|
||
|
||
| Функция | Описание |
|
||
|---------|----------|
|
||
| `timeout_heap_get_size(h)` | Текущий размер кучи (включая deleted-элементы). |
|
||
| `timeout_heap_get_freed_count(h)` | Количество узлов, освобождённых через `free_callback` (не реализовано в текущей версии — возвращает 0). |
|
||
|
||
### Структуры
|
||
|
||
```c
|
||
typedef struct {
|
||
TimeoutTime expiration; // время срабатывания (ключ сортировки)
|
||
void *data; // пользовательские данные
|
||
size_t *index_ptr; // указатель на внешнюю переменную с индексом (обновляется при перемещениях)
|
||
int deleted; // 0 = активен, 1 = помечен на удаление
|
||
} TimeoutEntry;
|
||
|
||
struct TimeoutHeap {
|
||
TimeoutEntry *heap; // динамический массив
|
||
size_t size; // текущее количество элементов
|
||
size_t capacity; // выделенная ёмкость
|
||
void* user_data; // аргумент для free_callback
|
||
void (*free_callback)(void*, void*); // освобождение данных deleted-узлов
|
||
};
|
||
```
|
||
|
||
Индексация в коде: внешний API использует 0-based индексы в массиве `heap[]`. Внутренние операции (`bubble_up`, `heapify_down`) оперируют 1-based индексами через макросы `PARENT(i)`, `LEFT_CHILD(i)`, `RIGHT_CHILD(i)` для упрощения арифметики кучи.
|