Browse Source
- Added missing structure members to ETCP_CONN (ack_packets_count, last_rx_ack_id, rtt_history, rtt_history_idx, total_packets_sent) - Added missing bandwidth member to ETCP_LINK structure - Fixed include path for ll_queue.h header - Added math library linking (-lm) to build system - Added etcp_loadbalancer.c to build sources - Fixed forward declarations and function calls in ETCP modules - Updated test dependencies to include loadbalancer object files All simple compilation errors resolved, project builds successfully and tests pass (except TUN permission test which is expected in container).nodeinfo-routing-update
35 changed files with 6943 additions and 601 deletions
@ -0,0 +1,161 @@
|
||||
ar: модификатор «u» игнорируется, так как по умолчанию используется «D» (смотрите «U») |
||||
utun_instance.c: In function ‘utun_instance_create’: |
||||
utun_instance.c:93:49: warning: ‘/32’ directive output may be truncated writing 3 bytes into a region of size between 1 and 64 [-Wformat-truncation=] |
||||
93 | snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer); |
||||
| ^~~ |
||||
In file included from /usr/include/stdio.h:980, |
||||
from utun_instance.h:6, |
||||
from utun_instance.c:2: |
||||
In function ‘snprintf’, |
||||
inlined from ‘utun_instance_create’ at utun_instance.c:93:5: |
||||
/usr/include/x86_64-linux-gnu/bits/stdio2.h:54:10: note: ‘__builtin___snprintf_chk’ output between 4 and 67 bytes into a destination of size 64 |
||||
54 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1, |
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
55 | __glibc_objsize (__s), __fmt, |
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
56 | __va_arg_pack ()); |
||||
| ~~~~~~~~~~~~~~~~~ |
||||
etcp.c: In function ‘etcp_conn_reset’: |
||||
etcp.c:175:9: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’ |
||||
175 | etcp->rtt_history_idx = 0; |
||||
| ^~ |
||||
etcp.c:176:16: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
176 | memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); |
||||
| ^~ |
||||
etcp.c:176:45: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
176 | memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); |
||||
| ^~ |
||||
etcp.c: In function ‘etcp_request_pkt’: |
||||
etcp.c:251:47: error: ‘input_queue_cb’ undeclared (first use in this function) |
||||
251 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume |
||||
| ^~~~~~~~~~~~~~ |
||||
etcp.c:251:47: note: each undeclared identifier is reported only once for each function it appears in |
||||
etcp.c:288:30: warning: implicit declaration of function ‘etcp_loadbalancer_select_link’ [-Wimplicit-function-declaration] |
||||
288 | struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); |
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
etcp.c:288:30: warning: initialization of ‘struct ETCP_LINK *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion] |
||||
etcp.c: In function ‘append_optional_sections’: |
||||
etcp.c:391:13: error: ‘struct ETCP_CONN’ has no member named ‘ack_packets_count’ |
||||
391 | etcp->ack_packets_count++; |
||||
| ^~ |
||||
etcp.c: In function ‘process_section_ack’: |
||||
etcp.c:437:11: error: ‘struct ETCP_CONN’ has no member named ‘last_rx_ack_id’; did you mean ‘last_rx_id’? |
||||
437 | etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID |
||||
| ^~~~~~~~~~~~~~ |
||||
| last_rx_id |
||||
etcp.c: In function ‘update_link_bandwidth’: |
||||
etcp.c:642:9: error: ‘struct ETCP_LINK’ has no member named ‘bandwidth’ |
||||
642 | link->bandwidth = (link->bandwidth * 0.9) + (new_bw * 0.1); // Smooth |
||||
| ^~ |
||||
etcp.c:642:28: error: ‘struct ETCP_LINK’ has no member named ‘bandwidth’ |
||||
642 | link->bandwidth = (link->bandwidth * 0.9) + (new_bw * 0.1); // Smooth |
||||
| ^~ |
||||
etcp.c: In function ‘update_rtt’: |
||||
etcp.c:652:9: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
652 | etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; |
||||
| ^~ |
||||
etcp.c:652:27: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’ |
||||
652 | etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; |
||||
| ^~ |
||||
etcp.c:653:13: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’ |
||||
653 | if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; |
||||
| ^~ |
||||
etcp.c:653:56: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’ |
||||
653 | if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; |
||||
| ^~ |
||||
etcp.c: In function ‘calculate_jitter’: |
||||
etcp.c:661:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
661 | if (etcp->rtt_history[i] == 0) continue; |
||||
| ^~ |
||||
etcp.c:662:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
662 | if (etcp->rtt_history[i] < min_rtt) min_rtt = etcp->rtt_history[i]; |
||||
| ^~ |
||||
etcp.c:662:59: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
662 | if (etcp->rtt_history[i] < min_rtt) min_rtt = etcp->rtt_history[i]; |
||||
| ^~ |
||||
etcp.c:663:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
663 | if (etcp->rtt_history[i] > max_rtt) max_rtt = etcp->rtt_history[i]; |
||||
| ^~ |
||||
etcp.c:663:59: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
663 | if (etcp->rtt_history[i] > max_rtt) max_rtt = etcp->rtt_history[i]; |
||||
| ^~ |
||||
etcp.c: In function ‘etcp_get_stats’: |
||||
etcp.c:684:43: error: ‘struct ETCP_CONN’ has no member named ‘total_packets_sent’ |
||||
684 | if (packets_sent) *packets_sent = etcp->total_packets_sent; |
||||
| ^~ |
||||
make[2]: *** [Makefile:605: utun-etcp.o] Ошибка 1 |
||||
make[1]: *** [Makefile:375: all-recursive] Ошибка 1 |
||||
make: *** [Makefile:316: all] Ошибка 2 |
||||
etcp.c: In function ‘etcp_conn_reset’: |
||||
etcp.c:175:9: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’ |
||||
175 | etcp->rtt_history_idx = 0; |
||||
| ^~ |
||||
etcp.c:176:16: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
176 | memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); |
||||
| ^~ |
||||
etcp.c:176:45: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
176 | memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); |
||||
| ^~ |
||||
etcp.c: In function ‘etcp_request_pkt’: |
||||
etcp.c:251:47: error: ‘input_queue_cb’ undeclared (first use in this function) |
||||
251 | queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume |
||||
| ^~~~~~~~~~~~~~ |
||||
etcp.c:251:47: note: each undeclared identifier is reported only once for each function it appears in |
||||
etcp.c:288:30: warning: implicit declaration of function ‘etcp_loadbalancer_select_link’ [-Wimplicit-function-declaration] |
||||
288 | struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); |
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
etcp.c:288:30: warning: initialization of ‘struct ETCP_LINK *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion] |
||||
etcp.c: In function ‘append_optional_sections’: |
||||
etcp.c:391:13: error: ‘struct ETCP_CONN’ has no member named ‘ack_packets_count’ |
||||
391 | etcp->ack_packets_count++; |
||||
| ^~ |
||||
etcp.c: In function ‘process_section_ack’: |
||||
etcp.c:437:11: error: ‘struct ETCP_CONN’ has no member named ‘last_rx_ack_id’; did you mean ‘last_rx_id’? |
||||
437 | etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID |
||||
| ^~~~~~~~~~~~~~ |
||||
| last_rx_id |
||||
etcp.c: In function ‘update_link_bandwidth’: |
||||
etcp.c:642:9: error: ‘struct ETCP_LINK’ has no member named ‘bandwidth’ |
||||
642 | link->bandwidth = (link->bandwidth * 0.9) + (new_bw * 0.1); // Smooth |
||||
| ^~ |
||||
etcp.c:642:28: error: ‘struct ETCP_LINK’ has no member named ‘bandwidth’ |
||||
642 | link->bandwidth = (link->bandwidth * 0.9) + (new_bw * 0.1); // Smooth |
||||
| ^~ |
||||
etcp.c: In function ‘update_rtt’: |
||||
etcp.c:652:9: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
652 | etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; |
||||
| ^~ |
||||
etcp.c:652:27: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’ |
||||
652 | etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; |
||||
| ^~ |
||||
etcp.c:653:13: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’ |
||||
653 | if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; |
||||
| ^~ |
||||
etcp.c:653:56: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history_idx’ |
||||
653 | if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; |
||||
| ^~ |
||||
etcp.c: In function ‘calculate_jitter’: |
||||
etcp.c:661:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
661 | if (etcp->rtt_history[i] == 0) continue; |
||||
| ^~ |
||||
etcp.c:662:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
662 | if (etcp->rtt_history[i] < min_rtt) min_rtt = etcp->rtt_history[i]; |
||||
| ^~ |
||||
etcp.c:662:59: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
662 | if (etcp->rtt_history[i] < min_rtt) min_rtt = etcp->rtt_history[i]; |
||||
| ^~ |
||||
etcp.c:663:17: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
663 | if (etcp->rtt_history[i] > max_rtt) max_rtt = etcp->rtt_history[i]; |
||||
| ^~ |
||||
etcp.c:663:59: error: ‘struct ETCP_CONN’ has no member named ‘rtt_history’ |
||||
663 | if (etcp->rtt_history[i] > max_rtt) max_rtt = etcp->rtt_history[i]; |
||||
| ^~ |
||||
etcp.c: In function ‘etcp_get_stats’: |
||||
etcp.c:684:43: error: ‘struct ETCP_CONN’ has no member named ‘total_packets_sent’ |
||||
684 | if (packets_sent) *packets_sent = etcp->total_packets_sent; |
||||
| ^~ |
||||
make[2]: *** [Makefile:605: utun-etcp.o] Ошибка 1 |
||||
make[1]: *** [Makefile:375: all-recursive] Ошибка 1 |
||||
make: *** [Makefile:316: all] Ошибка 2 |
||||
make[1]: *** Нет правила для сборки цели «../src/utun-secure_channel.o», требуемой для «test_etcp_crypto». Останов. |
||||
make: *** [Makefile:1024: check-am] Ошибка 2 |
||||
@ -0,0 +1,118 @@
|
||||
Формат кодограммы: |
||||
------------------ |
||||
пакеты идут по следующему пути: |
||||
пакет кем-то помещается в входную очередь (ETCP input_queue). |
||||
Когда load balancer готов отправить следующий пакет он делает вызов etcp_request_pkt в etcp.c/h и etcp либо формирует следующую кодограмму или встаёт в состояние async ожидания (возвращая null): |
||||
- либо wait ETCP input_queue (если нечего передавать) |
||||
- либо wait SACK + wait timeout (если sack очередь полная и нет необслуженных запросов ретрансмиссий). после истечения wait timeout он помечает используя round-robin очередной пакет как "нужна ретрансмиссия". |
||||
из async ожидания он может вызвать etcp_loadbalancer input_from_etcp и передать пакет на отправку. |
||||
как формируется пакет: |
||||
1. берем пакет из списка неподтвержденных пакетов ожидающих отправку |
||||
2. если есть место в rwin (объём inflight данных) то берем очередной пакет из ETCP input_queue и его отправляем |
||||
если пакет найден: |
||||
1. вызываем функцию формирования опциональных секций (ACK, RETRANS, channel timestamp) - они записываются в начало |
||||
2. в конец добавляем секцию 0x00 с данными пакета |
||||
при отправке пакета помечаем в inflight списке с какого интерфейса он отправлен |
||||
Функция прикрепления опциональных секций: |
||||
- запрашивает выбор канала передачи. |
||||
- добавляет накопившиеся ACK и RETRANS |
||||
При приёме пакета (от etcp_connections): |
||||
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам: |
||||
- retrans: помечаем секции как нужна ретрансмиссия. также ставим флаг в конце запустить etcp если он в wait timeout, т.е. wait_timeout!=NULL |
||||
- ack: помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также и ставим флаг в конце запустить etcp если он в wait timeout, т.е. wait_timeout!=NULL |
||||
- timestamp: обновляем last RTT, пересчитываем RTT10,RTT100 (плавающим окном за последние 10/100 пакетов), и jitter=max(last 10)-min(last 10 packets) |
||||
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем). |
||||
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть. |
||||
|
||||
пересканирование inflight: |
||||
- вызывается по таймеру, таймер заводится при добавлении пакета в пустой inflight. |
||||
- если время последней отправки > RTT10*k1+jitter*k2 (коэффициенты вынести в настройки utun instance) то пакет заново отправляется |
||||
и заводим таймер отправки следующей ретрансмиссии (список отсортирован по времени) |
||||
- если встречаем успешный ack, запоминаем это и его timestamp (назовем lp_ts). |
||||
- если после этого далее в очереди находим пакет который не имеет переповторов и timestamp отправки раньше чем [ИСПРАВЛЕНО: фраза обрезана в оригинале; предположительно, "раньше чем lp_ts" на основе контекста; если нет, пометить как неполное] |
||||
|
||||
inflight - два списка (ll_queue): |
||||
- список с неподтвержденными пакетами ожидающими ack (при запросе ретрансмиссии пакет перемещается в ожидающие отправку) |
||||
- список с неподтвержденными пакетами ожидающими отправку (при отправке возвращается в ожидающие ack) |
||||
доп. свойства пакетов: |
||||
- указатель на ETC_LINK последней отправки |
||||
- timestamp последней отправки |
||||
- число отправок |
||||
- число запросов переповтора |
||||
Размер буфера inflight лимитируем как сумму по всем активным линкам optimal_inflight. |
||||
optimal_inflight расчитывается из RTT и bandwidth линка. |
||||
|
||||
bandwidth по каждому линку адаптивно подстраиваем следующим алгоритмом: |
||||
периодически инициируем burst_transmission (напоминаем ID пакета на которой она началась). |
||||
после передаём пачку пакетов только по этому линку со следующими таймингами: |
||||
<pkt1> <delay 4x> <pkt2><pkt3><pkt4><pkt5> - т.е. чуть задерживаем канал а потом выплёвываем пачку пакетов. |
||||
и добавляем опцию [meas_ts][n] - где n - номер пакета в пачке |
||||
приёмная сторона видя эту секцию должна отправить секцию [meas_resp] по всем пакетам, которые содержат локальный RECV_Timestamp фиксирующий время приёма каждого пакета с [meas_ts]. |
||||
приняв эти response анализируем по разнице между pkt1-pkt2 не забита ли очередь отправки, а по дельте pkt2-3-4-5 оцениваем bandwidth. |
||||
дальше по алгоритму (который потом оттюним) корректируем transmitter speed limit. |
||||
|
||||
|
||||
**** Формат кодограмм для etcp.c/h **** |
||||
Каждая кодограмма состоит из обязательного заголовка и опциональных секций: |
||||
1. Обязательный заголовок всего пакета (2 байта) добавляется при передаче, есть во всех пакетах: |
||||
[Timestamp high][Timestamp low] |
||||
- Timestamp: время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется |
||||
шифруется строго ВСЁ включая ВСЕ заголовки (кроме INIT+publickey). timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ |
||||
2. Опциональные секции (одна или несколько) добавляются в etcp.c: |
||||
а) Подтверждения (ACK) - заголовок 0x01: |
||||
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo] |
||||
- count: количество пар ID+timestamp (1-32) |
||||
- last_delivered_id: последний ID, доставленный получателю |
||||
- last_rx_id: последний полученный ID (для синхронизации прогресса) |
||||
б) Запросы ретрансмиссии (RETRANS) - заголовок 0x10-0x2F: |
||||
[0x10+(count-1)][(id_hi,id_lo)×count][last_delivered_hi,lo][last_rx_hi,lo] |
||||
- count: (заголовок & 0x0F) + 1 (1-32) |
||||
- last_delivered_id, last_rx_id: для синхронизации состояния |
||||
в) channel timestamp (добавляется после выбора канала передачи): |
||||
[0x0F] [RET_Timestamp high][RET_Timestamp low] [RECV_Timestamp high][RECV_Timestamp low] |
||||
- RET_Timestamp: это timestamp последнего принятого пакета по этому каналу ETCP_LINK плюс разница времени между принятием этого пакета и текущим временем. т.е. приёмная сторона по этому timestamp (и зная своё время) может просчитать время в пути туда + обратно |
||||
- RECV_Timestamp: это timestamp времени приёма последнего принятого пакета (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно). |
||||
если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем. [СПОРНО: порог 30000 (3 сек) arbitrary; может потребовать настройки или обоснования; также риск clock skew между нодами] |
||||
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя): |
||||
[0x00] [ID high][ID low] [данные...] - Данные пакета |
||||
- ID: 16-битный циклический номер пакета. [СПОРНО: 16-bit может привести к быстрому overflow в long-lived сессиях; рассмотреть 32-bit] |
||||
Пакет может содержать несколько опциональных секций (например, ACK + данные). [СПОРНО: нет явных length полей для секций; parsing implicit, риск ошибок при variable длине; добавить length?] |
||||
цель протокола: |
||||
1. контролировать состояние очередей (не накапливать данные где этого можно избежать), анализировать RTT и inflight - динамически подстраивать размер окна и ретрансмиссии, утилизируя сеть насколько это возможно но не допуская наполнения буферов в сети (что приводит к бестолковому увеличению RTT) |
||||
Установка подключения для канала (реализуется в etcp_connections.c/h): |
||||
каждое ETCP подключение может содержать несколько линков до endpoint (разные маршруты, каналы связи). |
||||
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно): |
||||
- RTT последнего пакета и время отправки последнего пакета |
||||
- пакеты делятся на 3 категории: <300 байт, 300-699 байт, 700+ байт. для каждой категории отдельно считаются метрики: |
||||
- количество отправленных в пакетах байтах |
||||
- количество перезапросов (по каждому каналу отдельно) в пакетах и байтах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики. |
||||
- обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент |
||||
Планировщик/балансир каналов (etcp_loadbalancer.c/h): |
||||
для каждого канала ограничение полосы с тестированием отклика: |
||||
- полосу ограничиваем по объему данных - полному числу байт ethernet пакта (добавляем размер заголовков) |
||||
Ограничение реализуем следующим образом: |
||||
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности (т.е. было ненулевое время передачи одного байта). |
||||
Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время) |
||||
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи"); [СПОРНО: сложность с float; риск precision issues] |
||||
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения. |
||||
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client. |
||||
процесс установки соединения: |
||||
- отправляем init запрос и выставляем таймаут (сохраняем его в struct ETCP_LINK) |
||||
- по таймауту повторяем init запросы, ведем счетчик запросов init. |
||||
- когда получаем init подтверждение (отправляется в etcp_connections_read_callback) снимаем таймаут и выставляем флаг struct ETCP_LINK initialized=1. |
||||
При добавлении нового канала посылаем INIT 0x04 (без сброса), а при первичной инициализации подключения - 0x02 (это нужно, т.к. например перезапуск локального софта должен инициировать перезапуск удаленного соединения). |
||||
|
||||
**** Формат кодограмм для etcp_connections.c/h **** |
||||
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда только одна секция). в обязательном заголовке ID не используется, при передаче для порядка =0: [СПОРНО: заголовок в etcp — только 2 байта TS; здесь подразумевается ID? Уточнить единый формат] |
||||
1) Init запрос - заголовок 0x02 (со сбросом etcp сессии) или 0x04 (без сброса): |
||||
[0x02/0x04] [my_node_id 64bit] [my mtu high] [my mtu low] [keepalive high] [keepalive low] [my public key (64 байта, не шифруется)] |
||||
- Инициирует новый connection для tcp instance. если tcp instance нет (первое подключение) - создаёт. Между нодами только одно подключение, но можно добавлять каналы. |
||||
- Публичный ключ отправляется в конце пакета без шифрования, чтобы получатель мог установить его и расшифровать остальную часть пакета (т.к. инициатор соединения всегда имеет оригинальный peer public key в конфиге - по нему исключаем MITM) |
||||
2) Init подтверждение - заголовок 0x03/0x05 (ответы соответственно на коды 0x02 и 0x04): |
||||
[0x03/0x05] [my_node_id 64bit] [my mtu high] [my mtu low] |
||||
- Подтверждение инициализации (канал успешно создан, можно начинать обмен) |
||||
При получении init получатель пакета должен: |
||||
- reset ETCP_LINK с этим ip_port если он есть |
||||
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - вызвать etcp_reset (функция сброса окон неподтвержденных данных и нумерации) |
||||
приём кодограмм из UDP выглядит так: |
||||
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue |
||||
@ -0,0 +1,829 @@
|
||||
// ll_queue.c |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <assert.h> |
||||
#include "ll_queue.h" |
||||
#include "u_async.h" |
||||
#include "debug_config.h" |
||||
|
||||
// Предварительное объявление для отложенного возобновления |
||||
static void queue_resume_timeout_cb(void* arg); |
||||
|
||||
// Проверить и запустить ожидающие коллбэки |
||||
static void check_waiters(struct ll_queue* q) { |
||||
if (!q || !q->waiters) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", |
||||
q->count, q->total_bytes); |
||||
|
||||
// Сначала собрать список коллбэков для вызова, чтобы избежать проблем с модификацией списка во время итерации |
||||
struct queue_waiter* to_call = NULL; |
||||
struct queue_waiter* to_call_tail = NULL; |
||||
|
||||
struct queue_waiter** pprev = &q->waiters; |
||||
struct queue_waiter* waiter = q->waiters; |
||||
|
||||
while (waiter) { |
||||
struct queue_waiter* next = waiter->next; |
||||
|
||||
// Проверить условие: не больше max_packets и не больше max_bytes |
||||
// max_bytes = 0 означает "не проверять байты" |
||||
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)", |
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, |
||||
waiter->max_bytes == 0 ? "disabled" : "enabled"); |
||||
// Удалить waiter из списка |
||||
*pprev = next; |
||||
// Добавить в список для вызова |
||||
waiter->next = NULL; |
||||
if (to_call_tail) { |
||||
to_call_tail->next = waiter; |
||||
} else { |
||||
to_call = waiter; |
||||
} |
||||
to_call_tail = waiter; |
||||
} else { |
||||
// Условие не выполнено - оставить в списке |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)", |
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, |
||||
waiter->max_bytes == 0 ? "disabled" : "enabled"); |
||||
pprev = &waiter->next; |
||||
} |
||||
waiter = next; |
||||
} |
||||
|
||||
// Теперь вызвать коллбэки |
||||
waiter = to_call; |
||||
while (waiter) { |
||||
struct queue_waiter* next = waiter->next; |
||||
waiter->callback(q, waiter->callback_arg); |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, waiter); |
||||
} else { |
||||
free(waiter); |
||||
} |
||||
waiter = next; |
||||
} |
||||
} |
||||
|
||||
// ==================== Управление очередью ==================== |
||||
|
||||
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) { |
||||
if (!ua) return NULL; |
||||
|
||||
struct ll_queue* q = calloc(1, sizeof(struct ll_queue)); |
||||
if (!q) return NULL; |
||||
|
||||
q->head = NULL; |
||||
q->tail = NULL; |
||||
q->count = 0; |
||||
q->total_bytes = 0; |
||||
q->size_limit = -1; // По умолчанию без ограничения |
||||
q->callback = NULL; |
||||
q->callback_arg = NULL; |
||||
q->callback_suspended = 0; // Коллбэки разрешены изначально |
||||
q->resume_timeout_id = NULL; |
||||
q->ua = ua; |
||||
q->waiters = NULL; |
||||
q->pool=pool; |
||||
q->version = 0; |
||||
|
||||
return q; |
||||
} |
||||
|
||||
void queue_free(struct ll_queue* q) { |
||||
if (!q) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d", |
||||
q, q->head, q->tail, q->count); |
||||
|
||||
// Освободить все элементы (уменьшить счетчик ссылок) |
||||
struct ll_entry* entry = q->head; |
||||
int entry_count = 0; |
||||
while (entry) { |
||||
struct ll_entry* next = entry->next; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d", |
||||
entry, entry_count++, entry->ref_count); |
||||
entry->ref_count--; // Дополнительно уменьшить за присутствие в очереди |
||||
queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0 |
||||
entry = next; |
||||
} |
||||
|
||||
// Освободить все ожидающие коллбэки |
||||
struct queue_waiter* waiter = q->waiters; |
||||
int waiter_count = 0; |
||||
while (waiter) { |
||||
struct queue_waiter* next = waiter->next; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++); |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, waiter); |
||||
} else { |
||||
free(waiter); |
||||
} |
||||
waiter = next; |
||||
} |
||||
|
||||
// Отменить отложенное возобновление если запланировано |
||||
if (q->resume_timeout_id) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id); |
||||
uasync_cancel_timeout(q->ua, q->resume_timeout_id); |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q); |
||||
free(q); |
||||
} |
||||
|
||||
// ==================== Конфигурация очереди ==================== |
||||
|
||||
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) { |
||||
if (!q) return; |
||||
q->callback = cbk_fn; |
||||
q->callback_arg = arg; |
||||
} |
||||
|
||||
static void queue_resume_timeout_cb(void* arg) { |
||||
struct ll_queue* q = (struct ll_queue*)arg; |
||||
if (!q || !q->callback) return; |
||||
|
||||
// Очистить ID таймаута (таймаут сработал) |
||||
q->resume_timeout_id = NULL; |
||||
|
||||
// Разрешить коллбэки |
||||
q->callback_suspended = 0; |
||||
|
||||
// Если в очереди есть элементы, вызвать коллбэк с первым элементом |
||||
// Обработчик должен извлечь этот элемент вызовом queue_entry_get() |
||||
if (q->head) { |
||||
q->callback(q, q->head, q->callback_arg); |
||||
} |
||||
} |
||||
|
||||
void queue_resume_callback(struct ll_queue* q) { |
||||
if (!q || !q->callback) return; |
||||
|
||||
// Если уже есть отложенное возобновление, ничего не делать |
||||
if (q->resume_timeout_id) { |
||||
return; |
||||
} |
||||
|
||||
// Запланировать отложенное возобновление через uasync |
||||
q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb); |
||||
} |
||||
|
||||
void queue_set_size_limit(struct ll_queue* q, int lim) { |
||||
if (!q) return; |
||||
q->size_limit = lim; |
||||
} |
||||
|
||||
// ==================== Управление элементами ==================== |
||||
|
||||
struct ll_entry* queue_entry_new(size_t data_size) { |
||||
// Выделить память под структуру + область данных |
||||
struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size); |
||||
if (!entry) return NULL; |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; // Инициализировать prev для двусвязного списка |
||||
entry->size = data_size; |
||||
entry->ref_count = 1; // Начальный счетчик ссылок |
||||
// Область данных оставить неинициализированной для производительности |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d", |
||||
entry, data_size, entry->ref_count); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
void queue_entry_free(struct ll_entry* entry) { |
||||
if (!entry) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d", |
||||
entry, entry->ref_count); |
||||
|
||||
if (entry->ref_count <= 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d", |
||||
entry, entry->ref_count); |
||||
return; // Предотвратить double-free |
||||
} |
||||
|
||||
entry->ref_count--; |
||||
if (entry->ref_count == 0) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry); |
||||
free(entry); |
||||
} else { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p", |
||||
entry->ref_count, entry); |
||||
} |
||||
} |
||||
|
||||
// ==================== Операции с очередью ==================== |
||||
|
||||
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) return -1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d", |
||||
entry, entry->size, q->count); |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Увеличить счетчик ссылок при добавлении в очередь |
||||
entry->ref_count++; |
||||
|
||||
// Добавить в хвост (FIFO) - двусвязный список |
||||
entry->next = NULL; |
||||
entry->prev = q->tail; |
||||
|
||||
if (q->tail) { |
||||
q->tail->next = entry; |
||||
} else { |
||||
q->head = entry; // Очередь была пустой |
||||
} |
||||
q->tail = entry; |
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", |
||||
entry, q->count, q->total_bytes, entry->ref_count); |
||||
|
||||
// Инвалидировать позиции |
||||
q->version++; |
||||
|
||||
// Если коллбэки разрешены - вызвать коллбэк |
||||
// Это запускает автоматическую обработку очереди |
||||
if (!q->callback_suspended && q->callback) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry); |
||||
|
||||
// Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию |
||||
q->callback_suspended = 1; |
||||
q->callback(q, q->head, q->callback_arg); |
||||
// Не восстанавливать здесь - восстановление происходит через queue_resume_callback |
||||
} |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) return -1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d", |
||||
entry, entry->size, q->count); |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Увеличить счетчик ссылок при добавлении в очередь |
||||
entry->ref_count++; |
||||
|
||||
// Добавить в голову (LIFO, высокий приоритет) - двусвязный список |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
|
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", |
||||
entry, q->count, q->total_bytes, entry->ref_count); |
||||
|
||||
// Инвалидировать позиции |
||||
q->version++; |
||||
|
||||
// Если коллбэки разрешены - вызвать коллбэк |
||||
if (!q->callback_suspended && q->callback) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry); |
||||
q->callback_suspended = 1; |
||||
q->callback(q, q->head, q->callback_arg); |
||||
} |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
struct ll_entry* queue_entry_get(struct ll_queue* q) { |
||||
if (!q || !q->head) return NULL; |
||||
|
||||
struct ll_entry* entry = q->head; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d", |
||||
entry, entry->size, entry->ref_count); |
||||
|
||||
// Удалить из двусвязного списка |
||||
q->head = entry->next; |
||||
if (q->head) { |
||||
q->head->prev = NULL; |
||||
} else { |
||||
q->tail = NULL; // Очередь стала пустой |
||||
} |
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu", |
||||
entry, q->count, q->total_bytes); |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; // Полностью отсоединить от очереди |
||||
|
||||
// Уменьшить счетчик ссылок при извлечении из очереди |
||||
// entry->ref_count был увеличен при добавлении в очередь |
||||
// теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать |
||||
entry->ref_count--; |
||||
|
||||
// Инвалидировать позиции |
||||
q->version++; |
||||
|
||||
// При извлечении элемента приостанавливаем коллбэки |
||||
// Это предотвращает рекурсию если во время обработки добавляются новые элементы |
||||
q->callback_suspended = 1; |
||||
|
||||
// Проверить ожидающие коллбэки при извлечении элемента |
||||
// Это важно для waiters, которые ожидают уменьшения очереди |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count); |
||||
check_waiters(q); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
int queue_entry_count(struct ll_queue* q) { |
||||
if (!q) return 0; |
||||
return q->count; |
||||
} |
||||
|
||||
// ==================== Асинхронное ожидание ==================== |
||||
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, |
||||
queue_threshold_callback_fn callback, void* arg) { |
||||
if (!q || !callback) return NULL; |
||||
|
||||
// Создать новый waiter |
||||
struct queue_waiter* waiter; |
||||
if (q->pool) { |
||||
waiter = (struct queue_waiter*)memory_pool_alloc(q->pool); |
||||
} else { |
||||
waiter = malloc(sizeof(struct queue_waiter)); |
||||
} |
||||
if (!waiter) return NULL; |
||||
|
||||
waiter->max_packets = max_packets; |
||||
waiter->max_bytes = max_bytes; |
||||
waiter->callback = callback; |
||||
waiter->callback_arg = arg; |
||||
waiter->next = NULL; |
||||
|
||||
// Проверить условие немедленно |
||||
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) { |
||||
// Условие уже выполнено - вызвать коллбэк и освободить waiter |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback", |
||||
q->count, max_packets, q->total_bytes, max_bytes); |
||||
callback(q, arg); |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, waiter); |
||||
} else { |
||||
free(waiter); |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu", |
||||
q->count, q->total_bytes, max_packets, max_bytes); |
||||
|
||||
// Добавить в список ожидающих |
||||
waiter->next = q->waiters; |
||||
q->waiters = waiter; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully"); |
||||
return waiter; |
||||
} |
||||
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) { |
||||
if (!q || !waiter) return; |
||||
|
||||
// Найти и удалить waiter из списка |
||||
struct queue_waiter** pprev = &q->waiters; |
||||
struct queue_waiter* w = q->waiters; |
||||
|
||||
while (w) { |
||||
if (w == waiter) { |
||||
*pprev = w->next; |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, w); |
||||
} else { |
||||
free(w); |
||||
} |
||||
return; |
||||
} |
||||
pprev = &w->next; |
||||
w = w->next; |
||||
} |
||||
} |
||||
|
||||
// ==================== Статистика и метрики ==================== |
||||
|
||||
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) { |
||||
if (!q || !q->pool) { |
||||
if (waiter_allocations) *waiter_allocations = 0; |
||||
if (waiter_reuse) *waiter_reuse = 0; |
||||
return; |
||||
} |
||||
|
||||
memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse); |
||||
} |
||||
|
||||
// ==================== Управление позициями ==================== |
||||
|
||||
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) { |
||||
if (!queue) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL"); |
||||
return NULL; |
||||
} |
||||
|
||||
struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos)); |
||||
if (!pos) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position"); |
||||
return NULL; |
||||
} |
||||
|
||||
pos->queue = queue; |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; // Перед head |
||||
pos->valid = 1; |
||||
pos->version = queue->version; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue); |
||||
return pos; |
||||
} |
||||
|
||||
void queue_pos_free(struct ll_queue_pos* pos) { |
||||
if (!pos) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos); |
||||
free(pos); |
||||
} |
||||
|
||||
void queue_pos_reset(struct ll_queue_pos* pos) { |
||||
if (!pos) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL"); |
||||
return; |
||||
} |
||||
|
||||
// Обновить версию позиции при необходимости (вместо инвалидации) |
||||
if (pos->queue->version != pos->version) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: updating position version from %lu to %lu", |
||||
(unsigned long)pos->version, (unsigned long)pos->queue->version); |
||||
pos->version = pos->queue->version; |
||||
} |
||||
|
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
pos->valid = 1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset"); |
||||
} |
||||
|
||||
int queue_pos_next(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: invalid position"); |
||||
return 0; |
||||
} |
||||
|
||||
if (pos->queue->version != pos->version) { |
||||
pos->valid = 0; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: position invalidated due to queue version change"); |
||||
return 0; |
||||
} |
||||
|
||||
if (!pos->current) { |
||||
// Перед head - переходим к head |
||||
if (!pos->queue->head) { |
||||
// Очередь пуста - некуда переходить |
||||
return 0; |
||||
} |
||||
pos->current = pos->queue->head; |
||||
pos->prev = NULL; |
||||
pos->index = 0; |
||||
} else if (pos->current->next) { |
||||
// Есть следующий элемент |
||||
pos->prev = pos->current; |
||||
pos->current = pos->current->next; |
||||
pos->index++; |
||||
} else { |
||||
// Достигнут конец очереди |
||||
return 0; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_prev(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: invalid position"); |
||||
return 0; |
||||
} |
||||
|
||||
if (pos->queue->version != pos->version) { |
||||
pos->valid = 0; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: position invalidated due to queue version change"); |
||||
return 0; |
||||
} |
||||
|
||||
if (!pos->current) { |
||||
// Уже перед head - дальше некуда |
||||
return 0; |
||||
} else if (pos->current->prev) { |
||||
// Есть предыдущий элемент |
||||
pos->current = pos->current->prev; |
||||
pos->prev = pos->current->prev; |
||||
pos->index--; |
||||
} else { |
||||
// Достигнуто начало очереди |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_seek(struct ll_queue_pos* pos, int target_index) { |
||||
if (!pos || !pos->valid) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid position"); |
||||
return 0; |
||||
} |
||||
|
||||
// Обновить версию позиции при необходимости (вместо инвалидации) |
||||
if (pos->queue->version != pos->version) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: updating position version from %lu to %lu", |
||||
(unsigned long)pos->version, (unsigned long)pos->queue->version); |
||||
pos->version = pos->queue->version; |
||||
// Сбросить позицию, так как структура очереди могла измениться |
||||
queue_pos_reset(pos); |
||||
} |
||||
|
||||
if (target_index < -1 || target_index >= pos->queue->count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)", |
||||
target_index, pos->queue->count); |
||||
return 0; |
||||
} |
||||
|
||||
// Простая реализация: начинаем с начала и идем вперед |
||||
// Можно оптимизировать позже, если потребуется |
||||
queue_pos_reset(pos); |
||||
|
||||
for (int i = 0; i <= target_index; i++) { |
||||
if (!queue_pos_next(pos)) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index); |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) { |
||||
if (!pos || !pos->valid || !entry) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters"); |
||||
return -1; |
||||
} |
||||
|
||||
if (pos->queue->version != pos->version) { |
||||
pos->valid = 0; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: position invalidated due to queue version change"); |
||||
return -1; |
||||
} |
||||
|
||||
struct ll_queue* q = pos->queue; |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
entry->ref_count++; |
||||
|
||||
if (mode == 0) { |
||||
// Вставить перед current |
||||
if (!pos->current) { |
||||
// Вставить в head |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
} else { |
||||
// Вставить перед current |
||||
entry->next = pos->current; |
||||
entry->prev = pos->current->prev; |
||||
|
||||
if (pos->current->prev) { |
||||
pos->current->prev->next = entry; |
||||
} else { |
||||
q->head = entry; // Вставляем в начало |
||||
} |
||||
pos->current->prev = entry; |
||||
} |
||||
} else { |
||||
// Вставить после current |
||||
if (!pos->current) { |
||||
// Вставить в head (current = NULL, значит перед head) |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
} else { |
||||
// Вставить после current |
||||
entry->next = pos->current->next; |
||||
entry->prev = pos->current; |
||||
|
||||
if (pos->current->next) { |
||||
pos->current->next->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Вставляем в конец |
||||
} |
||||
pos->current->next = entry; |
||||
} |
||||
} |
||||
|
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
// Обновить версию очереди и позиции (вместо полной инвалидации) |
||||
q->version++; |
||||
|
||||
// Обновить текущую позицию, если она активна |
||||
if (pos->current) { |
||||
pos->version = q->version; |
||||
pos->valid = 1; // Сделать позицию снова валидной |
||||
// Корректировка индекса в зависимости от режима вставки |
||||
if (mode == 0) { |
||||
// Вставка перед текущим - индекс остается тем же |
||||
// pos->index не меняется |
||||
} else { |
||||
// Вставка после текущего - текущий элемент смещается |
||||
pos->index++; |
||||
} |
||||
} |
||||
pos->valid = 0; // Текущая позиция теперь invalid |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d", |
||||
entry, mode, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid || !pos->current) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element"); |
||||
return NULL; |
||||
} |
||||
|
||||
if (pos->queue->version != pos->version) { |
||||
pos->valid = 0; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: position invalidated due to queue version change"); |
||||
return NULL; |
||||
} |
||||
|
||||
struct ll_queue* q = pos->queue; |
||||
struct ll_entry* entry = pos->current; |
||||
|
||||
// Удалить из двусвязного списка |
||||
if (entry->prev) { |
||||
entry->prev->next = entry->next; |
||||
} else { |
||||
q->head = entry->next; // Удаляем head |
||||
} |
||||
|
||||
if (entry->next) { |
||||
entry->next->prev = entry->prev; |
||||
} else { |
||||
q->tail = entry->prev; // Удаляем tail |
||||
} |
||||
|
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
// Обновить позицию: перейти к следующему элементу |
||||
pos->current = entry->next; |
||||
pos->prev = entry->prev; |
||||
if (pos->current) { |
||||
// Остались элементы после удаленного |
||||
pos->index = pos->index; // Индекс остается тем же |
||||
} else { |
||||
// Удалили последний элемент |
||||
pos->index = q->count; // Указывает за конец |
||||
if (pos->index == 0) { |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
} |
||||
} |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; |
||||
entry->ref_count--; // Уменьшить счетчик ссылок |
||||
|
||||
// Обновить версию очереди и позицию (вместо полной инвалидации) |
||||
q->version++; |
||||
if (pos->current) { |
||||
pos->version = q->version; |
||||
pos->valid = 1; // Сделать позицию снова валидной |
||||
// pos->index уже обновлен выше |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d", |
||||
entry, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters"); |
||||
return -1; |
||||
} |
||||
|
||||
// Проверить, что элемент действительно в этой очереди |
||||
struct ll_entry* curr = q->head; |
||||
while (curr) { |
||||
if (curr == entry) { |
||||
break; |
||||
} |
||||
curr = curr->next; |
||||
} |
||||
|
||||
if (!curr) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Удалить из двусвязного списка |
||||
if (entry->prev) { |
||||
entry->prev->next = entry->next; |
||||
} else { |
||||
q->head = entry->next; // Удаляем head |
||||
} |
||||
|
||||
if (entry->next) { |
||||
entry->next->prev = entry->prev; |
||||
} else { |
||||
q->tail = entry->prev; // Удаляем tail |
||||
} |
||||
|
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; |
||||
entry->ref_count--; |
||||
|
||||
// Инвалидировать позиции |
||||
q->version++; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d", |
||||
entry, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
@ -0,0 +1,847 @@
|
||||
// ll_queue.c |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <assert.h> |
||||
#include "ll_queue.h" |
||||
#include "u_async.h" |
||||
#include "debug_config.h" |
||||
|
||||
// Предварительное объявление для отложенного возобновления |
||||
static void queue_resume_timeout_cb(void* arg); |
||||
|
||||
// Проверить и запустить ожидающие коллбэки |
||||
static void check_waiters(struct ll_queue* q) { |
||||
if (!q || !q->waiters) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", |
||||
q->count, q->total_bytes); |
||||
|
||||
// Сначала собрать список коллбэков для вызова, чтобы избежать проблем с модификацией списка во время итерации |
||||
struct queue_waiter* to_call = NULL; |
||||
struct queue_waiter* to_call_tail = NULL; |
||||
|
||||
struct queue_waiter** pprev = &q->waiters; |
||||
struct queue_waiter* waiter = q->waiters; |
||||
|
||||
while (waiter) { |
||||
struct queue_waiter* next = waiter->next; |
||||
|
||||
// Проверить условие: не больше max_packets и не больше max_bytes |
||||
// max_bytes = 0 означает "не проверять байты" |
||||
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)", |
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, |
||||
waiter->max_bytes == 0 ? "disabled" : "enabled"); |
||||
// Удалить waiter из списка |
||||
*pprev = next; |
||||
// Добавить в список для вызова |
||||
waiter->next = NULL; |
||||
if (to_call_tail) { |
||||
to_call_tail->next = waiter; |
||||
} else { |
||||
to_call = waiter; |
||||
} |
||||
to_call_tail = waiter; |
||||
} else { |
||||
// Условие не выполнено - оставить в списке |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)", |
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, |
||||
waiter->max_bytes == 0 ? "disabled" : "enabled"); |
||||
pprev = &waiter->next; |
||||
} |
||||
waiter = next; |
||||
} |
||||
|
||||
// Теперь вызвать коллбэки |
||||
waiter = to_call; |
||||
while (waiter) { |
||||
struct queue_waiter* next = waiter->next; |
||||
waiter->callback(q, waiter->callback_arg); |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, waiter); |
||||
} else { |
||||
free(waiter); |
||||
} |
||||
waiter = next; |
||||
} |
||||
} |
||||
|
||||
// ==================== Унифицированная система версий позиций ==================== |
||||
|
||||
// Унифицированная проверка версии позиции с автоматическим пересканированием |
||||
static int queue_pos_ensure_valid(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->queue) return 0; |
||||
|
||||
// Если позиция уже валидна и версия совпадает, всё хорошо |
||||
if (pos->valid && pos->version == pos->queue->version) { |
||||
return 1; |
||||
} |
||||
|
||||
// Несовпадение версии или позиция невалидна - нужно пересканировать |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "Position version %lu != queue version %lu, rescanning...", |
||||
(unsigned long)pos->version, (unsigned long)pos->queue->version); |
||||
|
||||
// Сохранить целевой индекс (где мы хотим быть) |
||||
int target_index = pos->index; |
||||
|
||||
// Обновить версию позиции до текущей |
||||
pos->version = pos->queue->version; |
||||
|
||||
// Обработать краевой случай: индекс больше не существует из-за изменения размера очереди |
||||
if (target_index >= pos->queue->count) { |
||||
// Позиция выходит за пределы очереди - инвалидируем её |
||||
pos->valid = 0; |
||||
return 0; |
||||
} |
||||
|
||||
// Простой подход: сбросить и перейти к тому же индексу |
||||
// Это найдёт элемент, который теперь находится в целевом индексе |
||||
queue_pos_reset(pos); |
||||
return queue_pos_seek(pos, target_index); |
||||
} |
||||
|
||||
// ==================== Управление очередью ==================== |
||||
|
||||
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) { |
||||
if (!ua) return NULL; |
||||
|
||||
struct ll_queue* q = calloc(1, sizeof(struct ll_queue)); |
||||
if (!q) return NULL; |
||||
|
||||
q->head = NULL; |
||||
q->tail = NULL; |
||||
q->count = 0; |
||||
q->total_bytes = 0; |
||||
q->size_limit = -1; // По умолчанию без ограничения |
||||
q->callback = NULL; |
||||
q->callback_arg = NULL; |
||||
q->callback_suspended = 0; // Коллбэки разрешены изначально |
||||
q->resume_timeout_id = NULL; |
||||
q->ua = ua; |
||||
q->waiters = NULL; |
||||
q->pool=pool; |
||||
q->version = 0; |
||||
|
||||
return q; |
||||
} |
||||
|
||||
void queue_free(struct ll_queue* q) { |
||||
if (!q) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d", |
||||
q, q->head, q->tail, q->count); |
||||
|
||||
// Освободить все элементы (уменьшить счетчик ссылок) |
||||
struct ll_entry* entry = q->head; |
||||
int entry_count = 0; |
||||
while (entry) { |
||||
struct ll_entry* next = entry->next; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d", |
||||
entry, entry_count++, entry->ref_count); |
||||
entry->ref_count--; // Дополнительно уменьшить за присутствие в очереди |
||||
queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0 |
||||
entry = next; |
||||
} |
||||
|
||||
// Освободить все ожидающие коллбэки |
||||
struct queue_waiter* waiter = q->waiters; |
||||
int waiter_count = 0; |
||||
while (waiter) { |
||||
struct queue_waiter* next = waiter->next; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++); |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, waiter); |
||||
} else { |
||||
free(waiter); |
||||
} |
||||
waiter = next; |
||||
} |
||||
|
||||
// Отменить отложенное возобновление если запланировано |
||||
if (q->resume_timeout_id) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id); |
||||
uasync_cancel_timeout(q->ua, q->resume_timeout_id); |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q); |
||||
free(q); |
||||
} |
||||
|
||||
// ==================== Конфигурация очереди ==================== |
||||
|
||||
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) { |
||||
if (!q) return; |
||||
q->callback = cbk_fn; |
||||
q->callback_arg = arg; |
||||
} |
||||
|
||||
static void queue_resume_timeout_cb(void* arg) { |
||||
struct ll_queue* q = (struct ll_queue*)arg; |
||||
if (!q || !q->callback) return; |
||||
|
||||
// Очистить ID таймаута (таймаут сработал) |
||||
q->resume_timeout_id = NULL; |
||||
|
||||
// Разрешить коллбэки |
||||
q->callback_suspended = 0; |
||||
|
||||
// Если в очереди есть элементы, вызвать коллбэк с первым элементом |
||||
// Обработчик должен извлечь этот элемент вызовом queue_entry_get() |
||||
if (q->head) { |
||||
q->callback(q, q->head, q->callback_arg); |
||||
} |
||||
} |
||||
|
||||
void queue_resume_callback(struct ll_queue* q) { |
||||
if (!q || !q->callback) return; |
||||
|
||||
// Если уже есть отложенное возобновление, ничего не делать |
||||
if (q->resume_timeout_id) { |
||||
return; |
||||
} |
||||
|
||||
// Запланировать отложенное возобновление через uasync |
||||
q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb); |
||||
} |
||||
|
||||
void queue_set_size_limit(struct ll_queue* q, int lim) { |
||||
if (!q) return; |
||||
q->size_limit = lim; |
||||
} |
||||
|
||||
// ==================== Управление элементами ==================== |
||||
|
||||
struct ll_entry* queue_entry_new(size_t data_size) { |
||||
// Выделить память под структуру + область данных |
||||
struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size); |
||||
if (!entry) return NULL; |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; // Инициализировать prev для двусвязного списка |
||||
entry->size = data_size; |
||||
entry->ref_count = 1; // Начальный счетчик ссылок |
||||
// Область данных оставить неинициализированной для производительности |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d", |
||||
entry, data_size, entry->ref_count); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
void queue_entry_free(struct ll_entry* entry) { |
||||
if (!entry) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d", |
||||
entry, entry->ref_count); |
||||
|
||||
if (entry->ref_count <= 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d", |
||||
entry, entry->ref_count); |
||||
return; // Предотвратить double-free |
||||
} |
||||
|
||||
entry->ref_count--; |
||||
if (entry->ref_count == 0) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry); |
||||
free(entry); |
||||
} else { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p", |
||||
entry->ref_count, entry); |
||||
} |
||||
} |
||||
|
||||
// ==================== Операции с очередью ==================== |
||||
|
||||
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) return -1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d", |
||||
entry, entry->size, q->count); |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Увеличить счетчик ссылок при добавлении в очередь |
||||
entry->ref_count++; |
||||
|
||||
// Добавить в хвост (FIFO) - двусвязный список |
||||
entry->next = NULL; |
||||
entry->prev = q->tail; |
||||
|
||||
if (q->tail) { |
||||
q->tail->next = entry; |
||||
} else { |
||||
q->head = entry; // Очередь была пустой |
||||
} |
||||
q->tail = entry; |
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", |
||||
entry, q->count, q->total_bytes, entry->ref_count); |
||||
|
||||
// Инвалидировать позиции |
||||
q->version++; |
||||
|
||||
// Если коллбэки разрешены - вызвать коллбэк |
||||
// Это запускает автоматическую обработку очереди |
||||
if (!q->callback_suspended && q->callback) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry); |
||||
|
||||
// Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию |
||||
q->callback_suspended = 1; |
||||
q->callback(q, q->head, q->callback_arg); |
||||
// Не восстанавливать здесь - восстановление происходит через queue_resume_callback |
||||
} |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) return -1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d", |
||||
entry, entry->size, q->count); |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Увеличить счетчик ссылок при добавлении в очередь |
||||
entry->ref_count++; |
||||
|
||||
// Добавить в голову (LIFO, высокий приоритет) - двусвязный список |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
|
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", |
||||
entry, q->count, q->total_bytes, entry->ref_count); |
||||
|
||||
// Инвалидировать позиции |
||||
q->version++; |
||||
|
||||
// Если коллбэки разрешены - вызвать коллбэк |
||||
if (!q->callback_suspended && q->callback) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry); |
||||
q->callback_suspended = 1; |
||||
q->callback(q, q->head, q->callback_arg); |
||||
} |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
struct ll_entry* queue_entry_get(struct ll_queue* q) { |
||||
if (!q || !q->head) return NULL; |
||||
|
||||
struct ll_entry* entry = q->head; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d", |
||||
entry, entry->size, entry->ref_count); |
||||
|
||||
// Удалить из двусвязного списка |
||||
q->head = entry->next; |
||||
if (q->head) { |
||||
q->head->prev = NULL; |
||||
} else { |
||||
q->tail = NULL; // Очередь стала пустой |
||||
} |
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu", |
||||
entry, q->count, q->total_bytes); |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; // Полностью отсоединить от очереди |
||||
|
||||
// Уменьшить счетчик ссылок при извлечении из очереди |
||||
// entry->ref_count был увеличен при добавлении в очередь |
||||
// теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать |
||||
entry->ref_count--; |
||||
|
||||
// Инвалидировать позиции |
||||
q->version++; |
||||
|
||||
// При извлечении элемента приостанавливаем коллбэки |
||||
// Это предотвращает рекурсию если во время обработки добавляются новые элементы |
||||
q->callback_suspended = 1; |
||||
|
||||
// Проверить ожидающие коллбэки при извлечении элемента |
||||
// Это важно для waiters, которые ожидают уменьшения очереди |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count); |
||||
check_waiters(q); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
int queue_entry_count(struct ll_queue* q) { |
||||
if (!q) return 0; |
||||
return q->count; |
||||
} |
||||
|
||||
// ==================== Асинхронное ожидание ==================== |
||||
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, |
||||
queue_threshold_callback_fn callback, void* arg) { |
||||
if (!q || !callback) return NULL; |
||||
|
||||
// Создать новый waiter |
||||
struct queue_waiter* waiter; |
||||
if (q->pool) { |
||||
waiter = (struct queue_waiter*)memory_pool_alloc(q->pool); |
||||
} else { |
||||
waiter = malloc(sizeof(struct queue_waiter)); |
||||
} |
||||
if (!waiter) return NULL; |
||||
|
||||
waiter->max_packets = max_packets; |
||||
waiter->max_bytes = max_bytes; |
||||
waiter->callback = callback; |
||||
waiter->callback_arg = arg; |
||||
waiter->next = NULL; |
||||
|
||||
// Проверить условие немедленно |
||||
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) { |
||||
// Условие уже выполнено - вызвать коллбэк и освободить waiter |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback", |
||||
q->count, max_packets, q->total_bytes, max_bytes); |
||||
callback(q, arg); |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, waiter); |
||||
} else { |
||||
free(waiter); |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu", |
||||
q->count, q->total_bytes, max_packets, max_bytes); |
||||
|
||||
// Добавить в список ожидающих |
||||
waiter->next = q->waiters; |
||||
q->waiters = waiter; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully"); |
||||
return waiter; |
||||
} |
||||
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) { |
||||
if (!q || !waiter) return; |
||||
|
||||
// Найти и удалить waiter из списка |
||||
struct queue_waiter** pprev = &q->waiters; |
||||
struct queue_waiter* w = q->waiters; |
||||
|
||||
while (w) { |
||||
if (w == waiter) { |
||||
*pprev = w->next; |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, w); |
||||
} else { |
||||
free(w); |
||||
} |
||||
return; |
||||
} |
||||
pprev = &w->next; |
||||
w = w->next; |
||||
} |
||||
} |
||||
|
||||
// ==================== Статистика и метрики ==================== |
||||
|
||||
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) { |
||||
if (!q || !q->pool) { |
||||
if (waiter_allocations) *waiter_allocations = 0; |
||||
if (waiter_reuse) *waiter_reuse = 0; |
||||
return; |
||||
} |
||||
|
||||
memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse); |
||||
} |
||||
|
||||
// ==================== Управление позициями ==================== |
||||
|
||||
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) { |
||||
if (!queue) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL"); |
||||
return NULL; |
||||
} |
||||
|
||||
struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos)); |
||||
if (!pos) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position"); |
||||
return NULL; |
||||
} |
||||
|
||||
pos->queue = queue; |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; // Перед head |
||||
pos->valid = 1; |
||||
pos->version = queue->version; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue); |
||||
return pos; |
||||
} |
||||
|
||||
void queue_pos_free(struct ll_queue_pos* pos) { |
||||
if (!pos) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos); |
||||
free(pos); |
||||
} |
||||
|
||||
void queue_pos_reset(struct ll_queue_pos* pos) { |
||||
if (!pos) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL"); |
||||
return; |
||||
} |
||||
|
||||
// Обновить версию позиции при необходимости (вместо инвалидации) |
||||
if (pos->queue->version != pos->version) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: updating position version from %lu to %lu", |
||||
(unsigned long)pos->version, (unsigned long)pos->queue->version); |
||||
pos->version = pos->queue->version; |
||||
} |
||||
|
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
pos->valid = 1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset"); |
||||
} |
||||
|
||||
int queue_pos_next(struct ll_queue_pos* pos) { |
||||
if (!pos) return 0; |
||||
|
||||
// Убедиться, что позиция валидна (автоматическое обновление при необходимости) |
||||
if (!queue_pos_ensure_valid(pos)) { |
||||
return 0; |
||||
} |
||||
|
||||
if (!pos->current) { |
||||
// Перед head - переходим к head |
||||
if (!pos->queue->head) { |
||||
// Очередь пуста - некуда переходить |
||||
return 0; |
||||
} |
||||
pos->current = pos->queue->head; |
||||
pos->prev = NULL; |
||||
pos->index = 0; |
||||
} else if (pos->current->next) { |
||||
// Есть следующий элемент |
||||
pos->prev = pos->current; |
||||
pos->current = pos->current->next; |
||||
pos->index++; |
||||
} else { |
||||
// Достигнут конец очереди |
||||
return 0; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_prev(struct ll_queue_pos* pos) { |
||||
if (!pos) return 0; |
||||
|
||||
// Убедиться, что позиция валидна (автоматическое обновление при необходимости) |
||||
if (!queue_pos_ensure_valid(pos)) { |
||||
return 0; |
||||
} |
||||
|
||||
if (!pos->current) { |
||||
// Уже перед head - дальше некуда |
||||
return 0; |
||||
} else if (pos->current->prev) { |
||||
// Есть предыдущий элемент |
||||
pos->current = pos->current->prev; |
||||
pos->prev = pos->current->prev; |
||||
pos->index--; |
||||
} else { |
||||
// Достигнуто начало очереди |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_seek(struct ll_queue_pos* pos, int target_index) { |
||||
if (!pos) return 0; |
||||
|
||||
// Убедиться, что позиция валидна (автоматическое обновление при необходимости) |
||||
if (!queue_pos_ensure_valid(pos)) { |
||||
return 0; |
||||
} |
||||
|
||||
if (target_index < -1 || target_index >= pos->queue->count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)", |
||||
target_index, pos->queue->count); |
||||
return 0; |
||||
} |
||||
|
||||
// Простая реализация: начинаем с начала и идем вперед |
||||
// Можно оптимизировать позже, если потребуется |
||||
queue_pos_reset(pos); |
||||
|
||||
for (int i = 0; i <= target_index; i++) { |
||||
if (!queue_pos_next(pos)) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index); |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) { |
||||
if (!pos || !pos->valid || !entry) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters"); |
||||
return -1; |
||||
} |
||||
|
||||
if (pos->queue->version != pos->version) { |
||||
pos->valid = 0; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: position invalidated due to queue version change"); |
||||
return -1; |
||||
} |
||||
|
||||
struct ll_queue* q = pos->queue; |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
entry->ref_count++; |
||||
|
||||
if (mode == 0) { |
||||
// Вставить перед current |
||||
if (!pos->current) { |
||||
// Вставить в head |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
} else { |
||||
// Вставить перед current |
||||
entry->next = pos->current; |
||||
entry->prev = pos->current->prev; |
||||
|
||||
if (pos->current->prev) { |
||||
pos->current->prev->next = entry; |
||||
} else { |
||||
q->head = entry; // Вставляем в начало |
||||
} |
||||
pos->current->prev = entry; |
||||
} |
||||
} else { |
||||
// Вставить после current |
||||
if (!pos->current) { |
||||
// Вставить в head (current = NULL, значит перед head) |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
} else { |
||||
// Вставить после current |
||||
entry->next = pos->current->next; |
||||
entry->prev = pos->current; |
||||
|
||||
if (pos->current->next) { |
||||
pos->current->next->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Вставляем в конец |
||||
} |
||||
pos->current->next = entry; |
||||
} |
||||
} |
||||
|
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
// Обновить версию очереди и позиции (вместо полной инвалидации) |
||||
q->version++; |
||||
|
||||
// Обновить текущую позицию, если она активна |
||||
if (pos->current) { |
||||
pos->version = q->version; |
||||
pos->valid = 1; // Сделать позицию снова валидной |
||||
// Корректировка индекса в зависимости от режима вставки |
||||
if (mode == 0) { |
||||
// Вставка перед текущим - индекс остается тем же |
||||
// pos->index не меняется |
||||
} else { |
||||
// Вставка после текущего - текущий элемент смещается |
||||
pos->index++; |
||||
} |
||||
} |
||||
// Позиция остается валидной - убрали строку pos->valid = 0; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d", |
||||
entry, mode, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid || !pos->current) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element"); |
||||
return NULL; |
||||
} |
||||
|
||||
// Убедиться, что позиция валидна (автоматическое обновление при необходимости) |
||||
if (!queue_pos_ensure_valid(pos)) { |
||||
return NULL; |
||||
} |
||||
|
||||
struct ll_queue* q = pos->queue; |
||||
struct ll_entry* entry = pos->current; |
||||
|
||||
// Удалить из двусвязного списка |
||||
if (entry->prev) { |
||||
entry->prev->next = entry->next; |
||||
} else { |
||||
q->head = entry->next; // Удаляем head |
||||
} |
||||
|
||||
if (entry->next) { |
||||
entry->next->prev = entry->prev; |
||||
} else { |
||||
q->tail = entry->prev; // Удаляем tail |
||||
} |
||||
|
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
// Обновить позицию: перейти к следующему элементу |
||||
pos->current = entry->next; |
||||
pos->prev = entry->prev; |
||||
if (pos->current) { |
||||
// Остались элементы после удаленного |
||||
pos->index = pos->index; // Индекс остается тем же |
||||
} else { |
||||
// Удалили последний элемент |
||||
pos->index = q->count; // Указывает за конец |
||||
if (pos->index == 0) { |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
} |
||||
} |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; |
||||
entry->ref_count--; // Уменьшить счетчик ссылок |
||||
|
||||
// Обновить версию очереди и позицию (вместо полной инвалидации) |
||||
q->version++; |
||||
if (pos->current) { |
||||
pos->version = q->version; |
||||
pos->valid = 1; // Сделать позицию снова валидной |
||||
// pos->index уже обновлен выше |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d", |
||||
entry, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters"); |
||||
return -1; |
||||
} |
||||
|
||||
// Проверить, что элемент действительно в этой очереди |
||||
struct ll_entry* curr = q->head; |
||||
while (curr) { |
||||
if (curr == entry) { |
||||
break; |
||||
} |
||||
curr = curr->next; |
||||
} |
||||
|
||||
if (!curr) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Удалить из двусвязного списка |
||||
if (entry->prev) { |
||||
entry->prev->next = entry->next; |
||||
} else { |
||||
q->head = entry->next; // Удаляем head |
||||
} |
||||
|
||||
if (entry->next) { |
||||
entry->next->prev = entry->prev; |
||||
} else { |
||||
q->tail = entry->prev; // Удаляем tail |
||||
} |
||||
|
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; |
||||
entry->ref_count--; |
||||
|
||||
// Инвалидировать позиции |
||||
q->version++; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d", |
||||
entry, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
@ -0,0 +1,744 @@
|
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <assert.h> |
||||
#include "ll_queue.h" |
||||
#include "u_async.h" |
||||
#include "debug_config.h" |
||||
|
||||
// Предварительное объявление для отложенного возобновления |
||||
static void queue_resume_timeout_cb(void* arg); |
||||
|
||||
// Вспомогательные функции для двусвязного списка |
||||
static void link_entry_after(struct ll_entry* existing, struct ll_entry* new_entry) { |
||||
if (!existing || !new_entry) return; |
||||
|
||||
new_entry->next = existing->next; |
||||
new_entry->prev = existing; |
||||
|
||||
if (existing->next) { |
||||
existing->next->prev = new_entry; |
||||
} |
||||
existing->next = new_entry; |
||||
} |
||||
|
||||
static void unlink_entry(struct ll_entry* entry) { |
||||
if (!entry) return; |
||||
|
||||
if (entry->prev) entry->prev->next = entry->next; |
||||
if (entry->next) entry->next->prev = entry->prev; |
||||
entry->next = NULL; |
||||
entry->prev = NULL; |
||||
} |
||||
|
||||
// Проверить и запустить ожидающие коллбэки |
||||
static void check_waiters(struct ll_queue* q) { |
||||
if (!q || !q->waiters) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", |
||||
q->count, q->total_bytes); |
||||
|
||||
struct queue_waiter** pprev = &q->waiters; |
||||
struct queue_waiter* waiter = q->waiters; |
||||
|
||||
while (waiter) { |
||||
struct queue_waiter* next = waiter->next; |
||||
|
||||
// Проверить условие: не больше max_packets и не больше max_bytes |
||||
// max_bytes = 0 означает "не проверять байты" |
||||
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)", |
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, |
||||
waiter->max_bytes == 0 ? "disabled" : "enabled"); |
||||
waiter->callback(q, waiter->callback_arg); |
||||
// Удалить waiter из списка |
||||
*pprev = next; |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, waiter); |
||||
} else { |
||||
free(waiter); |
||||
} |
||||
// pprev уже указывает на правильный следующий элемент |
||||
} else { |
||||
// Условие не выполнено - оставить в списке |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)", |
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, |
||||
waiter->max_bytes == 0 ? "disabled" : "enabled"); |
||||
pprev = &waiter->next; |
||||
} |
||||
waiter = next; |
||||
} |
||||
} |
||||
|
||||
// ==================== Управление очередью ==================== |
||||
|
||||
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) { |
||||
struct ll_queue* q = calloc(1, sizeof(struct ll_queue)); |
||||
if (!q) return NULL; |
||||
|
||||
q->head = NULL; |
||||
q->tail = NULL; |
||||
q->count = 0; |
||||
q->total_bytes = 0; |
||||
q->size_limit = -1; // По умолчанию без ограничения |
||||
q->callback = NULL; |
||||
q->callback_arg = NULL; |
||||
q->callback_suspended = 0; // Коллбэки разрешены изначально |
||||
q->resume_timeout_id = NULL; |
||||
q->ua = ua; |
||||
q->waiters = NULL; |
||||
q->pool=pool; |
||||
|
||||
return q; |
||||
} |
||||
|
||||
void queue_free(struct ll_queue* q) { |
||||
if (!q) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d", |
||||
q, q->head, q->tail, q->count); |
||||
|
||||
// Освободить все элементы (уменьшить счетчик ссылок) |
||||
struct ll_entry* entry = q->head; |
||||
int entry_count = 0; |
||||
while (entry) { |
||||
struct ll_entry* next = entry->next; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d", |
||||
entry, entry_count++, entry->ref_count); |
||||
queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0 |
||||
entry = next; |
||||
} |
||||
|
||||
// Освободить все ожидающие коллбэки |
||||
struct queue_waiter* waiter = q->waiters; |
||||
int waiter_count = 0; |
||||
while (waiter) { |
||||
struct queue_waiter* next = waiter->next; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++); |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, waiter); |
||||
} else { |
||||
free(waiter); |
||||
} |
||||
waiter = next; |
||||
} |
||||
|
||||
// Отменить отложенное возобновление если запланировано |
||||
if (q->resume_timeout_id) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id); |
||||
uasync_cancel_timeout(q->ua, q->resume_timeout_id); |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q); |
||||
free(q); |
||||
} |
||||
|
||||
// ==================== Конфигурация очереди ==================== |
||||
|
||||
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) { |
||||
if (!q) return; |
||||
q->callback = cbk_fn; |
||||
q->callback_arg = arg; |
||||
} |
||||
|
||||
static void queue_resume_timeout_cb(void* arg) { |
||||
struct ll_queue* q = (struct ll_queue*)arg; |
||||
if (!q || !q->callback) return; |
||||
|
||||
// Очистить ID таймаута (таймаут сработал) |
||||
q->resume_timeout_id = NULL; |
||||
|
||||
// Разрешить коллбэки |
||||
q->callback_suspended = 0; |
||||
|
||||
// Если в очереди есть элементы, вызвать коллбэк с первым элементом |
||||
// Обработчик должен извлечь этот элемент вызовом queue_entry_get() |
||||
if (q->head) { |
||||
q->callback(q, q->head, q->callback_arg); |
||||
} |
||||
} |
||||
|
||||
void queue_resume_callback(struct ll_queue* q) { |
||||
if (!q || !q->callback) return; |
||||
|
||||
// Если уже есть отложенное возобновление, ничего не делать |
||||
if (q->resume_timeout_id) { |
||||
return; |
||||
} |
||||
|
||||
// Запланировать отложенное возобновление через uasync |
||||
q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb); |
||||
} |
||||
|
||||
void queue_set_size_limit(struct ll_queue* q, int lim) { |
||||
if (!q) return; |
||||
q->size_limit = lim; |
||||
} |
||||
|
||||
// ==================== Управление элементами ==================== |
||||
|
||||
struct ll_entry* queue_entry_new(size_t data_size) { |
||||
// Выделить память под структуру + область данных |
||||
struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size); |
||||
if (!entry) return NULL; |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; // Инициализировать prev для двусвязного списка |
||||
entry->size = data_size; |
||||
entry->ref_count = 1; // Начальный счетчик ссылок |
||||
// Область данных оставить неинициализированной для производительности |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d", |
||||
entry, data_size, entry->ref_count); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
void queue_entry_free(struct ll_entry* entry) { |
||||
if (!entry) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d", |
||||
entry, entry->ref_count); |
||||
|
||||
if (entry->ref_count <= 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d", |
||||
entry, entry->ref_count); |
||||
return; // Предотвратить double-free |
||||
} |
||||
|
||||
entry->ref_count--; |
||||
if (entry->ref_count == 0) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry); |
||||
free(entry); |
||||
} else { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p", |
||||
entry->ref_count, entry); |
||||
} |
||||
} |
||||
|
||||
// ==================== Операции с очередью ==================== |
||||
|
||||
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) return -1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d", |
||||
entry, entry->size, q->count); |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Увеличить счетчик ссылок при добавлении в очередь |
||||
entry->ref_count++; |
||||
|
||||
// Добавить в хвост (FIFO) - двусвязный список |
||||
entry->next = NULL; |
||||
entry->prev = q->tail; |
||||
|
||||
if (q->tail) { |
||||
q->tail->next = entry; |
||||
} else { |
||||
q->head = entry; // Очередь была пустой |
||||
} |
||||
q->tail = entry; |
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", |
||||
entry, q->count, q->total_bytes, entry->ref_count); |
||||
|
||||
// Если коллбэки разрешены - вызвать коллбэк |
||||
// Это запускает автоматическую обработку очереди |
||||
if (!q->callback_suspended && q->callback) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry); |
||||
|
||||
// Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию |
||||
q->callback_suspended = 1; |
||||
q->callback(q, entry, q->callback_arg); |
||||
// Не восстанавливать здесь - восстановление происходит через queue_resume_callback |
||||
} |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) return -1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d", |
||||
entry, entry->size, q->count); |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Увеличить счетчик ссылок при добавлении в очередь |
||||
entry->ref_count++; |
||||
|
||||
// Добавить в голову (LIFO, высокий приоритет) - двусвязный список |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
|
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", |
||||
entry, q->count, q->total_bytes, entry->ref_count); |
||||
|
||||
// Если коллбэки разрешены - вызвать коллбэк |
||||
if (!q->callback_suspended && q->callback) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry); |
||||
q->callback(q, entry, q->callback_arg); |
||||
} |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
struct ll_entry* queue_entry_get(struct ll_queue* q) { |
||||
if (!q || !q->head) return NULL; |
||||
|
||||
struct ll_entry* entry = q->head; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d", |
||||
entry, entry->size, entry->ref_count); |
||||
|
||||
// Удалить из двусвязного списка |
||||
q->head = entry->next; |
||||
if (q->head) { |
||||
q->head->prev = NULL; |
||||
} else { |
||||
q->tail = NULL; // Очередь стала пустой |
||||
} |
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu", |
||||
entry, q->count, q->total_bytes); |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; // Полностью отсоединить от очереди |
||||
|
||||
// Уменьшить счетчик ссылок при извлечении из очереди |
||||
// entry->ref_count был увеличен при добавлении в очередь |
||||
// теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать |
||||
entry->ref_count--; |
||||
|
||||
// При извлечении элемента приостанавливаем коллбэки |
||||
// Это предотвращает рекурсию если во время обработки добавляются новые элементы |
||||
q->callback_suspended = 1; |
||||
|
||||
// Проверить ожидающие коллбэки при извлечении элемента |
||||
// Это важно для waiters, которые ожидают уменьшения очереди |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count); |
||||
check_waiters(q); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
int queue_entry_count(struct ll_queue* q) { |
||||
if (!q) return 0; |
||||
return q->count; |
||||
} |
||||
|
||||
// ==================== Асинхронное ожидание ==================== |
||||
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, |
||||
queue_threshold_callback_fn callback, void* arg) { |
||||
if (!q || !callback) return NULL; |
||||
|
||||
// Создать новый waiter |
||||
struct queue_waiter* waiter; |
||||
if (q->pool) { |
||||
waiter = (struct queue_waiter*)memory_pool_alloc(q->pool); |
||||
} else { |
||||
waiter = malloc(sizeof(struct queue_waiter)); |
||||
} |
||||
if (!waiter) return NULL; |
||||
|
||||
waiter->max_packets = max_packets; |
||||
waiter->max_bytes = max_bytes; |
||||
waiter->callback = callback; |
||||
waiter->callback_arg = arg; |
||||
waiter->next = NULL; |
||||
|
||||
// Проверить условие немедленно |
||||
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) { |
||||
// Условие уже выполнено - вызвать коллбэк и освободить waiter |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback", |
||||
q->count, max_packets, q->total_bytes, max_bytes); |
||||
callback(q, arg); |
||||
free(waiter); |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu", |
||||
q->count, q->total_bytes, max_packets, max_bytes); |
||||
|
||||
// Добавить в список ожидающих |
||||
waiter->next = q->waiters; |
||||
q->waiters = waiter; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully"); |
||||
return waiter; |
||||
} |
||||
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) { |
||||
if (!q || !waiter) return; |
||||
|
||||
// Найти и удалить waiter из списка |
||||
struct queue_waiter** pprev = &q->waiters; |
||||
struct queue_waiter* w = q->waiters; |
||||
|
||||
while (w) { |
||||
if (w == waiter) { |
||||
*pprev = w->next; |
||||
if (q->pool) { |
||||
memory_pool_free(q->pool, w); |
||||
} else { |
||||
free(w); |
||||
} |
||||
return; |
||||
} |
||||
pprev = &w->next; |
||||
w = w->next; |
||||
} |
||||
} |
||||
|
||||
// ==================== Статистика и метрики ==================== |
||||
|
||||
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) { |
||||
if (!q || !q->pool) { |
||||
if (waiter_allocations) *waiter_allocations = 0; |
||||
if (waiter_reuse) *waiter_reuse = 0; |
||||
return; |
||||
} |
||||
|
||||
memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse); |
||||
} |
||||
|
||||
// ==================== Управление позициями ==================== |
||||
|
||||
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) { |
||||
if (!queue) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL"); |
||||
return NULL; |
||||
} |
||||
|
||||
struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos)); |
||||
if (!pos) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position"); |
||||
return NULL; |
||||
} |
||||
|
||||
pos->queue = queue; |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; // Перед head |
||||
pos->valid = 1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue); |
||||
return pos; |
||||
} |
||||
|
||||
void queue_pos_free(struct ll_queue_pos* pos) { |
||||
if (!pos) return; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos); |
||||
free(pos); |
||||
} |
||||
|
||||
void queue_pos_reset(struct ll_queue_pos* pos) { |
||||
if (!pos) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL"); |
||||
return; |
||||
} |
||||
|
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
pos->valid = 1; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset"); |
||||
} |
||||
|
||||
int queue_pos_next(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: invalid position"); |
||||
return 0; |
||||
} |
||||
|
||||
if (!pos->current) { |
||||
// Перед head - переходим к head |
||||
if (!pos->queue->head) { |
||||
// Очередь пуста - некуда переходить |
||||
return 0; |
||||
} |
||||
pos->current = pos->queue->head; |
||||
pos->prev = NULL; |
||||
pos->index = 0; |
||||
} else if (pos->current->next) { |
||||
// Есть следующий элемент |
||||
pos->prev = pos->current; |
||||
pos->current = pos->current->next; |
||||
pos->index++; |
||||
} else { |
||||
// Достигнут конец очереди |
||||
return 0; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_prev(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: invalid position"); |
||||
return 0; |
||||
} |
||||
|
||||
if (!pos->current) { |
||||
// Уже перед head - дальше некуда |
||||
return 0; |
||||
} else if (pos->current->prev) { |
||||
// Есть предыдущий элемент |
||||
pos->current = pos->current->prev; |
||||
pos->prev = pos->current->prev; |
||||
pos->index--; |
||||
} else { |
||||
// Достигнуто начало очереди |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_seek(struct ll_queue_pos* pos, int target_index) { |
||||
if (!pos || !pos->valid) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid position"); |
||||
return 0; |
||||
} |
||||
|
||||
if (target_index < -1 || target_index >= pos->queue->count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)", |
||||
target_index, pos->queue->count); |
||||
return 0; |
||||
} |
||||
|
||||
// Простая реализация: начинаем с начала и идем вперед |
||||
// Можно оптимизировать позже, если потребуется |
||||
queue_pos_reset(pos); |
||||
|
||||
for (int i = 0; i <= target_index; i++) { |
||||
if (!queue_pos_next(pos)) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index); |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p", |
||||
pos->index, pos->current); |
||||
return 1; |
||||
} |
||||
|
||||
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) { |
||||
if (!pos || !pos->valid || !entry) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters"); |
||||
return -1; |
||||
} |
||||
|
||||
struct ll_queue* q = pos->queue; |
||||
|
||||
// Проверить лимит размера |
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry); |
||||
queue_entry_free(entry); |
||||
return -1; |
||||
} |
||||
|
||||
entry->ref_count++; |
||||
|
||||
if (mode == 0) { |
||||
// Вставить перед current |
||||
if (!pos->current) { |
||||
// Вставить в head |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
} else { |
||||
// Вставить перед current |
||||
entry->next = pos->current; |
||||
entry->prev = pos->current->prev; |
||||
|
||||
if (pos->current->prev) { |
||||
pos->current->prev->next = entry; |
||||
} else { |
||||
q->head = entry; // Вставляем в начало |
||||
} |
||||
pos->current->prev = entry; |
||||
} |
||||
} else { |
||||
// Вставить после current |
||||
if (!pos->current) { |
||||
// Вставить в head (current = NULL, значит перед head) |
||||
entry->next = q->head; |
||||
entry->prev = NULL; |
||||
if (q->head) { |
||||
q->head->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Очередь была пустой |
||||
} |
||||
q->head = entry; |
||||
} else { |
||||
// Вставить после current |
||||
entry->next = pos->current->next; |
||||
entry->prev = pos->current; |
||||
|
||||
if (pos->current->next) { |
||||
pos->current->next->prev = entry; |
||||
} else { |
||||
q->tail = entry; // Вставляем в конец |
||||
} |
||||
pos->current->next = entry; |
||||
} |
||||
} |
||||
|
||||
q->count++; |
||||
q->total_bytes += entry->size; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d", |
||||
entry, mode, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid || !pos->current) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element"); |
||||
return NULL; |
||||
} |
||||
|
||||
struct ll_queue* q = pos->queue; |
||||
struct ll_entry* entry = pos->current; |
||||
|
||||
// Удалить из двусвязного списка |
||||
if (entry->prev) { |
||||
entry->prev->next = entry->next; |
||||
} else { |
||||
q->head = entry->next; // Удаляем head |
||||
} |
||||
|
||||
if (entry->next) { |
||||
entry->next->prev = entry->prev; |
||||
} else { |
||||
q->tail = entry->prev; // Удаляем tail |
||||
} |
||||
|
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
// Обновить позицию: перейти к следующему элементу |
||||
pos->current = entry->next; |
||||
pos->prev = entry->prev; |
||||
if (pos->current) { |
||||
// Остались элементы после удаленного |
||||
pos->index = pos->index; // Индекс остается тем же |
||||
} else { |
||||
// Удалили последний элемент |
||||
pos->index = q->count; // Указывает за конец |
||||
if (pos->index == 0) { |
||||
pos->current = NULL; |
||||
pos->prev = NULL; |
||||
pos->index = -1; |
||||
} |
||||
} |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; |
||||
entry->ref_count--; // Уменьшить счетчик ссылок |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d", |
||||
entry, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) { |
||||
if (!q || !entry) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters"); |
||||
return -1; |
||||
} |
||||
|
||||
// Проверить, что элемент действительно в этой очереди |
||||
struct ll_entry* curr = q->head; |
||||
while (curr) { |
||||
if (curr == entry) { |
||||
break; |
||||
} |
||||
curr = curr->next; |
||||
} |
||||
|
||||
if (!curr) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry); |
||||
return -1; |
||||
} |
||||
|
||||
// Удалить из двусвязного списка |
||||
if (entry->prev) { |
||||
entry->prev->next = entry->next; |
||||
} else { |
||||
q->head = entry->next; // Удаляем head |
||||
} |
||||
|
||||
if (entry->next) { |
||||
entry->next->prev = entry->prev; |
||||
} else { |
||||
q->tail = entry->prev; // Удаляем tail |
||||
} |
||||
|
||||
q->count--; |
||||
q->total_bytes -= entry->size; |
||||
|
||||
entry->next = NULL; |
||||
entry->prev = NULL; |
||||
entry->ref_count--; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d", |
||||
entry, q->count); |
||||
|
||||
// Проверить ожидающие коллбэки |
||||
check_waiters(q); |
||||
|
||||
return 0; |
||||
} |
||||
@ -0,0 +1,224 @@
|
||||
// ll_queue.h |
||||
#ifndef LL_QUEUE_H |
||||
#define LL_QUEUE_H |
||||
|
||||
#include <stddef.h> // для size_t |
||||
#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 ll_queue_pos; // Управляющая структура позиции |
||||
|
||||
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки |
||||
// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент |
||||
typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg); |
||||
|
||||
// Структура элемента - переменный размер, данные расположены сразу после структуры |
||||
struct ll_entry { |
||||
struct ll_entry* next; // Указатель на следующий элемент в очереди |
||||
struct ll_entry* prev; // Указатель на предыдущий элемент в очереди |
||||
size_t size; // Размер данных элемента (байт) |
||||
int ref_count; // Счетчик ссылок для предотвращения double-free |
||||
}; |
||||
|
||||
// Структура условия ожидания (waiter) |
||||
struct queue_waiter { |
||||
int max_packets; // Максимальное количество пакетов |
||||
size_t max_bytes; // Максимальное количество байт (0 = не проверять байты) |
||||
void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова |
||||
void* callback_arg; // Аргумент коллбэка |
||||
struct queue_waiter* next; // Следующий ожидающий в списке |
||||
}; |
||||
|
||||
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; // 1 если коллбэки приостановлены (во время обработки) |
||||
|
||||
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления |
||||
struct UASYNC* ua; // Экземпляр uasync для таймеров |
||||
|
||||
struct queue_waiter* waiters; // Список ожидающих коллбэков |
||||
|
||||
// Пулы памяти для оптимизации аллокаций |
||||
struct memory_pool* pool; // Пул для структур struct queue_waiter |
||||
|
||||
uint64_t version; // Версия для инвалидации позиций |
||||
}; |
||||
|
||||
// ==================== Управление очередью ==================== |
||||
|
||||
// Создать новую пустую очередь |
||||
// ua - экземпляр uasync для таймеров (обязательный параметр) |
||||
// pool - если не null то использовать этот пул памяти |
||||
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h) |
||||
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool); |
||||
|
||||
// Освободить очередь и все её элементы |
||||
// Также отменяет отложенное возобновление если оно запланировано |
||||
void queue_free(struct ll_queue* q); |
||||
|
||||
// ==================== Конфигурация очереди ==================== |
||||
|
||||
// Установить функцию и аргумент коллбэка для автозабора из очереди |
||||
// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк |
||||
// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету. |
||||
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); |
||||
|
||||
// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя). |
||||
// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет. |
||||
// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0) |
||||
// Это предотвращает накопление рекурсии в стеке вызовов |
||||
void queue_resume_callback(struct ll_queue* q); |
||||
|
||||
// Установить максимальное количество элементов в очереди |
||||
// При превышении лимита новый элемент автоматически освобождается |
||||
void queue_set_size_limit(struct ll_queue* q, int lim); |
||||
|
||||
// ==================== Управление элементами ==================== |
||||
|
||||
// Создать новый элемент с областью данных указанного размера |
||||
// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт] |
||||
// Возвращает: указатель на элемент или NULL при ошибке выделения памяти |
||||
struct ll_entry* queue_entry_new(size_t data_size); |
||||
|
||||
// Освободить элемент (не влияет на связи в очереди) |
||||
void queue_entry_free(struct ll_entry* entry); |
||||
|
||||
// ==================== Операции с очередью ==================== |
||||
|
||||
// Добавить элемент в конец очереди (FIFO) |
||||
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк |
||||
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) |
||||
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
// Добавить элемент в начало очереди (LIFO, высокий приоритет) |
||||
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк |
||||
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) |
||||
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
// Извлечь элемент из начала очереди |
||||
// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию |
||||
// Возвращает: указатель на элемент или NULL если очередь пуста |
||||
struct ll_entry* queue_entry_get(struct ll_queue* q); |
||||
|
||||
// Получить текущее количество элементов в очереди |
||||
int queue_entry_count(struct ll_queue* q); |
||||
|
||||
// ==================== Вспомогательные функции ==================== |
||||
|
||||
// Получить указатель на область данных элемента |
||||
// Данные расположены сразу после структуры struct ll_entry |
||||
static inline void* ll_entry_data(struct ll_entry* entry) { |
||||
if (!entry) return NULL; |
||||
return (void*)(entry + 1); |
||||
} |
||||
|
||||
// Получить размер данных элемента |
||||
static inline size_t ll_entry_size(struct ll_entry* entry) { |
||||
if (!entry) return 0; |
||||
return entry->size; |
||||
} |
||||
|
||||
// ==================== Асинхронное ожидание ==================== |
||||
|
||||
// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь |
||||
// не более 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; |
||||
} |
||||
|
||||
// Получить статистику использования пулов памяти |
||||
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse); |
||||
|
||||
// ==================== Управление позициями ==================== |
||||
|
||||
// Структура управления позицией для навигации по очереди |
||||
struct ll_queue_pos { |
||||
struct ll_queue* queue; // Ссылка на очередь |
||||
struct ll_entry* current; // Текущий элемент (NULL = перед head) |
||||
struct ll_entry* prev; // Предыдущий элемент (для навигации) |
||||
int index; // Текущий индекс (-1 = перед head) |
||||
int valid; // Флаг валидности позиции |
||||
uint64_t version; // Версия очереди на момент создания/обновления позиции |
||||
}; |
||||
|
||||
// Создать новую позицию для очереди |
||||
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue); |
||||
|
||||
// Освободить позицию (не удаляет элементы очереди) |
||||
void queue_pos_free(struct ll_queue_pos* pos); |
||||
|
||||
// Сбросить позицию в начальное состояние (перед head) |
||||
void queue_pos_reset(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к следующему элементу |
||||
// Возвращает: 1 при успехе, 0 если достигнут конец |
||||
int queue_pos_next(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к предыдущему элементу |
||||
// Возвращает: 1 при успехе, 0 если достигнуто начало |
||||
int queue_pos_prev(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к конкретному индексу (0 = head) |
||||
// Возвращает: 1 при успехе, 0 если индекс неверен |
||||
int queue_pos_seek(struct ll_queue_pos* pos, int index); |
||||
|
||||
// Получить текущий элемент без удаления |
||||
static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) return NULL; |
||||
return pos->current; |
||||
} |
||||
|
||||
// Получить текущий индекс |
||||
static inline int queue_pos_index(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) return -1; |
||||
return pos->index; |
||||
} |
||||
|
||||
// Обновить позицию после изменения очереди (сбросить версию) |
||||
static inline void queue_pos_refresh(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->queue) return; |
||||
pos->version = pos->queue->version; |
||||
pos->valid = 1; |
||||
} |
||||
|
||||
// Вставить элемент относительно текущей позиции |
||||
// mode: 0 = перед current, 1 = после current |
||||
// Возвращает: 0 при успехе, -1 при ошибке |
||||
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode); |
||||
|
||||
// Удалить элемент из текущей позиции и вернуть его |
||||
// После удаления current указывает на следующий элемент (или NULL) |
||||
// Возвращает: указатель на удаленный элемент или NULL |
||||
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos); |
||||
|
||||
// Удалить конкретный элемент из очереди по указателю |
||||
// Возвращает: 0 при успехе, -1 если элемент не найден |
||||
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
#endif // LL_QUEUE_H |
||||
@ -0,0 +1,224 @@
|
||||
// ll_queue.h |
||||
#ifndef LL_QUEUE_H |
||||
#define LL_QUEUE_H |
||||
|
||||
#include <stddef.h> // для size_t |
||||
#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 ll_queue_pos; // Управляющая структура позиции |
||||
|
||||
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки |
||||
// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент |
||||
typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg); |
||||
|
||||
// Структура элемента - переменный размер, данные расположены сразу после структуры |
||||
struct ll_entry { |
||||
struct ll_entry* next; // Указатель на следующий элемент в очереди |
||||
struct ll_entry* prev; // Указатель на предыдущий элемент в очереди |
||||
size_t size; // Размер данных элемента (байт) |
||||
int ref_count; // Счетчик ссылок для предотвращения double-free |
||||
}; |
||||
|
||||
// Структура условия ожидания (waiter) |
||||
struct queue_waiter { |
||||
int max_packets; // Максимальное количество пакетов |
||||
size_t max_bytes; // Максимальное количество байт (0 = не проверять байты) |
||||
void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова |
||||
void* callback_arg; // Аргумент коллбэка |
||||
struct queue_waiter* next; // Следующий ожидающий в списке |
||||
}; |
||||
|
||||
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; // 1 если коллбэки приостановлены (во время обработки) |
||||
|
||||
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления |
||||
struct UASYNC* ua; // Экземпляр uasync для таймеров |
||||
|
||||
struct queue_waiter* waiters; // Список ожидающих коллбэков |
||||
|
||||
// Пулы памяти для оптимизации аллокаций |
||||
struct memory_pool* pool; // Пул для структур struct queue_waiter |
||||
|
||||
uint64_t version; // Версия для инвалидации позиций |
||||
}; |
||||
|
||||
// ==================== Управление очередью ==================== |
||||
|
||||
// Создать новую пустую очередь |
||||
// ua - экземпляр uasync для таймеров (обязательный параметр) |
||||
// pool - если не null то использовать этот пул памяти |
||||
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h) |
||||
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool); |
||||
|
||||
// Освободить очередь и все её элементы |
||||
// Также отменяет отложенное возобновление если оно запланировано |
||||
void queue_free(struct ll_queue* q); |
||||
|
||||
// ==================== Конфигурация очереди ==================== |
||||
|
||||
// Установить функцию и аргумент коллбэка для автозабора из очереди |
||||
// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк |
||||
// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету. |
||||
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); |
||||
|
||||
// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя). |
||||
// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет. |
||||
// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0) |
||||
// Это предотвращает накопление рекурсии в стеке вызовов |
||||
void queue_resume_callback(struct ll_queue* q); |
||||
|
||||
// Установить максимальное количество элементов в очереди |
||||
// При превышении лимита новый элемент автоматически освобождается |
||||
void queue_set_size_limit(struct ll_queue* q, int lim); |
||||
|
||||
// ==================== Управление элементами ==================== |
||||
|
||||
// Создать новый элемент с областью данных указанного размера |
||||
// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт] |
||||
// Возвращает: указатель на элемент или NULL при ошибке выделения памяти |
||||
struct ll_entry* queue_entry_new(size_t data_size); |
||||
|
||||
// Освободить элемент (не влияет на связи в очереди) |
||||
void queue_entry_free(struct ll_entry* entry); |
||||
|
||||
// ==================== Операции с очередью ==================== |
||||
|
||||
// Добавить элемент в конец очереди (FIFO) |
||||
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк |
||||
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) |
||||
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
// Добавить элемент в начало очереди (LIFO, высокий приоритет) |
||||
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк |
||||
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) |
||||
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
// Извлечь элемент из начала очереди |
||||
// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию |
||||
// Возвращает: указатель на элемент или NULL если очередь пуста |
||||
struct ll_entry* queue_entry_get(struct ll_queue* q); |
||||
|
||||
// Получить текущее количество элементов в очереди |
||||
int queue_entry_count(struct ll_queue* q); |
||||
|
||||
// ==================== Вспомогательные функции ==================== |
||||
|
||||
// Получить указатель на область данных элемента |
||||
// Данные расположены сразу после структуры struct ll_entry |
||||
static inline void* ll_entry_data(struct ll_entry* entry) { |
||||
if (!entry) return NULL; |
||||
return (void*)(entry + 1); |
||||
} |
||||
|
||||
// Получить размер данных элемента |
||||
static inline size_t ll_entry_size(struct ll_entry* entry) { |
||||
if (!entry) return 0; |
||||
return entry->size; |
||||
} |
||||
|
||||
// ==================== Асинхронное ожидание ==================== |
||||
|
||||
// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь |
||||
// не более 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; |
||||
} |
||||
|
||||
// Получить статистику использования пулов памяти |
||||
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse); |
||||
|
||||
// ==================== Управление позициями ==================== |
||||
|
||||
// Структура управления позицией для навигации по очереди |
||||
struct ll_queue_pos { |
||||
struct ll_queue* queue; // Ссылка на очередь |
||||
struct ll_entry* current; // Текущий элемент (NULL = перед head) |
||||
struct ll_entry* prev; // Предыдущий элемент (для навигации) |
||||
int index; // Текущий индекс (-1 = перед head) |
||||
int valid; // Флаг валидности позиции |
||||
uint64_t version; // Версия очереди на момент создания/обновления позиции |
||||
}; |
||||
|
||||
// Создать новую позицию для очереди |
||||
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue); |
||||
|
||||
// Освободить позицию (не удаляет элементы очереди) |
||||
void queue_pos_free(struct ll_queue_pos* pos); |
||||
|
||||
// Сбросить позицию в начальное состояние (перед head) |
||||
void queue_pos_reset(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к следующему элементу |
||||
// Возвращает: 1 при успехе, 0 если достигнут конец |
||||
int queue_pos_next(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к предыдущему элементу |
||||
// Возвращает: 1 при успехе, 0 если достигнуто начало |
||||
int queue_pos_prev(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к конкретному индексу (0 = head) |
||||
// Возвращает: 1 при успехе, 0 если индекс неверен |
||||
int queue_pos_seek(struct ll_queue_pos* pos, int index); |
||||
|
||||
// Получить текущий элемент без удаления |
||||
static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) return NULL; |
||||
return pos->current; |
||||
} |
||||
|
||||
// Получить текущий индекс |
||||
static inline int queue_pos_index(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) return -1; |
||||
return pos->index; |
||||
} |
||||
|
||||
// Обновить позицию после изменения очереди (сбросить версию) |
||||
static inline void queue_pos_refresh(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->queue) return; |
||||
pos->version = pos->queue->version; |
||||
pos->valid = 1; |
||||
} |
||||
|
||||
// Вставить элемент относительно текущей позиции |
||||
// mode: 0 = перед current, 1 = после current |
||||
// Возвращает: 0 при успехе, -1 при ошибке |
||||
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode); |
||||
|
||||
// Удалить элемент из текущей позиции и вернуть его |
||||
// После удаления current указывает на следующий элемент (или NULL) |
||||
// Возвращает: указатель на удаленный элемент или NULL |
||||
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos); |
||||
|
||||
// Удалить конкретный элемент из очереди по указателю |
||||
// Возвращает: 0 при успехе, -1 если элемент не найден |
||||
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
#endif // LL_QUEUE_H |
||||
@ -0,0 +1,210 @@
|
||||
#ifndef LL_QUEUE_H |
||||
#define LL_QUEUE_H |
||||
|
||||
#include <stddef.h> // для size_t |
||||
#include "memory_pool.h" // для struct memory_pool |
||||
|
||||
// Предварительные объявления |
||||
struct ll_queue; |
||||
struct ll_entry; |
||||
struct queue_waiter; |
||||
struct memory_pool; |
||||
struct ll_queue_pos; // Управляющая структура позиции |
||||
|
||||
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки |
||||
// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент |
||||
typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg); |
||||
|
||||
// Структура элемента - переменный размер, данные расположены сразу после структуры |
||||
struct ll_entry { |
||||
struct ll_entry* next; // Указатель на следующий элемент в очереди |
||||
struct ll_entry* prev; // Указатель на предыдущий элемент в очереди |
||||
size_t size; // Размер данных элемента (байт) |
||||
int ref_count; // Счетчик ссылок для предотвращения double-free |
||||
}; |
||||
|
||||
// Структура условия ожидания (waiter) |
||||
struct queue_waiter { |
||||
int max_packets; // Максимальное количество пакетов |
||||
size_t max_bytes; // Максимальное количество байт |
||||
void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова |
||||
void* callback_arg; // Аргумент коллбэка |
||||
struct queue_waiter* next; // Следующий ожидающий в списке |
||||
}; |
||||
|
||||
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; // 1 если коллбэки приостановлены (во время обработки) |
||||
|
||||
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления |
||||
struct UASYNC* ua; // Экземпляр uasync для таймеров |
||||
|
||||
struct queue_waiter* waiters; // Список ожидающих коллбэков |
||||
|
||||
// Пулы памяти для оптимизации аллокаций |
||||
struct memory_pool* pool; // Пул для структур struct queue_waiter |
||||
}; |
||||
|
||||
// ==================== Управление очередью ==================== |
||||
|
||||
// Создать новую пустую очередь |
||||
// ua - экземпляр uasync для таймеров (обязательный параметр) |
||||
// pool - если не null то использовать этот пул памяти |
||||
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h) |
||||
struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool); |
||||
|
||||
// Освободить очередь и все её элементы |
||||
// Также отменяет отложенное возобновление если оно запланировано |
||||
void queue_free(struct ll_queue* q); |
||||
|
||||
// ==================== Конфигурация очереди ==================== |
||||
|
||||
// Установить функцию и аргумент коллбэка для автозабора из очереди |
||||
// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк |
||||
// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету. |
||||
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); |
||||
|
||||
// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя). |
||||
// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет. |
||||
// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0) |
||||
// Это предотвращает накопление рекурсии в стеке вызовов |
||||
void queue_resume_callback(struct ll_queue* q); |
||||
|
||||
// Установить максимальное количество элементов в очереди |
||||
// При превышении лимита новый элемент автоматически освобождается |
||||
void queue_set_size_limit(struct ll_queue* q, int lim); |
||||
|
||||
// ==================== Управление элементами ==================== |
||||
|
||||
// Создать новый элемент с областью данных указанного размера |
||||
// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт] |
||||
// Возвращает: указатель на элемент или NULL при ошибке выделения памяти |
||||
struct ll_entry* queue_entry_new(size_t data_size); |
||||
|
||||
// Освободить элемент (не влияет на связи в очереди) |
||||
void queue_entry_free(struct ll_entry* entry); |
||||
|
||||
// ==================== Операции с очередью ==================== |
||||
|
||||
// Добавить элемент в конец очереди (FIFO) |
||||
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк |
||||
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) |
||||
int queue_entry_put(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
// Добавить элемент в начало очереди (LIFO, высокий приоритет) |
||||
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк |
||||
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) |
||||
int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
// Извлечь элемент из начала очереди |
||||
// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию |
||||
// Возвращает: указатель на элемент или NULL если очередь пуста |
||||
struct ll_entry* queue_entry_get(struct ll_queue* q); |
||||
|
||||
// Получить текущее количество элементов в очереди |
||||
int queue_entry_count(struct ll_queue* q); |
||||
|
||||
// ==================== Вспомогательные функции ==================== |
||||
|
||||
// Получить указатель на область данных элемента |
||||
// Данные расположены сразу после структуры struct ll_entry |
||||
static inline void* ll_entry_data(struct ll_entry* entry) { |
||||
return (void*)(entry + 1); |
||||
} |
||||
|
||||
// Получить размер данных элемента |
||||
static inline size_t ll_entry_size(struct ll_entry* entry) { |
||||
return entry->size; |
||||
} |
||||
|
||||
// ==================== Асинхронное ожидание ==================== |
||||
|
||||
// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь |
||||
// не более max_packets пакетов и не более max_bytes байт. |
||||
// Если условие уже выполнено, коллбэк вызывается немедленно. |
||||
// Можно зарегистрировать несколько ожиданий на одной очереди. |
||||
// Возвращает указатель на 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; |
||||
} |
||||
|
||||
// Получить статистику использования пулов памяти |
||||
void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse); |
||||
|
||||
// ==================== Управление позициями ==================== |
||||
|
||||
// Структура управления позицией для навигации по очереди |
||||
struct ll_queue_pos { |
||||
struct ll_queue* queue; // Ссылка на очередь |
||||
struct ll_entry* current; // Текущий элемент (NULL = перед head) |
||||
struct ll_entry* prev; // Предыдущий элемент (для навигации) |
||||
int index; // Текущий индекс (-1 = перед head) |
||||
int valid; // Флаг валидности позиции |
||||
}; |
||||
|
||||
// Создать новую позицию для очереди |
||||
struct ll_queue_pos* queue_pos_new(struct ll_queue* queue); |
||||
|
||||
// Освободить позицию (не удаляет элементы очереди) |
||||
void queue_pos_free(struct ll_queue_pos* pos); |
||||
|
||||
// Сбросить позицию в начальное состояние (перед head) |
||||
void queue_pos_reset(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к следующему элементу |
||||
// Возвращает: 1 при успехе, 0 если достигнут конец |
||||
int queue_pos_next(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к предыдущему элементу |
||||
// Возвращает: 1 при успехе, 0 если достигнуто начало |
||||
int queue_pos_prev(struct ll_queue_pos* pos); |
||||
|
||||
// Перейти к конкретному индексу (0 = head) |
||||
// Возвращает: 1 при успехе, 0 если индекс неверен |
||||
int queue_pos_seek(struct ll_queue_pos* pos, int index); |
||||
|
||||
// Получить текущий элемент без удаления |
||||
static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) return NULL; |
||||
return pos->current; |
||||
} |
||||
|
||||
// Получить текущий индекс |
||||
static inline int queue_pos_index(struct ll_queue_pos* pos) { |
||||
if (!pos || !pos->valid) return -1; |
||||
return pos->index; |
||||
} |
||||
|
||||
// Вставить элемент относительно текущей позиции |
||||
// mode: 0 = перед current, 1 = после current |
||||
// Возвращает: 0 при успехе, -1 при ошибке |
||||
int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode); |
||||
|
||||
// Удалить элемент из текущей позиции и вернуть его |
||||
// После удаления current указывает на следующий элемент (или NULL) |
||||
// Возвращает: указатель на удаленный элемент или NULL |
||||
struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos); |
||||
|
||||
// Удалить конкретный элемент из очереди по указателю |
||||
// Возвращает: 0 при успехе, -1 если элемент не найден |
||||
int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry); |
||||
|
||||
#endif // LL_QUEUE_H |
||||
@ -0,0 +1,467 @@
|
||||
// etcp.c - ETCP Protocol Implementation (refactored based on etcp_protocol.txt) |
||||
#include "etcp.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "crc32.h" |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <sys/time.h> |
||||
|
||||
// Constants from spec (adjust as needed) |
||||
#define MAX_INFLIGHT_BYTES 65536 // Initial window |
||||
#define RETRANS_K1 2.0f // RTT multiplier for retrans timeout |
||||
#define RETRANS_K2 1.5f // Jitter multiplier |
||||
#define ACK_DELAY_MS 2 // ACK timer delay (in 0.1ms units: 20) |
||||
#define BURST_DELAY_FACTOR 4 // Delay before burst |
||||
#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) |
||||
|
||||
// Internal functions |
||||
static void retrans_timer_cb(void* arg); |
||||
static void ack_timer_cb(void* arg); |
||||
static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt); |
||||
static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp); |
||||
static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
||||
static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
||||
static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
||||
static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
||||
static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
||||
static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
||||
static void append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, uint16_t* len, struct ETCP_LINK* link); |
||||
static void deliver_contiguous_packets(struct ETCP_CONN* etcp); |
||||
static void retrans_check(struct ETCP_CONN* etcp); |
||||
static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state); |
||||
static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt); |
||||
static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt); |
||||
static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link); |
||||
static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw); |
||||
static void free_inflight_list(inflight_packet_t** list); |
||||
|
||||
// Create ETCP connection |
||||
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { |
||||
struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); |
||||
if (!etcp) return NULL; |
||||
|
||||
etcp->instance = instance; |
||||
etcp->input_queue = queue_new(instance->ua, instance->pkt_pool); |
||||
etcp->output_queue = queue_new(instance->ua, instance->pkt_pool); |
||||
etcp->mtu = 1500; // Default |
||||
etcp->window_size = MAX_INFLIGHT_BYTES; |
||||
etcp->next_tx_id = 1; |
||||
|
||||
// Init crypto (as per original) |
||||
if (sc_init_ctx(&etcp->crypto_ctx, &instance->my_keys) != SC_OK) { |
||||
etcp_connection_close(etcp); |
||||
return NULL; |
||||
} |
||||
|
||||
return etcp; |
||||
} |
||||
|
||||
// Close connection |
||||
void etcp_connection_close(struct ETCP_CONN* etcp) { |
||||
if (!etcp) return; |
||||
|
||||
// Cancel timers |
||||
if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); |
||||
if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); |
||||
|
||||
// Free lists |
||||
free_inflight_list(&etcp->wait_ack_list); |
||||
free_inflight_list(&etcp->wait_send_list); |
||||
|
||||
rx_packet_t* rx = etcp->rx_list; |
||||
while (rx) { |
||||
rx_packet_t* next = rx->next; |
||||
free(rx->data); |
||||
free(rx); |
||||
rx = next; |
||||
} |
||||
|
||||
queue_free(etcp->input_queue); |
||||
queue_free(etcp->output_queue); |
||||
free(etcp); |
||||
} |
||||
|
||||
static void free_inflight_list(inflight_packet_t** list) { |
||||
inflight_packet_t* pkt = *list; |
||||
while (pkt) { |
||||
inflight_packet_t* next = pkt->next; |
||||
free(pkt->data); |
||||
free(pkt); |
||||
pkt = next; |
||||
} |
||||
*list = NULL; |
||||
} |
||||
|
||||
// Reset connection |
||||
void etcp_conn_reset(struct ETCP_CONN* etcp) { |
||||
if (!etcp) return; |
||||
|
||||
// Cancel timers |
||||
if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); |
||||
if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); |
||||
|
||||
// Clear lists |
||||
free_inflight_list(&etcp->wait_ack_list); |
||||
free_inflight_list(&etcp->wait_send_list); |
||||
|
||||
rx_packet_t* rx = etcp->rx_list; |
||||
while (rx) { |
||||
rx_packet_t* next = rx->next; |
||||
free(rx->data); |
||||
free(rx); |
||||
rx = next; |
||||
} |
||||
etcp->rx_list = NULL; |
||||
|
||||
// Reset IDs and state |
||||
etcp->next_tx_id = 1; |
||||
etcp->last_rx_id = 0; |
||||
etcp->last_delivered_id = 0; |
||||
etcp->unacked_bytes = 0; |
||||
etcp->pending_ack_count = 0; |
||||
etcp->pending_retrans_count = 0; |
||||
etcp->rtt_last = 0; |
||||
etcp->rtt_avg_10 = 0; |
||||
etcp->rtt_avg_100 = 0; |
||||
etcp->jitter = 0; |
||||
etcp->burst_in_progress = 0; |
||||
|
||||
// Clear queues but keep them |
||||
// Note: queue_clear not in original, but per spec to reset unconfirmed |
||||
// Assume queues are cleared externally if needed |
||||
} |
||||
|
||||
// Input decrypted packet from connections |
||||
void etcp_conn_input(struct ETCP_DGRAM* pkt) { |
||||
if (!pkt || !pkt->link || !pkt->link->etcp) return; |
||||
|
||||
struct ETCP_CONN* etcp = pkt->link->etcp; |
||||
uint8_t* data = pkt->data; |
||||
uint16_t len = pkt->data_len; |
||||
uint16_t ts = pkt->timestamp; |
||||
|
||||
// Update last activity |
||||
pkt->link->last_recv_local_time = get_current_time_units(); |
||||
pkt->link->last_recv_timestamp = ts; |
||||
|
||||
// Parse sections (per spec: scan sections) |
||||
uint16_t pos = 0; |
||||
while (pos < len) { |
||||
uint8_t type = data[pos]; |
||||
uint16_t sec_len = (data[pos+1] << 8) | data[pos+2]; // Assume 3-byte header: type(1) + len(2) |
||||
pos += 3; |
||||
|
||||
if (pos + sec_len > len) break; // Invalid |
||||
|
||||
uint8_t* sec_data = data + pos; |
||||
switch (type) { |
||||
case ETCP_SECTION_ACK: |
||||
process_section_ack(etcp, sec_data, sec_len); |
||||
break; |
||||
case ETCP_SECTION_RETRANS: |
||||
process_section_retrans(etcp, sec_data, sec_len); |
||||
break; |
||||
case ETCP_SECTION_TIMESTAMP: |
||||
process_section_timestamp(etcp, sec_data, sec_len); |
||||
break; |
||||
case ETCP_SECTION_PAYLOAD: |
||||
process_section_payload(etcp, sec_data, sec_len); |
||||
break; |
||||
case ETCP_SECTION_MEAS_TS: |
||||
process_section_meas_ts(etcp, sec_data, sec_len); |
||||
break; |
||||
case ETCP_SECTION_MEAS_RESP: |
||||
process_section_meas_resp(etcp, sec_data, sec_len); |
||||
break; |
||||
default: |
||||
// Ignore unknown |
||||
break; |
||||
} |
||||
pos += sec_len; |
||||
} |
||||
|
||||
// After processing |
||||
detect_gaps_and_request_retrans(etcp); |
||||
deliver_contiguous_packets(etcp); |
||||
|
||||
// If wait_timeout_active and flag set (from spec) |
||||
if (etcp->wait_timeout_active) { |
||||
// Trigger etcp_request_pkt if needed |
||||
// For now, assume load balancer polls |
||||
} |
||||
} |
||||
|
||||
// Request next packet for load balancer |
||||
struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { |
||||
if (!etcp) return NULL; |
||||
|
||||
// Check if waiting (per spec) |
||||
if (etcp->pending_retrans_count == 0 && queue_entry_count(etcp->input_queue) == 0) { |
||||
// Wait input_queue |
||||
queue_wait_threshold(etcp->input_queue, 0, 0, NULL, etcp); // Placeholder callback to resume |
||||
return NULL; |
||||
} |
||||
|
||||
if (etcp->unacked_bytes >= etcp->window_size) { |
||||
// Wait SACK or timeout |
||||
etcp->wait_timeout_active = 1; |
||||
return NULL; |
||||
} |
||||
|
||||
// Find packet to send (per spec) |
||||
inflight_packet_t* pkt = etcp->wait_send_list; // Prefer retrans |
||||
if (!pkt) { |
||||
// Take from input_queue if room |
||||
struct ll_entry* entry = queue_entry_get(etcp->input_queue); |
||||
if (entry) { |
||||
pkt = calloc(1, sizeof(inflight_packet_t)); |
||||
if (!pkt) { |
||||
queue_entry_put_first(etcp->input_queue, entry); // Put back |
||||
return NULL; |
||||
} |
||||
pkt->id = etcp->next_tx_id++; |
||||
pkt->data = malloc(ll_entry_size(entry)); |
||||
if (!pkt->data) { |
||||
free(pkt); |
||||
queue_entry_put_first(etcp->input_queue, entry); |
||||
return NULL; |
||||
} |
||||
memcpy(pkt->data, ll_entry_data(entry), ll_entry_size(entry)); |
||||
pkt->data_len = ll_entry_size(entry); |
||||
pkt->payload_len = pkt->data_len; // Assume |
||||
pkt->state = INFLIGHT_STATE_WAIT_SEND; |
||||
// Add to wait_send_list |
||||
pkt->next = etcp->wait_send_list; |
||||
etcp->wait_send_list = pkt; |
||||
queue_entry_free(entry); |
||||
} else { |
||||
return NULL; |
||||
} |
||||
} |
||||
|
||||
// Select link (call load balancer) |
||||
struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); // To implement in loadbalancer |
||||
if (!link) return NULL; |
||||
|
||||
// Build packet |
||||
uint8_t buf[1600]; |
||||
uint16_t len = 0; |
||||
|
||||
// Append optional sections |
||||
append_optional_sections(etcp, buf, &len, link); |
||||
|
||||
// Append payload section |
||||
buf[len++] = ETCP_SECTION_PAYLOAD; |
||||
buf[len++] = (pkt->payload_len >> 8) & 0xFF; |
||||
buf[len++] = pkt->payload_len & 0xFF; |
||||
memcpy(buf + len, pkt->data + (pkt->data_len - pkt->payload_len), pkt->payload_len); // Assume payload at end |
||||
len += pkt->payload_len; |
||||
|
||||
// Prepare DGRAM |
||||
struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); |
||||
if (!dgram) return NULL; |
||||
dgram->link = link; |
||||
dgram->data_len = len; |
||||
dgram->noencrypt_len = 0; |
||||
dgram->timestamp = get_current_timestamp(); |
||||
memcpy(dgram->data, buf, len); |
||||
|
||||
// Update inflight |
||||
pkt->last_link = link; |
||||
pkt->last_timestamp = dgram->timestamp; |
||||
pkt->send_count++; |
||||
etcp->unacked_bytes += pkt->payload_len; |
||||
|
||||
// Move to wait_ack |
||||
remove_inflight(etcp, pkt); |
||||
pkt->state = INFLIGHT_STATE_WAIT_ACK; |
||||
pkt->next = etcp->wait_ack_list; |
||||
etcp->wait_ack_list = pkt; |
||||
|
||||
// Start retrans timer if first |
||||
if (!etcp->retrans_timer) { |
||||
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); // 2ms |
||||
} |
||||
|
||||
return dgram; |
||||
} |
||||
|
||||
// Placeholder implementations for sections |
||||
static void append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, uint16_t* len, struct ETCP_LINK* link) { |
||||
// Add ACK, RETRANS, TIMESTAMP |
||||
// Example for ACK |
||||
if (etcp->pending_ack_count > 0) { |
||||
// Build ACK section |
||||
// ... |
||||
etcp->pending_ack_count = 0; |
||||
} |
||||
// Similarly for others |
||||
} |
||||
|
||||
static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
||||
// Parse ACKs, mark in inflight, remove if acked |
||||
// Update unacked_bytes |
||||
// Set lp_ts if needed for retrans check |
||||
} |
||||
|
||||
static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
||||
// Parse IDs, mark need_retrans, move to wait_send |
||||
} |
||||
|
||||
static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
||||
// Update RTT |
||||
uint16_t rtt = timestamp_diff(get_current_timestamp(), *(uint16_t*)data); // Assume format |
||||
update_rtt(etcp, rtt); |
||||
} |
||||
|
||||
static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
||||
// Extract ID from payload header (assume first 2 bytes ID) |
||||
uint16_t id = (data[0] << 8) | data[1]; |
||||
if (id <= etcp->last_rx_id) return; // Duplicate |
||||
|
||||
// Insert into rx_list sorted by ID |
||||
rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t)); |
||||
new_rx->id = id; |
||||
new_rx->timestamp = get_current_timestamp(); |
||||
new_rx->data = malloc(len - 2); |
||||
memcpy(new_rx->data, data + 2, len - 2); |
||||
new_rx->data_len = len - 2; |
||||
|
||||
// Insert sorted |
||||
rx_packet_t** ptr = &etcp->rx_list; |
||||
while (*ptr && (*ptr)->id < id) ptr = &(*ptr)->next; |
||||
new_rx->next = *ptr; |
||||
*ptr = new_rx; |
||||
|
||||
if (id > etcp->last_rx_id) etcp->last_rx_id = id; |
||||
|
||||
// Add to pending ACK |
||||
if (etcp->pending_ack_count < 32) { |
||||
etcp->pending_ack_ids[etcp->pending_ack_count] = id; |
||||
etcp->pending_ack_ts[etcp->pending_ack_count++] = new_rx->timestamp; |
||||
} |
||||
|
||||
// Schedule ACK if not |
||||
if (!etcp->ack_timer) { |
||||
etcp->ack_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_MS * 10, etcp, ack_timer_cb); |
||||
} |
||||
} |
||||
|
||||
static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
||||
// Send meas_resp for each |
||||
// ... |
||||
} |
||||
|
||||
static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
||||
// Calculate bandwidth, update link |
||||
// ... |
||||
} |
||||
|
||||
static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp) { |
||||
// Scan rx_list for gaps > last_delivered_id |
||||
// Add to pending_retrans_ids |
||||
} |
||||
|
||||
static void deliver_contiguous_packets(struct ETCP_CONN* etcp) { |
||||
rx_packet_t* rx = etcp->rx_list; |
||||
while (rx && rx->id == etcp->last_delivered_id + 1) { |
||||
// Deliver to output_queue |
||||
struct ll_entry* entry = queue_entry_new(rx->data_len); |
||||
memcpy(ll_entry_data(entry), rx->data, rx->data_len); |
||||
queue_entry_put(etcp->output_queue, entry); |
||||
etcp->last_delivered_id = rx->id; |
||||
|
||||
rx_packet_t* next = rx->next; |
||||
free(rx->data); |
||||
free(rx); |
||||
rx = next; |
||||
} |
||||
etcp->rx_list = rx; |
||||
} |
||||
|
||||
static void retrans_check(struct ETCP_CONN* etcp) { |
||||
uint64_t now = get_current_time_units(); |
||||
inflight_packet_t* pkt = etcp->wait_ack_list; |
||||
uint16_t lp_ts = 0; |
||||
while (pkt) { |
||||
uint16_t timeout = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); |
||||
if (timestamp_diff(now, pkt->last_timestamp) > timeout) { |
||||
move_to_wait_send(etcp, pkt); |
||||
} |
||||
// ... (lp_ts logic per spec) |
||||
pkt = pkt->next; |
||||
} |
||||
|
||||
// Reschedule timer |
||||
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); |
||||
} |
||||
|
||||
static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { |
||||
remove_inflight(etcp, pkt); |
||||
pkt->next = etcp->wait_send_list; |
||||
etcp->wait_send_list = pkt; |
||||
pkt->state = INFLIGHT_STATE_WAIT_SEND; |
||||
} |
||||
|
||||
static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { |
||||
// Remove from list (wait_ack or wait_send) |
||||
inflight_packet_t** list = (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? &etcp->wait_ack_list : &etcp->wait_send_list; |
||||
inflight_packet_t** ptr = list; |
||||
while (*ptr && *ptr != pkt) ptr = &(*ptr)->next; |
||||
if (*ptr) *ptr = pkt->next; |
||||
} |
||||
|
||||
static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state) { |
||||
inflight_packet_t* list = (state == INFLIGHT_STATE_WAIT_ACK) ? etcp->wait_ack_list : etcp->wait_send_list; |
||||
while (list) { |
||||
if (list->id == id) return list; |
||||
list = list->next; |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
static void retrans_timer_cb(void* arg) { |
||||
struct ETCP_CONN* etcp = arg; |
||||
retrans_check(etcp); |
||||
} |
||||
|
||||
static void ack_timer_cb(void* arg) { |
||||
struct ETCP_CONN* etcp = arg; |
||||
// Trigger send of ACK packet (via load balancer or direct) |
||||
// For now, assume appended in next pkt |
||||
etcp->ack_timer = NULL; |
||||
} |
||||
|
||||
static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt) { |
||||
etcp->rtt_last = new_rtt; |
||||
etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + new_rtt) / 10; |
||||
etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + new_rtt) / 100; |
||||
// Jitter: max-min last 10 (implement history if needed) |
||||
} |
||||
|
||||
static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link) { |
||||
// Initiate burst (per spec) |
||||
etcp->burst_in_progress = 1; |
||||
etcp->burst_start_id = etcp->next_tx_id; |
||||
// Schedule delayed packets |
||||
} |
||||
|
||||
static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw) { |
||||
// Update bandwidth |
||||
// link->bandwidth = new_bw; // Add to ETCP_LINK |
||||
} |
||||
|
||||
// Get stats (original) |
||||
void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, |
||||
size_t* pool_allocs, size_t* pool_reuse) { |
||||
if (packets_sent) *packets_sent = etcp->bytes_sent_total / 1000; // Approx |
||||
if (packets_recv) *packets_recv = etcp->bytes_received_total / 1000; |
||||
memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse); |
||||
} |
||||
|
||||
uint16_t get_current_timestamp() { |
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
return (uint16_t)((tv.tv_sec * 1000 + tv.tv_usec / 1000) & 0xFFFF); // ms wrap |
||||
} |
||||
@ -0,0 +1,489 @@
|
||||
// etcp.c - ETCP Protocol Implementation |
||||
#include "../lib/u_async.h" |
||||
#include "utun_instance.h" |
||||
#include "etcp.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/ll_queue.h" |
||||
#include "crc32.h" |
||||
#include "secure_channel.h" |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <sys/time.h> |
||||
#include <time.h> |
||||
|
||||
// Service packet headers (from protocol, but reset/init handled elsewhere) |
||||
#define ETCP_ACK_HEADER 0x01 |
||||
#define ETCP_RETRANS_HEADER_BASE 0x10 // 0x10 to 0x2F for retrans requests |
||||
#define ETCP_PAYLOAD_HEADER 0x00 |
||||
#define ETCP_INIT_RESPONSE 0x03 |
||||
|
||||
// Forward declarations of internal functions (adapted from etcp_master) |
||||
static void retransmit_check(struct ETCP_CONN* etcp); |
||||
static void update_metrics(struct ETCP_CONN* etcp, uint16_t rtt); |
||||
|
||||
static uint16_t timestamp_diff(uint16_t t1, uint16_t t2); |
||||
static int id_compare(uint16_t id1, uint16_t id2); |
||||
static void retransmit_packet(struct ETCP_CONN* etcp, uint16_t id); |
||||
|
||||
// Timer callbacks |
||||
static void tx_timer_callback(void* arg); |
||||
static void retransmit_timer_callback(void* arg); |
||||
|
||||
// Internal queue callbacks |
||||
static void tx_queue_callback(struct ll_queue* q, struct ll_entry* entry, void* arg); |
||||
static void schedule_ack_timer(struct ETCP_CONN* etcp); |
||||
static void request_retransmission_for_gaps(struct ETCP_CONN* etcp); |
||||
|
||||
static void queue_clear(struct ll_queue* q) { |
||||
struct ll_entry* entry; |
||||
while ((entry = queue_entry_get(q)) != NULL) { |
||||
queue_entry_free(entry); |
||||
} |
||||
} |
||||
|
||||
static void etcp_update_window(struct ETCP_CONN* etcp) { |
||||
etcp->window_size = 65536; // Initial window size |
||||
etcp->retrans_timer_period = 20; // Default retransmit period |
||||
} |
||||
|
||||
// Reset connection (adapted from etcp_reset in master, without packet sending) |
||||
void etcp_conn_reset(struct ETCP_CONN* etcp) { |
||||
if (!etcp) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: resetting instance"); |
||||
|
||||
// Cancel timers |
||||
if (etcp->next_tx_timer) { |
||||
uasync_cancel_timeout(etcp->instance->ua, etcp->next_tx_timer); |
||||
etcp->next_tx_timer = NULL; |
||||
} |
||||
if (etcp->retransmit_timer) { |
||||
uasync_cancel_timeout(etcp->instance->ua, etcp->retransmit_timer); |
||||
etcp->retransmit_timer = NULL; |
||||
} |
||||
|
||||
// Clear queues (but keep them) |
||||
queue_clear(etcp->input_queue); |
||||
queue_clear(etcp->output_queue); |
||||
|
||||
// Clear lists |
||||
rx_packet_t* rx = etcp->rx_list; |
||||
while (rx) { |
||||
rx_packet_t* next = rx->next; |
||||
if (rx->data) free(rx->data); |
||||
free(rx); |
||||
rx = next; |
||||
} |
||||
etcp->rx_list = NULL; |
||||
|
||||
sent_packet_t* sent = etcp->sent_list; |
||||
while (sent) { |
||||
sent_packet_t* next = sent->next; |
||||
if (sent->data) free(sent->data); |
||||
free(sent); |
||||
sent = next; |
||||
} |
||||
etcp->sent_list = NULL; |
||||
|
||||
// Reset metrics and stats |
||||
etcp->rtt_last = 0; |
||||
etcp->rtt_avg_10 = 0; |
||||
etcp->rtt_avg_100 = 0; |
||||
etcp->jitter = 0; |
||||
etcp->bytes_sent_total = 0; |
||||
etcp->retransmissions_count = 0; |
||||
etcp->ack_packets_count = 0; |
||||
etcp->control_packets_count = 0; |
||||
etcp->total_packets_sent = 0; |
||||
etcp->unique_packets_sent = 0; |
||||
etcp->bytes_received_total = 0; |
||||
|
||||
// Reset IDs |
||||
etcp->next_tx_id = 1; |
||||
etcp->last_sent_id = 0; |
||||
etcp->last_rx_id = 0; |
||||
etcp->last_delivered_id = 0; |
||||
|
||||
// Reset history |
||||
etcp->rtt_history_idx = 0; |
||||
etcp->rtt_history_count = 0; |
||||
|
||||
// Reset pending |
||||
etcp->pending_ack_count = 0; |
||||
etcp->pending_retransmit_count = 0; |
||||
|
||||
// Reset window |
||||
etcp->unacked_bytes = 0; |
||||
etcp->last_acked_id = 0; |
||||
etcp->last_rx_ack_id = 0; |
||||
etcp->next_retrans_time = 0; |
||||
etcp->window_blocked = 0; |
||||
|
||||
// Reset forward progress |
||||
etcp->oldest_missing_id = 0; |
||||
etcp->missing_since_time = 0; |
||||
|
||||
etcp_update_window(etcp); |
||||
} |
||||
|
||||
// Creating ETCP instance |
||||
// после создания надо добавить peer bublic key. |
||||
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { |
||||
if (!instance) return NULL; |
||||
|
||||
struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); |
||||
if (!etcp) return NULL; |
||||
|
||||
etcp->mtu = 1500; // Default MTU |
||||
|
||||
etcp->instance = instance; |
||||
|
||||
// Initialize crypto context |
||||
if (sc_init_ctx(&etcp->crypto_ctx, &etcp->instance->my_keys) != SC_OK) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_connection_create: failed to initialize crypto context for node %llu", (unsigned long long)instance->node_id); |
||||
free(etcp); |
||||
return NULL; |
||||
} |
||||
|
||||
// Initialize queues (tx_queue is input_queue) |
||||
etcp->input_queue = queue_new(instance->ua,NULL); |
||||
if (!etcp->input_queue) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "etcp_connection_create: failed to create input queue for node %llu", (unsigned long long)instance->node_id); |
||||
free(etcp); |
||||
return NULL; |
||||
} |
||||
|
||||
etcp->output_queue = queue_new(instance->ua,NULL); |
||||
if (!etcp->output_queue) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "etcp_connection_create: failed to create output queue for node %llu", (unsigned long long)instance->node_id); |
||||
queue_free(etcp->input_queue); |
||||
free(etcp); |
||||
return NULL; |
||||
} |
||||
|
||||
// Set callback for input_queue (tx_queue) |
||||
queue_set_callback(etcp->input_queue, tx_queue_callback, etcp); |
||||
|
||||
// Initialize state (from master) |
||||
etcp->bandwidth = 10000; // Default: 10000 bytes per timebase (0.1us) |
||||
etcp->last_sent_timestamp = get_current_timestamp(); |
||||
etcp->bytes_allowed = 0; |
||||
|
||||
// Reset all other fields to initial state |
||||
etcp_conn_reset(etcp); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_create: created instance with node_id=%llu, mtu=%d", |
||||
(unsigned long long)etcp->instance->node_id, etcp->mtu); |
||||
|
||||
etcp->next=instance->connections; |
||||
instance->connections=etcp; |
||||
|
||||
instance->connections_count++; |
||||
|
||||
return etcp; |
||||
} |
||||
|
||||
// Destroying ETCP instance |
||||
void etcp_connection_close(struct ETCP_CONN* etcp) { |
||||
if (!etcp) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_close: destroying instance"); |
||||
|
||||
// Cancel timers |
||||
if (etcp->next_tx_timer) { |
||||
uasync_cancel_timeout(etcp->instance->ua, etcp->next_tx_timer); |
||||
etcp->next_tx_timer = NULL; |
||||
} |
||||
if (etcp->retransmit_timer) { |
||||
uasync_cancel_timeout(etcp->instance->ua, etcp->retransmit_timer); |
||||
etcp->retransmit_timer = NULL; |
||||
} |
||||
|
||||
// Free queues |
||||
if (etcp->input_queue) queue_free(etcp->input_queue); |
||||
if (etcp->output_queue) queue_free(etcp->output_queue); |
||||
|
||||
// Free rx_list |
||||
rx_packet_t* rx = etcp->rx_list; |
||||
while (rx) { |
||||
rx_packet_t* next = rx->next; |
||||
if (rx->data) free(rx->data); |
||||
free(rx); |
||||
rx = next; |
||||
} |
||||
|
||||
// Free sent_list |
||||
sent_packet_t* sent = etcp->sent_list; |
||||
while (sent) { |
||||
sent_packet_t* next = sent->next; |
||||
if (sent->data) free(sent->data); |
||||
free(sent); |
||||
sent = next; |
||||
} |
||||
|
||||
free(etcp); |
||||
} |
||||
|
||||
// Process incoming packet (partial, truncated in original) |
||||
void etcp_conn_input(struct ETCP_DGRAM* pkt) { |
||||
if (!pkt) return; |
||||
struct ETCP_CONN* etcp=pkt->link->etcp; |
||||
if (!etcp) return; |
||||
|
||||
uint8_t* data = pkt->data; |
||||
size_t len = pkt->data_len; |
||||
|
||||
if (len < 4) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_input: packet too short (%zu bytes) from node %llu", len, (unsigned long long)etcp->instance->node_id); |
||||
return; // Min header |
||||
} |
||||
|
||||
uint16_t id = (data[0] << 8) | data[1]; |
||||
uint16_t timestamp = (data[2] << 8) | data[3]; |
||||
|
||||
// Check for INIT_RESPONSE packet (special control packet) |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] etcp_conn_input: id=%u, len=%zu, first bytes: %02x %02x %02x %02x %02x", |
||||
id, len, data[0], data[1], data[2], data[3], data[4]); |
||||
|
||||
data += 4; len -= 4; // Skip header for regular packet processing |
||||
|
||||
int has_payload = 0; |
||||
uint8_t* payload_data = NULL; |
||||
size_t payload_len = 0; |
||||
int is_duplicate = 0; |
||||
|
||||
while (len > 0) { |
||||
uint8_t section_header = *data++; |
||||
len--; |
||||
|
||||
if (section_header == ETCP_ACK_HEADER) { |
||||
// ACK section |
||||
uint8_t count = *data++; |
||||
len--; |
||||
for (uint8_t i = 0; i < count; i++) { |
||||
uint16_t ack_id = (data[0] << 8) | data[1]; |
||||
uint16_t ack_ts = (data[2] << 8) | data[3]; |
||||
data += 4; len -= 4; |
||||
|
||||
// Process ACK: remove from sent_list, update window |
||||
sent_packet_t** ptr = &etcp->sent_list; |
||||
while (*ptr) { |
||||
if ((*ptr)->id == ack_id) { |
||||
sent_packet_t* to_free = *ptr; |
||||
*ptr = to_free->next; |
||||
etcp->unacked_bytes -= to_free->payload_len; |
||||
update_metrics(etcp, timestamp_diff(get_current_timestamp(), ack_ts)); |
||||
free(to_free->data); |
||||
free(to_free); |
||||
break; |
||||
} |
||||
ptr = &(*ptr)->next; |
||||
} |
||||
} |
||||
|
||||
// last_delivered and last_rx |
||||
uint16_t last_delivered = (data[0] << 8) | data[1]; |
||||
uint16_t last_rx = (data[2] << 8) | data[3]; |
||||
data += 4; len -= 4; |
||||
|
||||
etcp->last_rx_ack_id = last_rx; |
||||
|
||||
} else if ((section_header & 0xF0) == ETCP_RETRANS_HEADER_BASE) { |
||||
// Retrans request |
||||
uint8_t count = (section_header & 0x0F) + 1; |
||||
for (uint8_t i = 0; i < count; i++) { |
||||
uint16_t retrans_id = (data[0] << 8) | data[1]; |
||||
data += 2; |
||||
len -= 2; |
||||
|
||||
retransmit_packet(etcp, retrans_id); |
||||
} |
||||
|
||||
// last_delivered and last_rx |
||||
uint16_t last_delivered = (data[0] << 8) | data[1]; |
||||
uint16_t last_rx = (data[2] << 8) | data[3]; |
||||
data += 4; len -= 4; |
||||
|
||||
} else if (section_header == ETCP_PAYLOAD_HEADER) { |
||||
// Payload section |
||||
has_payload = 1; |
||||
payload_data = data; |
||||
payload_len = len; |
||||
break; // Payload is last |
||||
} |
||||
} |
||||
|
||||
// Check for duplicate |
||||
rx_packet_t* rx = etcp->rx_list; |
||||
while (rx) { |
||||
if (rx->id == id) { |
||||
is_duplicate = 1; |
||||
break; |
||||
} |
||||
rx = rx->next; |
||||
} |
||||
|
||||
if (is_duplicate) { |
||||
// Add to pending ACKs |
||||
if (etcp->pending_ack_count < 32) { |
||||
etcp->pending_ack_ids[etcp->pending_ack_count] = id; |
||||
etcp->pending_ack_timestamps[etcp->pending_ack_count] = timestamp; |
||||
etcp->pending_ack_count++; |
||||
} |
||||
schedule_ack_timer(etcp); |
||||
return; |
||||
} |
||||
|
||||
// Insert into rx_list (sorted) |
||||
rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t)); |
||||
if (!new_rx) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "etcp_conn_input: failed to allocate memory for rx packet from node %llu", (unsigned long long)etcp->instance->node_id); |
||||
return; |
||||
} |
||||
|
||||
new_rx->id = id; |
||||
new_rx->timestamp = timestamp; |
||||
new_rx->has_payload = has_payload; |
||||
if (has_payload) { |
||||
new_rx->data = malloc(payload_len); |
||||
if (!new_rx->data) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "etcp_conn_input: failed to allocate %zu bytes for payload data from node %llu", payload_len, (unsigned long long)etcp->instance->node_id); |
||||
free(new_rx); |
||||
return; |
||||
} |
||||
memcpy(new_rx->data, payload_data, payload_len); |
||||
new_rx->data_len = payload_len; |
||||
etcp->bytes_received_total += payload_len; |
||||
} |
||||
|
||||
// Insert sorted |
||||
rx_packet_t** ptr = &etcp->rx_list; |
||||
while (*ptr && id_compare((*ptr)->id, id) < 0) { |
||||
ptr = &(*ptr)->next; |
||||
} |
||||
new_rx->next = *ptr; |
||||
*ptr = new_rx; |
||||
|
||||
// Update last_rx_id |
||||
if (id_compare(id, etcp->last_rx_id) > 0) { |
||||
etcp->last_rx_id = id; |
||||
} |
||||
|
||||
// Add to pending ACKs |
||||
if (etcp->pending_ack_count < 32) { |
||||
etcp->pending_ack_ids[etcp->pending_ack_count] = id; |
||||
etcp->pending_ack_timestamps[etcp->pending_ack_count] = timestamp; |
||||
etcp->pending_ack_count++; |
||||
} |
||||
|
||||
// Request retrans for gaps and deliver contiguous |
||||
request_retransmission_for_gaps(etcp); |
||||
schedule_ack_timer(etcp); |
||||
} |
||||
|
||||
|
||||
// Getting statistics (extended with master stats) |
||||
void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, |
||||
size_t* pool_allocs, size_t* pool_reuse) { |
||||
if (!etcp) return; |
||||
|
||||
if (packets_sent) *packets_sent = etcp->total_packets_sent; |
||||
if (packets_recv) *packets_recv = etcp->bytes_received_total / 100; // Approximate |
||||
|
||||
if (pool_allocs || pool_reuse) { |
||||
memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse); |
||||
} |
||||
} |
||||
|
||||
// ... (rest of functions from master, adapted to ETCP_CONN and using etcp_link_send for tx) |
||||
|
||||
// For tx: assume primary channel (etcp->channels), or iterate if multi-path |
||||
static void send_etcp_packet(struct ETCP_CONN* etcp, uint8_t* pkt, uint16_t len) { |
||||
if (!etcp->links) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "send_etcp_packet: no links available for node %llu", (unsigned long long)etcp->instance->node_id); |
||||
return; // No links |
||||
} |
||||
|
||||
// Send to primary link (or iterate for multi-path) |
||||
char dg_mem[1600]; |
||||
struct ETCP_DGRAM* dg=(struct ETCP_DGRAM*)&dg_mem; |
||||
dg->data_len=len; |
||||
dg->noencrypt_len=0; |
||||
dg->link=etcp->links;// first link TODO: добавить отправку по нескольким линкам |
||||
etcp_encrypt_send(dg); |
||||
} |
||||
|
||||
static void update_metrics(struct ETCP_CONN* etcp, uint16_t rtt) { |
||||
etcp->rtt_last = rtt; |
||||
// Update averages, jitter, etc. |
||||
// Placeholder: etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + rtt) / 10; |
||||
// etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + rtt) / 100; |
||||
} |
||||
|
||||
uint64_t get_current_time_units() { |
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
return ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100); |
||||
} |
||||
|
||||
uint16_t get_current_timestamp() { |
||||
return (uint16_t)get_current_time_units(); |
||||
} |
||||
|
||||
|
||||
static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { |
||||
if (t1 >= t2) return t1 - t2; |
||||
return (0xFFFF - t2) + t1 + 1; // Wrap around |
||||
} |
||||
|
||||
static int id_compare(uint16_t id1, uint16_t id2) { |
||||
int16_t diff = id1 - id2; |
||||
if (diff == 0) return 0; |
||||
return (diff > 0) ? 1 : -1; // Simple for now, add wrap logic if needed |
||||
} |
||||
|
||||
static void retransmit_packet(struct ETCP_CONN* etcp, uint16_t id) { |
||||
sent_packet_t* sent = etcp->sent_list; |
||||
while (sent) { |
||||
if (sent->id == id) { |
||||
send_etcp_packet(etcp, sent->data, sent->data_len); |
||||
etcp->retransmissions_count++; |
||||
break; |
||||
} |
||||
sent = sent->next; |
||||
} |
||||
} |
||||
|
||||
static void tx_queue_callback(struct ll_queue* q, struct ll_entry* entry, void* arg) { |
||||
(void)q; |
||||
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; |
||||
// Process entry to send packet |
||||
// Placeholder: uint8_t* data = ll_entry_data(entry); |
||||
// size_t len = ll_entry_size(entry); |
||||
// // Build packet and send |
||||
// send_etcp_packet(etcp, data, len); |
||||
queue_resume_callback(q); |
||||
} |
||||
|
||||
static void schedule_ack_timer(struct ETCP_CONN* etcp) { |
||||
// Placeholder: set timeout to send ACKs |
||||
} |
||||
|
||||
static void request_retransmission_for_gaps(struct ETCP_CONN* etcp) { |
||||
// Detect gaps in rx_list and add to pending_retransmit |
||||
} |
||||
|
||||
static void retransmit_check(struct ETCP_CONN* etcp) { |
||||
// Check sent_list for timeouts |
||||
} |
||||
|
||||
static void tx_timer_callback(void* arg) { |
||||
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; |
||||
// Handle next tx |
||||
} |
||||
|
||||
static void retransmit_timer_callback(void* arg) { |
||||
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; |
||||
retransmit_check(etcp); |
||||
} |
||||
@ -0,0 +1,95 @@
|
||||
// etcp_loadbalancer.c - Load Balancer Implementation (based on etcp_protocol.txt)
|
||||
#include "etcp_loadbalancer.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/u_async.h" |
||||
#include <stdlib.h> |
||||
|
||||
// Forward declaration
|
||||
static void init_timeout_cb(void* arg); |
||||
|
||||
// Constants
|
||||
#define TIMEBASE_NS 100000 // 0.1ms = 100us = 100000ns
|
||||
#define DELTA_TIME_NS 10000 // Example delta
|
||||
|
||||
// Internal
|
||||
static uint64_t get_current_nanotime() { |
||||
struct timespec ts; |
||||
clock_gettime(CLOCK_MONOTONIC, &ts); |
||||
return (uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec; |
||||
} |
||||
|
||||
// Select link for transmission (per spec)
|
||||
struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) { |
||||
if (!etcp || !etcp->links) return NULL; |
||||
|
||||
struct ETCP_LINK* best = NULL; |
||||
uint64_t min_load_time = UINT64_MAX; |
||||
|
||||
struct ETCP_LINK* link = etcp->links; |
||||
while (link) { |
||||
if (!link->initialized) { |
||||
// Initiate connection if client
|
||||
if (!link->is_server && !link->init_timer) { |
||||
// Send INIT (via etcp_encrypt_send with special dgram)
|
||||
// Set timer
|
||||
link->init_timer = uasync_set_timeout(etcp->instance->ua, link->init_timeout, link, init_timeout_cb); // Define cb
|
||||
} |
||||
link = link->next; |
||||
continue; |
||||
} |
||||
|
||||
// Update load time if inactive
|
||||
uint64_t now_ns = get_current_nanotime(); |
||||
uint64_t now_tb = now_ns / (TIMEBASE_NS / 10); // To 0.1ms units
|
||||
|
||||
if (link->last_activity < now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10))) { |
||||
link->last_activity = now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10)); |
||||
} |
||||
|
||||
// Check if can send (load time < now)
|
||||
if (link->last_activity < now_tb) { |
||||
if (link->last_activity < min_load_time) { |
||||
min_load_time = link->last_activity; |
||||
best = link; |
||||
} |
||||
} |
||||
|
||||
link = link->next; |
||||
} |
||||
|
||||
return best; |
||||
} |
||||
|
||||
// Init timeout callback (placeholder)
|
||||
static void init_timeout_cb(void* arg) { |
||||
struct ETCP_LINK* link = arg; |
||||
// Resend INIT, increment retry
|
||||
link->init_retry_count++; |
||||
if (link->init_retry_count > 5) { |
||||
// Fail
|
||||
etcp_link_close(link); |
||||
return; |
||||
} |
||||
// Resend...
|
||||
link->init_timeout *= 2; // Backoff
|
||||
link->init_timer = uasync_set_timeout(link->etcp->instance->ua, link->init_timeout, link, init_timeout_cb); |
||||
} |
||||
|
||||
// Bandwidth limit update (called after send)
|
||||
void etcp_loadbalancer_update_after_send(struct ETCP_LINK* link, size_t pkt_size) { |
||||
if (!link || link->bandwidth == 0) return; |
||||
|
||||
// Time to transmit (ns)
|
||||
double byte_time_ns = 1000000000.0 / (link->bandwidth * 8.0); // bits/sec to byte/ns
|
||||
uint64_t tx_time_ns = (uint64_t)(pkt_size * byte_time_ns); |
||||
|
||||
// To timebase (0.1ms units)
|
||||
uint64_t tx_time_tb = tx_time_ns / (TIMEBASE_NS / 10); |
||||
|
||||
// Update last_activity
|
||||
uint64_t now_tb = get_current_nanotime() / (TIMEBASE_NS / 10); |
||||
link->last_activity += tx_time_tb; |
||||
if (link->last_activity < now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10))) { |
||||
link->last_activity = now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10)); |
||||
} |
||||
} |
||||
@ -0,0 +1,20 @@
|
||||
// etcp_loadbalancer.h - Load Balancer for ETCP Channels
|
||||
#ifndef ETCP_LOADBALANCER_H |
||||
#define ETCP_LOADBALANCER_H |
||||
|
||||
#include "etcp.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
// Functions
|
||||
struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp); |
||||
|
||||
// Add more as needed (e.g., update bandwidth)
|
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif // ETCP_LOADBALANCER_H
|
||||
@ -1,63 +0,0 @@
|
||||
Формат кодограммы: |
||||
------------------ |
||||
Каждая кодограмма состоит из обязательного заголовка и опциональных секций: |
||||
1. Обязательный заголовок всего пакета (2 байта) добавляется при передаче, есть во всех пакетах: |
||||
[Timestamp high][Timestamp low] |
||||
- Timestamp: время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется |
||||
|
||||
штфруется строго ВСЁ включая ВСЕ заголовки (кроме INIT+publickey). timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ |
||||
|
||||
2. Опциональные секции (одна или несколько) добавляются в etcp.c: |
||||
а) Подтверждения (ACK) - заголовок 0x01: |
||||
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo] |
||||
- count: количество пар ID+timestamp (1-32) |
||||
- last_delivered_id: последний ID, доставленный получателю |
||||
- last_rx_id: последний полученный ID (для синхронизации прогресса) |
||||
б) Запросы ретрансмиссии - заголовок 0x10-0x2F: |
||||
[0x10+(count-1)][(id_hi,id_lo)×count][last_delivered_hi,lo][last_rx_hi,lo] |
||||
- count: (заголовок & 0x0F) + 1 (1-32) |
||||
- last_delivered_id, last_rx_id: для синхронизации состояния |
||||
|
||||
в) channel timestamp (добавляется после выбора канала передачи): |
||||
[0x0F] [RET_Timestamp high][RET_Timestamp low] [RECV_Timestamp high][RECV_Timestamp low] |
||||
- RET_Timestamp: это timestamp последнего принятого пакета по этому каналу ETCP_LINK плюс разница времени между принятием этого пакета и текущим временем. т.е. приёмная сторона по этому timestamp (и зная своё время) может просчитать время в пути туда + обратно |
||||
- RECV_Timestamp: это timestamp времени приёма (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно). |
||||
если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем. |
||||
|
||||
|
||||
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя): |
||||
[0x00] [ID high][ID low] [данные...] - Данные пакета |
||||
- ID: 16-битный циклический номер пакета. |
||||
|
||||
Пакет может содержать несколько опциональных секций (например, ACK + данные). |
||||
|
||||
|
||||
|
||||
Установка подключения для канала (реализщуется в etcp_connections): |
||||
|
||||
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения. |
||||
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client. |
||||
|
||||
процесс установки соединения: |
||||
- отправляем init запрос и выставляем таймаут (сохраняем его в struct ETCP_LINK) |
||||
- по таймауту повторяем init запросы, ведем счетчик запросов init. |
||||
- когда получаем init подтверждение (отправляется в etcp_connections_read_callback) снимаем таймаут и выставляем флаг struct ETCP_LINK initialized=1 |
||||
|
||||
|
||||
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда только одна секция). в обязательном заголовке ID не используется, при передаче для порядка =0: |
||||
|
||||
г) Init запрос - заголовок 0x02 (со сбросом etcp сессии) или 0x04 (бес сброса): |
||||
[0x02/0x04] [my_node_id 64bit] [my mtu high] [my mty low] [keepalive high] [keepalive low] [my publick key (64 байта, не шифруется)] |
||||
- Инициирует новый connection для tcp instance. если tcp instance нет (первое подключение) - создаёт. Между нодами только одно подключение, но можно добавлять каналы. |
||||
- Публичный ключ отправляется в конце пакета без шифрования, чтобы получатель мог установить его и расшифровать остальную часть пакета |
||||
|
||||
д) Init подтверждение - заголовок 0x03: |
||||
[0x03] [my_node_id 64bit] [my mtu high] [my mty low] |
||||
- Подтверждение инициализации |
||||
|
||||
При получении init получатель пакета должен: |
||||
- reset ETCP_LINK с этим ip_port если он есть |
||||
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - вызвать etcp_reset (функция сброса окон неподтвержденных данных и нумерации) |
||||
|
||||
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (сборка, дефрагментация в ETCP_CONN output_queue) |
||||
ETCP_CONN output_queue -> |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,407 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include <assert.h> |
||||
#include "../lib/ll_queue.h" |
||||
#include "../lib/u_async.h" |
||||
|
||||
// Тестовые данные
|
||||
struct test_data { |
||||
int value; |
||||
char name[32]; |
||||
}; |
||||
|
||||
// Простой контекст uasync для тестов
|
||||
struct test_context { |
||||
struct UASYNC* ua; |
||||
int callback_count; |
||||
}; |
||||
|
||||
// Коллбэк для тестов
|
||||
void test_callback(struct ll_queue* q, struct ll_entry* entry, void* arg) { |
||||
struct test_context* ctx = (struct test_context*)arg; |
||||
ctx->callback_count++; |
||||
printf("Callback called for entry %p\n", entry); |
||||
} |
||||
|
||||
// Базовый тест двусвязного списка
|
||||
void test_doubly_linked_basics() { |
||||
printf("=== Тест базовых операций двусвязного списка ===\n"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
|
||||
assert(q != NULL); |
||||
assert(queue_entry_count(q) == 0); |
||||
|
||||
// Создать и добавить несколько элементов
|
||||
for (int i = 0; i < 5; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
assert(entry != NULL); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = i; |
||||
snprintf(data->name, sizeof(data->name), "item_%d", i); |
||||
|
||||
assert(queue_entry_put(q, entry) == 0); |
||||
} |
||||
|
||||
assert(queue_entry_count(q) == 5); |
||||
|
||||
// Проверить, что prev/next указатели корректны
|
||||
struct ll_entry* curr = q->head; |
||||
int count = 0; |
||||
while (curr) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(curr); |
||||
printf(" Forward: %s (value=%d)\n", data->name, data->value); |
||||
|
||||
// Проверить двусвязность
|
||||
if (curr->prev) { |
||||
struct test_data* prev_data = (struct test_data*)ll_entry_data(curr->prev); |
||||
printf(" prev: %s\n", prev_data->name); |
||||
} |
||||
if (curr->next) { |
||||
struct test_data* next_data = (struct test_data*)ll_entry_data(curr->next); |
||||
printf(" next: %s\n", next_data->name); |
||||
} |
||||
|
||||
curr = curr->next; |
||||
count++; |
||||
} |
||||
assert(count == 5); |
||||
|
||||
// Проверить обратный проход
|
||||
curr = q->tail; |
||||
count = 0; |
||||
while (curr) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(curr); |
||||
printf(" Backward: %s (value=%d)\n", data->name, data->value); |
||||
curr = curr->prev; |
||||
count++; |
||||
} |
||||
assert(count == 5); |
||||
|
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
printf("✓ Базовые операции двусвязного списка работают\n\n"); |
||||
} |
||||
|
||||
// Тест навигации по позициям
|
||||
void test_position_navigation() { |
||||
printf("=== Тест навигации по позициям ===\n"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
|
||||
// Добавить тестовые данные
|
||||
for (int i = 0; i < 5; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = i; |
||||
snprintf(data->name, sizeof(data->name), "item_%d", i); |
||||
queue_entry_put(q, entry); |
||||
} |
||||
|
||||
struct ll_queue_pos* pos = queue_pos_new(q); |
||||
assert(pos != NULL); |
||||
|
||||
// Тест reset
|
||||
queue_pos_reset(pos); |
||||
assert(queue_pos_index(pos) == -1); |
||||
assert(queue_pos_current(pos) == NULL); |
||||
|
||||
// Тест next
|
||||
int count = 0; |
||||
while (queue_pos_next(pos)) { |
||||
struct ll_entry* entry = queue_pos_current(pos); |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
printf(" Position %d: %s (value=%d)\n", queue_pos_index(pos), data->name, data->value); |
||||
assert(queue_pos_index(pos) == count); |
||||
assert(data->value == count); |
||||
count++; |
||||
} |
||||
assert(count == 5); |
||||
|
||||
// Тест prev
|
||||
int prev_count = 0; |
||||
while (queue_pos_prev(pos)) { |
||||
struct ll_entry* entry = queue_pos_current(pos); |
||||
if (entry) { // Проверить, что entry не NULL
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
printf(" Back Position %d: %s (value=%d)\n", queue_pos_index(pos), data->name, data->value); |
||||
} else { |
||||
printf(" Back Position %d: NULL (before head)\n", queue_pos_index(pos)); |
||||
} |
||||
prev_count++; |
||||
} |
||||
assert(prev_count == 5); |
||||
|
||||
// Тест seek
|
||||
assert(queue_pos_seek(pos, 2) == 1); |
||||
assert(queue_pos_index(pos) == 2); |
||||
struct ll_entry* entry = queue_pos_current(pos); |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
assert(data->value == 2); |
||||
printf(" Seek to 2: %s (value=%d)\n", data->name, data->value); |
||||
|
||||
queue_pos_free(pos); |
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
printf("✓ Навигация по позициям работает\n\n"); |
||||
} |
||||
|
||||
// Тест вставки и удаления
|
||||
void test_insert_remove_operations() { |
||||
printf("=== Тест вставки и удаления ===\n"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
|
||||
// Создать начальную очередь: [0, 1, 2, 3, 4]
|
||||
for (int i = 0; i < 5; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = i; |
||||
snprintf(data->name, sizeof(data->name), "item_%d", i); |
||||
queue_entry_put(q, entry); |
||||
} |
||||
|
||||
struct ll_queue_pos* pos = queue_pos_new(q); |
||||
|
||||
// Тест вставки перед элементом 2
|
||||
queue_pos_seek(pos, 2); |
||||
struct ll_entry* new_entry = queue_entry_new(sizeof(struct test_data)); |
||||
struct test_data* new_data = (struct test_data*)ll_entry_data(new_entry); |
||||
new_data->value = 99; |
||||
snprintf(new_data->name, sizeof(new_data->name), "inserted_before_2"); |
||||
|
||||
assert(queue_pos_insert(pos, new_entry, 0) == 0); // Вставить перед
|
||||
assert(queue_entry_count(q) == 6); |
||||
|
||||
// Проверить вставку
|
||||
queue_pos_seek(pos, 2); |
||||
struct ll_entry* check_entry = queue_pos_current(pos); |
||||
struct test_data* check_data = (struct test_data*)ll_entry_data(check_entry); |
||||
assert(check_data->value == 99); |
||||
printf(" Inserted before position 2: %s\n", check_data->name); |
||||
|
||||
// Тест вставки после элемента 2 (теперь это вставленный элемент)
|
||||
new_entry = queue_entry_new(sizeof(struct test_data)); |
||||
new_data = (struct test_data*)ll_entry_data(new_entry); |
||||
new_data->value = 88; |
||||
snprintf(new_data->name, sizeof(new_data->name), "inserted_after_2"); |
||||
|
||||
assert(queue_pos_insert(pos, new_entry, 1) == 0); // Вставить после
|
||||
assert(queue_entry_count(q) == 7); |
||||
|
||||
// Проверить вставку
|
||||
queue_pos_next(pos); |
||||
check_entry = queue_pos_current(pos); |
||||
check_data = (struct test_data*)ll_entry_data(check_entry); |
||||
assert(check_data->value == 88); |
||||
printf(" Inserted after position 2: %s\n", check_data->name); |
||||
|
||||
// Тест удаления
|
||||
queue_pos_seek(pos, 3); // Вернемся к вставленному элементу
|
||||
struct ll_entry* removed_entry = queue_pos_remove(pos); |
||||
assert(removed_entry != NULL); |
||||
struct test_data* removed_data = (struct test_data*)ll_entry_data(removed_entry); |
||||
assert(removed_data->value == 88); |
||||
printf(" Removed element: %s\n", removed_data->name); |
||||
assert(queue_entry_count(q) == 6); |
||||
|
||||
queue_entry_free(removed_entry); |
||||
|
||||
// Тест удаления по указателю
|
||||
// Найдем элемент с value = 99 и удалим его
|
||||
struct ll_entry* curr = q->head; |
||||
while (curr) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(curr); |
||||
if (data->value == 99) { |
||||
assert(queue_entry_remove(q, curr) == 0); |
||||
queue_entry_free(curr); |
||||
break; |
||||
} |
||||
curr = curr->next; |
||||
} |
||||
assert(queue_entry_count(q) == 5); |
||||
printf(" Removed element by pointer: item_99\n"); |
||||
|
||||
queue_pos_free(pos); |
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
printf("✓ Вставка и удаление работают\n\n"); |
||||
} |
||||
|
||||
// Стресс-тест
|
||||
void test_stress() { |
||||
printf("=== Стресс-тест ===\n"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
|
||||
const int iterations = 1000; |
||||
const int max_queue_size = 50; |
||||
|
||||
srand(time(NULL)); |
||||
|
||||
for (int i = 0; i < iterations; i++) { |
||||
int operation = rand() % 4; |
||||
|
||||
switch (operation) { |
||||
case 0: { // Добавить в конец
|
||||
struct ll_entry* entry = queue_entry_new(sizeof(int)); |
||||
int* value = (int*)ll_entry_data(entry); |
||||
*value = rand(); |
||||
queue_entry_put(q, entry); |
||||
break; |
||||
} |
||||
case 1: { // Добавить в начало
|
||||
struct ll_entry* entry = queue_entry_new(sizeof(int)); |
||||
int* value = (int*)ll_entry_data(entry); |
||||
*value = rand(); |
||||
queue_entry_put_first(q, entry); |
||||
break; |
||||
} |
||||
case 2: { // Удалить из начала
|
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
if (entry) { |
||||
queue_entry_free(entry); |
||||
} |
||||
break; |
||||
} |
||||
case 3: { // Произвольная операция с позицией
|
||||
if (queue_entry_count(q) > 0) { |
||||
struct ll_queue_pos* pos = queue_pos_new(q); |
||||
int index = rand() % queue_entry_count(q); |
||||
|
||||
if (queue_pos_seek(pos, index)) { |
||||
// Случайная операция: вставка или удаление
|
||||
if (rand() % 2) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(int)); |
||||
int* value = (int*)ll_entry_data(entry); |
||||
*value = rand(); |
||||
queue_pos_insert(pos, entry, rand() % 2); |
||||
} else { |
||||
struct ll_entry* removed = queue_pos_remove(pos); |
||||
if (removed) { |
||||
queue_entry_free(removed); |
||||
} |
||||
} |
||||
} |
||||
queue_pos_free(pos); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Проверить размер очереди
|
||||
if (queue_entry_count(q) > max_queue_size) { |
||||
// Удалить лишние элементы
|
||||
while (queue_entry_count(q) > max_queue_size / 2) { |
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
if (entry) { |
||||
queue_entry_free(entry); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Периодическая проверка целостности
|
||||
if (i % 100 == 0) { |
||||
int count = 0; |
||||
struct ll_entry* curr = q->head; |
||||
while (curr) { |
||||
count++; |
||||
// Проверить двусвязность
|
||||
if (curr->prev) { |
||||
assert(curr->prev->next == curr); |
||||
} |
||||
if (curr->next) { |
||||
assert(curr->next->prev == curr); |
||||
} |
||||
curr = curr->next; |
||||
} |
||||
assert(count == queue_entry_count(q)); |
||||
} |
||||
} |
||||
|
||||
printf(" Прошло %d случайных операций\n", iterations); |
||||
printf(" Финальный размер очереди: %d\n", queue_entry_count(q)); |
||||
|
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
printf("✓ Стресс-тест пройден\n\n"); |
||||
} |
||||
|
||||
// Тест совместимости существующего API
|
||||
void test_api_compatibility() { |
||||
printf("=== Тест совместимости API ===\n"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
|
||||
// Тест старого API
|
||||
struct ll_entry* entries[5]; |
||||
for (int i = 0; i < 5; i++) { |
||||
entries[i] = queue_entry_new(sizeof(struct test_data)); |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entries[i]); |
||||
data->value = i; |
||||
snprintf(data->name, sizeof(data->name), "compat_%d", i); |
||||
} |
||||
|
||||
// Добавить в конец
|
||||
for (int i = 0; i < 5; i++) { |
||||
assert(queue_entry_put(q, entries[i]) == 0); |
||||
} |
||||
|
||||
assert(queue_entry_count(q) == 5); |
||||
|
||||
// Извлечь из начала
|
||||
for (int i = 0; i < 5; i++) { |
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
assert(entry == entries[i]); // Проверить порядок FIFO
|
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
printf(" Extracted: %s (value=%d)\n", data->name, data->value); |
||||
assert(data->value == i); |
||||
|
||||
queue_entry_free(entry); |
||||
} |
||||
|
||||
assert(queue_entry_count(q) == 0); |
||||
|
||||
// Тест put_first
|
||||
for (int i = 0; i < 3; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(int)); |
||||
int* value = (int*)ll_entry_data(entry); |
||||
*value = i; |
||||
queue_entry_put_first(q, entry); |
||||
} |
||||
|
||||
// Извлечь в обратном порядке (LIFO)
|
||||
for (int i = 0; i < 3; i++) { |
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
int* value = (int*)ll_entry_data(entry); |
||||
printf(" LIFO extracted: %d (expected %d)\n", *value, 2-i); |
||||
assert(*value == 2-i); |
||||
queue_entry_free(entry); |
||||
} |
||||
|
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
printf("✓ Совместимость API сохранена\n\n"); |
||||
} |
||||
|
||||
int main() { |
||||
printf("=== Запуск тестов doubly-linked ll_queue ===\n\n"); |
||||
|
||||
test_doubly_linked_basics(); |
||||
test_position_navigation(); |
||||
test_insert_remove_operations(); |
||||
test_stress(); |
||||
test_api_compatibility(); |
||||
|
||||
printf("=== Все тесты пройдены успешно! ===\n"); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,752 @@
|
||||
/**
|
||||
* Unified comprehensive test suite for ll_queue module |
||||
* Combines: basic operations, position navigation, doubly-linked list, stress testing |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include <assert.h> |
||||
#include <unistd.h> |
||||
#include <sys/time.h> |
||||
|
||||
#include "../lib/ll_queue.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/debug_config.h" |
||||
|
||||
/* Test statistics */ |
||||
static struct { |
||||
int tests_run; |
||||
int tests_passed; |
||||
int tests_failed; |
||||
|
||||
/* Performance metrics */ |
||||
double total_time_ms; |
||||
int operations_count; |
||||
|
||||
/* Error tracking */ |
||||
int memory_errors; |
||||
int invalid_parameter_errors; |
||||
} test_stats = {0}; |
||||
|
||||
/* Test result tracking */ |
||||
#define TEST_START(name) do { \ |
||||
printf("TEST: %s... ", name); \
|
||||
test_stats.tests_run++; \
|
||||
} while(0) |
||||
|
||||
#define TEST_PASS() do { \ |
||||
printf("PASS\n"); \
|
||||
test_stats.tests_passed++; \
|
||||
} while(0) |
||||
|
||||
#define TEST_FAIL(msg) do { \ |
||||
printf("FAIL: %s\n", msg); \
|
||||
test_stats.tests_failed++; \
|
||||
} while(0) |
||||
|
||||
#define ASSERT_TRUE(cond, msg) do { \ |
||||
if (!(cond)) { \
|
||||
TEST_FAIL(msg); \
|
||||
return; \
|
||||
} \
|
||||
} while(0) |
||||
|
||||
#define ASSERT_FALSE(cond, msg) ASSERT_TRUE(!(cond), msg) |
||||
#define ASSERT_NULL(ptr, msg) ASSERT_TRUE((ptr) == NULL, msg) |
||||
#define ASSERT_NOT_NULL(ptr, msg) ASSERT_TRUE((ptr) != NULL, msg) |
||||
#define ASSERT_EQ(a, b, msg) ASSERT_TRUE((a) == (b), msg) |
||||
#define ASSERT_NEQ(a, b, msg) ASSERT_TRUE((a) != (b), msg) |
||||
|
||||
/* Test data structure */ |
||||
struct test_data { |
||||
int value; |
||||
char name[32]; |
||||
int checksum; |
||||
}; |
||||
|
||||
/* Utility functions */ |
||||
static double get_time_ms() { |
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0; |
||||
} |
||||
|
||||
static struct test_data* create_test_data(int value) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
if (!entry) return NULL; |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = value; |
||||
snprintf(data->name, sizeof(data->name), "item_%d", value); |
||||
data->checksum = value * 7 + 3; // Simple checksum
|
||||
|
||||
return data; |
||||
} |
||||
|
||||
static int verify_test_data(struct test_data* data) { |
||||
if (!data) return 0; |
||||
return data->checksum == (data->value * 7 + 3); |
||||
} |
||||
|
||||
/* Test: Basic doubly-linked list operations */ |
||||
static void test_doubly_linked_basics() { |
||||
TEST_START("doubly_linked_basics"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
ASSERT_NOT_NULL(ua, "Failed to create uasync"); |
||||
|
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
ASSERT_NOT_NULL(q, "Failed to create queue"); |
||||
ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty"); |
||||
|
||||
// Create and add test elements
|
||||
for (int i = 0; i < 5; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
ASSERT_NOT_NULL(entry, "Failed to create entry"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = i; |
||||
snprintf(data->name, sizeof(data->name), "item_%d", i); |
||||
data->checksum = i * 7 + 3; |
||||
|
||||
ASSERT_EQ(queue_entry_put(q, entry), 0, "Failed to add entry to queue"); |
||||
} |
||||
|
||||
ASSERT_EQ(queue_entry_count(q), 5, "Wrong queue count"); |
||||
|
||||
// Verify forward traversal with doubly-linked integrity
|
||||
struct ll_entry* curr = q->head; |
||||
int forward_count = 0; |
||||
while (curr) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(curr); |
||||
ASSERT_TRUE(verify_test_data(data), "Data corruption in forward traversal"); |
||||
ASSERT_EQ(data->value, forward_count, "Wrong value in forward traversal"); |
||||
|
||||
// Verify doubly-linked integrity
|
||||
if (curr->prev) { |
||||
ASSERT_NOT_NULL(curr->prev->next, "Broken prev link"); |
||||
ASSERT_EQ(curr->prev->next, curr, "Invalid prev->next link"); |
||||
} |
||||
if (curr->next) { |
||||
ASSERT_NOT_NULL(curr->next->prev, "Broken next link"); |
||||
ASSERT_EQ(curr->next->prev, curr, "Invalid next->prev link"); |
||||
} |
||||
|
||||
curr = curr->next; |
||||
forward_count++; |
||||
} |
||||
ASSERT_EQ(forward_count, 5, "Wrong forward count"); |
||||
|
||||
// Verify backward traversal
|
||||
curr = q->tail; |
||||
int backward_count = 4; |
||||
while (curr) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(curr); |
||||
ASSERT_TRUE(verify_test_data(data), "Data corruption in backward traversal"); |
||||
ASSERT_EQ(data->value, backward_count, "Wrong value in backward traversal"); |
||||
curr = curr->prev; |
||||
backward_count--; |
||||
} |
||||
ASSERT_EQ(backward_count, -1, "Wrong backward count"); |
||||
|
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
TEST_PASS(); |
||||
} |
||||
|
||||
/* Test: Position navigation system */ |
||||
static void test_position_navigation() { |
||||
TEST_START("position_navigation"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
ASSERT_NOT_NULL(ua, "Failed to create uasync"); |
||||
|
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
ASSERT_NOT_NULL(q, "Failed to create queue"); |
||||
|
||||
// Add test elements
|
||||
for (int i = 0; i < 5; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
ASSERT_NOT_NULL(entry, "Failed to create entry"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = i; |
||||
snprintf(data->name, sizeof(data->name), "item_%d", i); |
||||
data->checksum = i * 7 + 3; |
||||
|
||||
ASSERT_EQ(queue_entry_put(q, entry), 0, "Failed to add entry"); |
||||
} |
||||
|
||||
struct ll_queue_pos* pos = queue_pos_new(q); |
||||
ASSERT_NOT_NULL(pos, "Failed to create position"); |
||||
|
||||
// Test initial state
|
||||
ASSERT_EQ(queue_pos_index(pos), -1, "Wrong initial index"); |
||||
ASSERT_NULL(queue_pos_current(pos), "Current should be NULL initially"); |
||||
|
||||
// Test forward navigation
|
||||
int count = 0; |
||||
while (queue_pos_next(pos)) { |
||||
struct ll_entry* entry = queue_pos_current(pos); |
||||
ASSERT_NOT_NULL(entry, "Current entry should not be NULL"); |
||||
ASSERT_EQ(queue_pos_index(pos), count, "Wrong index in forward navigation"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
ASSERT_TRUE(verify_test_data(data), "Data corruption in position navigation"); |
||||
ASSERT_EQ(data->value, count, "Wrong value in forward navigation"); |
||||
count++; |
||||
} |
||||
ASSERT_EQ(count, 5, "Wrong forward navigation count"); |
||||
|
||||
// Test backward navigation
|
||||
int prev_count = 0; |
||||
while (queue_pos_prev(pos)) { |
||||
struct ll_entry* entry = queue_pos_current(pos); |
||||
// Note: entry can be NULL when at position -1 (before head)
|
||||
if (entry) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
ASSERT_TRUE(verify_test_data(data), "Data corruption in backward navigation"); |
||||
} |
||||
prev_count++; |
||||
} |
||||
ASSERT_EQ(prev_count, 5, "Wrong backward navigation count"); |
||||
|
||||
// Test seek functionality
|
||||
ASSERT_EQ(queue_pos_seek(pos, 2), 1, "Seek to index 2 failed"); |
||||
ASSERT_EQ(queue_pos_index(pos), 2, "Wrong index after seek"); |
||||
|
||||
struct ll_entry* entry = queue_pos_current(pos); |
||||
ASSERT_NOT_NULL(entry, "Current entry should not be NULL after seek"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
ASSERT_TRUE(verify_test_data(data), "Data corruption after seek"); |
||||
ASSERT_EQ(data->value, 2, "Wrong value after seek"); |
||||
|
||||
// Test invalid seek
|
||||
ASSERT_EQ(queue_pos_seek(pos, -2), 0, "Invalid seek should fail"); |
||||
ASSERT_EQ(queue_pos_seek(pos, 10), 0, "Out of bounds seek should fail"); |
||||
|
||||
queue_pos_free(pos); |
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
TEST_PASS(); |
||||
} |
||||
|
||||
/* Test: Position-based insert and remove operations */ |
||||
static void test_position_insert_remove() { |
||||
TEST_START("position_insert_remove"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
ASSERT_NOT_NULL(ua, "Failed to create uasync"); |
||||
|
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
ASSERT_NOT_NULL(q, "Failed to create queue"); |
||||
|
||||
// Create initial queue: [0, 1, 2, 3, 4]
|
||||
for (int i = 0; i < 5; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
ASSERT_NOT_NULL(entry, "Failed to create entry"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = i; |
||||
snprintf(data->name, sizeof(data->name), "item_%d", i); |
||||
data->checksum = i * 7 + 3; |
||||
|
||||
ASSERT_EQ(queue_entry_put(q, entry), 0, "Failed to add entry"); |
||||
} |
||||
|
||||
struct ll_queue_pos* pos = queue_pos_new(q); |
||||
ASSERT_NOT_NULL(pos, "Failed to create position"); |
||||
|
||||
// Test insertion before position 2
|
||||
ASSERT_EQ(queue_pos_seek(pos, 2), 1, "Failed to seek to position 2"); |
||||
|
||||
struct ll_entry* new_entry = queue_entry_new(sizeof(struct test_data)); |
||||
ASSERT_NOT_NULL(new_entry, "Failed to create new entry"); |
||||
|
||||
struct test_data* new_data = (struct test_data*)ll_entry_data(new_entry); |
||||
new_data->value = 99; |
||||
snprintf(new_data->name, sizeof(new_data->name), "inserted_before_2"); |
||||
new_data->checksum = 99 * 7 + 3; |
||||
|
||||
ASSERT_EQ(queue_pos_insert(pos, new_entry, 0), 0, "Failed to insert before position"); |
||||
ASSERT_EQ(queue_entry_count(q), 6, "Wrong count after insertion"); |
||||
|
||||
// Verify insertion (refresh position first due to version change)
|
||||
queue_pos_refresh(pos); // Обновить позицию после изменения очереди
|
||||
ASSERT_EQ(queue_pos_seek(pos, 2), 1, "Failed to seek after insertion"); |
||||
struct ll_entry* check_entry = queue_pos_current(pos); |
||||
ASSERT_NOT_NULL(check_entry, "Current entry should not be NULL after insertion"); |
||||
|
||||
struct test_data* check_data = (struct test_data*)ll_entry_data(check_entry); |
||||
ASSERT_TRUE(verify_test_data(check_data), "Data corruption after insertion"); |
||||
ASSERT_EQ(check_data->value, 99, "Wrong value after insertion"); |
||||
|
||||
// Test insertion after position (refresh position first, then create new entry)
|
||||
queue_pos_refresh(pos); // Обновить позицию после предыдущей вставки
|
||||
|
||||
struct ll_entry* new_entry2 = queue_entry_new(sizeof(struct test_data)); |
||||
ASSERT_NOT_NULL(new_entry2, "Failed to create second new entry"); |
||||
|
||||
struct test_data* new_data2 = (struct test_data*)ll_entry_data(new_entry2); |
||||
new_data2->value = 88; |
||||
snprintf(new_data2->name, sizeof(new_data2->name), "inserted_after_2"); |
||||
new_data2->checksum = 88 * 7 + 3; |
||||
|
||||
ASSERT_EQ(queue_pos_insert(pos, new_entry2, 1), 0, "Failed to insert after position"); |
||||
ASSERT_EQ(queue_entry_count(q), 7, "Wrong count after second insertion"); |
||||
|
||||
// Test removal (refresh position first)
|
||||
queue_pos_refresh(pos); // Обновить позицию после вставки
|
||||
struct ll_entry* removed_entry = queue_pos_remove(pos); |
||||
ASSERT_NOT_NULL(removed_entry, "Failed to remove entry"); |
||||
ASSERT_EQ(queue_entry_count(q), 6, "Wrong count after removal"); |
||||
|
||||
struct test_data* removed_data = (struct test_data*)ll_entry_data(removed_entry); |
||||
ASSERT_TRUE(verify_test_data(removed_data), "Data corruption in removed entry"); |
||||
ASSERT_EQ(removed_data->value, 99, "Wrong value in removed entry"); |
||||
|
||||
queue_entry_free(removed_entry); |
||||
|
||||
// Test removal by pointer (find element with value 88 - the other inserted element)
|
||||
struct ll_entry* curr = q->head; |
||||
int found = 0; |
||||
while (curr) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(curr); |
||||
if (data->value == 88) { // Target the element that was inserted after position 2
|
||||
ASSERT_EQ(queue_entry_remove(q, curr), 0, "Failed to remove by pointer"); |
||||
queue_entry_free(curr); |
||||
found = 1; |
||||
break; |
||||
} |
||||
curr = curr->next; |
||||
} |
||||
ASSERT_TRUE(found, "Element with value 88 not found in queue after operations"); |
||||
ASSERT_EQ(queue_entry_count(q), 5, "Wrong count after pointer removal"); |
||||
|
||||
queue_pos_free(pos); |
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
TEST_PASS(); |
||||
} |
||||
|
||||
/* Test: Stress testing with random operations */ |
||||
static void test_stress_random_operations() { |
||||
TEST_START("stress_random_operations"); |
||||
|
||||
double start_time = get_time_ms(); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
ASSERT_NOT_NULL(ua, "Failed to create uasync"); |
||||
|
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
ASSERT_NOT_NULL(q, "Failed to create queue"); |
||||
|
||||
const int iterations = 1000; |
||||
const int max_queue_size = 50; |
||||
|
||||
srand(time(NULL)); |
||||
|
||||
for (int i = 0; i < iterations; i++) { |
||||
int operation = rand() % 5; // 5 types of operations
|
||||
|
||||
switch (operation) { |
||||
case 0: { // Add to tail
|
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
if (entry) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = rand() % 1000; |
||||
data->checksum = data->value * 7 + 3; |
||||
queue_entry_put(q, entry); |
||||
test_stats.operations_count++; |
||||
} |
||||
break; |
||||
} |
||||
case 1: { // Add to head
|
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
if (entry) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = rand() % 1000; |
||||
data->checksum = data->value * 7 + 3; |
||||
queue_entry_put_first(q, entry); |
||||
test_stats.operations_count++; |
||||
} |
||||
break; |
||||
} |
||||
case 2: { // Remove from head
|
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
if (entry) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
if (!verify_test_data(data)) { |
||||
test_stats.memory_errors++; |
||||
} |
||||
queue_entry_free(entry); |
||||
test_stats.operations_count++; |
||||
} |
||||
break; |
||||
} |
||||
case 3: { // Position-based operations
|
||||
if (queue_entry_count(q) > 0) { |
||||
struct ll_queue_pos* pos = queue_pos_new(q); |
||||
if (pos) { |
||||
int index = rand() % queue_entry_count(q); |
||||
if (queue_pos_seek(pos, index)) { |
||||
if (rand() % 2) { |
||||
// Insert operation
|
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
if (entry) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = rand() % 1000; |
||||
data->checksum = data->value * 7 + 3; |
||||
queue_pos_insert(pos, entry, rand() % 2); |
||||
test_stats.operations_count++; |
||||
} |
||||
} else { |
||||
// Remove operation
|
||||
struct ll_entry* removed = queue_pos_remove(pos); |
||||
if (removed) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(removed); |
||||
if (!verify_test_data(data)) { |
||||
test_stats.memory_errors++; |
||||
} |
||||
queue_entry_free(removed); |
||||
test_stats.operations_count++; |
||||
} |
||||
} |
||||
} |
||||
queue_pos_free(pos); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
case 4: { // Random remove by pointer
|
||||
if (queue_entry_count(q) > 0) { |
||||
// Find random element to remove
|
||||
int target_index = rand() % queue_entry_count(q); |
||||
struct ll_entry* curr = q->head; |
||||
for (int j = 0; j < target_index && curr; j++) { |
||||
curr = curr->next; |
||||
} |
||||
if (curr) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(curr); |
||||
if (verify_test_data(data)) { |
||||
queue_entry_remove(q, curr); |
||||
queue_entry_free(curr); |
||||
test_stats.operations_count++; |
||||
} |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Keep queue size reasonable
|
||||
while (queue_entry_count(q) > max_queue_size) { |
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
if (entry) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
if (!verify_test_data(data)) { |
||||
test_stats.memory_errors++; |
||||
} |
||||
queue_entry_free(entry); |
||||
} |
||||
} |
||||
|
||||
// Periodic integrity check
|
||||
if (i % 100 == 0) { |
||||
int count = 0; |
||||
struct ll_entry* curr = q->head; |
||||
while (curr) { |
||||
count++; |
||||
// Verify doubly-linked integrity
|
||||
if (curr->prev) { |
||||
if (curr->prev->next != curr) { |
||||
test_stats.memory_errors++; |
||||
} |
||||
} |
||||
if (curr->next) { |
||||
if (curr->next->prev != curr) { |
||||
test_stats.memory_errors++; |
||||
} |
||||
} |
||||
curr = curr->next; |
||||
} |
||||
if (count != queue_entry_count(q)) { |
||||
test_stats.memory_errors++; |
||||
} |
||||
} |
||||
} |
||||
|
||||
double end_time = get_time_ms(); |
||||
test_stats.total_time_ms += (end_time - start_time); |
||||
|
||||
ASSERT_EQ(test_stats.memory_errors, 0, "Memory corruption detected during stress test"); |
||||
ASSERT_TRUE(test_stats.operations_count > iterations / 2, "Too few operations performed"); |
||||
|
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
TEST_PASS(); |
||||
} |
||||
|
||||
/* Test: API compatibility with existing code */ |
||||
static void test_api_compatibility() { |
||||
TEST_START("api_compatibility"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
ASSERT_NOT_NULL(ua, "Failed to create uasync"); |
||||
|
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
ASSERT_NOT_NULL(q, "Failed to create queue"); |
||||
|
||||
// Test FIFO operations (existing API)
|
||||
struct ll_entry* entries[5]; |
||||
for (int i = 0; i < 5; i++) { |
||||
entries[i] = queue_entry_new(sizeof(struct test_data)); |
||||
ASSERT_NOT_NULL(entries[i], "Failed to create entry"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entries[i]); |
||||
data->value = i; |
||||
snprintf(data->name, sizeof(data->name), "fifo_%d", i); |
||||
data->checksum = i * 7 + 3; |
||||
} |
||||
|
||||
// Add to tail (FIFO)
|
||||
for (int i = 0; i < 5; i++) { |
||||
ASSERT_EQ(queue_entry_put(q, entries[i]), 0, "Failed to add entry (FIFO)"); |
||||
} |
||||
ASSERT_EQ(queue_entry_count(q), 5, "Wrong count after FIFO adds"); |
||||
|
||||
// Extract from head (FIFO)
|
||||
for (int i = 0; i < 5; i++) { |
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
ASSERT_NOT_NULL(entry, "Failed to get entry (FIFO)"); |
||||
ASSERT_EQ(entry, entries[i], "Wrong order in FIFO extraction"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
ASSERT_TRUE(verify_test_data(data), "Data corruption in FIFO"); |
||||
ASSERT_EQ(data->value, i, "Wrong value in FIFO extraction"); |
||||
|
||||
queue_entry_free(entry); |
||||
} |
||||
ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty after FIFO extraction"); |
||||
|
||||
// Test LIFO operations (existing API)
|
||||
for (int i = 0; i < 3; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
ASSERT_NOT_NULL(entry, "Failed to create entry"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = i; |
||||
data->checksum = i * 7 + 3; |
||||
|
||||
ASSERT_EQ(queue_entry_put_first(q, entry), 0, "Failed to add entry (LIFO)"); |
||||
} |
||||
ASSERT_EQ(queue_entry_count(q), 3, "Wrong count after LIFO adds"); |
||||
|
||||
// Extract from head (LIFO - should be reverse order)
|
||||
for (int i = 0; i < 3; i++) { |
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
ASSERT_NOT_NULL(entry, "Failed to get entry (LIFO)"); |
||||
|
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
ASSERT_TRUE(verify_test_data(data), "Data corruption in LIFO"); |
||||
ASSERT_EQ(data->value, 2 - i, "Wrong order in LIFO extraction"); |
||||
|
||||
queue_entry_free(entry); |
||||
} |
||||
ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty after LIFO extraction"); |
||||
|
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
TEST_PASS(); |
||||
} |
||||
|
||||
/* Test: Performance benchmarks */ |
||||
static void test_performance_benchmarks() { |
||||
TEST_START("performance_benchmarks"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
ASSERT_NOT_NULL(ua, "Failed to create uasync"); |
||||
|
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
ASSERT_NOT_NULL(q, "Failed to create queue"); |
||||
|
||||
const int benchmark_size = 10000; |
||||
double start_time, end_time; |
||||
|
||||
// Benchmark: Sequential add operations
|
||||
start_time = get_time_ms(); |
||||
for (int i = 0; i < benchmark_size; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
if (entry) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
data->value = i; |
||||
data->checksum = i * 7 + 3; |
||||
queue_entry_put(q, entry); |
||||
} |
||||
} |
||||
end_time = get_time_ms(); |
||||
printf("\n Sequential add (%d ops): %.2f ms (%.2f ops/sec)",
|
||||
benchmark_size, end_time - start_time,
|
||||
benchmark_size * 1000.0 / (end_time - start_time)); |
||||
|
||||
// Benchmark: Sequential remove operations
|
||||
start_time = get_time_ms(); |
||||
for (int i = 0; i < benchmark_size; i++) { |
||||
struct ll_entry* entry = queue_entry_get(q); |
||||
if (entry) { |
||||
struct test_data* data = (struct test_data*)ll_entry_data(entry); |
||||
if (!verify_test_data(data)) { |
||||
test_stats.memory_errors++; |
||||
} |
||||
queue_entry_free(entry); |
||||
} |
||||
} |
||||
end_time = get_time_ms(); |
||||
printf("\n Sequential remove (%d ops): %.2f ms (%.2f ops/sec)",
|
||||
benchmark_size, end_time - start_time, |
||||
benchmark_size * 1000.0 / (end_time - start_time)); |
||||
|
||||
// Benchmark: Position navigation
|
||||
// Rebuild queue for navigation benchmark
|
||||
for (int i = 0; i < benchmark_size / 10; i++) { |
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
if (entry) { |
||||
queue_entry_put(q, entry); |
||||
} |
||||
} |
||||
|
||||
struct ll_queue_pos* pos = queue_pos_new(q); |
||||
ASSERT_NOT_NULL(pos, "Failed to create position for benchmark"); |
||||
|
||||
start_time = get_time_ms(); |
||||
int navigation_ops = 0; |
||||
for (int i = 0; i < benchmark_size / 10; i++) { |
||||
// Forward and backward navigation
|
||||
while (queue_pos_next(pos)) navigation_ops++; |
||||
while (queue_pos_prev(pos)) navigation_ops++; |
||||
|
||||
// Random seek operations
|
||||
int target = rand() % queue_entry_count(q); |
||||
queue_pos_seek(pos, target); |
||||
navigation_ops++; |
||||
} |
||||
end_time = get_time_ms(); |
||||
printf("\n Position navigation (%d ops): %.2f ms (%.2f ops/sec)",
|
||||
navigation_ops, end_time - start_time, |
||||
navigation_ops * 1000.0 / (end_time - start_time)); |
||||
|
||||
queue_pos_free(pos); |
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
TEST_PASS(); |
||||
} |
||||
|
||||
/* Test: Edge cases and error conditions */ |
||||
static void test_edge_cases() { |
||||
TEST_START("edge_cases"); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
ASSERT_NOT_NULL(ua, "Failed to create uasync"); |
||||
|
||||
// Test NULL parameter handling
|
||||
ASSERT_NULL(queue_pos_new(NULL), "Should return NULL for NULL queue"); |
||||
ASSERT_NULL(queue_entry_get(NULL), "Should return NULL for NULL queue"); |
||||
ASSERT_EQ(queue_entry_put(NULL, NULL), -1, "Should fail on NULL parameters"); |
||||
|
||||
struct ll_queue* q = queue_new(ua, NULL); |
||||
ASSERT_NOT_NULL(q, "Failed to create queue"); |
||||
|
||||
struct ll_queue_pos* pos = queue_pos_new(q); |
||||
ASSERT_NOT_NULL(pos, "Failed to create position"); |
||||
|
||||
// Test operations on empty queue
|
||||
ASSERT_NULL(queue_entry_get(q), "Should return NULL from empty queue"); |
||||
ASSERT_NULL(queue_pos_current(pos), "Should return NULL for empty queue"); |
||||
ASSERT_EQ(queue_pos_seek(pos, 0), 0, "Seek should fail on empty queue"); |
||||
|
||||
// Test navigation on empty queue
|
||||
ASSERT_EQ(queue_pos_next(pos), 0, "Next should fail on empty queue"); |
||||
ASSERT_EQ(queue_pos_prev(pos), 0, "Prev should fail on empty queue"); |
||||
|
||||
// Test single element queue
|
||||
struct ll_entry* entry = queue_entry_new(sizeof(struct test_data)); |
||||
ASSERT_NOT_NULL(entry, "Failed to create entry"); |
||||
|
||||
ASSERT_EQ(queue_entry_put(q, entry), 0, "Failed to add single entry"); |
||||
ASSERT_EQ(queue_entry_count(q), 1, "Wrong count for single entry"); |
||||
|
||||
// Test navigation with single element
|
||||
ASSERT_EQ(queue_pos_seek(pos, 0), 1, "Should seek to single element"); |
||||
ASSERT_NOT_NULL(queue_pos_current(pos), "Should have current element"); |
||||
ASSERT_EQ(queue_pos_next(pos), 0, "Next should fail at end"); |
||||
ASSERT_EQ(queue_pos_seek(pos, 1), 0, "Should fail to seek beyond single element"); |
||||
|
||||
// Test removal of single element
|
||||
struct ll_entry* removed = queue_pos_remove(pos); |
||||
ASSERT_NOT_NULL(removed, "Should remove single element"); |
||||
ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty after removal"); |
||||
queue_entry_free(removed); |
||||
|
||||
// Test size limit enforcement
|
||||
queue_set_size_limit(q, 2); |
||||
|
||||
struct ll_entry* e1 = queue_entry_new(sizeof(struct test_data)); |
||||
struct ll_entry* e2 = queue_entry_new(sizeof(struct test_data)); |
||||
struct ll_entry* e3 = queue_entry_new(sizeof(struct test_data)); |
||||
|
||||
ASSERT_NOT_NULL(e1, "Failed to create entry 1"); |
||||
ASSERT_NOT_NULL(e2, "Failed to create entry 2"); |
||||
ASSERT_NOT_NULL(e3, "Failed to create entry 3"); |
||||
|
||||
ASSERT_EQ(queue_entry_put(q, e1), 0, "Should add first entry"); |
||||
ASSERT_EQ(queue_entry_put(q, e2), 0, "Should add second entry"); |
||||
ASSERT_EQ(queue_entry_put(q, e3), -1, "Should reject third entry (size limit)"); |
||||
ASSERT_EQ(queue_entry_count(q), 2, "Wrong count with size limit"); |
||||
|
||||
queue_entry_free(e3); // Free the rejected entry
|
||||
|
||||
queue_pos_free(pos); |
||||
queue_free(q); |
||||
uasync_destroy(ua); |
||||
TEST_PASS(); |
||||
} |
||||
|
||||
/* Main test runner */ |
||||
int main() { |
||||
printf("=== Unified Comprehensive ll_queue Test Suite ===\n"); |
||||
printf("Starting tests at %s\n", __TIME__); |
||||
|
||||
double suite_start_time = get_time_ms(); |
||||
|
||||
// Run all tests
|
||||
test_doubly_linked_basics(); |
||||
test_position_navigation(); |
||||
test_position_insert_remove(); |
||||
test_stress_random_operations(); |
||||
test_api_compatibility(); |
||||
test_performance_benchmarks(); |
||||
test_edge_cases(); |
||||
|
||||
double suite_end_time = get_time_ms(); |
||||
double total_suite_time = suite_end_time - suite_start_time; |
||||
|
||||
printf("\n=== Test Suite Summary ===\n"); |
||||
printf("Tests run: %d\n", test_stats.tests_run); |
||||
printf("Tests passed: %d\n", test_stats.tests_passed); |
||||
printf("Tests failed: %d\n", test_stats.tests_failed); |
||||
printf("Memory errors: %d\n", test_stats.memory_errors); |
||||
printf("Total operations: %d\n", test_stats.operations_count); |
||||
printf("Total time: %.2f ms\n", total_suite_time); |
||||
printf("Average ops/sec: %.2f\n", test_stats.operations_count * 1000.0 / total_suite_time); |
||||
|
||||
if (test_stats.tests_failed == 0 && test_stats.memory_errors == 0) { |
||||
printf("\n✅ ALL TESTS PASSED - ll_queue module is working correctly!\n"); |
||||
return 0; |
||||
} else { |
||||
printf("\n❌ TEST FAILURES DETECTED - Check output above for details\n"); |
||||
return 1; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue