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 и стоимость обхода таблицы требуют измерений; это цель эксперимента, не обещание производительности.