fix(ecstore): bind multipart convergence heal versions (#5786)

This commit is contained in:
cxymds
2026-08-07 09:54:47 +08:00
committed by GitHub
parent dd2e0328fd
commit b7b571dfa4
2 changed files with 180 additions and 57 deletions
+20 -42
View File
@@ -2055,6 +2055,26 @@ impl crate::storage_api_contracts::multipart::MultipartOperations for SetDisks {
)
.await?;
// Detach admission before any post-commit await: client cancellation
// must not couple durable convergence repair to cleanup work.
if convergence.needs_heal() {
let mut request = rustfs_common::heal_channel::create_heal_request_with_options(
bucket.to_string(),
Some(object.to_string()),
false,
Some(HealChannelPriority::Normal),
Some(self.pool_index),
Some(self.set_index),
);
request.object_version_id = fi
.version_id
.or_else(|| opts.version_suspended.then(Uuid::nil))
.map(|version_id| version_id.to_string());
tokio::spawn(async move {
let _ = rustfs_common::heal_channel::send_heal_request(request).await;
});
}
// Crash-consistency injection: hard power loss after the authoritative
// rename_data commit succeeded but before the stale part.N.meta cleanup.
// The new version is durably committed and visible, so a crash here must
@@ -2128,48 +2148,6 @@ impl crate::storage_api_contracts::multipart::MultipartOperations for SetDisks {
}
drop(object_lock_guard); // drop object lock guard to release the lock
// backlog#1321: enqueue heal only when the committed replicas actually
// need to converge — a partial commit (some disk failed/offline) or a
// signature divergence between committed replicas. A fully healthy MPU
// (identical signatures on every disk) is `AllSuccessIdentical` and
// submits nothing, which is the fix: the old `Option::is_some()` gate
// treated the mere existence of a version signature as "needs heal", so
// every healthy <=10-version completion self-enqueued.
//
// The submit is detached (`tokio::spawn`) so it stays off the ACK
// critical path AND survives cancellation of the completion future: the
// write is already durable and ACK-worthy, so the heal admission must
// not ride the client's request lifetime. The admission itself is
// bounded / deduplicated / observable (`send_heal_request` ->
// `HealAdmissionResult`), so this emits at most one submit per
// completion and coalesces with any in-flight heal for the same object.
//
// Scanner backstop (backlog#1321 patch): a `PartialCommit` whose
// completion is cancelled in the narrow window after the durable commit
// but before this spawn runs is not lost — the divergence it would have
// healed is exactly what the background scanner reconciles. `Unknown`
// (>10 versions, no signature produced) likewise relies on the scanner
// rather than self-enqueuing.
if convergence.needs_heal() {
let bucket = bucket.to_string();
let object = object.to_string();
let pool_index = self.pool_index;
let set_index = self.set_index;
tokio::spawn(async move {
let _ = rustfs_common::heal_channel::send_heal_request(
rustfs_common::heal_channel::create_heal_request_with_options(
bucket,
Some(object),
false,
Some(HealChannelPriority::Normal),
Some(pool_index),
Some(set_index),
),
)
.await;
});
}
for (i, op_disk) in online_disks.iter().enumerate() {
if let Some(disk) = op_disk
&& disk.is_online().await