Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
170 commits
Select commit Hold shift + click to select a range
898b266
Remove TODO - race condition is already handled
foodprocessor Oct 14, 2024
9f3d73e
Merge remote-tracking branch 'origin/main' into network-fault-tolerance
foodprocessor Oct 24, 2024
31c12e0
Add CloudUnreachableError from async-cloud branch
foodprocessor Oct 24, 2024
808abe7
Add config option to allow users to disable network fault tolerance (…
foodprocessor Oct 24, 2024
21456c4
Add new error for uncached directories
foodprocessor Oct 25, 2024
257bf5f
Handle cloud errors in ac StreamDir
foodprocessor Oct 25, 2024
5c46e98
Return descriptive errors to allow calling components to receive cach…
foodprocessor Oct 25, 2024
8ee353d
Add listingComplete flag, set it, and use it to guess whether items e…
foodprocessor Oct 26, 2024
fb0746d
tidy
foodprocessor Oct 26, 2024
08f443d
CreateDir, DeleteDir and StreamDir
foodprocessor Oct 26, 2024
86a743a
GetAttr
foodprocessor Nov 1, 2024
d90fc37
Merge branch 'main' into network-fault-tolerance
foodprocessor Nov 6, 2024
a90483d
parse S3 errors in List()
foodprocessor Nov 12, 2024
707ba3e
IsDirEmpty
foodprocessor Nov 12, 2024
bed166b
Fix tests
foodprocessor Nov 12, 2024
f1878fe
Lengthen wait to prevent inconsistent results
foodprocessor Nov 12, 2024
f7f5532
Add parameter to setupTestHelper to use mock instead of loopback
foodprocessor Nov 14, 2024
1350081
Delete file immediately (not async) in CachePurge
foodprocessor Nov 25, 2024
e58a516
Set test timeout to 1s.
foodprocessor Nov 25, 2024
e37f609
Remove unused deleteEvent channel
foodprocessor Nov 25, 2024
63f412d
Use cacheTimeoutMonitor even when timeout is zero.
foodprocessor Nov 26, 2024
3e71a0f
Do not use CachePurge for eviction, even when timeout is zero.
foodprocessor Nov 26, 2024
5d2e6af
fixup
foodprocessor Nov 26, 2024
8a6f792
Merge branch 'zero-timeout-evicts-all' into delete-files-synchronously
foodprocessor Nov 26, 2024
2b62986
Use fileLocks to keep file info coherent
foodprocessor Nov 26, 2024
1704b57
Merge remote-tracking branch 'origin/main' into delete-files-synchron…
foodprocessor Nov 26, 2024
77206e8
Remove locks in Chmod & FlushFile to avoid deadlock
foodprocessor Nov 27, 2024
383e8bf
Use object paths when locking files in RenameDir.
foodprocessor Nov 27, 2024
965676c
Close loopback file handles to avoid issues (especially on Windows).
foodprocessor Nov 27, 2024
4c77941
Prevent attribute data corruption
foodprocessor Nov 27, 2024
60321b5
Fix remaining race conditions detected in file cache
foodprocessor Nov 27, 2024
ff15366
Lock file to protect calls to stat.
foodprocessor Nov 27, 2024
4f3f95a
Merge branch 'fix-race-conditions' into delete-files-synchronously
foodprocessor Nov 27, 2024
7b45c80
Make timeout minimum 1s.
foodprocessor Nov 27, 2024
f810edf
Merge branch 'timeout-minimum-1s' into delete-files-synchronously
foodprocessor Nov 27, 2024
bcb288d
Reinstate test which was flaky due to a race condition
foodprocessor Nov 27, 2024
2ea9b61
Remove protection from stat call, which is not within the scope of th…
foodprocessor Nov 27, 2024
74e3769
Merge branch 'fix-race-conditions' into delete-files-synchronously
foodprocessor Nov 27, 2024
3fe1ede
Change test config default to default timeout (120s)
foodprocessor Nov 27, 2024
5b3a6a1
Slow down test check loops to 100ms
foodprocessor Nov 27, 2024
95f6228
Add custom configs for eviction tests.
foodprocessor Nov 27, 2024
f5707fb
Don't open to prevent eviction in tests (unless testing that mechanic).
foodprocessor Nov 27, 2024
0caeb2f
Open from cache when no object exists
foodprocessor Dec 21, 2024
16c8290
Rename downloadFile to openFileInternal
foodprocessor Dec 21, 2024
6eb909b
openFileInternal runs with flock already locked
foodprocessor Dec 21, 2024
787c623
Remove redundant code
foodprocessor Dec 21, 2024
a5b2e51
Replace lost flock.Inc
foodprocessor Dec 21, 2024
2d0e64a
Add test to verify that OpenFile changes what GetAttr returns when op…
foodprocessor Dec 21, 2024
730c271
Do not use &internal.ObjAttr when you mean nil!
foodprocessor Dec 21, 2024
b0c87be
Merge remote-tracking branch 'main' into delete-files-synchronously
foodprocessor Dec 31, 2024
b3136ef
Merge branch 'local-files-open-right-away' into delete-files-synchron…
foodprocessor Dec 31, 2024
1c83708
Add lazy open state in flock, since it affects the whole file's state…
foodprocessor Jan 1, 2025
d59c324
Refactor isDownloadRequired, and use LazyOpen state variable to allow…
foodprocessor Jan 1, 2025
d9d2845
Refactor openFile
foodprocessor Jan 1, 2025
e21dcc1
Update file state in CreateFile
foodprocessor Jan 1, 2025
80fee42
Use O_CREAT flag when testing creation with OpenFile.
foodprocessor Jan 1, 2025
dc7fc99
Merge branch 'local-files-open-right-away' into refactor-openFile
foodprocessor Jan 1, 2025
78be394
Merge branch 'refactor-openFile' into delete-files-synchronously
foodprocessor Jan 1, 2025
b0cbc15
Update state in DeleteFile
foodprocessor Jan 1, 2025
ffd5aea
Update file state in CachePurge (and not speculatively outside of it)
foodprocessor Jan 1, 2025
2ffa0cb
Improve openFile file state logic
foodprocessor Jan 1, 2025
9f47003
Protect file state in Close and Flush (and Sync by consequence)
foodprocessor Jan 1, 2025
094417d
make flushFileInternal update the file state itself
foodprocessor Jan 1, 2025
5e1a000
make renameCachedFile protect and update file states
foodprocessor Jan 1, 2025
80d659c
When locking more that one file, always go in lexical order to preven…
foodprocessor Jan 2, 2025
143e7c4
Add TODOs for directory operations
foodprocessor Jan 2, 2025
4841a19
Lock all files before renaming or deleting a whole directory in one o…
foodprocessor Jan 2, 2025
56a509c
Merge branch 'delete-files-synchronously' into maintain-file-state
foodprocessor Jan 2, 2025
d6a3c72
Make CachePurge update the file state
foodprocessor Jan 2, 2025
f712e7f
Remove file state flags which are never read, and the complexity they…
foodprocessor Jan 2, 2025
ccaca08
Protect file state in GetAttr and Chmod
foodprocessor Jan 2, 2025
b519f5b
Remove unused flags in platform-specific file cache files
foodprocessor Jan 2, 2025
1defe41
Avoid deadlock when flushFileInternal calls Chmod
foodprocessor Jan 2, 2025
74ed602
Don't use base name to find the object name when WalkDir is recursive
foodprocessor Jan 2, 2025
1b44c2d
Merge branch 'main' into network-fault-tolerance
foodprocessor Jan 3, 2025
681b140
Don't search and lock files in DeleteDir, since it is only called on …
foodprocessor Jan 7, 2025
c23d36b
Simplify DeleteDir, since the directory being deleted is empty
foodprocessor Jan 7, 2025
6655269
Revert accidental code comment change
foodprocessor Jan 2, 2025
d7c05a4
Update open file handles in RenameFile
foodprocessor Jan 3, 2025
603b9c8
Remove redundant (and failing) test
foodprocessor Jan 3, 2025
5748258
Do not attempt to reverse cloud rename if file is local only (case 2)
foodprocessor Jan 3, 2025
11ede4f
Don't worry about data loss if file is in the lazy open state, becaue…
foodprocessor Jan 4, 2025
ae3b385
Don't treat invalid AC items differently than non-existent ones.
foodprocessor Jan 6, 2025
1fff857
Do not call attribute cache in CreateDir when offline access is enabled
foodprocessor Jan 7, 2025
115ad77
Merge branch 'valid-handles-on-rename' into network-fault-tolerance
foodprocessor Jan 7, 2025
7ca2078
Merge remote-tracking branch 'origin/main' into network-fault-tolerance
foodprocessor Jan 17, 2025
31ab388
Make CreateDir only intercept calls when we are offline
foodprocessor Jan 23, 2025
87f973d
CreateDir: Do not lock flock. Improve log output.
foodprocessor Jan 23, 2025
ee26dfb
FC DeleteFile: Correct GetAttr call
foodprocessor Jan 24, 2025
3244cea
Tidy IsDirEmpty
foodprocessor Jan 28, 2025
53da64b
Add TODO for new API function
foodprocessor Jan 28, 2025
9001504
Keep and serve expired cache list segments when cloud is unreachable.
foodprocessor Jan 28, 2025
7c3fd1c
Only provide cached directory listings when we have a complete listin…
foodprocessor Jan 28, 2025
7bce1d1
Simplify DeleteDir logic
foodprocessor Feb 7, 2025
a3d2e8e
Finish RenameDir
foodprocessor Feb 7, 2025
116f241
Update pending cloud ops on rename (folder and file)
foodprocessor Feb 8, 2025
93b7291
CreateFile
foodprocessor Feb 8, 2025
0f628ca
Merge branch 'main' into network-fault-tolerance
foodprocessor Feb 8, 2025
9fb21fa
Merge main into network-fault-tolerance
foodprocessor Feb 11, 2025
e302deb
Merge fixup
foodprocessor Feb 11, 2025
30daaa4
DeleteFile
foodprocessor Feb 12, 2025
5c194f5
DeleteFile - finish and scrub
foodprocessor Feb 15, 2025
ab3efac
Merge remote-tracking branch 'origin/main' into network-fault-tolerance
foodprocessor Apr 28, 2025
5885ed2
Fix merge
foodprocessor Apr 28, 2025
d5e06b6
Tidy up switch style
foodprocessor Apr 28, 2025
6c5f164
Use switch for readability
foodprocessor Apr 28, 2025
62073ff
Fix merge
foodprocessor May 1, 2025
5130fb5
Improve offline helper functions
foodprocessor May 2, 2025
de7ccc5
Simplify CreateDir offline logic
foodprocessor May 2, 2025
d91b8c3
Reorganize CreateDir switch
foodprocessor May 2, 2025
31bd29a
Merge remote-tracking branch 'origin/main' into network-fault-tolerance
foodprocessor May 6, 2025
933188b
merge cleanup
foodprocessor May 6, 2025
f558a39
Set up first working mock test
foodprocessor May 6, 2025
5e8b86d
reduce diff
foodprocessor May 6, 2025
8eb58a0
fixup
foodprocessor May 6, 2025
2f6a1a3
DeleteDir
foodprocessor May 7, 2025
28da6af
simplify logic
foodprocessor May 7, 2025
916d6e4
Finished up to IsDirEmpty (WIP)
foodprocessor May 7, 2025
12d465a
Finished RenameDir (WIP)
foodprocessor May 8, 2025
28838a9
Recognize composite ErrNotExist errors returned by attribute cache
foodprocessor May 9, 2025
fdfc86b
offline logic - DeleteFile
foodprocessor May 9, 2025
15a069c
offline logic - OpenFile (WIP)
foodprocessor May 9, 2025
cff6084
Offline logic - openFileInternal
foodprocessor May 9, 2025
85ce661
Read, Write, Chmod, Flush, Close (WIP)
foodprocessor May 12, 2025
9e05148
GetAttr - simplify logic
foodprocessor May 12, 2025
18a31c3
Merge branch 'main' into network-fault-tolerance
foodprocessor May 12, 2025
2c485fc
Merge remote-tracking branch 'origin/main' into network-fault-tolerance
foodprocessor May 13, 2025
abb9aa4
Finish offline logic
foodprocessor May 13, 2025
ac05fdc
Prevent eviction for offline operations
foodprocessor May 13, 2025
6e862e0
Lint
foodprocessor May 13, 2025
f4ab1f0
Lint
foodprocessor May 13, 2025
e7dd2db
Upload pending operations on a separate thread
foodprocessor May 15, 2025
e373554
Use log.Warn for offline rename
foodprocessor May 15, 2025
c0d6058
Add iptables scripts to test offline aceess (picked from async-cloud …
foodprocessor May 16, 2025
e7784c1
Expand rule to include all Lyve Cloud regions (at least in the US).
foodprocessor May 17, 2025
3a22a5a
Use empty values
foodprocessor May 17, 2025
ed80051
Handle directory case using stat
foodprocessor May 17, 2025
333ff38
Add log output
foodprocessor May 20, 2025
3f408f2
Detect quota exceeded error as an offline error
foodprocessor May 22, 2025
4a826fb
Prevent upload unless HeadBucket returns no error.
foodprocessor May 22, 2025
edc390c
Merge remote-tracking branch 'origin/main' into network-fault-tolerance
foodprocessor May 28, 2025
4265f43
Merge remote-tracking branch 'origin/main' into network-fault-tolerance
foodprocessor Jun 9, 2025
70a1484
Bugfix: allow case 2 for directories
foodprocessor Jun 21, 2025
3db1894
Merge file cache changes from exponential backoff branch
foodprocessor Jun 21, 2025
a566794
Merge branch 'main' into network-fault-tolerance
foodprocessor Jun 30, 2025
1829a95
Exponential backoff offline cloud (#600)
foodprocessor Jul 8, 2025
409a641
Merge remote-tracking branch 'origin/main' into network-fault-tolerance
foodprocessor Aug 24, 2025
cb3eaac
Improve scheduler concurrency
foodprocessor Aug 24, 2025
f5ebe95
Declare start and stop where they are used instead of passing them.
foodprocessor Aug 24, 2025
4dfe3f7
When initializing during an open window, start uploading immediately,…
foodprocessor Aug 24, 2025
a42727f
Test initializing during an active window.
foodprocessor Aug 24, 2025
457f09f
Fix minor logging bug
foodprocessor Aug 24, 2025
40a1be8
Merge branch 'improve-scheduler' into network-fault-tolerance
foodprocessor Aug 24, 2025
6f74269
Optimize tests
foodprocessor Aug 25, 2025
78b1c60
Merge branch 'improve-scheduler' into network-fault-tolerance
foodprocessor Aug 25, 2025
5daa46a
Merge Log collector cmd into network-fault-tolerance
foodprocessor Sep 26, 2025
bf7409c
Merge commit '0fe5caf8f8247542cbcee0c3a287f362c0b8cdb6' into network-…
foodprocessor Sep 26, 2025
c47f7c0
Merge commit '7f50d2348cc2da3bf392085b772143cc9faa4138' into network-…
foodprocessor Sep 26, 2025
df34ff8
Merge #661 into network-fault-tolerance
foodprocessor Sep 26, 2025
9f660a3
Merge commit '769c2481355d7d5e1f3a6ed00b10200bea94b27a' into network-…
foodprocessor Sep 26, 2025
9aa47fb
Merge improve scheduler into network-fault-tolerance
foodprocessor Sep 27, 2025
f5bc63a
Merge branch 'main' into network-fault-tolerance
foodprocessor Sep 27, 2025
d341051
Make network fault tolerance respect the scheduler's window.
foodprocessor Sep 28, 2025
49d7182
Stop async uploads on exit.
foodprocessor Sep 29, 2025
fd88abb
Don't lazy write when async uploading
foodprocessor Sep 29, 2025
6d3e940
Merge offline and scheduler logic
foodprocessor Sep 29, 2025
e1540b9
Make invalid schedule fail the mount
foodprocessor Sep 29, 2025
6e973a0
Lint
foodprocessor Sep 29, 2025
8daa843
Save SyncPending flag states
foodprocessor Sep 30, 2025
014da68
Remove SyncPending flag from flock, since it's only used in file_cache.
foodprocessor Sep 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions common/lock_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ type LockMapItem struct {
mtx sync.RWMutex
downloadTime time.Time
// track if file is in lazy open state
LazyOpen bool
SyncPending bool
LazyOpen bool
}

// Map holding locks for all the files
Expand Down
45 changes: 45 additions & 0 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,51 @@ func CloudfuseVersion_() string {
return cloudfuseVersion_
}

// custom errors shared by different components
type CloudUnreachableError struct {
Message string
CloudStorageError error
}

func NewCloudUnreachableError(originalError error) CloudUnreachableError {
return CloudUnreachableError{
Message: "Failed to connect to cloud storage",
CloudStorageError: originalError,
}
}
func (e CloudUnreachableError) Error() string {
return fmt.Sprintf("%s. Here's why: %v", e.Message, e.CloudStorageError)
}
func (e CloudUnreachableError) Unwrap() error {
return e.CloudStorageError
}
func (e CloudUnreachableError) Is(target error) bool {
_, ok := target.(*CloudUnreachableError)
return ok
}

type NoCachedDataError struct {
Message string
CacheError error
}

func NewNoCachedDataError(originalError error) CloudUnreachableError {
return CloudUnreachableError{
Message: "Failed to connect to cloud storage",
CloudStorageError: originalError,
}
}
func (e NoCachedDataError) Error() string {
return fmt.Sprintf("%s. Here's why: %v", e.Message, e.CacheError)
}
func (e NoCachedDataError) Unwrap() error {
return e.CacheError
}
func (e NoCachedDataError) Is(target error) bool {
_, ok := target.(*NoCachedDataError)
return ok
}

var DefaultWorkDir string
var DefaultLogFilePath string
var StatsConfigFilePath string
Expand Down
153 changes: 104 additions & 49 deletions component/attr_cache/attr_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ package attr_cache

import (
"context"
"errors"
"fmt"
"os"
"path"
Expand All @@ -36,6 +37,7 @@ import (
"syscall"
"time"

"github.com/Seagate/cloudfuse/common"
"github.com/Seagate/cloudfuse/common/config"
"github.com/Seagate/cloudfuse/common/log"
"github.com/Seagate/cloudfuse/internal"
Expand Down Expand Up @@ -240,12 +242,12 @@ func (ac *AttrCache) deleteDirectory(path string, deletedAt time.Time) error {
}

// does the cache show this path as existing?
func (ac *AttrCache) pathExistsInCache(path string) bool {
func (ac *AttrCache) getItemIfExists(path string) *attrCacheItem {
item, found := ac.cache.get(path)
if !found {
return false
if found && item.exists() {
return item
}
return item.exists()
return nil
}

// returns the parent directory (without a trailing slash)
Expand Down Expand Up @@ -449,7 +451,9 @@ func (ac *AttrCache) CreateDir(options internal.CreateDirOptions) error {
exists: true,
cachedAt: time.Now(),
})
// update flags for tracking directory existence
// this is a new directory, so we have a complete (empty) listing for it
newDirAttrCacheItem.listingComplete = true
// update flag for tracking directory existence
if ac.cacheDirs {
newDirAttrCacheItem.markInCloud(false)
}
Expand Down Expand Up @@ -534,15 +538,25 @@ func (ac *AttrCache) StreamDir(
options.Name, numAdded, len(pathList))
}
}
}
// add cached items in
if len(cachedPathList) > 0 {
log.Info(
"AttrCache::StreamDir : %s merging in %d list cache entries...",
options.Name,
len(cachedPathList),
)
pathList = append(pathList, cachedPathList...)
} else if errors.Is(err, &common.CloudUnreachableError{}) {
// return expired cachedPathList
if cachedPathList != nil {
pathList = cachedPathList
nextToken = cachedToken
} else {
// return whatever entries we have (but only if the token is empty)
entry, found := ac.cache.get(options.Name)
if options.Token == "" && found && entry.listingComplete {
for _, v := range entry.children {
if v.exists() && v.valid() {
pathList = append(pathList, v.attr)
}
}
} else {
// the cloud is unavailable, and we have nothing to provide
err = common.NewNoCachedDataError(err)
}
}
}
// values should be returned in ascending order by key, without duplicates
// sort
Expand All @@ -559,7 +573,18 @@ func (ac *AttrCache) StreamDir(
return a.Path == b.Path
},
)
ac.cacheListSegment(pathList, options.Name, options.Token, nextToken)
// cache the listing (if there was no error)
if err == nil {
// record when the directory was listed, an up to what token
// this will allow us to serve directory listings from this cache
ac.cacheListSegment(pathList, options.Name, options.Token, nextToken)
// if the listing is complete, record the fact that we have a complete listing
if nextToken == "" {
ac.markListingComplete(options.Name)
}
} else {
log.Err("AttrCache::StreamDir : %s encountered error [%v]", options.Name, err)
}
log.Trace("AttrCache::StreamDir : %s returning %d entries", options.Name, len(pathList))
return pathList, nextToken, err
}
Expand All @@ -572,9 +597,8 @@ func (ac *AttrCache) fetchCachedDirList(
path string,
token string,
) ([]*internal.ObjAttr, string, error) {
var pathList []*internal.ObjAttr
if !ac.cacheOnList {
return pathList, "", fmt.Errorf("cache on list is disabled")
return nil, "", fmt.Errorf("cache on list is disabled")
}
// start accessing the cache
ac.cacheLock.RLock()
Expand All @@ -583,25 +607,22 @@ func (ac *AttrCache) fetchCachedDirList(
listDirCache, found := ac.cache.get(path)
if !found {
log.Warn("AttrCache::fetchCachedDirList : %s directory not found in cache", path)
return pathList, "", fmt.Errorf("%s directory not found in cache", path)
return nil, "", common.NewNoCachedDataError(
fmt.Errorf("%s directory not found in cache", path),
)
}
// is the requested data cached?
if listDirCache.listCache == nil {
listDirCache.listCache = make(map[string]listCacheSegment)
}
cachedListSegment, found := listDirCache.listCache[token]
if !found {
// the data for this token is not in the cache
// don't provide cached data when new (uncached) data is being requested
log.Info("AttrCache::fetchCachedDirList : %s listing segment %s not cached", path, token)
return pathList, "", fmt.Errorf("%s directory listing segment %s not cached", path, token)
return nil, "", fmt.Errorf("%s directory listing segment %s not cached", path, token)
}
// check timeout
if time.Since(cachedListSegment.cachedAt).Seconds() >= float64(ac.cacheTimeout) {
log.Info("AttrCache::fetchCachedDirList : %s listing segment %s cache expired", path, token)
// drop the invalid segment from the list cache
delete(listDirCache.listCache, token)
return pathList, "", fmt.Errorf(
return cachedListSegment.entries, "", fmt.Errorf(
"%s directory listing segment %s cache expired",
path,
token,
Expand Down Expand Up @@ -673,16 +694,19 @@ func (ac *AttrCache) cacheListSegment(
}
// add the new entry
listDirItem.listCache[token] = newListCacheSegment
// scan the listing cache and remove expired entries
for k, v := range listDirItem.listCache {
if currTime.Sub(v.cachedAt).Seconds() >= float64(ac.cacheTimeout) {
delete(listDirItem.listCache, k)
}
}
log.Trace("AttrCache::cacheListSegment : %s cached list entries \"%s\"-\"%s\" (%d items)",
listDirPath, token, nextToken, len(pathList))
}

func (ac *AttrCache) markListingComplete(listDirPath string) {
ac.cacheLock.Lock()
defer ac.cacheLock.Unlock()
listDirItem, found := ac.cache.get(listDirPath)
if found {
listDirItem.listingComplete = true
}
}

// IsDirEmpty: Whether or not the directory is empty
func (ac *AttrCache) IsDirEmpty(options internal.IsDirEmptyOptions) bool {
log.Trace("AttrCache::IsDirEmpty : %s", options.Name)
Expand All @@ -693,14 +717,15 @@ func (ac *AttrCache) IsDirEmpty(options internal.IsDirEmptyOptions) bool {
"AttrCache::IsDirEmpty : %s Dir cache is disabled. Checking with container",
options.Name,
)
// when offline, this will return false
return ac.NextComponent().IsDirEmpty(options)
}
// Is the directory in our cache?
ac.cacheLock.RLock()
pathInCache := ac.pathExistsInCache(options.Name)
ac.cacheLock.RUnlock()
defer ac.cacheLock.RUnlock()
item := ac.getItemIfExists(options.Name)
// If the directory does not exist in the attribute cache then let the next component answer
if !pathInCache {
if item == nil {
log.Debug(
"AttrCache::IsDirEmpty : %s not found in attr_cache. Checking with container",
options.Name,
Expand All @@ -709,10 +734,15 @@ func (ac *AttrCache) IsDirEmpty(options internal.IsDirEmptyOptions) bool {
}
log.Debug("AttrCache::IsDirEmpty : %s found in attr_cache", options.Name)
// Check if the cached directory is empty or not
if ac.anyContentsInCache(options.Name) {
if item.hasExistingChildren() {
log.Debug("AttrCache::IsDirEmpty : %s has a subpath in attr_cache", options.Name)
return false
}
// do we have a complete listing?
if item.listingComplete {
// we know the directory is empty
return true
}
// Dir is in cache but no contents are, so check with container
log.Debug(
"AttrCache::IsDirEmpty : %s children not found in cache. Checking with container",
Expand All @@ -721,16 +751,10 @@ func (ac *AttrCache) IsDirEmpty(options internal.IsDirEmptyOptions) bool {
return ac.NextComponent().IsDirEmpty(options)
}

func (ac *AttrCache) anyContentsInCache(prefix string) bool {
ac.cacheLock.RLock()
defer ac.cacheLock.RUnlock()

directory, found := ac.cache.get(prefix)
if found && directory.exists() {
for _, chldItem := range directory.children {
if chldItem.exists() {
return true
}
func (value *attrCacheItem) hasExistingChildren() bool {
for _, childItem := range value.children {
if childItem.exists() {
return true
}
}
return false
Expand All @@ -752,7 +776,7 @@ func (ac *AttrCache) RenameDir(options internal.RenameDirOptions) error {
if ac.cacheDirs {
// if attr_cache is tracking directories, validate this rename
// First, check if the destination directory already exists
if ac.pathExistsInCache(options.Dst) {
if ac.getItemIfExists(options.Dst) != nil {
return os.ErrExist
}
} else {
Expand Down Expand Up @@ -1121,9 +1145,10 @@ func (ac *AttrCache) GetAttr(options internal.GetAttrOptions) (*internal.ObjAttr
ac.cacheLock.Lock()
defer ac.cacheLock.Unlock()

switch err {
case nil:
switch {
case err == nil:
// Retrieved attributes so cache them
log.Debug("AttrCache::GetAttr : %s Caching record from cloud", options.Name)
ac.cache.insert(insertOptions{
attr: pathAttr,
exists: true,
Expand All @@ -1132,13 +1157,43 @@ func (ac *AttrCache) GetAttr(options internal.GetAttrOptions) (*internal.ObjAttr
if ac.cacheDirs {
ac.markAncestorsInCloud(getParentDir(options.Name), time.Now())
}
case syscall.ENOENT:
case err == syscall.ENOENT:
// cache this entity not existing
log.Debug("AttrCache::GetAttr : %s Caching ENOENT from cloud", options.Name)
ac.cache.insert(insertOptions{
attr: internal.CreateObjAttr(options.Name, 0, time.Now()),
exists: false,
cachedAt: time.Now(),
})
case errors.Is(err, &common.CloudUnreachableError{}):
// the cloud connection is down
// do we have an entry, but it's expired? Let's serve that.
if found && value.valid() {
// Serve the request from the attribute cache
if !value.exists() {
log.Debug("AttrCache::GetAttr : %s ENOENT found in cache (offline)", options.Name)
return value.attr, errors.Join(syscall.ENOENT, err)
} else {
log.Debug("AttrCache::GetAttr : %s Entry found in cache (offline)", options.Name)
return value.attr, err
}
} else {
// we have no cached data about this item
// but do we have a complete listing for its parent directory?
entry, found := ac.cache.get(getParentDir(options.Name))
if found && entry.listingComplete {
log.Debug("AttrCache::GetAttr : %s Not in directory (offline)", options.Name)
return nil, errors.Join(syscall.ENOENT, err)
} else {
// we have no way of knowing whether the requested item is in the directory in the cloud
// NOTE:
// the OS can call GetAttr on a file without listing its parent directory
// so having a valid file entry in cache does not mean we have a complete listing of its parent
// so we can't just check if the directory has any children as a proxy for whether it's been listed
log.Err("AttrCache::GetAttr : %s No cached data (offline)", options.Name)
return nil, common.NewNoCachedDataError(err)
}
}
}
return pathAttr, err
}
Expand Down
2 changes: 2 additions & 0 deletions component/attr_cache/cacheMap.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type attrCacheItem struct {
attrFlag common.BitMap16
children map[string]*attrCacheItem
parent *attrCacheItem

listingComplete bool
}

// all cache entries are organized into this structure
Expand Down
Loading
Loading