diff --git a/core/crates/ployzd/src/corrosion/service.rs b/core/crates/ployzd/src/corrosion/service.rs index 6e0ac0cafa..1f685fe94d 100644 --- a/core/crates/ployzd/src/corrosion/service.rs +++ b/core/crates/ployzd/src/corrosion/service.rs @@ -120,7 +120,7 @@ impl CorrosionConfig { let admin = AdminClient::new(self.paths.admin_socket()); let docker = Docker::connect_with_socket_defaults()?; let service = DockerService { - service: ManagedService::host(docker, self.container_name.clone(), IMAGE), + service: managed(docker, self.container_name.clone()), data_dir: self.paths.data_dir.clone(), run_dir: self.paths.run_dir.clone(), }; @@ -223,11 +223,18 @@ struct InstalledFiles { /// handle, for a reset the daemon did not finish before it stopped. pub async fn remove_retained(run_dir: &Path) -> Result<(), Error> { let docker = Docker::connect_with_socket_defaults()?; - remove( - &ManagedService::host(docker, DEFAULT_CONTAINER_NAME, IMAGE), - run_dir, - ) - .await + remove(&managed(docker, DEFAULT_CONTAINER_NAME), run_dir).await +} + +/// Seconds a replaced or removed Corrosion gets after SIGTERM. Its graceful exit waits up +/// to 5 s for gossip connections that peers keep open to go idle, and the daemon waits +/// on that stop (#3680). Nothing durable rides on it: a killed Corrosion keeps every +/// committed write, its graceful exit already drops queued changes, and peers resend +/// those through sync, as after a crash. +const STOP_GRACE_SECONDS: i32 = 1; + +fn managed(docker: Docker, name: impl Into) -> ManagedService { + ManagedService::host(docker, name, IMAGE).with_stop_grace(STOP_GRACE_SECONDS) } async fn remove(service: &ManagedService, run_dir: &Path) -> Result<(), Error> { diff --git a/core/crates/ployzd/src/docker/managed_service.rs b/core/crates/ployzd/src/docker/managed_service.rs index ec6ed6ec6a..07a39128c3 100644 --- a/core/crates/ployzd/src/docker/managed_service.rs +++ b/core/crates/ployzd/src/docker/managed_service.rs @@ -7,7 +7,10 @@ use bollard::{ Docker, errors::Error as DockerError, models::{ContainerCreateBody, ContainerInspectResponse}, - query_parameters::{CreateContainerOptionsBuilder, RemoveContainerOptionsBuilder}, + query_parameters::{ + CreateContainerOptionsBuilder, RemoveContainerOptionsBuilder, StopContainerOptions, + StopContainerOptionsBuilder, + }, }; use ployz_core::PullPolicy; use sha2::{Digest as _, Sha256}; @@ -106,6 +109,7 @@ pub(crate) struct ManagedService { engine: Engine, name: String, image: &'static str, + stop_grace_seconds: Option, } #[derive(Clone)] @@ -120,6 +124,7 @@ impl ManagedService { engine: Engine::Host(docker), name: name.into(), image, + stop_grace_seconds: None, } } @@ -132,9 +137,17 @@ impl ManagedService { engine: Engine::Endpoint(docker), name: name.into(), image, + stop_grace_seconds: None, } } + /// Seconds Docker waits after SIGTERM before it kills the container, instead of + /// its 10 s default. + pub(crate) fn with_stop_grace(mut self, seconds: i32) -> Self { + self.stop_grace_seconds = Some(seconds); + self + } + pub(crate) async fn ensure_host( &self, desired: DesiredContainer, @@ -228,13 +241,22 @@ impl ManagedService { } async fn stop(&self) -> Result<(), DockerError> { - match self.docker().stop_container(&self.name, None).await { + match self + .docker() + .stop_container(&self.name, self.stop_options()) + .await + { Ok(()) => Ok(()), Err(error) if is_already_stopped(&error) || is_not_found(&error) => Ok(()), Err(error) => Err(error), } } + fn stop_options(&self) -> Option { + self.stop_grace_seconds + .map(|seconds| StopContainerOptionsBuilder::default().t(seconds).build()) + } + pub(crate) async fn remove(&self) -> Result<(), DockerError> { self.stop().await?; match self