From 63607d810c0d91222b27543153bb9306cef964e1 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 8 Jun 2026 22:57:39 -0700 Subject: [PATCH] fix(ec): reject an unstamped shard when the caller has an encode identity The read guard required both identities nonzero, so a current (stamped) caller accepted a holder with identity 0 and could be served a stale pre-upgrade shard. Reject when the caller is stamped and the holder differs (including unstamped); stay lenient only when the caller itself has no identity (pre-upgrade reader). A skipped shard recovers from parity. --- weed/server/volume_grpc_erasure_coding.go | 8 +++++--- weed/storage/store_ec.go | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/weed/server/volume_grpc_erasure_coding.go b/weed/server/volume_grpc_erasure_coding.go index 4d4d9dc2e..b9045d1e5 100644 --- a/weed/server/volume_grpc_erasure_coding.go +++ b/weed/server/volume_grpc_erasure_coding.go @@ -657,9 +657,11 @@ func (vs *VolumeServer) VolumeEcShardRead(req *volume_server_pb.VolumeEcShardRea if !found { return fmt.Errorf("not found ec shard %d.%d", req.VolumeId, req.ShardId) } - // Reject a shard from a different encode run than the caller's index; the - // caller then recovers from parity. 0 on either side = pre-upgrade volume. - if req.EncodeTsNs != 0 && ecVolume.EncodeTsNs != 0 && req.EncodeTsNs != ecVolume.EncodeTsNs { + // Reject a shard whose identity doesn't match the caller's index; the caller + // then recovers from parity. Lenient only when the caller has no identity + // (pre-upgrade reader): a known caller must not accept an unstamped holder, + // which would serve a stale pre-upgrade shard. + if req.EncodeTsNs != 0 && req.EncodeTsNs != ecVolume.EncodeTsNs { return fmt.Errorf("ec shard %d.%d belongs to a different encode run", req.VolumeId, req.ShardId) } diff --git a/weed/storage/store_ec.go b/weed/storage/store_ec.go index 795d138d4..d4aeaadfb 100644 --- a/weed/storage/store_ec.go +++ b/weed/storage/store_ec.go @@ -527,9 +527,11 @@ func (s *Store) readLocalEcShardInterval(ecVolume *erasure_coding.EcVolume, shar if !found { return fmt.Errorf("shard %d for volume %d: %w", shardId, ecVolume.VolumeId, errShardNotLocal) } - // Skip a local shard from a different encode run than the caller's index; - // treat it as not-local so the read recovers from the correct generation. - if ecVolume.EncodeTsNs != 0 && ownerVolume.EncodeTsNs != 0 && ecVolume.EncodeTsNs != ownerVolume.EncodeTsNs { + // Skip a local shard whose identity doesn't match the caller's index, so the + // read recovers from the correct generation. Lenient only when the caller has + // no identity (pre-upgrade): a known caller must not accept an unstamped local + // shard, which would serve a stale pre-upgrade generation. + if ecVolume.EncodeTsNs != 0 && ecVolume.EncodeTsNs != ownerVolume.EncodeTsNs { glog.V(1).Infof("skip local ec shard %d.%d from a different encode run: caller EncodeTsNs %d, local %d", ecVolume.VolumeId, shardId, ecVolume.EncodeTsNs, ownerVolume.EncodeTsNs) return fmt.Errorf("shard %d for volume %d: %w", shardId, ecVolume.VolumeId, errShardNotLocal) }