Describe OOM session interruption and normalizer ownership precisely

This commit is contained in:
evgeny committed 2026-10-23 12:42:05 +02:00
1 parent bbe43b9842
commit b1e94eff34
3 files changed
+36 -11

No files matched your search

+19
View File
@@ -324,3 +324,22 @@ only after STCP handshake. It completes the same session confirmation before INI
REINIT notifies stream consumers without tearing down physical ownership. The
router retains end-to-end inflight; BGP repeats JOIN_GROUP and TABLE_REQUEST because
its previous control messages may have been discarded by the stream reset.
Отказы памяти и прерывание потока:
- OOM до сохранения RX DATA в recv_q не создаёт ACK; отправитель повторяет DATA.
- ACK означает принятие в текущий транспортный поток, а успешный etcp_send —
передачу владения транспорту. Оба не гарантируют завершение операции сервиса
после прерывания сессии. Надёжность операции через REINIT обеспечивает её владелец.
- OOM после принятия данных в normalizer или при подготовке TX inflight вызывает
fatal REINIT: новая локальная эпоха, блокировка DATA, отбрасывание старых очередей,
частичных кодограмм и физических попыток. Старые payload не воспроизводятся.
Физические линки сохраняются; очистка очередей/reset не выделяет память.
- Активный обработчик освобождает собственный извлечённый фрагмент ровно один раз;
после reset/close из callback он прекращает разбор старой кодограммы/пакета.
- Согласование новой эпохи выполняется после очистки. OOM control-пакета оставляет
повтор активным, без продления исходного срока 10с. Отказ обязательного session-
таймера или генерации локальной эпохи закрывает соединение с диагностикой.
Если невозможно выделить даже обязательный deferred-close token внутри callback,
процесс завершается через abort: соединение не оставляется в зависшем состоянии.
- После подтверждения эпох владельцы получают REINIT и восстанавливают операции
своего протокола; при невозможности согласования соединение закрывается.
+2 -1
View File
@@ -15,7 +15,8 @@ struct ETCP_CONN;
#define ETCP_SESSION_HEADER_SIZE 17
#define ETCP_SESSION_CONTROL_SIZE 25
/* Запустить обязательное подтверждение; при таймауте неподтверждённое соединение закрывается. */
/* Запустить обязательное подтверждение: повтор через 500мс, общий срок 10с от start.
* OOM control-пакета оставляет повтор активным; OOM таймера или истечение срока закрывает соединение. */
void etcp_session_start(struct ETCP_CONN* conn);
/* Предложить ненулевую эпоху пира для challenge; это ещё не разрешает приём DATA новой эпохи. */
void etcp_session_observe(struct ETCP_CONN* conn, uint64_t peer_epoch);
+15 -10
View File
@@ -1,12 +1,12 @@
# Packet Normalizer (фрагментация/дефрагментация)
## 1. Назначение
Модуль адаптирует произвольные IP-пакеты к передаче через ETCP-канал с ограниченным MTU. Состоит из двух половин:
Модуль адаптирует кодограммы сервисов к передаче через ETCP-канал с ограниченным MTU. Содержимое кодограммы непрозрачно для normalizer. Состоит из двух половин:
- **Packer** (упаковщик) — разбивает входящие пакеты на фрагменты размером `frag_size` и помещает их в `etcp->input_queue`. Фрагменты собираются в общий буфер пачками для экономии накладных расходов, сброс по таймеру (call_soon) при пустой входной очереди.
- **Unpacker** (распаковщик) — собирает фрагменты из `etcp->output_queue` обратно в исходные пакеты по заголовку длины (2 байта LE), помещает результат в `pn->output` (который читает ETCP router/клиент).
Каждое ETCP-соединение имеет свой экземпляр `PKTNORM` (создаётся в `etcp.c:247`).
Каждое ETCP-соединение владеет своим экземпляром `PKTNORM`.
## 2. Как пользоваться
@@ -32,13 +32,18 @@ queue_data_put(pn->input, entry);
```
### Формат фрагментов
Каждый фрагмент, уходящий в ETCP, содержит конкатенацию пакетов, каждый предварён 2-байтовым LE-заголовком длины:
Поток содержит конкатенацию кодограмм, каждая предварена 2-байтовым LE-заголовком длины:
```
[packet1_len:2 LE][packet1_data][packet2_len:2 LE][packet2_data]...
```
Поток разделяется на фрагменты ETCP: данные одной кодограммы могут продолжаться в следующем фрагменте. Заголовок длины packer записывает целиком; максимально допустимая длина кодограммы — `PKTNORM_MAX_DGRAM_SIZE`.
### Сброс состояния
При разрыве/переподключении вызывать `pn_reset()`. При ошибке сборки (некорректный заголовок) вызывается `etcp_conn_reinit()` — полный перезапуск соединения.
Владелец вызывает `pn_reset()` при сбросе ETCP-потока. Он отменяет flush/waiter, очищает обе очереди normalizer и частичные буферы без выделения памяти. Сам `pn_reset()` не запускает handshake.
Неверный заголовок/длина и OOM после принятия данных вызывают `etcp_conn_fatal_reinit()`: новая локальная эпоха, очистка старого потока и согласование с пиром при сохранённых физических линках. Старые кодограммы не воспроизводятся; после согласования владельцы получают REINIT и восстанавливают операции своего протокола. До сохранения RX DATA в `recv_q` OOM обрабатывает сам ETCP: отбрасывает фрагмент без ACK.
Normalizеr не удерживает отдельную OOM-очередь или retry-таймер. После reset/close из пользовательского callback обработчик прекращает старый разбор; извлечённый им фрагмент остаётся его собственностью до однократного освобождения. Счётчик активных callbacks откладывает освобождение normalizer при закрытии.
## 3. API
@@ -50,7 +55,7 @@ queue_data_put(pn->input, entry);
|------|----------|
| `input` | Входная очередь packer (`pn_input`) — сюда кладут пакеты для отправки |
| `output` | Выходная очередь unpacker (`pn_output`) — отсюда читают собранные пакеты |
| `frag_size` | Максимальный размер одного фрагмента ETCP (`mtu - ACK_REZERV - UDP_HDR - UDP_SC_HDR`) |
| `frag_size` | Размер DATA-фрагмента: MTU минус IP/UDP overhead, `SC_ENCRYPT_OVERHEAD`, `ETCP_PAYLOAD_HEADER_SIZE` и `ACK_REZERV`; overhead учитывает IPv6-линки |
| `data` / `data_ptr` / `data_size` | Буфер-накопитель packer (из `data_pool`) |
| `recvpart` | Частично собранный пакет unpacker |
| `flush_timer` | Таймер сброса накопленного буфера (call_soon) |
@@ -66,7 +71,7 @@ queue_data_put(pn->input, entry);
**`pn_deinit(pn)`** — освобождает все ресурсы: очереди, буферы, таймеры, waiter-ы. Вызывает `routing_del_conn()` (deprecated, no-op). Снимает callback с `etcp->output_queue` для предотвращения use-after-free.
**`pn_reset(pn)`** — сбрасывает состояние packer и unpacker (буферы, pending, recvpart, таймер). Используется при переподключении.
**`pn_reset(pn)`** — отбрасывает `input`/`output`, буферы packer/unpacker, pending и recvpart; отменяет flush и waiter. Не выделяет память, допускает NULL; используется владельцем при сбросе потока. Обычные callbacks очередей сохраняются.
**`pn_unpacker_reset_state(pn)`** — сбрасывает только unpacker (recvpart).
@@ -74,17 +79,17 @@ queue_data_put(pn->input, entry);
### Внутренние callback-и
**`packer_cb`** — callback на `pn->input`. Ждёт освобождения `etcp->input_queue` через `queue_waiter_wait`, затем вызывает `etcp_input_ready_cb`.
**`packer_cb`** — callback на `pn->input`. Пока сессия согласуется или закрывается, обработку не запускает. Ждёт освобождения `etcp->input_queue` через `queue_waiter_wait`, затем вызывает `etcp_input_ready_cb`. Отказ ожидания вызывает fatal REINIT.
**`etcp_input_ready_cb`** — основной цикл packer. Извлекает пакет из `pn->input`, записывает 2-байтовый заголовок длины, копирует данные в буфер `pn->data`. При заполнении буфера (или если осталось <3 байт) вызывает `pn_send_to_etcp`. Если входная очередь пуста — ставит `flush_timer` через `uasync_call_soon` для сброса хвоста.
**`pn_flush_cb`** — таймерный callback, вызывает `pn_send_to_etcp` при простое.
**`pn_send_to_etcp(pn)`** — выделяет `ETCP_FRAGMENT` из `io_pool`, передаёт в него накопленный буфер `pn->data`, отправляет в `etcp->input_queue`. Буфер после этого обнуляется (передан во владение фрагмента).
**`pn_send_to_etcp(pn)`** — выделяет `ETCP_FRAGMENT` из `io_pool`, передаёт в него накопленный буфер `pn->data`, отправляет в `etcp->input_queue`. Указатель normalizer обнуляется до синхронного callback очереди. Возвращает 0 при успехе/пустом буфере, -1 при ошибке или прерывании сессии; ошибку выделения/постановки обрабатывает fatal REINIT.
**`pn_unpacker_cb`** — callback на `etcp->output_queue`. В цикле извлекает фрагменты, парсит 2-байтовые заголовки длины, копирует данные в `pn->recvpart`. При полной сборке пакета помещает его в `pn->output`. При ошибке вызывает `etcp_conn_reinit`.
**`pn_unpacker_cb`** — callback на `etcp->output_queue`. Извлекает фрагменты, парсит длины, копирует данные в `pn->recvpart`. Перед выдачей целой кодограммы в `pn->output` снимает `recvpart` и обновляет статистику. OOM, неверный заголовок или отказ очереди вызывают fatal REINIT. После каждого пользовательского callback проверяет reset_count/close и прекращает старый разбор при изменении сессии.
**`pn_buf_renew(pn)`** — выделяет новый буфер из `data_pool` если `pn->data == NULL`. При нехватке места (<3 байт) вызывает `pn_send_to_etcp` для сброса текущего буфера.
**`pn_buf_renew(pn)`** — выделяет буфер из `data_pool`, если `pn->data == NULL`; при нехватке места (<3 байт) сначала вызывает `pn_send_to_etcp`. Возвращает 0 при успехе, -1 при ошибке/прерывании. OOM вызывает fatal REINIT.
### Зависимости
- `etcp.h` / `etcp_api.h` — структуры `ETCP_CONN`, `ETCP_FRAGMENT`, `etcp_int_recv`