Field Constraints and Value Ranges
Request Fields (C→S)
| Field | Type | Valid range | Meaning | On violation |
|---|---|---|---|---|
op | ASCII 1B | A(0x41) · R(0x52) | Request kind | E bad_op |
wait | u8 | 0 – 255 (seconds) | 0 is an immediate attempt with no queue; 1..255 is the acquire-wait cap | — (always valid by type) |
lease | u8 | 1 – 250 (seconds) | Lease. 0 and 251..255 are rejected/reserved | E bad_lease |
owner | u64 BE | the full 0 – 2^64-1 | Acquire-attempt identifier (client-generated) | — (server does not validate) |
token | u64 BE | a server-issued value | Names what to release (R only) | N on mismatch |
key | UTF-8 | 1 – 128 bytes | Lock name. No whitespace (0x20) or newline (0x0A) | E bad_key |
- There is no infinite wait or lease. Official clients round sub-second values up, validate the range, and return an explicit input error before sending instead of clamping an out-of-range value. The default lease is 30 seconds.
- In the official client API,
waitis the whole acquire limit from call entry through the local queue, definitely-unsent connection retries, the write, and the response. The client creates one monotonic operation deadline, and immediately before sending the writer puts the u8 ceiling of the remaining seconds into the frame'swait. A late connection or definitely-unsent failover therefore cannot restart the server wait from the beginning. At the deadline, a request still queued times out definitely unsent; if even one byte may have been sent, the exact session is closed and the result isIndeterminate. Only an originalwait=0uses wire0, and that immediate attempt has a separate finite I/O deadline so a stalled transport cannot block forever. If a correlatedAfrom just before the deadline is discovered late at the Ticket delivery gate, the server waiter is already resolved: keep the session open, compensate the known exact token, and returnIndeterminate. CorrelatedT/Bremain definitive non-acquisitions. - For an original
wait=0, wire and server behavior remain one immediate, non-queued attempt. Within its separate five-second transport deadline, a definitely-unsent failure may select another connection and retry with the same owner. Once any byte may have been sent, the acquire is never retransmitted. - The official client API requires
min_work_budget, which is not carried on the wire. It must covercritical work + expected pause + DB commit/rollback completion, be in0..250seconds, and not exceed the normalized lease. Violations fail before any frame is sent with anUnsupportedDuration/InsufficientLease-class error. - Key length is measured in bytes, not characters — Korean is 3 bytes per character in UTF-8, so at most 42 characters.
owneris only a response correlation ID, not authority. Reusing it does not return an active token or renew a lease. Concurrent in-flight requests from one client must still use distinct non-zero values so responses remain unambiguous. Official clients fail closed if their local counter reaches zero or wraps.bad_keycovers an empty key, one over 128 bytes, one containing whitespace or a newline, and invalid UTF-8, all alike. Official clients additionally never send\r— a key ending in\rwould silently become a different key.- Explicit release and compensating release for an already-known token have one absolute 5-second deadline from call/enqueue time, including reconnects and retries.
Rconfirms success;Nmeans already gone or not the current token. No observed response is never assumed to mean success. A full bounded compensation queue falls back to the lease-expiry safety net.
Response Fields (S→C)
| Field | Type | Range | Appears in |
|---|---|---|---|
token | u64 BE | 1 or greater, increasing with every grant | A (newly issued) · R/N (request echo) |
owner | u64 BE | the request value verbatim | A · T · B (request echo) |
key | UTF-8 | the request value verbatim (1–128B) | A · T · B · R · N |
addr | UTF-8 | host:port | M · L |
reason | ASCII | one of eight | E |
The server never issues token 0. Single mode uses a counter for the current process lifetime; cluster mode uses a consensus-replicated counter and fails closed before overflow. Wall clock or randomness is not claimed to provide monotonicity across restart.
Fixed-Header Lengths
The fixed-length binary run that follows the op. These bytes may contain 0x0A, so a parser must consume exactly this many before it starts scanning for the newline.
| Direction | op | Fixed header | Composition |
|---|---|---|---|
| C→S | A | 10 B | wait(1) + lease(1) + owner(8) |
| C→S | R | 8 B | token(8) |
| S→C | A | 16 B | token(8) + owner(8) |
| S→C | T · B | 8 B | owner(8) |
| S→C | R · N | 8 B | token(8) |
| S→C | M · L · E | 0 B | none (text starts right after the op) |
Too few bytes yields E bad_request.
Frame Size
| Item | Value |
|---|---|
Frame limit (excluding \n) | 192 bytes — beyond that, E line_too_long |
Largest A request | 1 + 10 + 128 + 1 = 140 B |
Largest R request | 1 + 8 + 128 + 1 = 138 B |
Largest A response | 1 + 16 + 128 + 1 = 146 B |
Empty frame (a lone \n) | keep-alive — ignored by the server |
The 192B limit sits comfortably above the largest possible frame (146B), so a client that follows the spec can never hit it. After a structurally malformed or oversized frame, framing is no longer trusted and the connection is closed.
Auth Handshake
| Item | Value |
|---|---|
| Hash | SHA-256 (named in the challenge line) |
| nonce | 8 base64url characters |
| Response digest | 43 base64url characters (unpadded) |
| Line limit | 256 bytes |
| Time limit | 10 seconds (including TLS negotiation) — the connection is closed if exceeded |
See Auth Handshake for the full procedure.
Server-Side Limits
Values a client never sets directly, but which still shape its behaviour.
| Item | Default | Setting | When exceeded |
|---|---|---|---|
| Waiters per key | 2048 (hard cap 16384) | MAX_WAITERS | B (busy) |
| Total waiters | 16384 (hard cap 65536) | MAX_TOTAL_WAITERS | That acquire gets B |
| Concurrent client connections | 1024 (hard cap 8192) | MAX_CONNECTIONS | Connection closed immediately |
| Backlogged replies per connection | 256 | (compile-time constant) | Slow client connection closed |
| Global in-flight acquire | 4096 | (compile-time constant) | That acquire gets B |
| Global in-flight release | 512, separate lane | (compile-time constant) | Bounded wait until connection close |
| Cluster active keys | 65536 | (compile-time constant) | A new-key acquire gets B |
The read buffer (4096B), response batch (64), 5-second progress timeout for a started client frame, and the single-mode expiry sweep (5 seconds) are compile-time constants, not environment variables. Cluster expiry uses a token-validating deadline min-heap rather than a full scan. For the full list of configurable values and their valid ranges, see Configuration (Env Vars).
Acquire, new-key, and total-waiter admission closes at 90% of each hard limit and reopens only after usage falls below 75%. A new acquire can therefore get B before the hard limit; capacity reserved for release and Raft recovery is separate from this hysteresis.
Violation → Response Summary
| Situation | Response | Connection |
|---|---|---|
| Unknown op | E bad_op | closed |
| Fixed header too short | E bad_request | closed |
lease = 0 or 251..255 | E bad_lease | closed |
| Key empty / over 128B / contains whitespace or newline / not UTF-8 | E bad_key | closed |
| Frame over 192B | E line_too_long | closed |
| Auth digest mismatch | E auth_failed | closed |
| Wait queue full | B + owner + key | kept |
| Release token mismatch | N + token + key | kept |
For every error reason and its connection handling, see Response · Error E.