feat(grid-agent): supervise grid sessions (#121)
Some checks failed
CI / rust-skia (Rust only) (push) Successful in 2m47s
CI / required (push) Failing after 2m54s

This commit is contained in:
2026-08-17 21:56:15 +00:00
parent e3b9d575f9
commit 3553c83ffa
13 changed files with 2377 additions and 36 deletions

View File

@@ -4,6 +4,8 @@ use metacrate_grid_agent::{
use std::error::Error;
use std::fmt;
use std::path::PathBuf;
#[cfg(feature = "live-grid")]
use std::sync::Arc;
const MAX_ARGUMENTS: usize = 8;
@@ -84,9 +86,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
return Ok(());
}
if config.mode != OperatingMode::OfflineFake {
#[cfg(feature = "live-grid")]
return run_live(config, options.run_once).await;
#[cfg(not(feature = "live-grid"))]
return Err(CliError(
"this architecture issue starts only the offline backend; live login is owned by a later milestone issue"
.into(),
"live grid mode requires rebuilding with --features live-grid".into(),
)
.into());
}
@@ -133,3 +137,101 @@ async fn main() -> Result<(), Box<dyn Error>> {
println!("grid agent stopped cleanly");
Ok(())
}
#[cfg(feature = "live-grid")]
async fn run_live(
config: metacrate_grid_agent::AgentConfig,
run_once: bool,
) -> Result<(), Box<dyn Error>> {
use metacrate_grid_agent::{
GridSessionBackend, LibremetaverseClientOwner, SessionObservation, SessionState,
SessionSupervisor,
};
let connection = config.grid.clone().ok_or_else(|| {
CliError("validated live configuration did not contain a grid connection".into())
})?;
let owner = LibremetaverseClientOwner::new()?;
let backend = owner.session_backend(connection)?;
let erased: Arc<dyn GridSessionBackend> = Arc::new(backend);
let mut handle = SessionSupervisor::new(
erased,
config.reconnect,
config.limits.control_queue,
config.limits.observable_queue,
)?
.start();
if run_once {
let readiness = tokio::time::timeout(config.timeouts.startup, async {
loop {
match handle.next_observation().await {
Some(SessionObservation::Transition { status, .. }) if status.agent_ready => {
return Ok::<(), CliError>(());
}
Some(SessionObservation::Transition {
status:
metacrate_grid_agent::SessionStatus {
state: SessionState::AuthenticationBlocked,
..
},
..
}) => {
return Err(CliError(
"grid authentication/configuration requires operator action".into(),
));
}
Some(_) => {}
None => {
return Err(CliError(
"session supervisor stopped before readiness".into(),
));
}
}
}
})
.await;
let readiness = match readiness {
Ok(result) => result,
Err(_) => Err(CliError("timed out waiting for full grid readiness".into())),
};
if let Err(error) = readiness {
handle.shutdown().await?;
return Err(error.into());
}
handle.shutdown().await?;
println!("grid agent completed one supervised login/logout cycle");
return Ok(());
}
println!("grid agent session supervisor started; press Ctrl-C to stop");
let mut signal_error = None;
loop {
tokio::select! {
signal = tokio::signal::ctrl_c() => {
if let Err(error) = signal {
signal_error = Some(error);
}
break;
}
event = handle.next_observation() => {
let Some(event) = event else { break; };
if let SessionObservation::Transition { status, reason, retry_in } = event {
println!(
"grid session state={} generation={} transport_connected={} agent_ready={} reason={reason:?} retry_in={retry_in:?}",
status.state.as_str(),
status.generation,
status.transport_connected,
status.agent_ready,
);
}
}
}
}
handle.shutdown().await?;
if let Some(error) = signal_error {
return Err(error.into());
}
println!("grid agent stopped cleanly");
Ok(())
}