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 derived partition puts the producer and every
blockconsumer in one process group, because crossing a process boundary costs a real hop. A hand-writtenprocess_groups:may split the edge, and is accepted when it is creditable — exactly one in-graph producer and no non-blockconsumers — in which case the supervisor mints a cross-process credit word and the deferred producer parks on it until the consumer drains. A split is refused before any worker starts in two shapes: amulti_publisher_topicstopic with two or more in-graph writers, and a topic whoseblockconsumers sit alongside non-blocksiblings. Co-locating the flow in one group avoids both — the limit is on crossing the boundary, not on the graph. 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.