-
Notifications
You must be signed in to change notification settings - Fork 473
feat(simple reader): Updating MRDInstance when minObject is updated in inode #4259
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
b993cf6
ccc7025
fd8d2ba
1f38585
70b273c
de6509a
05c3e3e
d168c31
b0f523c
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 |
|---|---|---|
|
|
@@ -57,15 +57,66 @@ type MrdInstance struct { | |
| } | ||
|
|
||
| // NewMrdInstance creates a new MrdInstance for a given GCS object. | ||
| // Passed config should not be nil. Code will panic otherwise. | ||
| func NewMrdInstance(obj *gcs.MinObject, bucket gcs.Bucket, cache *lru.Cache, inodeId fuseops.InodeID, config *cfg.Config) *MrdInstance { | ||
| return &MrdInstance{ | ||
| object: obj, | ||
| mrdInstance := MrdInstance{ | ||
| bucket: bucket, | ||
| mrdCache: cache, | ||
| inodeId: inodeId, | ||
| config: config, | ||
| object: obj, | ||
| } | ||
| return &mrdInstance | ||
| } | ||
|
|
||
| // SetMinObject sets the gcs.MinObject stored in the MrdInstance to passed value, only if it's non nil. | ||
| // If the generation of the object has changed, it recreates the MRD pool to ensure consistency. | ||
| func (mi *MrdInstance) SetMinObject(minObj *gcs.MinObject) error { | ||
| if minObj == nil { | ||
| return fmt.Errorf("MrdInstance::SetMinObject: Missing MinObject") | ||
| } | ||
|
|
||
| mi.poolMu.Lock() | ||
| defer mi.poolMu.Unlock() | ||
|
|
||
| oldObj := mi.object | ||
| // If generation matches, just update the object (e.g. for size updates) and return. | ||
| if oldObj != nil && oldObj.Generation == minObj.Generation { | ||
| mi.object = minObj | ||
| return nil | ||
| } | ||
|
|
||
| // Generations differ, need to create and swap a new pool. | ||
| if err := mi.createAndSwapPool(minObj); err != nil { | ||
| return fmt.Errorf("MrdInstance::SetMinObject: failed to create and swap pool: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // createAndSwapPool creates a new MRD pool and swaps it with the existing one. | ||
| // It also updates the minObject with the passed object & closes the old pool. | ||
| // LOCKS_REQUIRED(mi.poolMu). | ||
| func (mi *MrdInstance) createAndSwapPool(obj *gcs.MinObject) error { | ||
| newPool, err := NewMRDPool(&MRDPoolConfig{PoolSize: int(mi.config.Mrd.PoolSize), object: obj, bucket: mi.bucket}, nil) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| oldPool := mi.mrdPool | ||
| mi.mrdPool = newPool | ||
| mi.object = obj | ||
|
|
||
| if oldPool != nil { | ||
| go oldPool.Close() | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // GetMinObject returns the gcs.MinObject stored in MrdInstance. | ||
|
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. Is this only for testing? If yes, can we mention that in the comment. |
||
| func (mi *MrdInstance) GetMinObject() *gcs.MinObject { | ||
| mi.poolMu.RLock() | ||
| defer mi.poolMu.RUnlock() | ||
| return mi.object | ||
| } | ||
|
|
||
| // getMRDEntry returns a valid MRDEntry from the pool. | ||
|
|
@@ -182,36 +233,29 @@ func (mi *MrdInstance) ensureMRDPool() (err error) { | |
| // file inode when the backing GCS object's generation changes, invalidating | ||
| // all existing downloader instances. | ||
| func (mi *MrdInstance) RecreateMRD() error { | ||
| // Create the new pool first to avoid a period where mrdPool is nil. | ||
| newPool, err := NewMRDPool(&MRDPoolConfig{ | ||
| PoolSize: int(mi.config.Mrd.PoolSize), | ||
| object: mi.object, | ||
| bucket: mi.bucket, | ||
| }, nil) | ||
| if err != nil { | ||
| return fmt.Errorf("MrdInstance::RecreateMRD Error in recreating MRD: %w", err) | ||
| } | ||
|
|
||
| mi.poolMu.Lock() | ||
| oldPool := mi.mrdPool | ||
| mi.mrdPool = newPool | ||
| mi.poolMu.Unlock() | ||
| defer mi.poolMu.Unlock() | ||
|
|
||
| // Close the old pool after swapping. | ||
| if oldPool != nil { | ||
| oldPool.Close() | ||
| obj := mi.object | ||
| if obj == nil { | ||
| return fmt.Errorf("MrdInstance::RecreateMRD: object is nil") | ||
| } | ||
|
|
||
| if err := mi.createAndSwapPool(obj); err != nil { | ||
| return fmt.Errorf("MrdInstance::RecreateMRD: failed to create new pool: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // closePool closes all MRD instances in the pool and releases associated resources. | ||
| func (mi *MrdInstance) closePool() { | ||
| mi.poolMu.Lock() | ||
| defer mi.poolMu.Unlock() | ||
| if mi.mrdPool != nil { | ||
| // Close the pool. | ||
| mi.mrdPool.Close() | ||
| mi.mrdPool = nil | ||
| pool := mi.mrdPool | ||
| mi.mrdPool = nil | ||
| if pool != nil { | ||
| go pool.Close() | ||
|
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. Is this just for performance? Do we know, how much will be save? I am just worried about the go-routine resource leak, in case multiple such Close gets stuck.
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. Yes, it's for performance. It will wait for the active requests on the MRD to complete and then destroy the MRD. It will do this serially for each MRD in the pool. |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -254,7 +298,7 @@ func (mi *MrdInstance) IncrementRefCount() { | |
| // Remove from cache | ||
| deletedEntry := mi.mrdCache.Erase(getKey(mi.inodeId)) | ||
| if deletedEntry != nil { | ||
| logger.Tracef("MrdInstance::IncrementRefCount: MrdInstance (%s) erased from cache", mi.object.Name) | ||
| logger.Tracef("MrdInstance::IncrementRefCount: MrdInstance Inode (%d) erased from cache", mi.inodeId) | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -303,13 +347,14 @@ func (mi *MrdInstance) DecrementRefCount() { | |
| // This is a safe order. | ||
| evictedValues, err := mi.mrdCache.Insert(getKey(mi.inodeId), mi) | ||
| if err != nil { | ||
| logger.Errorf("MrdInstance::DecrementRefCount: Failed to insert MrdInstance for object (%s) into cache, destroying immediately: %v", mi.object.Name, err) | ||
| logger.Errorf("MrdInstance::DecrementRefCount: Failed to insert MrdInstance for inode (%d) into cache, destroying immediately: %v", mi.inodeId, err) | ||
| // The instance could not be inserted into the cache. Since the refCount is 0, | ||
| // we must close it now to prevent it from being leaked. | ||
| mi.closePool() | ||
| return | ||
| } | ||
| logger.Tracef("MrdInstance::DecrementRefCount: MrdInstance for object (%s) added to cache", mi.object.Name) | ||
|
|
||
| logger.Tracef("MrdInstance::DecrementRefCount: MrdInstance for inode (%d) added to cache", mi.inodeId) | ||
|
|
||
| // Do not proceed if no eviction happened. | ||
| if evictedValues == nil { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after the inode gets updated with new generation we should invalidate the MRD instance and create it again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're leaving object unfinalized, then the generation won't change and we don't have to recreate the MRD. I can recreate it if FinalizeFileForRapid is set to true. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled generation change scenario in SetMinObject.