refactor(app): clarify application and binary roles

Rename the application Cargo package from irtt-cli to irtt-rs
(crates/irtt-cli -> crates/irtt-app), and hard-rename its scriptable
client binary from irtt-cli to irtt-client, now that the application
also ships a first-class server and the old name reads as an umbrella
CLI for both roles.

Give each dedicated binary (irtt-client, irtt-tui, irtt-server) its
own entry point instead of funneling through the multicall
dispatcher's argv0/subcommand logic, so a dedicated binary only links
the applet code for the role it implements and always runs that role
regardless of the name it's invoked or copied under. irtt-rs remains
the feature-selectable multicall dispatcher and is now the only binary
that inspects argv0 or subcommands.

Move future application releases to the irtt-rs/vX.Y.Z tag namespace
(regenerated via `dist generate`), and bump the application package to
0.6.0 as a breaking distribution change. Library crate versions are
unaffected.
This commit is contained in:
Nils Andreas Svee committed 2026-08-20 11:44:35 +07:00
1 parent 7129ee0bc5
commit 7bea3dd54e
49 files changed
+365 -213

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
use std::{
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use irtt_server::ServerSet;
use super::args::ServerArgs;
/// How often the shutdown future samples the process-wide signal flag.
///
/// The flag a signal handler sets has no async wake mechanism, so the future
/// polls it on a timer rather than spinning. `ServerSet::run` owns everything
/// after that future becomes ready, including fanning it out to the listeners.
const SHUTDOWN_POLL_INTERVAL: Duration = Duration::from_millis(50);
/// Runs the configured listeners until the process is asked to shut down.
///
/// The applet owns exactly one current-thread Tokio runtime and one
/// [`ServerSet`], whatever number of addresses was requested. A single `--bind`
/// is an ordinary set of one and takes the same path as a set of several: one
/// construction, one shutdown future, one supervision rule. Listener
/// concurrency is async I/O concurrency and needs no worker threads.
pub fn run_server(
args: ServerArgs,
shutdown_requested: &AtomicBool,
) -> Result<(), Box<dyn std::error::Error>> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
runtime.block_on(serve(args, shutdown_requested))
}
async fn serve(
args: ServerArgs,
shutdown_requested: &AtomicBool,
) -> Result<(), Box<dyn std::error::Error>> {
let config = args.server_config();
let set = ServerSet::bind(args.bind, config).await?;
// Only once every listener is up. Announcing one address and then failing
// to bind the next would report a service that never started.
for addr in set.local_addrs() {
eprintln!("irtt-server: listening on {addr}");
}
set.run(shutdown_signal(shutdown_requested)).await?;
Ok(())
}
async fn shutdown_signal(shutdown_requested: &AtomicBool) {
while !shutdown_requested.load(Ordering::Relaxed) {
tokio::time::sleep(SHUTDOWN_POLL_INTERVAL).await;
}
}
#[cfg(test)]
mod tests {
use clap::Parser;
use super::*;
/// The ordinary invocation, and the one that keeps the single-listener case
/// on the same orchestration path as every other.
#[test]
fn an_already_requested_shutdown_binds_one_listener_and_returns() {
let args = ServerArgs::try_parse_from(["irtt-server", "--bind", "127.0.0.1:0"]).unwrap();
let shutdown_requested = AtomicBool::new(true);
run_server(args, &shutdown_requested).unwrap();
}
#[test]
fn an_already_requested_shutdown_binds_every_listener_and_returns() {
let args = ServerArgs::try_parse_from([
"irtt-server",
"--bind",
"127.0.0.1:0",
"--bind",
"127.0.0.1:0",
])
.unwrap();
let shutdown_requested = AtomicBool::new(true);
run_server(args, &shutdown_requested).unwrap();
}
}