Сейчас идёт тестирование: код на GitHub будет открыт после завершения.

Ограничения полей и диапазоны значений ​

Поля запроса (C→S) ​

ПолеТипДопустимый диапазонЗначениеПри нарушении
opASCII 1BA(0x41) · R(0x52)Вид запросаE bad_op
waitu80–255 (секунд)0 — немедленная попытка без очереди; 1..255 — предел ожидания— (всегда допустимо по типу)
leaseu81–250 (секунд)Аренда; 0 и 251..255 отклонены/зарезервированыE bad_lease
owneru64 BEвесь диапазон 0 – 2^64-1Идентификатор попытки захвата (создаётся клиентом)— (сервер не проверяет)
tokenu64 BEзначение, выданное серверомУказывает, что освобождать (только R)N при несовпадении
keyUTF-81 – 128 байтИмя блокировки. Пробел (0x20) и перевод строки (0x0A) недопустимыE bad_key
  • Бесконечных wait/lease нет. Официальные клиенты округляют доли секунды вверх, проверяют диапазон и до отправки возвращают явную ошибку ввода, не выполняя clamp. Аренда по умолчанию — 30 секунд.
  • В официальном client API wait — это общий предел acquire от начала вызова через local queue, повторы подключения с гарантированно неотправленным запросом и write до ответа. Один monotonic operation deadline создаётся только один раз; непосредственно перед фактической отправкой writer записывает в wait frame округлённое вверх до u8 число оставшихся секунд. Поэтому позднее подключение или гарантированно неотправленный failover не может начать server wait заново. Если к deadline запрос всё ещё находится в queue, он завершается timeout как гарантированно неотправленный; если мог быть отправлен хотя бы один байт, закрывается именно эта session и возвращается Indeterminate. Только исходный wait=0 использует wire 0; у этой немедленной попытки есть отдельный конечный I/O deadline, чтобы зависший transport не блокировал навсегда. Если уже сопоставленный перед deadline ответ A обнаружен поздно на gate выдачи Ticket, server waiter уже завершён: session не закрывается, известный exact token компенсирующе освобождается, а результатом остаётся Indeterminate. Сопоставленные T/B остаются окончательными результатами без получения lock.
  • Для исходного wait=0 поведение wire и server остаётся одной немедленной попыткой без queue. В пределах отдельного пятисекундного transport deadline гарантированно неотправленный сбой может выбрать другое подключение и повторить запрос с тем же owner. После того как мог быть отправлен хотя бы один байт, acquire никогда не повторяется.
  • Официальный API требует min_work_budget, которого нет в wire. Он покрывает критическую работу + ожидаемую паузу + завершение DB commit/rollback, лежит в 0..250 секунд и не превышает нормализованную аренду. Иначе до отправки возвращается ошибка класса UnsupportedDuration/InsufficientLease.
  • Длина ключа измеряется в байтах, а не в символах — кириллица в UTF-8 занимает 2 байта на символ, то есть не более 64 символов.
  • owner — лишь ID корреляции ответа, а не полномочие. Повтор значения не возвращает active token и не продлевает lease. Одновременные in-flight запросы клиента используют разные ненулевые значения; при 0 или wrap счётчика официальные клиенты fail-closed.
  • Явный и компенсационный release известного token имеют абсолютные 5 секунд от call/enqueue, включая переподключения и повторы. R подтверждает успех; N означает уже отсутствует или token не текущий. Отсутствие ответа не считается успехом; полная ограниченная очередь компенсации полагается на lease expiry.
  • bad_key относится и к пустому ключу, и к превышению 128 байт, и к наличию пробела или перевода строки, и к некорректному UTF-8. Официальные клиенты дополнительно никогда не отправляют \r — ключ, заканчивающийся на \r, незаметно становится другим ключом.

Поля ответа (S→C) ​

