Make joining a network idempotent and shut down cleanly on every path
Running `tsunagi up` twice with the same arguments failed with "network ... is already active", and then dropped the iroh endpoint without closing it. A configured network is activated automatically at startup, so the second run found it already up. `join_network` is declarative — "be a member of this network" — so joining one that is already active now succeeds and changes nothing. `activate_network` stays strict for callers that specifically want to know whether an inactive network was started. The CLI now closes the agent on the error path too, and handles SIGTERM as well as Ctrl-C, so a service manager stopping the agent gets the same clean shutdown an interactive user does. Also documents the two lookups people conflate: resolving one endpoint's address is iroh's public pkarr/DNS service and works today, which is why `--peer <endpoint-id>` needs no address; finding who is in a network is this project's `NetworkDiscovery` and is still static bootstrap only. Notes in the README and the threat model that `n0` and `direct` publish this endpoint's addresses to a public third-party service. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,42 @@ async fn a_restarted_agent_keeps_its_identity_and_reconnects() {
|
||||
drop(dir);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn joining_a_network_twice_is_not_an_error() {
|
||||
let discovery = SharedMemoryDiscovery::new();
|
||||
let (name, secret) = network("idempotent-join");
|
||||
|
||||
let agent = TestAgent::spawn(&discovery).await.unwrap();
|
||||
|
||||
// Joining is declarative: saying it twice in one run must be fine.
|
||||
let first = agent.agent.join_network(&name, &secret).await.unwrap();
|
||||
let again = agent.agent.join_network(&name, &secret).await.unwrap();
|
||||
assert_eq!(first, again);
|
||||
assert_eq!(agent.agent.list_networks().await.unwrap().len(), 1);
|
||||
|
||||
// Activating explicitly is the strict version and does report it.
|
||||
assert!(matches!(
|
||||
agent.agent.activate_network(first).await,
|
||||
Err(Error::NetworkAlreadyActive(_))
|
||||
));
|
||||
|
||||
// And after a restart, where the network came back up on its own, the
|
||||
// same command must still succeed. This is what running the CLI twice
|
||||
// does.
|
||||
let dir = agent.stop().await;
|
||||
let restarted = Agent::spawn(config_with(dir.path(), &discovery))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(restarted.is_active(first).await, "auto-start brought it up");
|
||||
let rejoined = restarted.join_network(&name, &secret).await.unwrap();
|
||||
assert_eq!(rejoined, first);
|
||||
assert!(restarted.network_status(first).await.is_ok());
|
||||
|
||||
restarted.shutdown().await;
|
||||
drop(restarted);
|
||||
drop(dir);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn readiness_does_not_wait_for_anyone_else() {
|
||||
let discovery = SharedMemoryDiscovery::new();
|
||||
|
||||
Reference in New Issue
Block a user