mirror of
https://github.com/pgsty/minio.git
synced 2026-11-10 17:45:32 +03:00
fix(ilm): queue version heals when an undecodable history refuses a purge
A purge of all versions reads the history of the key in every pool before it checks the drives. When half of the drives of a set could not be read or decoded, objectVersions refused the history with a read quorum error and the purge failed with InsufficientWriteQuorum before deleteAllDrives, which alone queued a heal of each version when a drive did not decode. With parity of half the drives, a key whose metadata did not decode on exactly half of them still read and healed, but no purge queued that heal: the refusal repeated until a scanner or drive heal happened to repair the drives. objectVersions now refuses such a history with undecodableHistory, which wraps the read quorum error and carries the IDs of the versions the decodable drives hold, the null version by its explicit ID. The purge queues a heal of each of them before it fails, from the history of the rule check as from the placement read, and still returns InsufficientWriteQuorum. The threshold of unknown drives, the refusal and every other caller of the history are unchanged; expiryApplies matches the read quorum error through the wrapper. TestPurgeAllVersionsHealsUndecodableHistory corrupts the metadata CRC on four of eight drives; before this change the purge refused the key without queuing any heal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -250,6 +250,21 @@ func (z *erasureServerPools) deleteAllVersions(ctx context.Context, bucket, obje
|
||||
start := time.Now()
|
||||
name := decodeDirObject(object)
|
||||
fail := func(err error) (ObjectInfo, error) {
|
||||
// A history refused for metadata that did not decode: queue a heal
|
||||
// of each version the drives show, as deleteAllDrives does for a
|
||||
// drive it checks, so that a later attempt finds the key whole. The
|
||||
// purge remains refused.
|
||||
var undecodable undecodableHistory
|
||||
if errors.As(err, &undecodable) {
|
||||
for _, id := range undecodable.ids {
|
||||
globalMRFState.addPartialOp(PartialOperation{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
VersionID: id,
|
||||
Queued: time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if isErrReadQuorum(err) {
|
||||
err = InsufficientWriteQuorum{}
|
||||
}
|
||||
@@ -268,6 +283,11 @@ func (z *erasureServerPools) deleteAllVersions(ctx context.Context, bucket, obje
|
||||
all.versions, all.events, all.err = z.lifecycleEventsAllPools(ctx, bucket, object)
|
||||
if all.err != nil || len(all.versions) > 0 {
|
||||
if err := all.expiryApplies(bucket, object, ObjectOptions{DeletePrefix: true}); err != nil {
|
||||
if all.err != nil {
|
||||
// The history refused, not the rule: fail with it, which
|
||||
// callers see as the same write quorum error.
|
||||
err = all.err
|
||||
}
|
||||
return fail(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user