Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ docker run --rm --network host -v impossible-inferences-data:/data impossible-in
```

The first `docker run` is setup; the second is serve. Ordinary container startup is offline after
setup. Bridge-port publishing is intentionally unsupported because the server refuses non-loopback
setup. `docker stop` (SIGTERM) and Ctrl+C both drain in-flight requests and stop the private runtime
within the configured shutdown timeout. Bridge-port publishing is intentionally unsupported because the server refuses non-loopback
binds.

## License
Expand Down
18 changes: 17 additions & 1 deletion crates/impossible-inferences-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ async fn serve(config: EffectiveConfig) -> Result<(), Box<dyn std::error::Error>
let cancellation = CancellationToken::new();
let signal = cancellation.clone();
tokio::spawn(async move {
if tokio::signal::ctrl_c().await.is_ok() {
if shutdown_requested().await {
let _ = signal.cancel();
}
});
Expand Down Expand Up @@ -291,6 +291,22 @@ async fn serve(config: EffectiveConfig) -> Result<(), Box<dyn std::error::Error>
Ok(())
}

/// Waits for Ctrl+C or, on Unix, SIGTERM (`docker stop`, `kill`, service managers).
///
/// Returns `false` only when no shutdown signal can be observed.
async fn shutdown_requested() -> bool {
#[cfg(unix)]
if let Ok(mut terminate) =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
{
return tokio::select! {
result = tokio::signal::ctrl_c() => result.is_ok(),
received = terminate.recv() => received.is_some(),
};
}
tokio::signal::ctrl_c().await.is_ok()
}

const fn unavailable_engine_status() -> EngineStatus {
EngineStatus {
ready: false,
Expand Down
42 changes: 42 additions & 0 deletions crates/impossible-inferences-server/tests/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,45 @@ fn real_binary_enforces_public_failures_and_body_deadline() -> Result<(), Box<dy
assert!(response.contains("deadline_exceeded"));
Ok(())
}

#[cfg(unix)]
#[test]
fn real_binary_drains_and_exits_cleanly_on_sigterm() -> Result<(), Box<dyn std::error::Error>> {
let reservation = TcpListener::bind(("127.0.0.1", 0))?;
let port = reservation.local_addr()?.port();
drop(reservation);
let child = Command::new(env!("CARGO_BIN_EXE_impossible-inferences-server"))
.args([
"serve",
"--bind",
&format!("127.0.0.1:{port}"),
"--shutdown-timeout-ms",
"1000",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
let mut child = ChildGuard(child);
wait_until_live(port)?;

let status = Command::new("kill")
.args(["-TERM", &child.0.id().to_string()])
.status()?;
assert!(status.success(), "kill -TERM failed: {status}");

let deadline = Instant::now() + Duration::from_secs(5);
let exit = loop {
if let Some(exit) = child.0.try_wait()? {
break exit;
}
if Instant::now() >= deadline {
return Err("server did not exit after SIGTERM".into());
}
thread::sleep(Duration::from_millis(25));
};
assert!(
exit.success(),
"SIGTERM did not trigger graceful shutdown: {exit}"
);
Ok(())
}
Loading