Fix: Remove usleep from event loops and fix segfault in tests
- Removed usleep(5000) from all test event loops - Changed to use single shared uasync for server and client instances - Removed uasync_destroy from utun_instance_destroy to prevent double-free - Added explicit uasync_destroy calls in all tests and main program - Fixed segfault in test_pkt_normalizer_etcp and test_etcp_100_packets - Added DEBUG_TRACE to all functions in etcp.c and etcp_connections.c Tests now run without artificial delays and complete successfully.
This commit is contained in:
1 parent
8a3f01fbf8
commit
beafb9b198
20 files changed
+599
-251
No files matched your search
+245
-130
@@ -1,4 +1,3 @@
|
||||
// ll_queue.h - Упрощенная архитектура: разделение создания элементов и работы с очередью
|
||||
#ifndef LL_QUEUE_H
|
||||
#define LL_QUEUE_H
|
||||
|
||||
@@ -6,169 +5,285 @@
|
||||
#include <stdint.h> // для uint64_t
|
||||
#include "memory_pool.h" // для struct memory_pool
|
||||
|
||||
// Предварительные объявления
|
||||
struct ll_queue;
|
||||
struct ll_entry;
|
||||
struct queue_waiter;
|
||||
struct memory_pool;
|
||||
struct UASYNC;
|
||||
/**
|
||||
* @file ll_queue.h
|
||||
* @brief Упрощённая двусвязная очередь с поддержкой автозабора элементов через callback,
|
||||
* порогового ожидания освобождения места, поиска по ID и гибкого управления памятью.
|
||||
*
|
||||
* Основные особенности:
|
||||
* - FIFO-очередь (с возможностью добавления в начало для приоритета)
|
||||
* - Автоматический вызов callback при появлении элементов (один элемент за раз)
|
||||
* - Обязательный вызов queue_resume_callback после обработки элемента
|
||||
* - Поддержка пулов памяти для entry и отдельно для dgram
|
||||
* - Хеш-таблица для быстрого поиска по ID (опционально)
|
||||
* - Встроенный одиночный waiter для ожидания освобождения очереди до заданного порога
|
||||
* - Использует uasync для отложенного возобновления callback'ов (без рекурсии в стеке)
|
||||
*
|
||||
* @note Важные правила использования автозабора (queue_set_callback):
|
||||
* 1. Коллбэк получает управление только если в очереди есть элементы И !callback_suspended
|
||||
* 2. Внутри коллбэка ОБЯЗАТЕЛЬНО: queue_data_get() → обработать элемент → queue_resume_callback()
|
||||
* 3. Без вызова queue_resume_callback очередь навсегда застрянет
|
||||
* 4. Не извлекайте элементы вручную вне коллбэка — это нарушит логику
|
||||
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
|
||||
*
|
||||
* @note Память:
|
||||
* - queue_free() освобождает ТОЛЬКО структуру очереди и хеш-таблицу
|
||||
* - Все элементы должны быть извлечены через queue_data_get() и освобождены через queue_entry_free()
|
||||
* - dgram освобождается отдельно через queue_dgram_free() или кастомную dgram_free_fn
|
||||
*/
|
||||
|
||||
// Автозабор элемента из очереди. вызывается когда в очереди что-то есть и коллбэк не занят обработкой.
|
||||
// Параметры: указатель на очередь, указатель на структуру элемента (struct ll_entry*), пользовательский аргумент
|
||||
// Когда коллбэк закончит обрабатывать элемент он должен вызвать queue_resume_callback - сообщить о готовности получить следующий элемент (приём очередным вызовом коллбэка в следующем цикле mainloop).
|
||||
/**
|
||||
* @struct ll_entry
|
||||
* @brief Элемент очереди (переменного размера).
|
||||
*
|
||||
* Память выделяется одним блоком: [struct ll_entry + data[size]].
|
||||
* Поле dgram — отдельный блок (malloc / пул / внешняя память).
|
||||
*/
|
||||
struct ll_entry {
|
||||
struct ll_entry* next; ///< Следующий элемент в очереди
|
||||
struct ll_entry* prev; ///< Предыдущий элемент в очереди
|
||||
uint16_t size; ///< Размер пользовательского буфера data[] (байт)
|
||||
uint16_t len; ///< Актуальная длина данных в dgram
|
||||
uint16_t memlen; ///< Выделенный размер под dgram
|
||||
uint16_t int_len; ///< Внутреннее (не использовать)
|
||||
uint8_t* dgram; ///< Указатель на данные пакета
|
||||
void (*dgram_free_fn)(uint8_t* data, void* arg); ///< Кастомная функция освобождения dgram
|
||||
struct memory_pool* dgram_pool; ///< Пул для dgram (если используется)
|
||||
uint32_t id; ///< Идентификатор для поиска (задаётся при добавлении)
|
||||
struct ll_entry* hash_next; ///< Следующий в хеш-цепочке
|
||||
struct memory_pool* pool; ///< Пул, из которого выделен сам entry (NULL = malloc)
|
||||
uint8_t data[0]; ///< Гибкий массив пользовательских данных (размер = size)
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef queue_callback_fn
|
||||
* @brief Коллбэк автозабора элементов из очереди.
|
||||
* @param q указатель на очередь
|
||||
* @param arg пользовательский аргумент (из queue_set_callback)
|
||||
*
|
||||
* @note Внутри функции:
|
||||
* - Вызвать queue_data_get(q) для получения элемента
|
||||
* - Обработать элемент (можно асинхронно)
|
||||
* - После завершения обработки вызвать queue_resume_callback(q)
|
||||
*/
|
||||
typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
|
||||
|
||||
// Структура элемента - переменный размер, данные расположены сразу после структуры
|
||||
struct ll_entry {
|
||||
struct ll_entry* next; // Указатель на следующий элемент в очереди
|
||||
struct ll_entry* prev; // Указатель на предыдущий элемент в очереди
|
||||
uint16_t size; // Размер доступной памяти после блока ll_entry - т.е. data[size]. используется для добавления доп. параметров
|
||||
|
||||
uint16_t len; // размер пакета (dgram)
|
||||
uint16_t memlen; // размер выделенной памяти (dgram)
|
||||
uint16_t int_len; // размер (private, not use!)
|
||||
uint8_t* dgram; // данные пакета
|
||||
void (*dgram_free_fn)(uint8_t* data, void* arg); // функция освобождения блока
|
||||
struct memory_pool* dgram_pool; // Пул, из которого выделен этот элемент (NULL, если выделен через malloc)
|
||||
|
||||
uint32_t id; // Идентификатор для хеш-поиска
|
||||
struct ll_entry* hash_next; // Следующий в хеш-цепочке
|
||||
struct memory_pool* pool; // Пул, из которого выделен этот элемент (NULL, если выделен через malloc)
|
||||
uint8_t data[0];
|
||||
};
|
||||
|
||||
// Ожидающий добавления элементов в очередь (пока очередь не освободться до нужного размера чтобы не забивать)
|
||||
struct queue_waiter {
|
||||
int max_packets; // Максимальное количество пакетов
|
||||
size_t max_bytes; // Максимальное количество байт (0 = не проверять байты)
|
||||
void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова
|
||||
void* callback_arg; // Аргумент коллбэка
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef queue_threshold_callback_fn
|
||||
* @brief Одноразовый коллбэк при достижении порога очереди (queue_wait_threshold).
|
||||
* @param q указатель на очередь
|
||||
* @param arg пользовательский аргумент
|
||||
*/
|
||||
typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
|
||||
|
||||
// Структура очереди
|
||||
struct ll_queue {
|
||||
struct ll_entry* head; // Первый элемент (извлекается отсюда)
|
||||
struct ll_entry* tail; // Последний элемент (добавляется сюда)
|
||||
int count; // Текущее количество элементов
|
||||
size_t total_bytes; // Общий размер данных всех элементов (байт)
|
||||
int size_limit; // Максимальное количество (-1 = без ограничения)
|
||||
|
||||
queue_callback_fn callback; // Функция коллбэка
|
||||
void* callback_arg; // Пользовательский аргумент для коллбэка
|
||||
int callback_suspended;
|
||||
|
||||
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления
|
||||
struct UASYNC* ua; // Экземпляр uasync для таймеров
|
||||
|
||||
struct queue_waiter waiter; // Встроенный одиночный waiter
|
||||
|
||||
// Хеш-таблица для быстрого поиска по ID
|
||||
struct ll_entry** hash_table;
|
||||
size_t hash_size;
|
||||
/**
|
||||
* @struct queue_waiter
|
||||
* @brief Описание ожидания освобождения места в очереди (встроен в ll_queue — только один).
|
||||
*/
|
||||
struct queue_waiter {
|
||||
int max_packets; ///< Максимально допустимое количество элементов
|
||||
size_t max_bytes; ///< Максимально допустимый объём данных (0 = не проверять)
|
||||
queue_threshold_callback_fn callback; ///< Коллбэк (вызывается один раз)
|
||||
void* callback_arg; ///< Аргумент коллбэка
|
||||
};
|
||||
|
||||
// ==================== Управление очередью ====================
|
||||
/**
|
||||
* @struct ll_queue
|
||||
* @brief Структура очереди.
|
||||
*/
|
||||
struct ll_queue {
|
||||
struct ll_entry* head; ///< Голова очереди (отсюда извлекаем)
|
||||
struct ll_entry* tail; ///< Хвост очереди (сюда добавляем)
|
||||
int count; ///< Текущее количество элементов
|
||||
size_t total_bytes; ///< Суммарный объём данных (сумма int_len)
|
||||
int size_limit; ///< Максимальное количество элементов (-1 = без лимита)
|
||||
|
||||
// Создать новую пустую очередь
|
||||
// ua - экземпляр uasync для таймеров (обязательный параметр)
|
||||
// hash_size - размер хеш-таблицы для быстрого поиска (0 = без хеш-таблицы)
|
||||
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти
|
||||
queue_callback_fn callback; ///< Коллбэк автозабора
|
||||
void* callback_arg; ///< Аргумент коллбэка
|
||||
int callback_suspended; ///< 1 = коллбэки временно приостановлены
|
||||
|
||||
void* resume_timeout_id; ///< ID таймера uasync для отложенного resume
|
||||
struct UASYNC* ua; ///< Экземпляр uasync (обязателен для таймеров)
|
||||
|
||||
struct queue_waiter waiter; ///< Встроенный waiter (только один)
|
||||
|
||||
struct ll_entry** hash_table; ///< Хеш-таблица для поиска по id (если hash_size > 0)
|
||||
size_t hash_size; ///< Размер хеш-таблицы
|
||||
};
|
||||
|
||||
/* ==================== Создание / уничтожение ==================== */
|
||||
|
||||
/**
|
||||
* @brief Создаёт новую пустую очередь.
|
||||
* @param ua экземпляр uasync (обязателен для отложенного resume коллбэков)
|
||||
* @param hash_size размер хеш-таблицы (0 = поиск по id отключён)
|
||||
* @return указатель на очередь или NULL при ошибке выделения памяти
|
||||
*/
|
||||
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size);
|
||||
|
||||
// Освободить очередь и все её элементы
|
||||
// ВАЖНО: освобождает только структуру очереди, элементы в очереди НЕ освобождаются!
|
||||
// Элементы должны быть предварительно извлечены через queue_data_get() и освобождены через queue_data_free()
|
||||
/**
|
||||
* @brief Освобождает структуру очереди.
|
||||
* @param q очередь
|
||||
*
|
||||
* @warning НЕ ОСВОБОЖДАЕТ элементы в очереди!
|
||||
* Их нужно извлечь через queue_data_get() и освободить через queue_entry_free().
|
||||
*/
|
||||
void queue_free(struct ll_queue* q);
|
||||
|
||||
// Установить максимальное количество элементов в очереди
|
||||
// При превышении лимита новый элемент автоматически освобождается
|
||||
/* ==================== Конфигурация ==================== */
|
||||
|
||||
/**
|
||||
* @brief Устанавливает максимальное количество элементов в очереди.
|
||||
* @param q очередь
|
||||
* @param lim лимит (-1 = без ограничения)
|
||||
*
|
||||
* При превышении лимита новые элементы автоматически освобождаются.
|
||||
*/
|
||||
void queue_set_size_limit(struct ll_queue* q, int lim);
|
||||
|
||||
// ==================== Асинхронное ожидание передачи ====================
|
||||
/* ==================== Автозабор элементов ==================== */
|
||||
|
||||
// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь
|
||||
// не более max_packets пакетов и не более max_bytes байт (если max_bytes != 0).
|
||||
// Если условие уже выполнено, коллбэк вызывается немедленно.
|
||||
// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
|
||||
// Отменить ожидание (удалить waiter из списка)
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
|
||||
|
||||
// Получить общий размер данных в очереди (байт)
|
||||
static inline size_t queue_total_bytes(struct ll_queue* q) {
|
||||
if (!q) return 0;
|
||||
return q->total_bytes;
|
||||
}
|
||||
|
||||
// ==================== Асинхронное ожидание приёма ====================
|
||||
|
||||
// Установить функцию и аргумент коллбэка для автозабора из очереди
|
||||
// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк
|
||||
// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету.
|
||||
/**
|
||||
* @brief Устанавливает коллбэк для автоматического извлечения элементов.
|
||||
* @param q очередь
|
||||
* @param cbk_fn функция-коллбэк
|
||||
* @param arg пользовательский аргумент
|
||||
*
|
||||
* Коллбэк вызывается автоматически при наличии элементов и разрешённом состоянии.
|
||||
*/
|
||||
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
|
||||
|
||||
// обрабатывается строго одина пакет за вызов
|
||||
// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя).
|
||||
// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет.
|
||||
// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0)
|
||||
// Это предотвращает накопление рекурсии в стеке вызовов
|
||||
/**
|
||||
* @brief Возобновляет работу коллбэка после обработки элемента.
|
||||
* @param q очередь
|
||||
*
|
||||
* @warning ОБЯЗАТЕЛЬНО вызывать после завершения обработки элемента в коллбэке.
|
||||
* Иначе автозабор элементов навсегда остановится.
|
||||
*/
|
||||
void queue_resume_callback(struct ll_queue* q);
|
||||
|
||||
// ==================== Управление элементами ====================
|
||||
/* ==================== Пороговое ожидание ==================== */
|
||||
|
||||
// выделить ll_entry и память под кодограмму
|
||||
struct ll_entry* ll_alloc_lldgram(uint16_t len);
|
||||
/**
|
||||
* @brief Регистрирует одноразовый коллбэк, который сработает, когда очередь освободится до заданного порога.
|
||||
* @param q очередь
|
||||
* @param max_packets максимальное количество элементов
|
||||
* @param max_bytes максимальный объём данных (0 = не проверять)
|
||||
* @param callback функция, вызываемая при достижении порога
|
||||
* @param arg аргумент коллбэка
|
||||
* @return указатель на waiter (для отмены) или NULL, если условие уже выполнено (коллбэк вызван сразу)
|
||||
*/
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
|
||||
// Создать новый элемент с областью данных указанного размера
|
||||
// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт]
|
||||
// Возвращает: указатель на структуру элемента (struct ll_entry*) или NULL при ошибке выделения памяти
|
||||
struct ll_entry* queue_entry_new(size_t data_size);
|
||||
/**
|
||||
* @brief Отменяет ранее установленное ожидание порога.
|
||||
* @param q очередь
|
||||
* @param waiter указатель, возвращённый queue_wait_threshold()
|
||||
*/
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
|
||||
|
||||
// Создать новый элемент из пула (размер был определен при создании пула)
|
||||
// Возвращает: указатель на структуру элемента (struct ll_entry*) или NULL при ошибке выделения памяти
|
||||
struct ll_entry* queue_entry_new_from_pool(struct memory_pool* pool);
|
||||
/* ==================== Работа с данными ==================== */
|
||||
|
||||
// Освободить только entry (не влияет на очереди, dgram не освобождает)
|
||||
void queue_entry_free(struct ll_entry* entry);
|
||||
|
||||
// Освободить только entry->dgram (не влияет на очереди)
|
||||
void queue_dgram_free(struct ll_entry* entry);
|
||||
//void queue_data_free(void* data);
|
||||
|
||||
// ==================== Операции с очередью ====================
|
||||
|
||||
// Добавить элемент в конец очереди (FIFO)
|
||||
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
|
||||
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
|
||||
/**
|
||||
* @brief Добавляет элемент в конец очереди (FIFO).
|
||||
* @param q очередь
|
||||
* @param entry элемент
|
||||
* @param id идентификатор для поиска
|
||||
* @return 0 — успех, -1 — превышен лимит (элемент освобождён)
|
||||
*/
|
||||
int queue_data_put(struct ll_queue* q, struct ll_entry* entry, uint32_t id);
|
||||
|
||||
// Добавить элемент в начало очереди (LIFO, высокий приоритет)
|
||||
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
|
||||
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
|
||||
/**
|
||||
* @brief Добавляет элемент в начало очереди (LIFO, высокий приоритет).
|
||||
* @param q очередь
|
||||
* @param entry элемент
|
||||
* @param id идентификатор
|
||||
* @return 0 — успех, -1 — превышен лимит (элемент освобождён)
|
||||
*/
|
||||
int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry, uint32_t id);
|
||||
|
||||
// Извлечь элемент из начала очереди
|
||||
// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию
|
||||
// Возвращает: указатель на структуру элемента (struct ll_entry*) или NULL если очередь пуста
|
||||
// ПРИМЕЧАНИЕ: не освобождает память элемента
|
||||
/**
|
||||
* @brief Извлекает элемент из начала очереди.
|
||||
* @param q очередь
|
||||
* @return элемент или NULL, если очередь пуста
|
||||
*
|
||||
* @note Автоматически приостанавливает коллбэки (callback_suspended = 1)
|
||||
* После обработки элемента необходимо вызвать queue_resume_callback().
|
||||
*/
|
||||
struct ll_entry* queue_data_get(struct ll_queue* q);
|
||||
|
||||
// Получить текущее количество элементов в очереди
|
||||
/**
|
||||
* @brief Возвращает текущее количество элементов в очереди.
|
||||
* @param q очередь
|
||||
* @return количество элементов
|
||||
*/
|
||||
int queue_entry_count(struct ll_queue* q);
|
||||
|
||||
// ==================== Поиск и удаление по ID ====================
|
||||
/* ==================== Управление памятью элементов ==================== */
|
||||
|
||||
// Найти элемент по ID
|
||||
// Возвращает: указатель на структуру элемента (struct ll_entry*) или NULL если не найден
|
||||
/**
|
||||
* @brief Выделяет элемент с отдельным буфером dgram (malloc).
|
||||
* @param len требуемый размер dgram
|
||||
* @return элемент или NULL при ошибке
|
||||
*/
|
||||
struct ll_entry* ll_alloc_lldgram(uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Создаёт элемент с пользовательским буфером data[] (malloc одним блоком).
|
||||
* @param data_size размер data[]
|
||||
* @return элемент или NULL при ошибке
|
||||
*/
|
||||
struct ll_entry* queue_entry_new(size_t data_size);
|
||||
|
||||
/**
|
||||
* @brief Создаёт элемент из пула памяти.
|
||||
* @param pool пул
|
||||
* @return элемент или NULL, если пул пуст
|
||||
*/
|
||||
struct ll_entry* queue_entry_new_from_pool(struct memory_pool* pool);
|
||||
|
||||
/**
|
||||
* @brief Освобождает структуру элемента (возвращает в пул или free).
|
||||
* @param entry элемент
|
||||
*
|
||||
* @note НЕ освобождает dgram! Используйте queue_dgram_free() отдельно.
|
||||
*/
|
||||
void queue_entry_free(struct ll_entry* entry);
|
||||
|
||||
/**
|
||||
* @brief Освобождает буфер dgram элемента.
|
||||
* @param entry элемент
|
||||
*/
|
||||
void queue_dgram_free(struct ll_entry* entry);
|
||||
|
||||
/* ==================== Поиск и удаление ==================== */
|
||||
|
||||
/**
|
||||
* @brief Находит элемент по идентификатору (требуется hash_size > 0 при создании).
|
||||
* @param q очередь
|
||||
* @param id искомый идентификатор
|
||||
* @return элемент или NULL
|
||||
*/
|
||||
struct ll_entry* queue_find_data_by_id(struct ll_queue* q, uint32_t id);
|
||||
|
||||
// Удалить элемент из очереди по указателю на структуру элемента
|
||||
// Возвращает: 0 при успехе, -1 если элемент не найден
|
||||
// ПРИМЕЧАНИЕ: НЕ изменяет ref_count элемента, просто удаляет из очереди
|
||||
/**
|
||||
* @brief Удаляет элемент из очереди (не освобождает память).
|
||||
* @param q очередь
|
||||
* @param entry элемент
|
||||
* @return 0 — успех, -1 — элемент не найден в очереди
|
||||
*/
|
||||
int queue_remove_data(struct ll_queue* q, struct ll_entry* entry);
|
||||
|
||||
/* ==================== Утилиты ==================== */
|
||||
|
||||
/**
|
||||
* @brief Возвращает суммарный объём данных в очереди.
|
||||
* @param q очередь
|
||||
* @return количество байт
|
||||
*/
|
||||
static inline size_t queue_total_bytes(struct ll_queue* q) {
|
||||
return q ? q->total_bytes : 0;
|
||||
}
|
||||
|
||||
#endif // LL_QUEUE_H
|
||||
Reference in new issue
Block a user