Перейти к содержанию

Liza 0.0.31 — TCP Proxy Operations

Статус: первый прикладной срез реализован; PR #13 слит. examples/apps/liza_proxy_ops.liza добавляет адреса, CLI и connect deadline. Исходный прокси 0.0.30 сохранён без изменений как baseline R2-B/R2-C. Idle timeout, graceful shutdown и счётчики остаются следующим срезом; полной эксплуатационной готовности этот PR не заявляет.

python -m liza build examples/apps/liza_proxy_ops.liza -O 2 -o build/liza-proxy-ops
./build/liza-proxy-ops --listen 127.0.0.1:9004 --upstream 127.0.0.1:9003 --connect-timeout-ms 5000

Исходные ограничения и совместимость

В baseline 0.0.30 main задаёт upstream 9003 и listener 9004 в исходнике. net.reactor_create и net.reactor_connect принимают только порт, runtime использует IPv4 loopback. tests/test_proxy.py подставляет upstream в исходник перед компиляцией. В baseline источника монотонного времени нет; reactor_poll(1000) ограничивает один вызов ожидания, но не время connect.

Сохраняются восемь пар, 16 сокетов reactor, два участка по 2048 байтов на пару и 32768 байтов пользовательских буферов. Новые пользовательские буферы в event loop не выделяются. Старые портовые операции остаются совместимыми loopback-обёртками, чтобы echo, router и исходный proxy не пришлось менять одновременно. Новые операции: net.ipv4(a,b,c,d,port), net.ipv4_bits(address), net.reactor_create_at(address,capacity), net.reactor_connect_at(reactor,address) и net.monotonic_ms()->Result<u64,ReactorError>. Старые операции не удалены.

Типизированный адрес

Первый net.Address — копируемое значение IPv4 endpoint: четыре октета u8 и порт u16. Это не владелец сокета, не DNS-имя и не нативный sockaddr. Структура не сериализуется через её физический layout; преобразование в сетевой byte order явно выполняется на платформенной границе.

Listener допускает порт 0 для выбора ОС. Upstream требует порт 1…65535 и не принимает unspecified/multicast/broadcast назначения. Значение адреса само по себе может представлять такие октеты; ограничения назначения проверяются отдельно. DNS, IPv6, upstream pool и автоматическое разрешение имён не входят в этот срез. По умолчанию остаётся 127.0.0.1; привязка listener к другим адресам должна быть явно запрошена.

Разбор CLI принимает только числовую форму a.b.c.d:port: четыре десятичных октета 0…255 и десятичный порт. Пробелы, знаки, пустые компоненты, хвостовые символы и октеты с ведущим нулём, кроме самого 0, отвергаются. Числа проверяются до сужения; ошибочный ввод возвращает ожидаемую ошибку, не trap. Длина endpoint ограничена 21 ASCII-байтом. Парсер работает на Liza поверх ограниченного доступа к аргументу, без скрытой аллокации строки. cli.ascii_at(arg,index)->Option<u8> читает один ASCII-символ, включая завершающий NUL. Индекс >=64, выход за конец аргумента или не-ASCII дают None. Вызов проверяет префикс до указанного индекса, поэтому его стоимость ограничена 64 кодовыми единицами; он не создаёт view или строку. POSIX использует байты, Windows — ASCII-подмножество UTF-16 argv. Разбор endpoint и duration — examples/apps/proxy/config.liza.

CLI

liza-proxy-ops [--listen 127.0.0.1:9004]
           [--upstream 127.0.0.1:9003]
           [--connect-timeout-ms 5000]
           [--once | --ephemeral]

Этот интерфейс реализован приложением liza_proxy_ops.liza. Повторные и неизвестные опции, отсутствие значения, некорректные endpoint или duration дают код 2 и диагностику в stderr до создания сокетов. Код 3 остаётся ошибкой выполнения. На stdout сохраняется единственная строка с фактическим портом listener, необходимая существующим тестам.

--once сохраняет однократное обслуживание и выбор ephemeral listener; --ephemeral выбирает ephemeral listener без ограничения одной парой. Эти два флага взаимоисключающие. С ними разрешён явный --listen только с портом 0; ненулевой порт — ошибка конфигурации, не скрытое переопределение. Оба режима допускают --upstream и --connect-timeout-ms.

Connect deadline

Default — 5000 мс, допустимый диапазон — 1…2147483647 мс. Ноль не означает бесконечное ожидание. Время монотонное, u64 в миллисекундах, с явной ошибкой получения; календарное время не используется. На POSIX используется CLOCK_MONOTONIC с усечением наносекунд до миллисекунд; на Windows — GetTickCount64. Участие suspend во времени зависит от платформы; переносимая гарантия ограничена неубыванием при нормальной работе часов. Жёсткая гарантия wall-clock задержки не заявляется.

