-
Notifications
You must be signed in to change notification settings - Fork 474
feat(Type Cache Deprecation): LookUp Child changes #4290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e67738d
004a7c4
f23a1f4
8f18e49
7c7e473
3aba5a8
84f0065
27fde0e
8d43913
e137a4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -27,6 +27,7 @@ import ( | |||
| "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" | ||||
| "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" | ||||
| "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" | ||||
| "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" | ||||
| "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" | ||||
| "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" | ||||
| "github.com/jacobsa/fuse/fuseops" | ||||
|
|
@@ -251,6 +252,8 @@ type dirInode struct { | |||
| // Represents if folder has been unlinked in hierarchical bucket. This is not getting used in | ||||
| // non-hierarchical bucket. | ||||
| unlinked bool | ||||
|
|
||||
| metadataCacheTtlSecs int64 | ||||
| } | ||||
|
|
||||
| var _ DirInode = &dirInode{} | ||||
|
|
@@ -306,6 +309,7 @@ func NewDirInode( | |||
| isUnsupportedPathSupportEnabled: cfg.EnableUnsupportedPathSupport, | ||||
| isEnableTypeCacheDeprecation: cfg.EnableTypeCacheDeprecation, | ||||
| unlinked: false, | ||||
| metadataCacheTtlSecs: cfg.MetadataCache.TtlSecs, | ||||
| } | ||||
| // readObjectsUnlocked is used by the prefetcher so the background worker performs GCS I/O without the lock, | ||||
| // acquiring d.mu only to update the cache. | ||||
|
|
@@ -338,19 +342,19 @@ func (d *dirInode) checkInvariants() { | |||
| } | ||||
|
|
||||
| func (d *dirInode) lookUpChildFile(ctx context.Context, name string) (*Core, error) { | ||||
| return findExplicitInode(ctx, d.Bucket(), NewFileName(d.Name(), name)) | ||||
| return findExplicitInode(ctx, d.Bucket(), NewFileName(d.Name(), name), false) | ||||
| } | ||||
|
|
||||
| func (d *dirInode) lookUpChildDir(ctx context.Context, name string) (*Core, error) { | ||||
| childName := NewDirName(d.Name(), name) | ||||
| if d.isBucketHierarchical() { | ||||
| return findExplicitFolder(ctx, d.Bucket(), childName) | ||||
| return findExplicitFolder(ctx, d.Bucket(), childName, false) | ||||
| } | ||||
|
|
||||
| if d.implicitDirs { | ||||
| return findDirInode(ctx, d.Bucket(), childName) | ||||
| return findDirInode(ctx, d.Bucket(), childName, d.isEnableTypeCacheDeprecation) | ||||
| } | ||||
| return findExplicitInode(ctx, d.Bucket(), childName) | ||||
| return findExplicitInode(ctx, d.Bucket(), childName, false) | ||||
| } | ||||
|
|
||||
| // Look up the file for a (file, dir) pair with conflicting names, overriding | ||||
|
|
@@ -384,10 +388,11 @@ func (d *dirInode) lookUpConflicting(ctx context.Context, name string) (*Core, e | |||
|
|
||||
| // findExplicitInode finds the file or dir inode core backed by an explicit | ||||
| // object in GCS with the given name. Return nil if such object does not exist. | ||||
| func findExplicitInode(ctx context.Context, bucket *gcsx.SyncerBucket, name Name) (*Core, error) { | ||||
| func findExplicitInode(ctx context.Context, bucket *gcsx.SyncerBucket, name Name, fetchOnlyFromCache bool) (*Core, error) { | ||||
| // Call the bucket. | ||||
| req := &gcs.StatObjectRequest{ | ||||
| Name: name.GcsObjectName(), | ||||
| Name: name.GcsObjectName(), | ||||
| FetchOnlyFromCache: fetchOnlyFromCache, | ||||
| } | ||||
|
|
||||
| m, _, err := bucket.StatObject(ctx, req) | ||||
|
|
@@ -403,17 +408,25 @@ func findExplicitInode(ctx context.Context, bucket *gcsx.SyncerBucket, name Name | |||
| return nil, fmt.Errorf("StatObject: %w", err) | ||||
| } | ||||
|
|
||||
| // Treat this as an implicit directory. Since implicit objects lack metadata | ||||
| // (only the name is preserved), we set MinObject to nil. This ensures the | ||||
| // inode is correctly assigned the ImplicitDir Core Type. | ||||
| if fetchOnlyFromCache && m.Generation == 0 { | ||||
| m = nil | ||||
Tulsishah marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
| } | ||||
|
|
||||
| return &Core{ | ||||
| Bucket: bucket, | ||||
| FullName: name, | ||||
| MinObject: m, | ||||
| }, nil | ||||
| } | ||||
|
|
||||
| func findExplicitFolder(ctx context.Context, bucket *gcsx.SyncerBucket, name Name) (*Core, error) { | ||||
| func findExplicitFolder(ctx context.Context, bucket *gcsx.SyncerBucket, name Name, fetchOnlyFromCache bool) (*Core, error) { | ||||
| // Call the bucket. | ||||
| req := &gcs.GetFolderRequest{ | ||||
| Name: name.GcsObjectName(), | ||||
| Name: name.GcsObjectName(), | ||||
| FetchOnlyFromCache: fetchOnlyFromCache, | ||||
| } | ||||
Tulsishah marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
| folder, err := bucket.GetFolder(ctx, req) | ||||
|
|
||||
|
|
@@ -437,14 +450,15 @@ func findExplicitFolder(ctx context.Context, bucket *gcsx.SyncerBucket, name Nam | |||
|
|
||||
| // findDirInode finds the dir inode core where the directory is either explicit | ||||
| // or implicit. Returns nil if no such directory exists. | ||||
| func findDirInode(ctx context.Context, bucket *gcsx.SyncerBucket, name Name) (*Core, error) { | ||||
| func findDirInode(ctx context.Context, bucket *gcsx.SyncerBucket, name Name, isTypeCacheDeprecated bool) (*Core, error) { | ||||
| if !name.IsDir() { | ||||
| return nil, fmt.Errorf("%q is not directory", name) | ||||
| } | ||||
|
|
||||
| req := &gcs.ListObjectsRequest{ | ||||
| Prefix: name.GcsObjectName(), | ||||
| MaxResults: 1, | ||||
| Prefix: name.GcsObjectName(), | ||||
| MaxResults: 1, | ||||
| IsTypeCacheDeprecated: isTypeCacheDeprecated, | ||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is typeCacheDeprecated flag being passed as part of listObjects request>?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is to enable new caching logic in fast stat bucket
|
||||
| } | ||||
| listing, err := bucket.ListObjects(ctx, req) | ||||
| if err != nil { | ||||
|
|
@@ -583,40 +597,93 @@ func (d *dirInode) LookUpChild(ctx context.Context, name string) (*Core, error) | |||
| return d.lookUpConflicting(ctx, name) | ||||
| } | ||||
|
|
||||
| cachedType := metadata.UnknownType | ||||
|
|
||||
| // 1. Optimization: If Type Cache is deprecated, attempt a lookup via the Stat Cache first. | ||||
| // We skip this if the metadata cache TTL is 0, as the cache layer is inactive. | ||||
| if d.IsTypeCacheDeprecated() && d.metadataCacheTtlSecs != 0 { | ||||
| var cacheMissErr *caching.CacheMissError | ||||
|
|
||||
| // Try File | ||||
| fileResult, err := findExplicitInode(ctx, d.Bucket(), NewFileName(d.Name(), name), true) | ||||
| if err != nil && !errors.As(err, &cacheMissErr) { | ||||
| return nil, err | ||||
| } | ||||
|
|
||||
| // Try Directory | ||||
| var dirResult *Core | ||||
| if d.Bucket().BucketType().Hierarchical { | ||||
| dirResult, err = findExplicitFolder(ctx, d.Bucket(), NewDirName(d.Name(), name), true) | ||||
| } else { | ||||
| dirResult, err = findExplicitInode(ctx, d.Bucket(), NewDirName(d.Name(), name), true) | ||||
| } | ||||
| if err != nil && !errors.As(err, &cacheMissErr) { | ||||
| return nil, err | ||||
| } | ||||
|
|
||||
| if dirResult != nil { | ||||
| return dirResult, nil | ||||
| } else if fileResult != nil { | ||||
| return fileResult, nil | ||||
| } | ||||
| } | ||||
|
|
||||
| // 2. Legacy: If Type Cache is NOT deprecated, fetch the type hint. | ||||
| if !d.IsTypeCacheDeprecated() { | ||||
| cachedType = d.cache.Get(d.cacheClock.Now(), name) | ||||
| } | ||||
|
|
||||
| // 3. Main Lookup Logic (Unified) | ||||
| result, err := d.fetchCoreEntity(ctx, name, cachedType) | ||||
| if err != nil { | ||||
| return nil, err | ||||
| } | ||||
|
|
||||
| // 4. Legacy: Update Type Cache if needed. | ||||
| if !d.IsTypeCacheDeprecated() { | ||||
| if result != nil { | ||||
| d.cache.Insert(d.cacheClock.Now(), name, result.Type()) | ||||
| } else if d.enableNonexistentTypeCache && cachedType == metadata.UnknownType { | ||||
| d.cache.Insert(d.cacheClock.Now(), name, metadata.NonexistentType) | ||||
| } | ||||
| } | ||||
|
|
||||
| return result, nil | ||||
| } | ||||
|
|
||||
| // fetchCoreEntity contains all the existing logic for looking up children | ||||
| // without worrying about the isTypeCacheDeprecated flag. | ||||
| func (d *dirInode) fetchCoreEntity(ctx context.Context, name string, cachedType metadata.Type) (*Core, error) { | ||||
| group, ctx := errgroup.WithContext(ctx) | ||||
|
|
||||
| var fileResult *Core | ||||
| var dirResult *Core | ||||
| var err error | ||||
|
|
||||
| lookUpFile := func() (err error) { | ||||
| fileResult, err = findExplicitInode(ctx, d.Bucket(), NewFileName(d.Name(), name)) | ||||
| fileResult, err = findExplicitInode(ctx, d.Bucket(), NewFileName(d.Name(), name), false) | ||||
| return | ||||
| } | ||||
| lookUpExplicitDir := func() (err error) { | ||||
| dirResult, err = findExplicitInode(ctx, d.Bucket(), NewDirName(d.Name(), name)) | ||||
| dirResult, err = findExplicitInode(ctx, d.Bucket(), NewDirName(d.Name(), name), false) | ||||
| return | ||||
| } | ||||
| lookUpImplicitOrExplicitDir := func() (err error) { | ||||
| dirResult, err = findDirInode(ctx, d.Bucket(), NewDirName(d.Name(), name)) | ||||
| dirResult, err = findDirInode(ctx, d.Bucket(), NewDirName(d.Name(), name), d.isEnableTypeCacheDeprecation) | ||||
| return | ||||
| } | ||||
| lookUpHNSDir := func() (err error) { | ||||
| dirResult, err = findExplicitFolder(ctx, d.Bucket(), NewDirName(d.Name(), name)) | ||||
| dirResult, err = findExplicitFolder(ctx, d.Bucket(), NewDirName(d.Name(), name), false) | ||||
| return | ||||
| } | ||||
| var cachedType metadata.Type | ||||
| if d.IsTypeCacheDeprecated() { | ||||
| // TODO: Add deprecation logic. | ||||
| cachedType = metadata.UnknownType | ||||
| } else { | ||||
| cachedType = d.cache.Get(d.cacheClock.Now(), name) | ||||
| } | ||||
|
|
||||
| switch cachedType { | ||||
| case metadata.ImplicitDirType: | ||||
| dirResult = &Core{ | ||||
| return &Core{ | ||||
| Bucket: d.Bucket(), | ||||
| FullName: NewDirName(d.Name(), name), | ||||
| MinObject: nil, | ||||
| } | ||||
| }, nil | ||||
| case metadata.ExplicitDirType: | ||||
| if d.isBucketHierarchical() { | ||||
| group.Go(lookUpHNSDir) | ||||
|
|
@@ -625,6 +692,7 @@ func (d *dirInode) LookUpChild(ctx context.Context, name string) (*Core, error) | |||
| } | ||||
| case metadata.RegularFileType, metadata.SymlinkType: | ||||
| group.Go(lookUpFile) | ||||
|
|
||||
| case metadata.NonexistentType: | ||||
| return nil, nil | ||||
| case metadata.UnknownType: | ||||
|
|
@@ -642,29 +710,16 @@ func (d *dirInode) LookUpChild(ctx context.Context, name string) (*Core, error) | |||
| group.Go(lookUpExplicitDir) | ||||
| } | ||||
| } | ||||
|
|
||||
| } | ||||
|
|
||||
| if err := group.Wait(); err != nil { | ||||
| if err = group.Wait(); err != nil { | ||||
| return nil, err | ||||
| } | ||||
|
|
||||
| var result *Core | ||||
| if dirResult != nil { | ||||
| result = dirResult | ||||
| } else if fileResult != nil { | ||||
| result = fileResult | ||||
| return dirResult, nil | ||||
| } | ||||
|
|
||||
| if !d.IsTypeCacheDeprecated() { | ||||
| if result != nil { | ||||
| d.cache.Insert(d.cacheClock.Now(), name, result.Type()) | ||||
| } else if d.enableNonexistentTypeCache && cachedType == metadata.UnknownType { | ||||
| d.cache.Insert(d.cacheClock.Now(), name, metadata.NonexistentType) | ||||
| } | ||||
| } | ||||
|
|
||||
| return result, nil | ||||
| return fileResult, nil | ||||
| } | ||||
|
|
||||
| func (d *dirInode) IsUnlinked() bool { | ||||
|
|
@@ -681,10 +736,11 @@ func (d *dirInode) ReadDescendants(ctx context.Context, limit int) (map[Name]*Co | |||
| descendants := make(map[Name]*Core) | ||||
| for { | ||||
| listing, err := d.bucket.ListObjects(ctx, &gcs.ListObjectsRequest{ | ||||
| Delimiter: "", // recursively | ||||
| Prefix: d.Name().GcsObjectName(), | ||||
| ContinuationToken: tok, | ||||
| MaxResults: limit + 1, // to exclude itself | ||||
| Delimiter: "", // recursively | ||||
| Prefix: d.Name().GcsObjectName(), | ||||
| ContinuationToken: tok, | ||||
| MaxResults: limit + 1, // to exclude itself | ||||
| IsTypeCacheDeprecated: d.isEnableTypeCacheDeprecation, | ||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here. we should not pass flag values as part of request object for every call.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do this then with list call caching will happen correctly with type cache deprecation otherwise implicit directories will not get cached correctly. |
||||
| }) | ||||
| if err != nil { | ||||
| return nil, fmt.Errorf("list objects: %w", err) | ||||
|
|
@@ -736,6 +792,7 @@ func (d *dirInode) listObjectsAndBuildCores(ctx context.Context, tok string, max | |||
| ProjectionVal: gcs.NoAcl, | ||||
| IncludeFoldersAsPrefixes: d.includeFoldersAsPrefixes, | ||||
| StartOffset: listStartOffset, | ||||
| IsTypeCacheDeprecated: d.isEnableTypeCacheDeprecation, | ||||
| } | ||||
|
|
||||
| listing, err := d.bucket.ListObjects(ctx, req) | ||||
|
|
@@ -1209,6 +1266,7 @@ func (d *dirInode) deletePrefixRecursively(ctx context.Context, prefix string) e | |||
| Delimiter: "/", // Use Delimiter to separate nested folders (CollapsedRuns) | ||||
| ContinuationToken: tok, | ||||
| IncludeFoldersAsPrefixes: d.includeFoldersAsPrefixes, | ||||
| IsTypeCacheDeprecated: d.isEnableTypeCacheDeprecation, | ||||
| }) | ||||
| if err != nil { | ||||
| return fmt.Errorf("listing objects under prefix %q: %w", prefix, err) | ||||
|
|
||||
Uh oh!
There was an error while loading. Please reload this page.