From dffcd19d21e2751085715ab81ea103e7f6bf8757 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 21 Oct 2026 17:27:04 +0200 Subject: [PATCH] Clarify queue callback scheduling and ownership contracts --- lib/ll_queue.h | 81 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/lib/ll_queue.h b/lib/ll_queue.h index 783d8116..fad9fa71 100644 --- a/lib/ll_queue.h +++ b/lib/ll_queue.h @@ -75,7 +75,7 @@ struct ll_entry { uint8_t* dgram; // Указатель на данные пакета void (*dgram_free_fn)(uint8_t* data); // Кастомная функция освобождения dgram struct memory_pool* dgram_pool; // Пул для dgram (если используется) - uint32_t index_hash; // Хеш по первым 4 байтам индекса + uint32_t index_hash; // FNV-1a (32 бита) по всем index_size байтам data[] от index_offset struct ll_entry* hash_next; // Следующий в хеш-цепочке struct memory_pool* pool; // Пул, из которого выделен сам entry (NULL = malloc) uint8_t data[0]; // Гибкий массив пользовательских данных (размер = size) @@ -219,19 +219,24 @@ void queue_set_size_limit(struct ll_queue* q, int lim); /** * @brief Устанавливает коллбэк для автоматического извлечения элементов. * @param q очередь - * @param cbk_fn функция-коллбэк + * @param cbk_fn функция-коллбэк (NULL отключает автозабор) * @param arg пользовательский аргумент * - * Коллбэк вызывается автоматически при наличии элементов и разрешённом состоянии. + * Только заменяет функцию и аргумент: не запускает обработку, не снимает callback_suspended + * и не отменяет уже запланированный resume. Отложенный resume использует новые функцию и аргумент. + * Для запуска автозабора из уже заполненной очереди вызовите queue_resume_callback(). */ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); /** - * @brief Возобновляет работу коллбэка после обработки элемента в следующем event loop. + * @brief Разрешает автозабор и планирует отложенный вызов коллбэка. * @param q очередь * - * @warning ОБЯЗАТЕЛЬНО вызывать после завершения обработки элемента в коллбэке. - * Иначе автозабор элементов навсегда остановится. + * Сразу снимает callback_suspended. Если есть элементы и callback, планирует uasync_call_soon, + * если resume ещё не запланирован. При исполнении повторно проверяет наличие элементов, + * callback и отсутствие приостановки. Последующий put в пустую очередь может вызвать callback + * синхронно, если callback_defer == 0. + * @note Вызывать после завершения обработки извлечённого элемента для продолжения автозабора. */ void queue_resume_callback(struct ll_queue* q); @@ -268,8 +273,11 @@ void queue_set_callback_defer(struct ll_queue* q, int enable); * @param cbk_fn коллбэк (NULL для отмены) * @param arg пользовательский аргумент * - * Коллбэк вызывается асинхронно (через uasync_call_soon) когда count становится 0. - * После вызова сбрасывается — для повторного использования нужно вызвать снова. + * Заменяет регистрацию и отменяет её прежний отложенный вызов. При count == 0 сразу планирует + * uasync_call_soon; иначе вызов планируется при опустошении через queue_data_get/queue_remove_data. + * При исполнении снова проверяет count: если очередь заполнилась, регистрация сохраняется + * до следующего опустошения. Функция и аргумент сбрасываются ДО вызова callback, + * поэтому внутри него можно зарегистрировать новое уведомление. */ void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); @@ -284,12 +292,16 @@ void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); /** * @brief Регистрирует ожидание освобождения очереди до общего порога. * @param q очередь - * @param h полностью обнулённый handle: `struct queue_waiter_handle h = {0}` + * @param h handle, полностью обнулённый перед первым использованием: `struct queue_waiter_handle h = {0}` * @param callback функция, вызываемая при достижении порога * @param arg аргумент коллбэка * @return 1 — условие выполнено (callback вызван или отложен), 0 — ожидает порог, -1 — ошибка * - * Если handle уже ожидает или имеет отложенный callback — старое ожидание отменяется. + * При корректных аргументах повторная регистрация в той же очереди отменяет прежнее ожидание. + * Если h->internal != NULL, перед переносом в другую очередь обязательно вызовите + * queue_waiter_cancel(old_q, h): очередь старого ожидания в handle не хранится. + * Уже отложенный callback (h->call_soon_id != NULL) отменяется по сохранённому h->defer_q, + * поэтому в этом состоянии handle можно передать другой очереди без отдельной отмены. * При queue_free отменяются также уже запланированные уведомления. */ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h, @@ -297,44 +309,61 @@ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h, /** * @brief Отменяет ожидание (удаляет из списка, если зарегистрирован). - * @param q очередь + * @param q очередь исходного ожидания (для h->internal); не NULL * @param h указатель на handle * - * Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает. + * Отложенный callback отменяется через сохранённую h->defer_q. Если ожидания и отложенного + * callback нет — ничего не делает. При h->internal != NULL передавать другую очередь нельзя. */ void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h); // ==================== Работа с данными ==================== +/** + * Общие правила владения для всех четырёх queue_data_put*: + * - При q == NULL или entry == NULL возвращается -1 без освобождения памяти. + * - Элемент должен быть извлечён из прежней очереди: owner, prev и next должны быть NULL. + * Повторная вставка связанного элемента приводит к abort(). + * - При остальных отказах (лимит, неверный вид put или границы индекса) освобождаются + * сначала dgram через queue_dgram_free(), затем entry через queue_entry_free(). + * - При успехе владение передаётся очереди/потребителю. Put может синхронно вызвать callback, + * который обработает и освободит entry и даже очередь до возврата из put. + * Использовать эти указатели после put можно только при гарантированном времени жизни. + */ + /** * @brief Добавляет элемент в конец очереди (FIFO). - * @param q очередь - * @param entry элемент - * @return 0 — успех, -1 — превышен лимит (элемент освобождён) + * @param q очередь без хеш-таблицы (hash_size == 0) + * @param entry элемент; правила передачи владения см. выше + * @return 0 — добавлен, -1 — NULL-аргумент, переполнение или hash_size > 0 */ int queue_data_put(struct ll_queue* q, struct ll_entry* entry); /** * @brief Добавляет элемент в начало очереди (LIFO, высокий приоритет). - * @param q очередь - * @param entry элемент - * @return 0 — успех, -1 — превышен лимит (элемент освобождён) + * @param q очередь без хеш-таблицы (hash_size == 0) + * @param entry элемент; правила передачи владения см. выше + * @return 0 — добавлен, -1 — NULL-аргумент, переполнение или hash_size > 0 */ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry); /** * @brief Добавляет элемент в конец очереди с хеш-индексом (использует index_offset/index_size из очереди). - * @param q очередь - * @param entry элемент - * @return 0 — успех, -1 — ошибка + * @param q очередь с настроенной хеш-таблицей (hash_size > 0, index_size > 0) + * @param entry элемент; ключ занимает index_size байт в data[] от index_offset и должен помещаться в entry->size + * @return 0 — добавлен, -1 — NULL-аргумент, переполнение или неверные границы/нулевой размер индекса + * @note Владение см. выше. При hash_size == 0 реализация может вернуть 0, добавив элемент без индексации; + * наличие хеш-таблицы — предусловие, а не гарантированная проверка с возвратом -1. */ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry); /** * @brief Добавляет элемент в начало очереди с хеш-индексом (использует index_offset/index_size из очереди). - * @param q очередь - * @param entry элемент - * @return 0 — успех, -1 — ошибка + * @param q очередь с настроенной хеш-таблицей (hash_size > 0, index_size > 0) + * @param entry элемент; ключ занимает index_size байт в data[] от index_offset и должен помещаться в entry->size + * @return 0 — добавлен, -1 — NULL-аргумент, переполнение или неверные границы/нулевой размер индекса + * @note Владение см. выше. При hash_size == 0 реализация может вернуть 0, добавив элемент без индексации; + * наличие хеш-таблицы — предусловие, а не гарантированная проверка с возвратом -1. */ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry); @@ -393,6 +422,10 @@ void queue_entry_free(struct ll_entry* entry); /** * @brief Освобождает буфер dgram элемента. * @param entry элемент + * + * При dgram != NULL вызывает dgram_free_fn, если она задана; иначе возвращает буфер в dgram_pool, + * если задан пул; иначе вызывает u_free. Затем сбрасывает dgram и len. Сам entry не освобождает. + * Внешнему буферу нужен подходящий dgram_free_fn. */ void queue_dgram_free(struct ll_entry* entry);