fix(ec): remove a stale .vif on full teardown of a shard-only node

A shard copy installs shards + .ecx before .vif, so an interrupted copy after a
teardown could mount the new files under the previous run's identity / version /
shard ratio / dat_file_size carried by the surviving .vif. Remove .vif during
full teardown, gated on .idx absence so a source-volume holder keeps its live
.vif. In Rust this lives in a teardown-only helper so the reconcile / load-
fallback paths (which share the base removal) still preserve .vif.
This commit is contained in:
Chris Lu
2026-06-09 10:52:52 -07:00
parent f69b7b854c
commit 5e818e9640
3 changed files with 38 additions and 1 deletions
+1 -1
View File
@@ -2609,7 +2609,7 @@ impl VolumeServer for VolumeGrpcService {
let mut store = self.state.store.write().unwrap();
let _ = store.remove_ec_volume(vid);
for loc in &store.locations {
loc.remove_ec_volume_files(&req.collection, vid);
loc.remove_ec_volume_files_full_teardown(&req.collection, vid);
}
}
self.state.volume_state_notify.notify_one();
@@ -328,6 +328,28 @@ impl DiskLocation {
}
}
/// Full-teardown variant: everything remove_ec_volume_files clears, PLUS the
/// normal-volume <base>.vif on a SHARD-ONLY node. An interrupted shard copy
/// (which installs shards + .ecx before .vif) could otherwise mount a fresh
/// generation under the prior run's identity / ratio / dat_file_size carried by
/// a stale .vif. Gated on .idx absence so a source-volume holder keeps its live
/// .vif. Reconcile/load-fallback call remove_ec_volume_files directly and
/// intentionally preserve it, mirroring Go's removeEcVolumeFiles (reconcile) vs
/// removeStaleEcArtifacts (teardown) split.
pub(crate) fn remove_ec_volume_files_full_teardown(&self, collection: &str, vid: VolumeId) {
self.remove_ec_volume_files(collection, vid);
let base = volume_file_name(&self.directory, collection, vid);
let idx_base = volume_file_name(&self.idx_directory, collection, vid);
if !std::path::Path::new(&format!("{}.idx", idx_base)).exists() {
let _ = fs::remove_file(format!("{}.vif", idx_base));
if self.idx_directory != self.directory
&& !std::path::Path::new(&format!("{}.idx", base)).exists()
{
let _ = fs::remove_file(format!("{}.vif", base));
}
}
}
/// Find a volume by ID.
pub fn find_volume(&self, vid: VolumeId) -> Option<&Volume> {
self.volumes.get(&vid)
+15
View File
@@ -557,6 +557,21 @@ func removeStaleEcArtifacts(dataBaseFileName, indexBaseFileName string, total in
record(removeFileIfExists(dataBaseFileName + ".ecj"))
record(removeBitrotSidecars(dataBaseFileName))
}
// Canonical <base>.vif. A shard copy installs shards + .ecx before .vif, so an
// interrupted copy can leave a stale .vif whose run identity / shard ratio /
// dat_file_size a fresh generation would inherit. Remove it only on a shard-only
// EC node: where a normal <base>.idx exists this is the source volume holder and
// the .vif belongs to that live volume — keep it. This mirrors the !hasIdxFile
// gate in the per-shard delete path.
if _, statErr := os.Stat(indexBaseFileName + ".idx"); os.IsNotExist(statErr) {
record(removeFileIfExists(indexBaseFileName + ".vif"))
if dataBaseFileName != indexBaseFileName {
if _, dStatErr := os.Stat(dataBaseFileName + ".idx"); os.IsNotExist(dStatErr) {
record(removeFileIfExists(dataBaseFileName + ".vif"))
}
}
}
return firstErr
}