Skip to main content

Module early

Module early 

Source
Expand description

Records that could not be delivered when they were raised: before any sink was installed, or while another thread was inside a sink (issue 2000).

§The problem this exists for

crate::init publishes the sink list. A record raised before that call has nowhere to go, and for most of this crate’s life it was constructed, dispatched and DROPPED — silently, and invisibly to its author, who cannot know what the board did before reaching their code.

Issue 0708 answered by requiring every board boot funnel to call init_default(), gated on funnels spelled pub fn run*. That is a SEARCH for boot paths and it kept losing: NuttX’s funnel is pub extern "C" fn nsh_main, and three board crates did not link nros-log in the configuration holding the funnel at all.

Issue 0710 answered by having dispatch install the platform sink itself. That removed the search — but it put nros_platform_log_write on a path EVERY binary executes, which turned a pluggable delivery into a LINK-TIME requirement. Seven test targets in nros-rmw-cffi and most of nros-tests stopped linking under check-workspace-features, and no Cargo feature can fix that: nros-platform-cffi and nros-rmw-bridge enable nros-node/rmw-cffi unconditionally, so feature unification turns any forwarded gate back ON for every member of a workspace build. A feature is a property of the BUILD; what the question needs is a property of the BINARY.

§A funnel is not always a board method — issues 1048 and 1123

The search above kept losing for one more reason, and it is worth naming because it defeats the obvious grep: not every boot funnel lives in a board crate. nros::main! emits a different entry shape per framework, and two of them reach no BoardEntry method at all —

  • Framework::Zephyr (and its single-node sibling nros::zephyr_component_main!) emits rust_main directly, because Zephyr owns the C main and a Rust staticlib cannot take it over. That is why nros-board-zephyr has only run_tiers to install from, and why a single-tier pure-Rust Zephyr image published no sink list for two phases (issue 1123) while its log::info!s worked fine.
  • a [[bridge]] system replaces the register/spin body with nros_bridge::run_from_config_str, so it reaches no board either.

Both now call nros_platform::log::init_default() — reached through nros-platform (which re-exports nros_platform_cffi::log) because that is the crate a generated Entry and the Zephyr example leaves already declare; giving each leaf a nros-platform-cffi dep of its own is the link-time requirement 0710 rejected.

So the enumeration is board crates PLUS the framework arms of nros-macros’ main_macro.rs PLUS nros’ entry macros. Sweep it with:

grep -rn 'Framework::[A-Za-z]* => ' packages/core/nros-macros/src/main_macro.rs
grep -rn 'init_default()' packages/boards packages/core/nros-macros packages/api/nros

§What this does instead

Hold the records. A record raised with no sinks installed is copied into a bounded static ring here; crate::init drains it into whatever sinks the board actually chose. Nothing is dropped, no board can forget, and this crate touches no platform symbol to do it — the facade stays a facade.

It is also STRICTLY better than installing a default sink was: the early records land in the sink the board picked, rather than in whichever one dispatch guessed before the board had spoken.

§Cost, and how to decline it

EARLY_DEPTH * (format buffer + name + header) of static RAM, all of it in .bss. The depth is the NROS_LOG_EARLY_RECORDS knob (issue 1037; builtin 4, resolved by build.rs on the RFC-0049 ladder — it replaced the early-records-<N> features), because a 64 KB MCU and a Linux host do not want the same number. 0 declines the buffer entirely and restores the pre-0708 behaviour of dropping, with the count below still kept so the loss is at least reportable.

Overflow is counted, never silently absorbed: overflowed returns how many records did not fit, and the next delivery reports the count through the sinks (nros_log logger, at warn).

§The same queue holds records that met a BUSY dispatcher — issue 2000

Dispatch runs one record at a time: a flag is held across every sink.log(), so a sink that logs cannot recurse into itself, and two threads never write to a sink at once. Until issue 2000 a record that found the flag held was DROPPED, which was right for the recursion it was written for and wrong for everything else: a record from ANOTHER thread vanished whenever any thread was mid-delivery. nros-log cannot tell the two apart without a thread identity, which no no_std target here gives it for free.

So such a record is held here instead, and the thread holding the flag delivers it before letting go. Nobody waits and nothing interleaves inside a sink. Two properties follow, both bounded on purpose:

  • A sink that logs is not refused, it is postponed. The holder makes at most DRAIN_PASSES passes, each over the records already held when the pass began, so a sink that logs on every write adds a bounded number of lines per dispatch and can never loop or grow the stack. What is left waits for the next dispatch or crate::flush.
  • A record cannot be stranded by the hand-off. The raiser publishes the record, then tries the flag once more; the holder releases the flag, then looks for held records once more. Under SeqCst one of the two always sees the other, so either the raiser delivers it or the holder does. The only record left behind is one raised during the holder’s LAST pass, which the next dispatch or flush delivers.

The knob sizes both uses, because they are one question: how many records may be waiting for a sink at once.

Constants§

DRAIN_PASSES
Passes a dispatcher makes over the held records before it lets go. See the module docs: this is what bounds a sink that logs.

Functions§

early_depth
Records held at once. See the module docs for the trade.
emit_until_delivered
Raise a record with emit until delivered() sees it, re-raising it whenever overflowed counts a loss, for as long as keep_waiting() says to. Returns whether delivered() saw it.
overflowed
How many records did not fit and were lost, since the program started.