Для попытки connect сохраняется started_ms до её запуска. При очередной обработке незавершённого подключения:

now < started_ms              -> ошибка часов, управляемое завершение
now - started_ms >= timeout   -> timeout, закрытие обоих соединений пары
иначе                        -> проверка finish_connect

Вычитание выполняется только после проверки порядка. Сложение абсолютного deadline не требуется, поэтому оно не может переполниться. Если deadline истёк в момент проверки, timeout имеет приоритет над наблюдаемым завершением connect. Если завершение уже было принято и connected=true, connect deadline больше не применяется.

Перед poll время ожидания ограничивается минимумом 1000 мс и оставшегося времени всех ожидающих подключений. После poll часы читаются снова. Уже истёкшие попытки завершаются без нового положительного ожидания. Прерывания poll и readiness не продлевают срок; таймер не перезапускается на каждой итерации. ОС может задержать исполнение, поэтому обещается обработка истечения при следующей возможности event loop, а не real-time дедлайн.

Timeout — ожидаемое завершение пары. Оба ID проходят cleanup, запись сбрасывается, слот становится доступным. Уже прочитанные от клиента байты могут быть отброшены: их доставка при ошибке connect не гарантируется. Ошибка чтения часов останавливает сервер через общий путь cleanup; она не превращается в бесконечное ожидание или арифметический trap.

Разделение реализации

Уровень Изменения первого среза
Liza stdlib Значение адреса, проверяемый разбор и доступ к ограниченному CLI-вводу
Платформенная граница IPv4 адрес для bind/connect, монотонные часы, ошибки через Result
Приложение Конфигурация, время начала connect в Pair, вычисление poll timeout и cleanup
Компилятор Только необходимые сигнатуры/ABI для новых stdlib-операций; без новой семантики Core
Тесты Один бинарник с разными конфигурациями; управляемые часы и connect для границ времени

Не требуется превращать reactor в планировщик задач. Хранение таймера пары и решение о timeout остаются в Liza. Имеющийся контракт sticky connect error и проверка SO_ERROR сохраняются. Readiness не становится гарантией успеха.

Проверки

  • Один O0/O2 бинарник без изменения исходника работает с двумя различными upstream и заданным listener; проверяются фактические адреса назначения.
  • Парсер отвергает переполнения, не-ASCII, слишком длинные и неоднозначные значения без traps и без создания сетевых ресурсов.
  • Loopback-обёртки сохраняют существующие echo/reactor/router тесты.
  • Управляемые часы проверяют границу timeout-1, timeout, timeout+1, большие значения u64, ошибку часов и защиту вычитания.
  • Управляемый незавершённый connect истекает, освобождает оба ID и позволяет повторно использовать слот; другой клиент продолжает обслуживаться.
  • Readiness и Interrupted не сбрасывают таймер. EOF клиента до connect сохраняет существующий half-close контракт до успеха либо timeout.
  • Реальные loopback-тесты проверяют бинарные потоки, backpressure, reset и half-close на Linux/macOS/Windows при O0/O2.
  • Тест timeout не зависит от случайно недоступного IP или сетевого firewall: длительное Connecting воспроизводится тестовым runtime, отдельно от настоящих интеграционных соединений.

Связь с Verified и следующие этапы

Изменение Pair и event loop меняет CFG и исходные хеши. R2-B привязан к определённым участкам IR 0.0.30: его результат нельзя перенести на новую версию редактированием номеров блоков без проверки новых предусловий. Baseline liza_proxy.liza сохранён byte-identical, новый event loop находится в liza_proxy_ops.liza. Пока отдельный перенос доказательства не сделан, верификация нового event loop не заявляется и прежние проверки не отключаются.

R2-C развивается отдельно на baseline 0.0.30. Verification Bundle V1 также независим. Наличие CLI и deadline не устанавливает end_to_end_verified=true и не расширяет гарантий сетевого runtime.

Следующий прикладной PR: idle timeout, ограниченный drain при graceful shutdown и счётчики. До него не увеличивается лимит восьми пар. Эксперимент с 1024 парами следует после управляемого освобождения: 4 МиБ relay-буферов при прежнем размере участков плюс отдельно измеряемые метаданные, сокеты и память ОС. Масштабируемый readiness и стоимость обхода таблицы требуют измерений; это цель эксперимента, не обещание производительности.