ПолеТипДиапазонВ каких ответах
tokenu64 BE1 и больше, растёт с каждой выдачейA (новая выдача) · R/N (эхо запроса)
owneru64 BEзначение запроса без измененийA · T · B (эхо запроса)
keyUTF-8значение запроса без изменений (1–128 Б)A · T · B · R · N
addrUTF-8host:portM · L
reasonASCIIодна из восьмиE

Сервер никогда не выдаёт token 0. Single использует счётчик в пределах жизни процесса; cluster — реплицируемый консенсусом счётчик и fail-closed до overflow. Wall clock и случайность не гарантируют монотонность после рестарта.

Длины фиксированных заголовков ​

Двоичный участок фиксированной длины сразу после op. Эти байты могут содержать 0x0A, поэтому парсер обязан вычитать именно столько байт до того, как начнёт искать перевод строки.

НаправлениеopФиксированный заголовокСостав
C→SA10 Bwait(1) + lease(1) + owner(8)
C→SR8 Бtoken(8)
S→CA16 Бtoken(8) + owner(8)
S→CT · B8 Бowner(8)
S→CR · N8 Бtoken(8)
S→CM · L · E0 Бнет (текст идёт сразу за op)

Если байт не хватает — E bad_request.

Размер кадра ​

ПунктЗначение
Предел кадра (без \n)192 байта — при превышении: E line_too_long
Максимальный запрос A1 + 10 + 128 + 1 = 140 Б
Максимальный запрос R1 + 8 + 128 + 1 = 138 Б
Максимальный ответ A1 + 16 + 128 + 1 = 146 Б
Пустой кадр (только \n)keep-alive — сервер игнорирует

Лимит 192B выше максимального frame (146B), поэтому корректный клиент его не достигает. После структурно неверного или oversized frame framing считается ненадёжным, и соединение закрывается.

Рукопожатие аутентификации ​

ПунктЗначение
ХешSHA-256 (указан в строке challenge)
nonce8 символов base64url
Дайджест ответа43 символа base64url (без padding)
Предел строки256 байт
Ограничение по времени10 секунд (включая TLS-согласование) — при превышении соединение закрывается

Подробная процедура — в разделе Рукопожатие аутентификации.

Ограничения сервера ​

Клиент не задаёт эти значения напрямую, но они влияют на поведение.

ЭлементПо умолчаниюНастройкаПри превышении
Waiter на key2048 (предел 16384)MAX_WAITERSB (busy)
Всего waiters16384 (предел 65536)MAX_TOTAL_WAITERSB для acquire
Одновременные client-соединения1024 (предел 8192)MAX_CONNECTIONSНемедленное закрытие
Очередь replies на соединение256compile-timeЗакрытие медленного соединения
Глобальные in-flight acquire4096compile-timeB для acquire
Глобальные in-flight release512, отдельная lanecompile-timeОграниченное ожидание до закрытия
Активные cluster keys65536compile-timeB для новой key

Read buffer (4096B), batch ответов (64), progress timeout начатого client frame (5 с) и single expiry sweep (5 с) — compile-time константы. Cluster expiry использует проверяющий token deadline min-heap вместо полного scan. См. конфигурацию.

Admission всех acquire, новых key и всех waiter закрывается на 90% hard limit и открывается лишь после снижения ниже 75%. Поэтому новый acquire может получить B до предела; резерв для release и восстановления Raft отделён от этого гистерезиса.

Нарушение → ответ: сводка ​

СитуацияОтветСоединение
Неизвестный opE bad_opзакрыто
Не хватает фиксированного заголовкаE bad_requestзакрыто
lease = 0 или 251..255E bad_leaseзакрыто
Ключ пуст / больше 128 Б / содержит пробел или перевод строки / не UTF-8E bad_keyзакрыто
Кадр больше 192 БE line_too_longзакрыто
Несовпадение дайджеста аутентификацииE auth_failedзакрыто
Очередь ожидания переполненаB + owner + keyсохраняется
Несовпадение токена при освобожденииN + token + keyсохраняется

Полный перечень причин ошибок и обработку соединения см. в разделе Ответ · Ошибка E.