feat(nesinit): carry the session's address out of the guest

Whatever serves media in a box knows how it can be reached, and the person who
needs to know is not in the box. Standard output here is a log file inside a
VM, so the control channel is the delivery path rather than a convenience --
which makes this init's job and not a detail of whichever component happens to
bind the port. The socket it reads was already documented as being read this
way; nothing read it.

Polled rather than read once, because an address is not a value but the best
answer so far. An endpoint discovers more ways to reach it after it binds, so
the first answer is the one that works on a local network and fails from
anywhere else. Only a changed answer is forwarded.

It dials rather than listens, which is the opposite of the relay next door and
deliberate: there the guest listens because the workload starts later, and here
the server is the long-lived one. Dialling also makes a server that has not
bound yet something to retry rather than something to wait for without knowing
whether it is coming.

The address itself is never logged. It is a capability to reach the session,
and a log inside the guest is the one place it has no reason to be.

A carrier that stops does not end a session: whatever was already reported is
still correct, and the workload's exit still has to be.
This commit is contained in:
Wanjohi
2026-09-06 13:55:35 +03:00
parent 7f7e39de60
commit 3d24a8e130
4 changed files with 391 additions and 15 deletions

View File

@@ -4,13 +4,14 @@
// the workload the channel describes, and turn the end of either into an
// ordered shutdown.
use std::path::Path;
use std::path::{Path, PathBuf};
use std::time::Duration;
use nesinit::payload::{self, Ports};
use nesinit::reap::{self, Waiters};
use nesinit::session::{self, Outcome};
use nesinit::shutdown::{self, Machine};
use nesinit::ticket;
use nesinit::workload::{Process, Workload};
use nesprotocol::lifecycle::CONTROL_PORT;
use tokio::signal::unix::{SignalKind, signal};
@@ -25,6 +26,13 @@ const GRACE: Duration = Duration::from_secs(10);
/// deep queue holds stale copies of it rather than protecting anything.
const RELAY_DEPTH: usize = 8;
/// How many addresses may be waiting to be forwarded.
///
/// Two, because only the newest one matters: an address is superseded by the
/// next one rather than added to, so a deeper queue holds stale copies of it
/// and delays the one that is current.
const ADDRESS_DEPTH: usize = 2;
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
@@ -97,8 +105,14 @@ async fn guest(waiters: &Waiters, workload: &mut Process) -> anyhow::Result<Outc
from_workload: up_rx,
};
// Started before the workload, like the relay, and for the same reason:
// whatever serves the address may bind the moment it comes up, and nothing
// here should be the reason a session waits to be reachable.
let (found_tx, mut found_rx) = tokio::sync::mpsc::channel(ADDRESS_DEPTH);
tokio::spawn(ticket::carry(PathBuf::from(ticket::SOCKET), found_tx));
let outcome = tokio::select! {
outcome = session::run(channel, workload, &mut ports) => outcome?,
outcome = session::run(channel, workload, &mut ports, &mut found_rx) => outcome?,
signal = asked_to_stop() => {
signal?;
tracing::info!("asked to stop");