use tokio::net::TcpListener;
use super::ShutdownOutcome;
use super::transports::{shutdown_signal, transport_bind, transport_result};
use crate::control::{IncarnationState, PidFileGuard, RecordUpdate};
use crate::error::ServerError;
use crate::shutdown;
use crate::state::ServerState;
pub(super) async fn serve_until_shutdown(
state: &ServerState,
pid_file_guard: &PidFileGuard,
shutdown_tx: &tokio::sync::watch::Sender<bool>,
grpc: &mut tokio::task::JoinHandle<Result<(), ServerError>>,
http: &mut tokio::task::JoinHandle<Result<(), ServerError>>,
) -> Result<shutdown::ShutdownReport, ServerError> {
Ok(tokio::select! {
result = &mut *grpc => {
flip_to_draining(pid_file_guard, "the gRPC transport returned");
transport_result("gRPC", result)?;
state.shutdown()?;
transport_exit_report(state)
},
result = &mut *http => {
flip_to_draining(pid_file_guard, "the HTTP transport returned");
transport_result("HTTP", result)?;
state.shutdown()?;
transport_exit_report(state)
},
result = shutdown_signal() => {
result?;
flip_to_draining(pid_file_guard, "a termination signal was observed");
let _receiver_count = shutdown_tx.send(true);
let report = shutdown::drain_after_first_signal(state.clone(), async {
let _ = shutdown_signal().await;
}).await?;
if !matches!(report.outcome, ShutdownOutcome::Forced) {
transport_result("gRPC", grpc.await)?;
transport_result("HTTP", http.await)?;
}
report
},
})
}
fn flip_to_draining(pid_file_guard: &PidFileGuard, because: &str) {
match pid_file_guard.update_own(|record| {
record.state = IncarnationState::Draining;
record.stage = None;
record.stage_detail = None;
}) {
Ok(RecordUpdate::Written) => {
tracing::info!(because, "home record flipped to DRAINING");
}
Ok(RecordUpdate::Unclaimed | RecordUpdate::NotOurs) => {}
Err(error) => tracing::warn!(
%error,
because,
"could not flip this home's pid record to DRAINING; a successor boot \
will read it as SERVING and refuse itself as a colliding sibling. \
Start the successor after this process exits"
),
}
}
pub(super) struct OpenedDoors {
pub(super) grpc_listener: TcpListener,
pub(super) http_listener: TcpListener,
pub(super) bound_grpc: std::net::SocketAddr,
pub(super) bound_http: std::net::SocketAddr,
pub(super) identity_pid: u32,
}
pub(super) async fn bind_doors(
pid_file_guard: &PidFileGuard,
grpc_address: std::net::SocketAddr,
http_address: std::net::SocketAddr,
drain_timeout: std::time::Duration,
) -> Result<OpenedDoors, ServerError> {
let grpc_listener = TcpListener::bind(grpc_address)
.await
.map_err(|source| transport_bind("grpc", grpc_address, source))?;
let http_listener = TcpListener::bind(http_address)
.await
.map_err(|source| transport_bind("http", http_address, source))?;
let bound_grpc = grpc_listener
.local_addr()
.map_err(|source| transport_bind("grpc", grpc_address, source))?;
let bound_http = http_listener
.local_addr()
.map_err(|source| transport_bind("http", http_address, source))?;
let update = pid_file_guard.update_own(|record| {
record.state = IncarnationState::Serving;
record.http_address = Some(bound_http);
record.grpc_address = Some(bound_grpc);
record.drain_timeout_seconds = drain_timeout.as_secs();
record.stage = None;
record.stage_detail = None;
})?;
let pid_record = pid_file_guard.record()?;
match crate::control::incarnation::probe(&pid_record) {
crate::control::IncarnationProbe::Verified { .. } => {}
other => {
tracing::error!(
pid = pid_record.pid,
probe = ?other,
"the pid record this server just wrote does not verify against its \
own live process: the incarnation instrument is unstable on this \
host, and `aion server stop`/`status` will refuse to trust the \
record. The server runs; the control verbs will not address it"
);
}
}
tracing::info!(
pid = pid_record.pid,
home_claimed = pid_file_guard.holds_claim(),
record_filled = matches!(update, RecordUpdate::Written),
"home claim resolved"
);
Ok(OpenedDoors {
grpc_listener,
http_listener,
bound_grpc,
bound_http,
identity_pid: pid_record.pid,
})
}
fn transport_exit_report(state: &ServerState) -> shutdown::ShutdownReport {
shutdown::ShutdownReport {
outcome: ShutdownOutcome::Clean,
drain_timeout: state.runtime_config().drain_timeout,
delivered_drain_requests: 0,
parked: Vec::new(),
parked_declared: Vec::new(),
managed_workers_stopped: Vec::new(),
managed_workers_unstopped: Vec::new(),
}
}