mirror of
https://github.com/pgsty/minio.git
synced 2026-09-24 11:55:59 +03:00
fix(replication): scope resync dispatch to the target being resynced
The resync worker pool runs for a single target (opts.arn), but the dispatch loop admitted any object whose ExistingObjResync.mustResync() was true for ANY target. On a bucket with per-target rules (A and B), a resync of A would pull in objects that only qualify for B - even with a single active resync, since qualification is any-target. After the outcome-based classification (previous change) such a cross-target object leaves A absent from its per-object result and is counted as an A failure - an object A was never responsible for. Scope admission to the resync's own target: dispatch an object only if it must resync for opts.arn specifically (mustResyncTarget), via a small pure helper objectNeedsResyncForARN. Only opts.arn carries this resync's ResetID, and that reset is already folded into its per-target decision, so the per-target check both scopes dispatch and honors the reset. Each target has its own resyncBucket, so no cross-target object is dropped - it is handled by that target's resync. The classifier's absent-ARN failure path is now unreachable for normally dispatched objects and remains only as defense-in-depth (e.g. a config/lock error before replication is attempted). Delete-marker/version-purge handling, the null-version exclusion, the finalization ordering and the outcome-based classification are unchanged. Adds a table-driven regression for the predicate: with A/B rules and a resync of A, an object qualifying only for B is not admitted; any-target scoping admits it and fails the test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L7qJqWwy8oFA6aCXWRzXQe Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -3029,6 +3029,19 @@ func resyncResultFor(rinfos replicatedInfos, arn string, roi ReplicateObjectInfo
|
||||
return st
|
||||
}
|
||||
|
||||
// objectNeedsResyncForARN reports whether roi must be resynced for target arn
|
||||
// specifically. The resync worker pool is scoped to a single target (opts.arn),
|
||||
// so an object that only qualifies for a different target must be skipped here:
|
||||
// admitting it would replicate it for arn's peers only, leaving arn absent from
|
||||
// the per-object result, which resyncResultFor then (correctly, but
|
||||
// misleadingly) counts as a failure for arn - an object arn was never
|
||||
// responsible for. Only opts.arn carries this resync's ResetID, and that reset
|
||||
// is already folded into its per-target decision, so the per-target check both
|
||||
// scopes dispatch and honors the reset.
|
||||
func objectNeedsResyncForARN(roi ReplicateObjectInfo, arn string) bool {
|
||||
return roi.ExistingObjResync.mustResyncTarget(arn)
|
||||
}
|
||||
|
||||
// resyncBucket resyncs all qualifying objects as per replication rules for the target
|
||||
// ARN
|
||||
func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI ObjectLayer, heal bool, opts resyncOpts) {
|
||||
@@ -3196,7 +3209,12 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
||||
}
|
||||
lastCheckpoint = ""
|
||||
roi := getHealReplicateObjectInfo(res.Item, rcfg)
|
||||
if !roi.ExistingObjResync.mustResync() {
|
||||
// Scope dispatch to this resync's target: the worker pool is for
|
||||
// opts.arn, so skip objects that only need resync for a different
|
||||
// target (each target has its own resync). Without this, a cross-target
|
||||
// object leaves opts.arn absent from its per-object result and is
|
||||
// miscounted as an opts.arn failure.
|
||||
if !objectNeedsResyncForARN(roi, opts.arn) {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
|
||||
Reference in New Issue
Block a user