in turbonfs/src/nfs_inode.cpp [1663:1791]
bool nfs_inode::release(fuse_req_t req)
{
assert(opencnt > 0);
AZLogDebug("[{}:{}] nfs_inode::release({}{}), new opencnt is {}",
get_filetype_coding(), ino, fmt::ptr(req),
is_silly_renamed ? ", silly_renamed" : "", opencnt - 1);
/*
* If regular file and last opencnt is being dropped, we should flush
* the cache. This is required for CTO consistency.
* If this is a silly renamed file for which the last opencnt is being
* dropped, then we simply drop the cache and proceed to unlink the file.
* We do the flush() only if 'req' is valid. This ensures that we never
* call flush when called from rename_callback(), which is a libnfs thread.
* If not last opencnt and not silly renamed file or inode belongs to a
* dir, then simply reduce opencnt and return. Caller will call the fuse
* callback.
*/
if (is_regfile() && !is_silly_renamed && req && (opencnt == 1)) {
client->flush(req, get_fuse_ino());
/*
* flush() would call the fuse callback, so we do not want unlink()
* below to call it again, also we don't want caller to call the fuse
* callback.
*/
req = nullptr;
}
/*
* Check once more while decrementing opencnt atomically, in case multiple
* threads race with release() and all find opencnt!=1 above.
*
* Note: With opencnt dropped to 0, if some other thread unlinks the file
* we won't do silly-rename and go ahead and unlink the file at the
* server. This means the following flush may result in NFS WRITEs
* being sent for a deleted file. Server will fail these writes.
* flush_cache_and_wait() ignores these failures.
*/
const bool last_close = (--opencnt == 0);
if (last_close && req && is_regfile() && !is_silly_renamed) {
client->flush(req, get_fuse_ino());
req = nullptr;
}
if (!last_close) {
/*
* If we didn't call flush() above, then caller must call the fuse
* callback.
*/
return (req != nullptr);
}
/*
* Since the last open count on the inode is dropped and the inode is now
* truly getting deleted, invalidate the attribute cache and clear the data
* cache.
*
* This is the close side of cto consistency. Any open after this point
* will cause the file data to be fetched from the server.
*
* Note: For directory inodes this will clear the readdirectory_cache for
* the inode. Few things to note:
* 1. With kernel readdir cache enabled, this should not affect
* readdir performance. Infact this is a good thing to do as
* readdirectory_cache for a directory will be rarely needed after
* a directory is enumerated fully and its fd closed.
* 2. Since our readdirectory_cache doubles as DNLC cache too, this
* may affect lookups as they won't hit the cache now.
*
* TODO: Should this invalidation be controlled using a config?
*
* Note: We pass false for shutdown here as we don't want force purging
* of inuse/dirty membufs. Note that it's possible that after we
* decide that it's the last close of the file (and thus we should
* purge the cache), application can open another handle on the file
* and start read/write which can grab filecache membufs. We should
* leave them out. Flushing the cache will not cause any correctness
* issue, just some inefficiency.
* Another possibility is that nfs_inode::wait_for_ongoing_flush()
* releases the flush lock while still holding the inuse count on
* the membufs. Above client->flush() will proceed and we may come
* here with inuse count on the membufs. Again, we can leave them
* out and let wait_for_ongoing_flush() release those.
*/
invalidate_cache(true /* purge_now */, false /* shutdown */);
invalidate_attribute_cache();
/*
* If not silly_renamed then we are done, else need to unlink the original
* file which we had deferred earlier.
*/
if (!is_silly_renamed) {
/*
* If we didn't call flush() above, then caller must call the fuse
* callback.
*/
return (req != nullptr);
}
/*
* Once we schedule unlink of the silly-renamed file, clear the
* is_silly_renamed flag from the inode, so that we don't attempt deletion
* of the silly renamed file again.
*/
is_silly_renamed = false;
/*
* Delete the silly rename file.
* Note that we will now respond to fuse when the unlink completes.
* The caller MUST arrange to *not* respond to fuse.
* Silly rename is done only for regular files.
*/
assert(!silly_renamed_name.empty());
assert(parent_ino != 0);
assert(is_regfile());
AZLogDebug("[{}] Deleting silly renamed file, {}/{}, req: {}",
ino, parent_ino, silly_renamed_name, fmt::ptr(req));
client->unlink(req, parent_ino,
silly_renamed_name.c_str(), true /* for_silly_rename */);
/*
* Either flush() would have called the fuse callback, or unlink() would
* call when it completes, caller should not.
*/
return false;
}