mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-20 04:54:24 +02:00
filer.remote.sync: stamp entries with IF_CHUNKS_EQUAL so a stale write-back cannot delete live chunks
updateLocalEntry records the RemoteEntry stamp after an upload by writing the event's entry back with UpdateEntry. The filer deletes every stored chunk absent from an updated entry, so when the file was rewritten while its upload was in flight (or the event is a replay), the stale snapshot deletes the rewrite's chunks: the entry then points at the new fid with no needle behind it, and the rewrite's own upload fails and is skipped as superseded. The stamp write now carries WriteCondition IF_CHUNKS_EQUAL over the event's chunk fids, evaluated by the filer under the path lock. A refused stamp means the filer moved past this event; the superseding event follows in the log and stamps the current entry, so the refusal is logged and skipped like a superseded upload. Reproduction: weed server -filer plus a weed server -s3 remote, remote.mount, filer.remote.sync; hold the remote (docker pause) so one upload stays in flight, rewrite the file through the filer, unpause. Before: the entry's chunk is 404 on every volume server. After: the stale stamp is refused, the rewrite's chunk stays live and reads back after a vacuum.
This commit is contained in:
1 parent
d848b8ed00
commit
071d9d2f3c
2 files changed
+90
-1
No files matched your search
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -808,3 +810,47 @@ func TestRenameDeleteOldKeyNotFoundStillWrites(t *testing.T) {
|
||||
t.Errorf("writes = %+v, want the new key %s written", remote.writes, remote_storage.FormatLocation(wantWrite))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfChunksEqualCarriesTheEventChunkFids(t *testing.T) {
|
||||
entry := &filer_pb.Entry{
|
||||
Name: "f",
|
||||
Chunks: []*filer_pb.FileChunk{
|
||||
{FileId: "3,01a"},
|
||||
{Fid: &filer_pb.FileId{VolumeId: 4, FileKey: 0x2b, Cookie: 0x0c}},
|
||||
},
|
||||
}
|
||||
cond := ifChunksEqual(entry)
|
||||
if len(cond.Clauses) != 1 || cond.Clauses[0].Kind != filer_pb.WriteCondition_IF_CHUNKS_EQUAL {
|
||||
t.Fatalf("condition = %v, want one IF_CHUNKS_EQUAL clause", cond)
|
||||
}
|
||||
got := cond.Clauses[0].Fids
|
||||
want := []string{"3,01a", entry.Chunks[1].GetFileIdString()}
|
||||
if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
|
||||
t.Fatalf("fids = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
inline := &filer_pb.Entry{Name: "small", Content: []byte("bytes")}
|
||||
if fids := ifChunksEqual(inline).Clauses[0].Fids; len(fids) != 0 {
|
||||
t.Fatalf("inline entry fids = %v, want none", fids)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFailedPrecondition(t *testing.T) {
|
||||
refused := status.Error(codes.FailedPrecondition, "precondition failed: /buckets/b/f")
|
||||
cases := []struct {
|
||||
name string
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{"nil", nil, false},
|
||||
{"status", refused, true},
|
||||
{"wrapped status", fmt.Errorf("update entry: %w", refused), true},
|
||||
{"message only", errors.New("rpc error: code = FailedPrecondition desc = precondition failed: /f"), true},
|
||||
{"other", status.Error(codes.Unavailable, "filer down"), false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := isFailedPrecondition(c.err); got != c.want {
|
||||
t.Errorf("%s: isFailedPrecondition = %v, want %v", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user