On this page
What a machine-interface command is waiting on
How a command of the machine interface says what it waits on while it waits, and refuses a request that would stand behind the vault.
What a command is waiting on
A command that waits on the fleet store, a lock, a host or a child process says so on stderr before it waits, so a command that has been quiet for twenty minutes names the request it stands on instead of looking hung. Every wait writes up to three lines, each one write ending in a newline:
czekam: <what>; gdzie: <where>; rodzaj: <kind>; od: <UTC time, RFC 3339>; id: <pid>-<n>
koniec czekania: <what>; trwalo: <seconds>s; id: <pid>-<n>
blad czekania: <what>; gdzie: <where>; trwalo: <seconds>s; przyczyna: <cause>; id: <pid>-<n>
czekamis written before the wait begins.koniec czekaniafollows when what was waited for arrived;blad czekaniawhen the wait failed, with the operation's own error and every cause under it.rodzajis one ofobject-api(a fleet-store read, write, listing or stat),blokada(a lock another process holds),host(a channel to a host),proces(a child process),baza(a database),siec(another network service),terminal(a key or a line typed on the terminal the program runs on),zgoda(the operator's answer to a grant a program asked for) orsesja(another agent session's work). The last three are waits nothing answers by itself; Oko's wait watch acts on them by their kind (oko sessions waits).idis the waiting process and the wait's number in it. Waits interleave, so the id pairs a start with its end. Aczekamline with no end under its id, from a process still alive, is a wait still open: it has lasted fromoduntil now.- Fields are separated by
;and each field after the first isname: value. A value never holds;or a line break; they are replaced by,and a space. - An object API request reads as its method, path and decoded query, and
where it waits as
object APIand the store's origin, for exampleczekam: GET /api/object uri=stado://probierz/registry.json versioned=true; gdzie: object API <store origin>; rodzaj: object-api; ….
Nothing limits how long a wait may take and nothing repeats it: the lines
only make it visible. Stdout, and with it every --json envelope, never
carries them. Oko's oko sessions agents reads the same lines from an agent
session's tool output and terminal, and shows each open wait with its age.
A blad czekania line is the end of that request, not a pause before the
next one. A dependency that refuses the connection — nothing listens at the
address, as with an object API that is not running — has answered: an object
or release read ends the command at once, non-zero, with the operation, the
address and the refusal as its cause (the object API refused the object GET after 0 bytes: …), and sends no second request into the same refusal. A
ranged read whose range is cut short or whose connection drops ends the same
way, naming how many bytes arrived.
Every open wait is also a file in a register on its machine,
$STADO_WAITS_DIR or ~/.stado/waits/<pid>-<n>, holding its czekam
line; the file is written before the line and removed when the wait ends or
fails, so a wait is visible however the waiting program's stderr was
filtered. stado waits reads the register:
stado waits
stado waits --json
register <directory>: <n> open, <m> ended
open czekam: …; id: <pid>-<n>
ended czekam: …; id: <pid>-<n> (process <pid> no longer runs; removed from the register)
--json prints {"register": <directory>, "open": [...], "ended": [...]},
each wait as {"id", "pid", "line"}. A process killed while it waited never
removes its file; the reading that finds such a wait reports it under
ended, with the line it held when its process died, and removes its file,
so the next reading holds only waits a running process stands in. The
refusals, each with the exit status of its class:
the register of open waits cannot be read: neither STADO_WAITS_DIR nor HOME is set, so the register of open waits has no directory
the register of open waits cannot be read: <file> is not named <pid>-<n>, so it is no wait of this register; remove it
the register of open waits cannot be read: reading <file>: <cause>
the register of open waits cannot be read: listing <directory>: <cause>
the register of open waits cannot be read: process <pid> no longer runs, so its wait <id> ended, but removing <file>: <cause>
A wait a program does not perform in Rust itself — a grant dialog in a harness, a terminal read a supervisor sees standing, another session an executor waits on — is registered for the waiting process by whoever knows of it, with the same line:
stado waits open --kind <kind> --what <what> --where <where> --pid <pid> [--awaits <kind>:<id>] [--json]
stado waits close <id> [--json]
open writes <pid>-<n> into the register, n being the moment of
registration in nanoseconds so it never meets the numbers that process
counts its own waits by, and prints registered <id>: process <pid> waits on <kind> for <what> at <where> or, with --json, {"id", "pid", "kind", "what", "where"}. The wait is listed like every other and ends when its
process does, or with close, which prints closed <id> or, for a wait
already gone, <id> was no longer in the register: it had ended or its process was gone ({"id", "removed"} with --json). For example, a
harness about to show a grant dialog for a build runs stado waits open --kind zgoda --what build:stado --where "grant dialog" --pid <its pid>, and
closes it when the dialog is answered. Refused, with exit status 2:
stado waits open --kind takes one of the register's kinds, siec, object-api, host, blokada, proces, baza, terminal, zgoda, sesja; got <word>
stado waits open --what names what the wait is for and where; it was empty
and with the exit status of its class, the register of open waits cannot be written: <id> is not a wait id: an id is <pid>-<n>, as stado waits lists it, … creating <directory>: <cause>, … writing <file>: <cause>
or … removing <file>: <cause>. The real journey cargo test --manifest-path stado-rs/Cargo.toml --test wait-registered registers a zgoda wait for
its own process and a terminal wait for a child it kills, and checks the
listing, the close and every refusal above.
A wait on a lock file whose file names its holder as "pid": <n> — every
product installation's surface lock — is also an edge of the machine's
wait-for graph: beside the
register, <register>.graph/<id> (~/.stado/waits.graph/<id>) holds the
wait's resource, here lock:<path>, until the wait ends. A lock is one kind of
resource the graph knows; a wait can await any of them, each written
<kind>:<id>:
| kind | what it is | who holds it |
|---|---|---|
lock |
a lock file | the "pid" its own file names, and any recorded holding |
host |
a fleet host a process keeps a channel to or waits to reach | a recorded holding |
service |
a service by name | the process serving it, as recorded |
revision |
a product revision, <product>@<revision> |
the operation delivering it, as recorded |
unit |
a service-manager unit | the process managing it, as recorded |
port |
a listening port, <address>:<port> |
the process bound to it, as recorded |
What a process holds beside a lock file's own record lives beside the
register too, <register>.holds/<pid>-<n> (~/.stado/waits.holds/…), one
resource per file. A Rust program records its own holding with
stado_wait::hold(&resource), released when the answer is dropped; any
other program records it for a process that holds something:
stado waits hold --resource <kind>:<id> --pid <pid> [--json]
stado waits release <id> [--json]
stado waits holds [--json]
hold prints recorded <id>: process <pid> holds <kind> <id> ({"id", "pid", "resource"}); release prints released <id>, or <id> was no longer recorded: it had been released or its process was gone ({"id", "removed"}); holds lists every holding of a running process ({"holds": [{"id", "pid", "resource"}]}) and removes those of processes that ended. A
wait registered for another process awaits a resource with stado waits open … --awaits <kind>:<id>, which adds "awaits" to its answer and , awaiting <kind> <id> to its line.
Before a wait on a resource starts — stado_wait::begin_on,
blocking_on, until_on, blocking_on_lock, or stado waits open --awaits — Stado follows the graph from the resource's holders: each
holder, every resource that holder waits on, those resources' holders, and
so on. When a chain reaches the waiting process itself, or a process whose
completion needs it (one of its ancestors, which waits for it to end), the
wait could only stand until someone killed a process by hand, so it never
starts. Its line says why:
czekanie odmowione: <what>; gdzie: <kind> <id>; przyczyna: a wait cycle: process <pid> (<command>) waits on <kind> <id>, held by process <holder>; process <holder> (<command>) waits on <kind> <id>, held by process <next>; …; process <last holder> needs process <pid> to end, so none of these waits can end
and the command fails: a lock wait with waiting for <lock> was refused: a wait cycle: …, stado waits open --awaits with the refusal class's status
and the wait was not registered: it would close a wait cycle: …. An
installation started by a step of an installation holding the same
surface's lock is refused at once this way; so is an installation whose
lock's holder waits on a lock its own parent holds, and an installation
whose converge waits on a service whose process waits for a product revision
that installation holds. A resource no running process holds ends every
chain through it, so a wait whose chains end there starts as before. A
graph that cannot be read does not stop a program's own wait; graf czekan nieodczytany: <kind> <id>; przyczyna: <cause> stands beside its line, and
graf czekan niezapisany: id: <id>; przyczyna: <cause> when its edge or a
holding's release cannot be written. stado waits open --awaits asked for
the check, so a graph it cannot read is its refusal: the register of open waits cannot be written: the wait-for graph cannot be read: <cause>. Each
refusal of a resource names what is wrong, with exit status 2:
stado waits hold --resource: "<text>" is no resource: a resource is <kind>:<id>, kind one of lock, host, service, revision, unit, port
stado waits open --awaits: <word> is no resource kind; a resource is <kind>:<id>, kind one of lock, host, service, revision, unit, port
stado waits hold --resource: a <kind> resource needs an id that is not empty and holds no control character; got "<id>"
and release of something that is not an id fails with <id> is not a holding id: an id is <pid>-<n>, as stado waits holds lists it.
A cycle can still form after its waits began, when a resource passes to a
new holder. stado waits cycles names every cycle standing among the waits
of running processes, once each:
stado waits cycles [--json]
With none it prints no wait cycle stands among this machine's waits and
exits 0. With any it fails with the refusal class's status and <n> wait cycle(s) stand on this machine; end one process in each: <cycle> | <cycle>; --json prints {"cycles": [{"links": [{"pid", "command", "awaits", "held_by"}], "closed_by", "said"}]} on stdout either way, each
awaits as <kind>:<id>. A graph it cannot read fails with the wait-for graph of this machine's waits cannot be read: <cause>. Stado Desktop's
Waits section reads the same commands with Read wait cycles, showing
each cycle's text, and Read held resources, showing each holding as
process <pid> holds <kind>:<id>. The real journey cargo test --manifest-path stado-rs/Cargo.toml --test wait-cycle-install-lock holds a
product's real surface lock and runs stado product install … --wait three
ways: under a lock its own parent holds (refused, naming the cycle), under a
lock a process that waits on nothing holds (the wait stands, no cycle
listed), and through another install's wait (refused, then a standing cycle
that stado waits cycles names). The real journey cargo test --manifest-path stado-rs/Cargo.toml --test wait-cycle-resource runs stado waits hold | open --awaits | cycles | release | holds for itself and a
child: the child holds service:example-api and waits for
revision:example@next; a wait of the test on the service while it holds
the revision is refused naming both links, one while nothing holds the
revision is registered, a cycle formed by holding the revision afterwards
is listed and ends with its release, and each malformed resource and id is
refused as above.
The lines come from one library, the stado-wait crate in Stado's
repository (stado-rs/wait), which Stado re-exports as stado::wait and
which any other Rust program of the fleet depends on by revision instead of
copying it. Its calls take what is waited for, where, and the wait itself:
until for a future, blocking for blocking work, begin with done,
failed or settle for a wait that spans a loop, send, request and
request_blocking for an HTTP request (method and path only, never the
query), output, status and their _async forms for a child process, and
sdk for an AWS SDK call. A database is waited on by name and credential
item, and a child whose command line carries a credential (psql with its
URL) by the statement it runs. Tama's block-silent-waits refuses a write
that adds a Rust request, child-process wait, file lock or TCP connection
outside these calls. The HTTP and AWS SDK waits are the crate's http
feature and the cancellable and tokio child waits its async feature,
both on by default; a program that waits only on blocking work depends on
it with default-features = false and takes neither reqwest nor tokio,
keeping blocking, begin, output, status, child_output, the
register of open waits and the wait-for graph.
A request that would stand behind the vault is refused with the line
The object API compares every bearer against Skarbiec: a namespace bearer's
version on each object request, the bearer itself when the version moved,
the release publisher's, the host-health role's and the service and machine
clients' on each request of theirs. Each of those reads is a wait the server
writes as above, under a key (the namespace, release:<item>, host-health,
service:<item>, machine:<item>). A request is never queued behind
another's read:
- A namespace bearer read holds its namespace while it runs. A request that
finds its key held is answered
503at once, with the body{"error":"object authorization unavailable","cause":"czekam: …"}— theczekamline of the read it would have stood behind, so the caller reads what the server waits on, where and since when. - Once a read under a key failed, every later request under it is answered
503with that failure'sblad czekanialine ascause(the vault's own answer inprzyczyna, for exampleHTTP 403orHTTP 503with gpg'swaiting for lock (held by <pid>)), except the first, which reads again while holding the key; its success clears the failure. - A request gated on a closed authorization boundary answers the same body
with the boundary's label, its verifier's last sentence and when that
verdict was reached as
cause. Theerrorword names the boundary (object,release,service,registry,host-health); the machine routes carry the same cause in theirAUTH_UNAVAILABLEmessage.
GET /api/state.json carries the same state under vault:
consultations.<key>.open is the czekam line of the read holding the key
and consultations.<key>.failed the blad czekania line of its last failed
read, each null when there is none; keyring_lock_repair is the declared
skarbiec crypto repair the vault host's own object API last ran because a
vault answer named the process holding the keyring's lock — its holder,
started and outcome (null while it runs) — or null
(Declared repair). stado doctor's gateway-auth
row names the same holder and the repair when the vault answers its verifiers
that way. Nothing here times a wait out: the server's own read still runs to
the vault's answer, and only the requests that would have queued behind it
are refused.
Source: this website