Pick an input policy
depth = N is the real queue depth, honored exactly — default 10, maximum 64. Every unit of depth reserves a full-sized shared-memory slot, so a deep queue on an image-class topic reserves a great deal of /dev/shm. If you want more than 64, the consumer is permanently slower than the producer and the answer is a policy, not a bigger queue.
block has real constraints
block is lossless end to end, which is exactly why it constrains the graph:
- It is only installed when every consumer of the topic declares it.
- The topic must have an in-graph producer — an external publisher cannot be deferred, and the build says so.
- Under multi-process execution, the producer and every
blockconsumer land in one process group automatically, because a producer can only be deferred from inside its own process. A hand-writtenprocess_groups:that splits one is refused before any worker starts. See Run a graph across processes. - A slow consumer holds its producer back. That is the trade you are making: latency for completeness.
Cap the producer instead
When the right answer is “this node should not run this often”, cap the node rather than the edge:throttle_ms defers the node’s own tick while less than N ms have passed since its last fire. It is mutually exclusive with period_ms — a period already pins the rate — and composes with block: the tick defers if either gate fires.
Set deadlines
Deadlines are QoS, not triggers. They do not change when a node fires; they tell you when reality diverged from the contract, with a counter and a structured warning.expect_within_msis how you notice a producer that stalled without disconnecting. A non-trigger input holds its last value, so a frozen sensor otherwise looks like a healthy one publishing the same reading.promise_within_msis a promise about your output, which makes it the deadline a downstream team can hold you to.tick_within_mscatches the tick that occasionally takes 40 ms in a 20 ms loop — the one that never shows up in an average.
React in code
Handle any of these in the node itself with#[on_event]. The event’s parameter type selects the event, and the filter names the port:
Handlers run at the tail of a successful tick, in declaration order, and are edge-triggered — one event per regime, not one per occurrence. They do not run at all if
tick() returned Err. Every handler needs a filter matching its event’s scope, and there is no node-wide handler and no tick-deadline event: tick_within_ms is a counter.
BackpressureEvent fires for all three policies, so branch on event.policy. Its dropped field is the number of messages actually lost this regime — always 0 under block, which is flow control rather than loss.
Observe from outside
Every policy and deadline also increments a counter, so a diagnostic node or an operator can read the same facts without changing node code: per-input drop, decimation, and defer counts, and per-input and per-output deadline misses. Counters are the observable truth; the log line is a convenience.Next steps
Node macro reference
Every attribute and its exact validation rules.
Trigger policies
When a node fires in the first place.