crates/gunnar-server/tests/push_spool_sweep.rs
//! **A spool a crash left behind is swept when the server starts.**
//!
//! A pushed pack of 64 MiB or more is spooled to `incoming-*.pack` beside the
//! repository's store while it arrives (`storage::read_pushed_pack_spooling`),
//! and the file is removed when the push lands or fails — by `Drop`. A process
//! that dies mid-receive runs no `Drop`: the spool stays, full-size, under a
//! name nothing else ever reads, and on a data set near its floor that is the
//! room the next push is refused for. cov-13's eight 512 MiB pushes were 4 GiB
//! of exactly such files at the moment the box ran out.
//!
//! `Server::start` sweeps them before the first door opens — both engines'
//! places (`<repo>.git/gunnar-git.znippy/` for znippy, `<repo>.git/` for gix) —
//! and leaves every other file alone.
use std::sync::Arc;
use gunnar_core::{NewRepo, RepoRef};
use gunnar_server::stores::ServerStores;
#[test]
fn spool_files_a_crash_left_are_swept_at_start_and_nothing_else_is_touched() {
let dir = tempfile::tempdir().expect("tempdir");
let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
stores
.catalog()
.create_namespace("team")
.expect("namespace");
let path = RepoRef::new("team", "one").expect("legal");
let repo = stores
.create_repo(NewRepo::new(path), false)
.expect("create");
let git_dir = stores
.repo_store(&repo)
.expect("open")
.git_dir()
.to_path_buf();
let znippy = git_dir.join("gunnar-git.znippy");
std::fs::create_dir_all(&znippy).unwrap();
let left = [
znippy.join("incoming-a1B2c3.pack"),
git_dir.join("incoming-d4E5f6.pack"),
];
for f in &left {
std::fs::write(f, vec![7u8; 1 << 20]).unwrap();
}
// Not a spool: a name that merely contains the word, and a non-pack.
let kept = [
znippy.join("not-incoming-x.pack"),
znippy.join("incoming-notes.txt"),
];
for f in &kept {
std::fs::write(f, b"keep me").unwrap();
}
// An LFS upload's spool beside the transaction log, left the same way.
let lfs_spool = stores.txlog().dir().join(gunnar_core::txlog::SPOOL_DIR);
std::fs::create_dir_all(&lfs_spool).unwrap();
let lfs_left = lfs_spool.join("4e07408562bedb8b-123.part");
std::fs::write(&lfs_left, vec![1u8; 4096]).unwrap();
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("runtime");
let config = gunnar_server::ServeConfig {
no_grpc: true,
..gunnar_server::ServeConfig::ephemeral()
};
let server = runtime
.block_on(gunnar_server::Server::start(Arc::clone(&stores), config))
.expect("the server starts");
for f in &left {
assert!(
!f.exists(),
"a spool a crash left behind is still there after start: {}",
f.display()
);
}
assert!(
!lfs_left.exists(),
"an LFS upload's spool a crash left is still there after start"
);
for f in &kept {
assert!(
f.exists(),
"the sweep removed a file that is not a spool: {}",
f.display()
);
}
let _ = runtime.block_on(server.shutdown());
}