mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-31 17:30:42 +01:00
fix(ec): fence stale-worker EC shard cleanup by encode generation (#9953)
* feat(ec): add encode_ts_ns to the EC task params, shard-unmount, and shard-delete RPCs The generation fence for stale EC-worker cleanup needs the encode generation on three messages: ErasureCodingTaskParams (admin issues it), VolumeEcShardsUnmountRequest, and VolumeEcShardsDeleteRequest (the worker carries it to the volume server). Additive fields only; 0 preserves the existing unfenced behavior. Mirror the two volume-server fields in the Rust volume server's proto copy. * feat(ec): issue the EC encode generation from the admin and carry it on the worker Stamp each EC proposal's encode_ts_ns from the admin's per-cycle DetectionSequence (a single-clock value) so generations are globally ordered even though detection runs on a rotating worker. The worker writes that generation into the distributed .vif and passes it on its shard unmount/delete RPCs; it falls back to a local timestamp for the .vif only on the unfenced legacy/shell path (keeping the read guard on). * fix(ec): fence the stale-worker EC shard unmount and teardown by generation A reaped-but-still-running EC worker's cleanupStaleEcShards issued a generation-blind unmount + full teardown that could unmount and then overwrite a newer run's live shards on a shared node. Both RPCs now carry the encode generation: the volume server unmounts/deletes a disk only when its .vif generation is strictly older than the request, and preserves a same-or-newer generation, a generation-0 (recovered or pre-upgrade) volume, and an unreadable .vif. Unload is per-disk, never node-wide. Request generation 0 keeps the blanket teardown for the shell pre-encode cleanup and pre-upgrade callers. Mirrored in the Rust volume server. * test(ec): cover the generation-fenced teardown and unmount End-to-end volume-server tests: a fenced FullTeardown wipes a strictly- older generation, preserves a newer one, preserves a generation-0 volume, and blanket-wipes on request generation 0; the gen-aware unmount preserves a same-or-newer mounted generation; and the .vif generation reader handles present/absent/no-config cases. * test(ec): pin the fenced .vif==teardown generation and the unreadable-.vif preserve A fenced run must stamp the admin generation verbatim into the .vif so it matches the generation sent on the teardown RPCs; add a regression test that sets the task generation and asserts the .vif carries it exactly. Also cover the present-but-unparseable .vif case (reads as generation 0, preserved) and correct the readEcGenerationTsNs docstring accordingly. * fix(ec): surface EC full-teardown filesystem errors in the Rust volume server remove_ec_volume_files(_full_teardown) discarded every fs::remove_file error, so a teardown that failed on permissions or a full disk still returned full_teardown_done=true and left stale artifacts to collide with the next encode. Return io::Result, ignore NotFound, propagate the first real error, and have the teardown RPC surface it -- matching the Go contract. The best-effort reconcile/load-cleanup callers keep ignoring it. * refactor(ec): reuse the EC volume lookup on unmount and short-circuit the gen read Address review: the Rust unmount fence reuses the ec_vol it already fetched instead of a second find_ec_volume; the Go .vif generation reader breaks out of the data/idx loop early when the two dirs are the same.
This commit is contained in:
1 parent
561768a426
commit
284796c7b6
16 files changed
+549
-49
No files matched your search
@@ -2662,15 +2662,54 @@ impl VolumeServer for VolumeGrpcService {
|
||||
let vid = VolumeId(req.volume_id);
|
||||
|
||||
if req.full_teardown {
|
||||
// Pre-encode cleanup: evict the volume and wipe every EC artifact for it
|
||||
// on every disk, not just the listed shards, so a remote node retains no
|
||||
// stale generation that a fresh gen-0 copy would collide with. Echo the
|
||||
// acknowledgement so the caller can tell a pre-upgrade server apart.
|
||||
{
|
||||
if req.encode_ts_ns == 0 {
|
||||
// Blanket teardown (shell pre-encode cleanup / pre-upgrade caller): evict
|
||||
// the volume and wipe every EC artifact for it on every disk, not just the
|
||||
// listed shards, so a remote node retains no stale generation a fresh copy
|
||||
// collides with. Echo the acknowledgement so the caller can tell a
|
||||
// pre-upgrade server apart.
|
||||
{
|
||||
let mut store = self.state.store.write().unwrap();
|
||||
let _ = store.remove_ec_volume(vid);
|
||||
for loc in &store.locations {
|
||||
loc.remove_ec_volume_files_full_teardown(&req.collection, vid)
|
||||
.map_err(|e| {
|
||||
Status::internal(format!(
|
||||
"full teardown of ec volume {}: {}",
|
||||
req.volume_id, e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Generation-fenced teardown (stale-worker pre-distribute cleanup): wipe
|
||||
// only a disk whose .vif generation is strictly OLDER than the request;
|
||||
// preserve same-or-newer, generation 0 (recovered/pre-upgrade live volume),
|
||||
// and an unreadable .vif, so a stale run never wipes a newer run's live
|
||||
// shards. Unload and remove only the strictly-older disks, never node-wide.
|
||||
let mut store = self.state.store.write().unwrap();
|
||||
let _ = store.remove_ec_volume(vid);
|
||||
for loc in &store.locations {
|
||||
loc.remove_ec_volume_files_full_teardown(&req.collection, vid);
|
||||
for disk_id in 0..store.locations.len() {
|
||||
let disk_gen = store.locations[disk_id].ec_generation_ts_ns(&req.collection, vid);
|
||||
let older = matches!(disk_gen, Some(g) if g > 0 && g < req.encode_ts_ns);
|
||||
if !older {
|
||||
tracing::info!(
|
||||
volume_id = vid.0,
|
||||
disk_id,
|
||||
?disk_gen,
|
||||
req = req.encode_ts_ns,
|
||||
"ec full teardown preserved: generation not older than request"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
store.locations[disk_id].remove_ec_volume(vid);
|
||||
store.locations[disk_id]
|
||||
.remove_ec_volume_files_full_teardown(&req.collection, vid)
|
||||
.map_err(|e| {
|
||||
Status::internal(format!(
|
||||
"fenced teardown of ec volume {}: {}",
|
||||
req.volume_id, e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
self.state.volume_state_notify.notify_one();
|
||||
@@ -2728,9 +2767,11 @@ impl VolumeServer for VolumeGrpcService {
|
||||
// Matches Go: for _, shardId := range req.ShardIds { err = vs.store.UnmountEcShards(...) }
|
||||
let mut store = self.state.store.write().unwrap();
|
||||
for &shard_id in &req.shard_ids {
|
||||
store.unmount_ec_shard(vid, shard_id).map_err(|e| {
|
||||
Status::internal(format!("unmount {}.{}: {}", req.volume_id, shard_id, e))
|
||||
})?;
|
||||
store
|
||||
.unmount_ec_shard(vid, shard_id, req.encode_ts_ns)
|
||||
.map_err(|e| {
|
||||
Status::internal(format!("unmount {}.{}: {}", req.volume_id, shard_id, e))
|
||||
})?;
|
||||
}
|
||||
drop(store);
|
||||
self.state.volume_state_notify.notify_one();
|
||||
|
||||
Reference in new issue
Block a user