filer: route conditional UpdateEntry to the entry's owner filer

Two filers locking the same path locally could still pass a stale
condition on the non-owner while the owner's entry had moved on. When a
condition or expected_extended precondition is set, forward the request
to the entry's owner the same way conditional CreateEntry does, with
is_moved bounding the hop.
This commit is contained in:
Chris Lu committed 2026-09-23 16:34:38 -07:00
1 parent 4f1178e0ba
commit 92bd112cfd
4 files changed
+71 -2

No files matched your search

+26
View File
@@ -662,6 +662,32 @@ func (fs *FilerServer) UpdateEntry(ctx context.Context, req *filer_pb.UpdateEntr
fullpath := util.Join(req.Directory, req.Entry.Name)
// A conditional or preconditioned update is a read-then-write that the
// per-path lock below only makes atomic on this filer. Route it to the
// entry's ring owner so one filer's lock arbitrates every writer
// cluster-wide; is_moved bounds this to one hop.
if !req.IsMoved && (conditionIsSet(req.Condition) || len(req.ExpectedExtended) > 0) {
var ownerResp *filer_pb.UpdateEntryResponse
handled, forwardErr := fs.forwardToWriteOwner(ctx, entryRouteKey(util.FullPath(fullpath)), func(owner pb.ServerAddress) error {
glog.V(2).InfofCtx(ctx, "UpdateEntry %s: forwarding to owner %s", fullpath, owner)
req.IsMoved = true
return pb.WithFilerClient(false, 0, owner, fs.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
forwarded, e := client.UpdateEntry(ctx, req)
if e != nil {
return e
}
ownerResp = forwarded
return nil
})
})
if handled {
if forwardErr != nil {
return &filer_pb.UpdateEntryResponse{}, forwardErr
}
return ownerResp, nil
}
}
// Serialize concurrent mutations to the same path on this filer so the
// read (preconditions, garbage diff) and the write are atomic. Callers
// route a key's writes to this owner filer, making this local lock