refactor: improve networking layer with enhanced engine, muxer, and diagnostics

- Refactored connection engine with better state management
- Improved muxer with enhanced protocol handling
- Updated connection bridge and diagnostics
- Added new network constants and configuration options
- Enhanced session tracking and error handling
- Updated dependencies in Cargo.toml
This commit is contained in:
Kirill
2026-06-30 12:34:04 +07:00
parent 38dfd588c1
commit 18cad76e38
16 changed files with 1051 additions and 277 deletions
+71
View File
@@ -0,0 +1,71 @@
# Стабилизация Netrunner: устранение «эффекта домино»
Краткая выжимка по правкам. Подробности — в коде (комментарии на местах).
## 1. Почему падал один leg и закрывались все стримы
Цепочка до правок:
1. `engine.rs::handle_outbound` имел **жёсткий** таймаут записи `20s`. При RTT > 2.5 с
физический сокет не успевал «слить» данные → таймаут → `TunnelWriteStuck`
writer-задача возвращает `Err` → весь `TunnelEngine::run()` выходит с `Err`,
leg целиком умирает.
2. `muxer.rs::send_to_network` (ветка данных): при первой же неудаче
`data_tx.send()` (канал мёртвого leg закрыт) — сразу `upload_failed`,
`remove_leg`, и **возврат `Err`**.
3. `bridge.rs::run_tcp_bridge` (upload): на `Err` от `send_data_safe` делал
`break``token.cancel()` → закрывался **весь мост, т.е. стрим**.
Так как `select_leg` использует sticky-привязки, на упавшем leg висели десятки
стримов. Они все одновременно упирались в закрытый канал и **разом** убивали свои
мосты — это и есть домино (`upload_failed` для 81, 79, 107 …).
## 2. Что изменено
### A. Адаптивный таймаут записи (вместо жёстких 20 с)
`muxer.rs::adaptive_write_timeout(floor)` — масштабирует таймаут как `~8 × RTT`,
с полом `floor` и потолком `60 с`. Применён:
- `engine.rs::handle_outbound` (запись в туннель), пол = 20 с;
- `bridge.rs` download (запись в локальный сокет), пол = `BRIDGE_STREAM_WRITE_TIMEOUT`.
Эффект: «медленный, но живой» leg при высоком RTT больше не убивается по таймауту.
### B. Failover вместо смерти стрима (`muxer.rs::send_to_network`)
Ветка данных теперь цикл:
- неудача `data_tx.send()``message` восстанавливается из `SendError`,
мёртвый leg эвиктится (`remove_leg`), стрим переселяется на следующий
лучший leg и **повторяет отправку**;
- `Err` возвращается **только если живых leg не осталось вовсе**.
Цикл конечен: `remove_leg` убирает leg из кэша, `select_leg` не вернёт его снова.
### C. Очистка привязок при эвикте (`muxer.rs::clear_bindings_for_leg`)
`remove_leg` / `force_remove_leg` теперь сбрасывают `stream_bindings` упавшего
leg → остальные стримы при следующей отправке честно ребалансируются, а не
долбятся в мёртвый leg.
### D. Graceful pause / buffer (`bridge.rs::run_tcp_bridge`, upload)
Если `send_data_safe` всё-таки вернул `Err` (все leg одновременно лежат), мост
**не закрывается**. Текущий чанк удерживается, идёт ретрай раз в
`STREAM_PAUSE_RETRY` (250 мс) в пределах `STREAM_PAUSE_BUDGET` (30 с). Пока мы не
читаем дальше — работает TCP backpressure, источник сам приостанавливается, данные
не теряются. Стрим закрывается только если за бюджет ни один leg не поднялся или
пришёл `token.cancel()`.
## 3. Новые константы (`constants.rs`)
- `STREAM_PAUSE_BUDGET = 30s` — сколько стрим ждёт восстановления leg.
- `STREAM_PAUSE_RETRY = 250ms` — период опроса во время паузы.
## 4. Итог
Сбой одного leg теперь = бесшовный перенос стримов на соседние leg; полный отказ
всех leg = короткая пауза с буфером, а не каскадный сброс. `leg_reconnecting`
перестаёт быть «лавиной».
## 5. Проверка
Сборку из-под агента запустить не удалось (апстрим-фильтр харнесса блокирует
вызов cargo). Запустите вручную:
```
cargo check -p netrunner-core
cargo test -p netrunner-core
```