mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-03 18:47:41 +01:00
Flush was the only path stamping the watermark, so a truncate or setattr through saveEntry, a server-side CopyFileRange, or a remote cache download whose response carried no metadata event could still be rolled back by an older queued subscription event in a read-through directory. saveEntry now stamps any open handle for the saved path, and every site tolerates a response without an event: the metadata cache tracks the newest filer log timestamp seen in any applied event, and that position, captured before the RPC, is a safe lower bound for the state the filer served — anything it returns is at least as new as every event already delivered.
493 lines
18 KiB
Go
493 lines
18 KiB
Go
package mount
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/go-fuse/v2/fuse"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"github.com/seaweedfs/seaweedfs/weed/mount/meta_cache"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
)
|
|
|
|
func newInvalidateTestWFS(t *testing.T) *WFS {
|
|
t.Helper()
|
|
|
|
// Map filer uid 2000 to local uid 1000 to verify the event entry gets the
|
|
// same id translation a filer lookup would apply.
|
|
uidGidMapper, err := meta_cache.NewUidGidMapper("1000:2000", "")
|
|
if err != nil {
|
|
t.Fatalf("create uid/gid mapper: %v", err)
|
|
}
|
|
|
|
root := util.FullPath("/")
|
|
wfs := &WFS{
|
|
signature: 1,
|
|
inodeToPath: NewInodeToPath(root, 0),
|
|
fhMap: NewFileHandleToInode(),
|
|
fhLockTable: util.NewLockTable[FileHandleId](),
|
|
hardLinkLockTable: util.NewLockTable[string](),
|
|
option: &Option{
|
|
ChunkSizeLimit: 1024,
|
|
ConcurrentReaders: 1,
|
|
VolumeServerAccess: "filerProxy",
|
|
// Nothing listens here: any secondary lookup during invalidation
|
|
// fails, like the transient filer error that pins a handle.
|
|
FilerAddresses: []pb.ServerAddress{
|
|
pb.NewServerAddressWithGrpcPort("127.0.0.1:1", 1),
|
|
},
|
|
GrpcDialOption: grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
UidGidMapper: uidGidMapper,
|
|
},
|
|
}
|
|
|
|
wfs.metaCache = meta_cache.NewMetaCache(
|
|
filepath.Join(t.TempDir(), "meta"),
|
|
uidGidMapper,
|
|
root,
|
|
false,
|
|
func(path util.FullPath) { wfs.inodeToPath.MarkChildrenCached(path) },
|
|
func(path util.FullPath) bool { return wfs.inodeToPath.IsChildrenCached(path) },
|
|
wfs.invalidateOpenFileHandle,
|
|
nil,
|
|
)
|
|
t.Cleanup(wfs.metaCache.Shutdown)
|
|
|
|
return wfs
|
|
}
|
|
|
|
// An update event must refresh an open file handle from the entry the event
|
|
// itself carries. A second lookup can fail transiently or serve stale cached
|
|
// metadata, and the subscription cursor has already advanced, so a missed
|
|
// refresh leaves the handle pinned to the old entry until an unrelated event
|
|
// for the same path arrives.
|
|
func TestUpdateEventRefreshesOpenFileHandle(t *testing.T) {
|
|
wfs := newInvalidateTestWFS(t)
|
|
|
|
inode := wfs.inodeToPath.Lookup(util.FullPath("/dir/file"), time.Now().Unix(), false, false, 0, false)
|
|
fh := wfs.fhMap.AcquireFileHandle(wfs, inode, &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 88},
|
|
})
|
|
|
|
updateResp := &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: "file"},
|
|
NewEntry: &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 180020, Uid: 2000},
|
|
Chunks: []*filer_pb.FileChunk{{FileId: "1,ab1", Size: 180020}},
|
|
},
|
|
NewParentPath: "/dir",
|
|
},
|
|
}
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), updateResp, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
t.Fatalf("apply update event: %v", err)
|
|
}
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
|
|
entry := fh.GetEntry().GetEntry()
|
|
if entry.Attributes.FileSize != 180020 {
|
|
t.Fatalf("open handle file size = %d, want 180020", entry.Attributes.FileSize)
|
|
}
|
|
if len(entry.GetChunks()) != 1 {
|
|
t.Fatalf("open handle chunks = %d, want 1", len(entry.GetChunks()))
|
|
}
|
|
if entry.Attributes.Uid != 1000 {
|
|
t.Fatalf("open handle uid = %d, want filer uid 2000 mapped to local 1000", entry.Attributes.Uid)
|
|
}
|
|
|
|
// A delete leaves the handle with its last entry so unlinked-but-open
|
|
// reads keep working.
|
|
deleteResp := &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: "file"},
|
|
},
|
|
}
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), deleteResp, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
t.Fatalf("apply delete event: %v", err)
|
|
}
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
|
|
if size := fh.GetEntry().GetEntry().Attributes.FileSize; size != 180020 {
|
|
t.Fatalf("open handle file size after delete = %d, want 180020", size)
|
|
}
|
|
}
|
|
|
|
// A queued invalidation must not roll the handle back over newer state a
|
|
// local flush installed while the event sat in the queue. The local store is
|
|
// ordered by the apply loop, so it resolves the refresh for cached
|
|
// directories.
|
|
func TestQueuedEventDoesNotRollBackNewerLocalState(t *testing.T) {
|
|
wfs := newInvalidateTestWFS(t)
|
|
|
|
wfs.inodeToPath.MarkChildrenCached(util.FullPath("/"))
|
|
wfs.inodeToPath.Lookup(util.FullPath("/dir"), time.Now().Unix(), true, false, 0, false)
|
|
wfs.inodeToPath.MarkChildrenCached(util.FullPath("/dir"))
|
|
|
|
inode := wfs.inodeToPath.Lookup(util.FullPath("/dir/file"), time.Now().Unix(), false, false, 0, false)
|
|
fh := wfs.fhMap.AcquireFileHandle(wfs, inode, &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 88},
|
|
})
|
|
|
|
updateEvent := func(size uint64) *filer_pb.SubscribeMetadataResponse {
|
|
return &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: "file"},
|
|
NewEntry: &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: size},
|
|
},
|
|
NewParentPath: "/dir",
|
|
},
|
|
}
|
|
}
|
|
|
|
// Hold the handle lock so the queued invalidation cannot apply yet.
|
|
testLock := wfs.fhLockTable.AcquireLock("test", fh.fh, util.ExclusiveLock)
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), updateEvent(100), meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
t.Fatalf("apply subscriber event: %v", err)
|
|
}
|
|
|
|
// A local flush lands after the event was queued: newer state goes into
|
|
// the handle and, via the local apply, into the local store.
|
|
fh.SetEntry(&filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 200},
|
|
})
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), updateEvent(200), meta_cache.LocalMetadataResponseApplyOptions); err != nil {
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
t.Fatalf("apply local event: %v", err)
|
|
}
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
|
|
if size := fh.GetEntry().GetEntry().Attributes.FileSize; size != 200 {
|
|
t.Fatalf("open handle file size = %d, want 200 (queued size-100 event must not roll back the newer local state)", size)
|
|
}
|
|
}
|
|
|
|
// During a directory build, an event touching the building directory is
|
|
// buffered: its store write is deferred to build completion while its
|
|
// invalidation runs immediately, against a store that may not reflect the
|
|
// listing yet. Build completion must re-invalidate every buffered event —
|
|
// including snapshot-covered ones — so the handle lands on the completed
|
|
// directory's state.
|
|
func TestBufferedBuildEventReinvalidatesOnCompletion(t *testing.T) {
|
|
wfs := newInvalidateTestWFS(t)
|
|
|
|
wfs.inodeToPath.MarkChildrenCached(util.FullPath("/"))
|
|
wfs.inodeToPath.Lookup(util.FullPath("/dir"), time.Now().Unix(), true, false, 0, false)
|
|
|
|
inode := wfs.inodeToPath.Lookup(util.FullPath("/dir/file"), time.Now().Unix(), false, false, 0, false)
|
|
fh := wfs.fhMap.AcquireFileHandle(wfs, inode, &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 88},
|
|
})
|
|
|
|
if err := wfs.metaCache.BeginDirectoryBuild(context.Background(), util.FullPath("/dir")); err != nil {
|
|
t.Fatalf("begin build: %v", err)
|
|
}
|
|
|
|
// Covered by the upcoming listing snapshot (TsNs 900 <= snapshot 1000);
|
|
// its immediate invalidation runs before the listing inserts the newer
|
|
// entry, so the handle picks up the event's state.
|
|
event := &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
TsNs: 900,
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: "file"},
|
|
NewEntry: &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 100},
|
|
},
|
|
NewParentPath: "/dir",
|
|
},
|
|
}
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), event, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
t.Fatalf("apply buffered event: %v", err)
|
|
}
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
if size := fh.GetEntry().GetEntry().Attributes.FileSize; size != 100 {
|
|
t.Fatalf("open handle file size mid-build = %d, want 100 (event state)", size)
|
|
}
|
|
|
|
// The listing then inserts the newer entry the snapshot already covers.
|
|
if err := wfs.metaCache.InsertEntry(context.Background(), &filer.Entry{
|
|
FullPath: "/dir/file",
|
|
Attr: filer.Attr{
|
|
Crtime: time.Unix(1, 0),
|
|
Mtime: time.Unix(1, 0),
|
|
Mode: 0100644,
|
|
FileSize: 300,
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("insert listing entry: %v", err)
|
|
}
|
|
|
|
if err := wfs.metaCache.CompleteDirectoryBuild(context.Background(), util.FullPath("/dir"), 1000); err != nil {
|
|
t.Fatalf("complete build: %v", err)
|
|
}
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
if size := fh.GetEntry().GetEntry().Attributes.FileSize; size != 300 {
|
|
t.Fatalf("open handle file size after build completion = %d, want 300 (snapshot-covered event must re-invalidate)", size)
|
|
}
|
|
}
|
|
|
|
// A hit in the local store only resolves an invalidation when the parent
|
|
// directory is cached. An uncached parent receives no store writes, so a
|
|
// leftover entry there is stale and must not mask the event.
|
|
func TestUncachedDirStaleStoreEntryDoesNotMaskEvent(t *testing.T) {
|
|
wfs := newInvalidateTestWFS(t)
|
|
|
|
inode := wfs.inodeToPath.Lookup(util.FullPath("/dir/file"), time.Now().Unix(), false, false, 0, false)
|
|
fh := wfs.fhMap.AcquireFileHandle(wfs, inode, &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 88},
|
|
})
|
|
|
|
// Leftover store entry under a parent that is not children-cached.
|
|
if err := wfs.metaCache.InsertEntry(context.Background(), &filer.Entry{
|
|
FullPath: "/dir/file",
|
|
Attr: filer.Attr{
|
|
Crtime: time.Unix(1, 0),
|
|
Mtime: time.Unix(1, 0),
|
|
Mode: 0100644,
|
|
FileSize: 88,
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("insert stale entry: %v", err)
|
|
}
|
|
|
|
updateResp := &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
TsNs: 1000,
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: "file"},
|
|
NewEntry: &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 180020},
|
|
},
|
|
NewParentPath: "/dir",
|
|
},
|
|
}
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), updateResp, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
t.Fatalf("apply update event: %v", err)
|
|
}
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
|
|
if size := fh.GetEntry().GetEntry().Attributes.FileSize; size != 180020 {
|
|
t.Fatalf("open handle file size = %d, want 180020 (stale store entry must not mask the event)", size)
|
|
}
|
|
}
|
|
|
|
// In a read-through directory neither a local flush nor the event reaches the
|
|
// local store, so ordering falls to the filer log timestamps: an event at or
|
|
// before the handle's last filer-acknowledged local mutation is old news and
|
|
// must not roll the handle back.
|
|
func TestQueuedEventOlderThanFlushedStateIsIgnored(t *testing.T) {
|
|
wfs := newInvalidateTestWFS(t)
|
|
|
|
inode := wfs.inodeToPath.Lookup(util.FullPath("/dir/file"), time.Now().Unix(), false, false, 0, false)
|
|
fh := wfs.fhMap.AcquireFileHandle(wfs, inode, &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 88},
|
|
})
|
|
|
|
// Hold the handle lock so the queued invalidation cannot apply yet.
|
|
testLock := wfs.fhLockTable.AcquireLock("test", fh.fh, util.ExclusiveLock)
|
|
older := &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
TsNs: 1000,
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: "file"},
|
|
NewEntry: &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 100},
|
|
},
|
|
NewParentPath: "/dir",
|
|
},
|
|
}
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), older, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
t.Fatalf("apply subscriber event: %v", err)
|
|
}
|
|
|
|
// A local flush lands: the filer acknowledged it with a later log
|
|
// timestamp than the queued event.
|
|
fh.SetEntry(&filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 200},
|
|
})
|
|
fh.advanceLocalEntryTs(2000)
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
|
|
if size := fh.GetEntry().GetEntry().Attributes.FileSize; size != 200 {
|
|
t.Fatalf("open handle file size = %d, want 200 (event at TsNs 1000 predates the flush at 2000)", size)
|
|
}
|
|
}
|
|
|
|
type saveEntryTestServer struct {
|
|
filer_pb.UnimplementedSeaweedFilerServer
|
|
}
|
|
|
|
func (s *saveEntryTestServer) UpdateEntry(ctx context.Context, req *filer_pb.UpdateEntryRequest) (*filer_pb.UpdateEntryResponse, error) {
|
|
return &filer_pb.UpdateEntryResponse{
|
|
MetadataEvent: &filer_pb.SubscribeMetadataResponse{
|
|
Directory: req.Directory,
|
|
TsNs: 2000,
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: req.Entry.Name},
|
|
NewEntry: req.Entry,
|
|
NewParentPath: req.Directory,
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// saveEntry (truncate, setattr) must advance the open handle's watermark from
|
|
// the acknowledged mutation's log timestamp, or an older queued event rolls
|
|
// the mutation back in a read-through directory.
|
|
func TestSaveEntryKeepsOpenHandleAheadOfOlderEvents(t *testing.T) {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("listen: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = listener.Close() })
|
|
server := pb.NewGrpcServer()
|
|
filer_pb.RegisterSeaweedFilerServer(server, &saveEntryTestServer{})
|
|
go server.Serve(listener)
|
|
t.Cleanup(server.Stop)
|
|
|
|
wfs := newInvalidateTestWFS(t)
|
|
wfs.option.FilerAddresses = []pb.ServerAddress{
|
|
pb.NewServerAddressWithGrpcPort("127.0.0.1:1", listener.Addr().(*net.TCPAddr).Port),
|
|
}
|
|
|
|
inode := wfs.inodeToPath.Lookup(util.FullPath("/dir/file"), time.Now().Unix(), false, false, 0, false)
|
|
fh := wfs.fhMap.AcquireFileHandle(wfs, inode, &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 88},
|
|
})
|
|
|
|
testLock := wfs.fhLockTable.AcquireLock("test", fh.fh, util.ExclusiveLock)
|
|
older := &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
TsNs: 1000,
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: "file"},
|
|
NewEntry: &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 100},
|
|
},
|
|
NewParentPath: "/dir",
|
|
},
|
|
}
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), older, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
t.Fatalf("apply subscriber event: %v", err)
|
|
}
|
|
|
|
// A truncate-style mutation: the filer acknowledges it at TsNs 2000 and
|
|
// the handle takes the new entry.
|
|
saved := &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 200},
|
|
}
|
|
if code := wfs.saveEntry(util.FullPath("/dir/file"), saved); code != fuse.OK {
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
t.Fatalf("saveEntry status = %v, want OK", code)
|
|
}
|
|
fh.SetEntry(saved)
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
|
|
if size := fh.GetEntry().GetEntry().Attributes.FileSize; size != 200 {
|
|
t.Fatalf("open handle file size = %d, want 200 (saveEntry at TsNs 2000 outranks the queued event at 1000)", size)
|
|
}
|
|
}
|
|
|
|
// When a filer response carries no metadata event, the watermark falls back
|
|
// to the latest filer log timestamp already seen: the filer served state at
|
|
// least that new, so queued events at or before it must not roll the fresh
|
|
// handle state back.
|
|
func TestNilAckEventFallsBackToLatestSeenTs(t *testing.T) {
|
|
wfs := newInvalidateTestWFS(t)
|
|
|
|
inode := wfs.inodeToPath.Lookup(util.FullPath("/dir/file"), time.Now().Unix(), false, false, 0, false)
|
|
fh := wfs.fhMap.AcquireFileHandle(wfs, inode, &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 88},
|
|
})
|
|
|
|
// An unrelated event advances the mount's known filer log position.
|
|
unrelated := &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
TsNs: 1500,
|
|
EventNotification: &filer_pb.EventNotification{
|
|
NewEntry: &filer_pb.Entry{
|
|
Name: "other",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 1},
|
|
},
|
|
},
|
|
}
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), unrelated, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
t.Fatalf("apply unrelated event: %v", err)
|
|
}
|
|
if got := wfs.metaCache.LatestEventTsNs(); got != 1500 {
|
|
t.Fatalf("LatestEventTsNs = %d, want 1500", got)
|
|
}
|
|
|
|
testLock := wfs.fhLockTable.AcquireLock("test", fh.fh, util.ExclusiveLock)
|
|
older := &filer_pb.SubscribeMetadataResponse{
|
|
Directory: "/dir",
|
|
TsNs: 1000,
|
|
EventNotification: &filer_pb.EventNotification{
|
|
OldEntry: &filer_pb.Entry{Name: "file"},
|
|
NewEntry: &filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 100},
|
|
},
|
|
NewParentPath: "/dir",
|
|
},
|
|
}
|
|
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), older, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
t.Fatalf("apply subscriber event: %v", err)
|
|
}
|
|
|
|
// A filer RPC that returned no event (remote cache already populated,
|
|
// server-side copy) installs fresh state; the baseline captured before
|
|
// the RPC stands in for the missing event timestamp.
|
|
baselineTsNs := wfs.metaCache.LatestEventTsNs()
|
|
fh.SetEntry(&filer_pb.Entry{
|
|
Name: "file",
|
|
Attributes: &filer_pb.FuseAttributes{FileSize: 200},
|
|
})
|
|
fh.noteFilerAck(baselineTsNs, nil)
|
|
wfs.fhLockTable.ReleaseLock(fh.fh, testLock)
|
|
|
|
wfs.metaCache.WaitForEntryInvalidations()
|
|
|
|
if size := fh.GetEntry().GetEntry().Attributes.FileSize; size != 200 {
|
|
t.Fatalf("open handle file size = %d, want 200 (queued event at TsNs 1000 predates the known log position 1500)", size)
|
|
}
|
|
}
|