Ограничения полей и диапазоны значений
Поля запроса (C→S)
| Поле | Тип | Допустимый диапазон | Значение | При нарушении |
|---|---|---|---|---|
op | ASCII 1B | A(0x41) · R(0x52) | Вид запроса | E bad_op |
wait | u8 | 0–255 (секунд) | 0 — немедленная попытка без очереди; 1..255 — предел ожидания | — (всегда допустимо по типу) |
lease | u8 | 1–250 (секунд) | Аренда; 0 и 251..255 отклонены/зарезервированы | E bad_lease |
owner | u64 BE | весь диапазон 0 – 2^64-1 | Идентификатор попытки захвата (создаётся клиентом) | — (сервер не проверяет) |
token | u64 BE | значение, выданное сервером | Указывает, что освобождать (только R) | N при несовпадении |
key | UTF-8 | 1 – 128 байт | Имя блокировки. Пробел (0x20) и перевод строки (0x0A) недопустимы | E bad_key |
- Бесконечных wait/lease нет. Официальные клиенты округляют доли секунды вверх, проверяют диапазон и до отправки возвращают явную ошибку ввода, не выполняя clamp. Аренда по умолчанию — 30 секунд.
- В официальном client API
wait— это общий предел acquire от начала вызова через local queue, повторы подключения с гарантированно неотправленным запросом и write до ответа. Один monotonic operation deadline создаётся только один раз; непосредственно перед фактической отправкой writer записывает вwaitframe округлённое вверх до u8 число оставшихся секунд. Поэтому позднее подключение или гарантированно неотправленный failover не может начать server wait заново. Если к deadline запрос всё ещё находится в queue, он завершается timeout как гарантированно неотправленный; если мог быть отправлен хотя бы один байт, закрывается именно эта session и возвращаетсяIndeterminate. Только исходныйwait=0использует wire0; у этой немедленной попытки есть отдельный конечный 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)
| Поле | Тип | Диапазон | В каких ответах |
|---|---|---|---|
token | u64 BE | 1 и больше, растёт с каждой выдачей | A (новая выдача) · R/N (эхо запроса) |
owner | u64 BE | значение запроса без изменений | A · T · B (эхо запроса) |
key | UTF-8 | значение запроса без изменений (1–128 Б) | A · T · B · R · N |
addr | UTF-8 | host:port | M · L |
reason | ASCII | одна из восьми | E |
Сервер никогда не выдаёт token 0. Single использует счётчик в пределах жизни процесса; cluster — реплицируемый консенсусом счётчик и fail-closed до overflow. Wall clock и случайность не гарантируют монотонность после рестарта.
Длины фиксированных заголовков
Двоичный участок фиксированной длины сразу после op. Эти байты могут содержать 0x0A, поэтому парсер обязан вычитать именно столько байт до того, как начнёт искать перевод строки.
| Направление | op | Фиксированный заголовок | Состав |
|---|---|---|---|
| C→S | A | 10 B | wait(1) + lease(1) + owner(8) |
| C→S | R | 8 Б | token(8) |
| S→C | A | 16 Б | token(8) + owner(8) |
| S→C | T · B | 8 Б | owner(8) |
| S→C | R · N | 8 Б | token(8) |
| S→C | M · L · E | 0 Б | нет (текст идёт сразу за op) |
Если байт не хватает — E bad_request.
Размер кадра
| Пункт | Значение |
|---|---|
Предел кадра (без \n) | 192 байта — при превышении: E line_too_long |
Максимальный запрос A | 1 + 10 + 128 + 1 = 140 Б |
Максимальный запрос R | 1 + 8 + 128 + 1 = 138 Б |
Максимальный ответ A | 1 + 16 + 128 + 1 = 146 Б |
Пустой кадр (только \n) | keep-alive — сервер игнорирует |
Лимит 192B выше максимального frame (146B), поэтому корректный клиент его не достигает. После структурно неверного или oversized frame framing считается ненадёжным, и соединение закрывается.
Рукопожатие аутентификации
| Пункт | Значение |
|---|---|
| Хеш | SHA-256 (указан в строке challenge) |
| nonce | 8 символов base64url |
| Дайджест ответа | 43 символа base64url (без padding) |
| Предел строки | 256 байт |
| Ограничение по времени | 10 секунд (включая TLS-согласование) — при превышении соединение закрывается |
Подробная процедура — в разделе Рукопожатие аутентификации.
Ограничения сервера
Клиент не задаёт эти значения напрямую, но они влияют на поведение.
| Элемент | По умолчанию | Настройка | При превышении |
|---|---|---|---|
| Waiter на key | 2048 (предел 16384) | MAX_WAITERS | B (busy) |
| Всего waiters | 16384 (предел 65536) | MAX_TOTAL_WAITERS | B для acquire |
| Одновременные client-соединения | 1024 (предел 8192) | MAX_CONNECTIONS | Немедленное закрытие |
| Очередь replies на соединение | 256 | compile-time | Закрытие медленного соединения |
| Глобальные in-flight acquire | 4096 | compile-time | B для acquire |
| Глобальные in-flight release | 512, отдельная lane | compile-time | Ограниченное ожидание до закрытия |
| Активные cluster keys | 65536 | compile-time | B для новой 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 отделён от этого гистерезиса.
Нарушение → ответ: сводка
| Ситуация | Ответ | Соединение |
|---|---|---|
| Неизвестный op | E bad_op | закрыто |
| Не хватает фиксированного заголовка | E bad_request | закрыто |
lease = 0 или 251..255 | E bad_lease | закрыто |
| Ключ пуст / больше 128 Б / содержит пробел или перевод строки / не UTF-8 | E bad_key | закрыто |
| Кадр больше 192 Б | E line_too_long | закрыто |
| Несовпадение дайджеста аутентификации | E auth_failed | закрыто |
| Очередь ожидания переполнена | B + owner + key | сохраняется |
| Несовпадение токена при освобождении | N + token + key | сохраняется |
Полный перечень причин ошибок и обработку соединения см. в разделе Ответ · Ошибка E.