6.9 KiB
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, пользователь напрямую с ним не работает. Приводится для понимания внутреннего устройства.
Создание и уничтожение
TimeoutHeap* h = timeout_heap_create(16); // начальная ёмкость 16
timeout_heap_set_free_callback(h, ua, timeout_node_free_callback);
// ... использование ...
timeout_heap_destroy(h); // уничтожает все элементы через free_callback
Вставка и извлечение
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
}
Отмена
// O(n) — линейный поиск по expiration+data:
timeout_heap_cancel(h, expiration, data);
// O(log n) — удаление по индексу (для внешнего отслеживания позиции):
timeout_heap_cancel_at(h, index, data);
Ленивое удаление: как это работает
cancel/cancel_atставитdeleted = 1,expiration = 0, вызываетbubble_up— элемент всплывает в корень.- При следующем
peek/pop: если кореньdeleted, он удаляется из кучи (remove_root), данные освобождаются черезfree_callback. Повторяется, пока корень не окажется активным. - 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). |
Структуры
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) для упрощения арифметики кучи.