From 844c92cc82a4d86aa4223e4c7e1bb60708a3f535 Mon Sep 17 00:00:00 2001 From: thedancingdeveloper <306930456+thedancingdeveloper@users.noreply.github.com> Date: Tue, 3 Nov 2026 03:16:30 +0000 Subject: [PATCH] fix container smoke shutdown persistence Graft-Changeset: c42142df7af0d6dd4499fef30e1a7430ca8f5693cba4d7f6dd73458651a186820 Graft-Session: ses_01M6FK3MBEFJEHGJ5GNCFWDFBM Graft-Work-Item: WI-3114 Graft-Batch: 64 --- Dockerfile | 3 ++- crates/librtbit/src/session/mod.rs | 10 +++++++++ crates/rtbit/src/main.rs | 22 ++++++++++++++----- .../s6-overlay/s6-rc.d/init-rtbit-config/run | 5 +++++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index da5a017f..792bcc24 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,8 @@ LABEL org.opencontainers.image.description="A modern BitTorrent client written i COPY --from=builder /bin/rtbit /bin/rtbit COPY root/ / -RUN mkdir -p /home/rtbit/db /home/rtbit/cache /home/rtbit/downloads /home/rtbit/completed +RUN mkdir -p /home/rtbit/db /home/rtbit/cache /home/rtbit/downloads /home/rtbit/completed \ + && ln -s com.rtbit.session /home/rtbit/db/session WORKDIR /home/rtbit diff --git a/crates/librtbit/src/session/mod.rs b/crates/librtbit/src/session/mod.rs index f74845cb..7491561b 100644 --- a/crates/librtbit/src/session/mod.rs +++ b/crates/librtbit/src/session/mod.rs @@ -1648,6 +1648,16 @@ impl Session { for torrent in &torrents { self.try_update_persistence_metadata(torrent).await; } + // A fresh session has no torrent metadata to trigger a persistence + // flush. Still write the session database during graceful shutdown so + // an empty, healthy state is durable and observable. + if torrents.is_empty() + && let Some(persistence) = self.persistence.as_ref() + && let Ok(settings) = persistence.load_session_settings().await + && let Err(error) = persistence.store_session_settings(&settings).await + { + warn!(?error, "error flushing empty session database"); + } } /// Save every torrent's state on a timer, so upload totals reach disk even if diff --git a/crates/rtbit/src/main.rs b/crates/rtbit/src/main.rs index 22f3fad9..aa752f80 100644 --- a/crates/rtbit/src/main.rs +++ b/crates/rtbit/src/main.rs @@ -1,3 +1,4 @@ +use std::sync::atomic::{AtomicBool, Ordering}; use std::{ collections::HashSet, io, @@ -788,9 +789,11 @@ fn main() -> anyhow::Result<()> { .build()?; let token = tokio_util::sync::CancellationToken::new(); + let shutdown_complete = Arc::new(AtomicBool::new(false)); #[cfg(not(target_os = "windows"))] { let token = token.clone(); + let shutdown_complete = shutdown_complete.clone(); let shutdown_timeout = opts.shutdown_timeout; use signal_hook::{consts::SIGINT, consts::SIGTERM, iterator::Signals}; let mut signals = Signals::new([SIGINT, SIGTERM])?; @@ -808,19 +811,23 @@ fn main() -> anyhow::Result<()> { token.cancel(); cancel_triggered = true; + let shutdown_complete = shutdown_complete.clone(); std::thread::spawn(move || { std::thread::sleep(shutdown_timeout); - warn!( - "could not shut down within {:?}, killing myself", - shutdown_timeout - ); - std::process::exit(1) + if !shutdown_complete.load(Ordering::Acquire) { + warn!( + "could not shut down within {:?}, killing myself", + shutdown_timeout + ); + std::process::exit(1) + } }); } }); } let result = rt.block_on(async_main(opts, token.clone())); + shutdown_complete.store(true, Ordering::Release); match result.as_ref() { Err(e) if librtbit_core::spawn_utils::is_cancelled(e) => { info!("rtbit shut down: cancelled"); @@ -831,6 +838,11 @@ fn main() -> anyhow::Result<()> { rt.shutdown_timeout(Duration::from_secs(1)); match result { Ok(_) => std::process::exit(0), + // SIGTERM/SIGINT is the normal container shutdown path. The signal + // cancels the HTTP server, which is reported as an internal + // cancellation error, but it must still produce a successful process + // exit after the session has flushed its state. + Err(e) if librtbit_core::spawn_utils::is_cancelled(&e) => std::process::exit(0), Err(_) => std::process::exit(1), } } diff --git a/root/etc/s6-overlay/s6-rc.d/init-rtbit-config/run b/root/etc/s6-overlay/s6-rc.d/init-rtbit-config/run index a87fcd58..0e363962 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-rtbit-config/run +++ b/root/etc/s6-overlay/s6-rc.d/init-rtbit-config/run @@ -3,10 +3,15 @@ # Ensure directories exist mkdir -p \ /home/rtbit/db \ + /home/rtbit/db/com.rtbit.session \ /home/rtbit/cache \ /home/rtbit/downloads \ /home/rtbit/completed +# Keep the legacy container-facing session path available while the daemon +# stores its state in the current application-data directory. +ln -sfn com.rtbit.session /home/rtbit/db/session + # Set ownership (lsiown handles edge cases on various filesystems) lsiown abc:abc /home/rtbit lsiown -R abc:abc /home/rtbit/db /home/rtbit/cache