Wire protocol¶
The TCP protocol is line-based UTF-8. Every request is exactly three newline-terminated lines:
Every response is one newline-terminated line:
\r\n is accepted as a line ending; trailing \r is stripped.
Caps¶
| Constant | Value | Notes |
|---|---|---|
MaxLineBytes |
256 | Cap on cmd, key, and most arg lines. |
MaxAuthTokenBytes |
64 KiB | Larger cap for the auth token line. |
Lines longer than the cap return error with code 12. The
oversized line is drained to newline so the framing stays in sync.
Authentication¶
When --auth-token is set, the first command on a fresh connection
must be auth:
Anything else returns error_auth. The server adds a 100 ms cool-
down before closing on auth failure.
The key for auth is irrelevant; _ is convention. The token is
compared in constant time.
Commands¶
l — lock acquire (single-phase)¶
l
<key>
<timeout> [<lease_ttl>] # both in seconds
ok <token> <lease_ttl> # granted
timeout # acquire timeout fired
error_max_locks # unique-key cap reached
error_max_waiters # per-key waiter cap reached
error_lease_expired # rare; granted slot's lease expired before observation
error_limit_mismatch # this key was already created as a sem with limit != 1
timeout=0 is non-blocking (try-lock).
r — lock release¶
n — lock renew¶
n
<key>
<token> [<lease_ttl>]
ok <remaining_seconds>
error # token not held, or lease already expired
If lease_ttl is omitted, the server uses --default-lease-ttl.
e — lock enqueue (two-phase, phase 1)¶
e
<key>
[<lease_ttl>]
acquired <token> <lease_ttl> # capacity available; you hold the lock
queued # waiter registered; call `w` next
error_already_enqueued # this connection already has phase-1 state for this key
error_max_locks
error_max_waiters
error_limit_mismatch
w — lock wait (two-phase, phase 2)¶
w
<key>
<timeout>
ok <token> <lease_ttl> # grant arrived
timeout # wait timeout fired (waiter still in queue if connection stays open? no — popped)
error_not_enqueued # no matching `e` on this connection
error_lease_expired
Must be called from the same connection that issued the matching
e. The connection is what binds the two phases.
Semaphores: sl, sr, sn, se, sw¶
Identical to l/r/n/e/w but for semaphores. The differences
are the arg shapes:
sl se
<key> <key>
<timeout> <limit> [<lease>] <limit> [<lease>]
sr sn sw
<key> <key> <key>
<token> <token> [<lease>] <timeout>
The same key cannot be used as both a lock and a semaphore;
mismatching limit returns error_limit_mismatch. A semaphore
with limit=1 is wire-equivalent to a lock but the namespaces are
separate (lock: vs sem: prefix internally).
ping — keepalive¶
The two trailing fields are unused. Convention: _.
stats — server snapshot¶
Body of the <json> is the same shape as GET /v1/stats:
{
"connections": 14,
"locks": [{"key":"...","owner_conn_id":42,"lease_expires_in_s":18.5,"waiters":0}],
"semaphores": [...],
"idle_locks": [...],
"idle_semaphores": [...]
}
Status table¶
| Status | Meaning |
|---|---|
ok |
Generic success. May carry a token + lease, or stats JSON. |
acquired |
Two-phase fast path: enqueue immediately granted you the slot. |
queued |
Two-phase: waiter registered; call w/sw next. |
timeout |
Acquire/wait timeout fired without a grant. |
error |
Generic error. Token not held, or unknown protocol error. |
error_auth |
Auth handshake failed. |
error_max_locks |
--max-locks cap reached. |
error_max_waiters |
--max-waiters cap reached for this key. |
error_limit_mismatch |
Sem limit doesn't match the existing key's limit. |
error_not_enqueued |
wait without a matching enqueue on this connection. |
error_already_enqueued |
enqueue while this connection already has phase-1 state for this key. |
error_lease_expired |
Granted slot's lease expired before we could observe it. |
error_draining |
Server is in graceful shutdown. |
Disconnect semantics¶
Closing the TCP connection triggers LockManager.CleanupConnection:
- Pending waiters (
l/slblocking, ore/sefollowed by now/sw) are cancelled. The disconnected client can never observe a future grant. - Held tokens are released only when
--auto-release-on-disconnect=true(the default). With it set tofalse, held slots remain reachable via lease expiry.
Read errors and framing¶
If --read-timeout fires mid-frame, or a single line exceeds
MaxLineBytes, the server writes error and disconnects. The
framing can't be safely resumed after either condition.