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

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

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

ПолеТипДопустимый диапазонЗначениеПри нарушении
opASCII 1BA(0x41) · R(0x52)Вид запросаE bad_op
waitu80255 (секунд)0 — немедленная попытка без очереди; 1..255 — предел ожидания— (всегда допустимо по типу)
leaseu81250 (секунд)Аренда; 0 и 251..255 отклонены/зарезервированыE bad_lease
owneru64 BEвесь диапазон 02^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.