in turbonfs/src/nfs_client.cpp [88:215]
void nfs_client::shutdown()
{
assert(!shutting_down);
shutting_down = true;
/*
* Shutdown libnfs RPC transport, so that we don't get any new callbacks
* after we cleanup our data structures below.
*/
transport.close();
AZLogInfo("Stopped transport!");
auto end_delete = inode_map.end();
for (auto it = inode_map.begin(), next_it = it; it != end_delete; it = next_it) {
++next_it;
struct nfs_inode *inode = it->second;
assert(inode->magic == NFS_INODE_MAGIC);
const bool unexpected_refs =
((inode->lookupcnt + inode->dircachecnt) == 0);
if (unexpected_refs) {
AZLogError("[BUG] [{}:{}] Inode with 0 ref still present in "
"inode_map at shutdown: lookupcnt={}, "
"dircachecnt={}, forget_expected={}, "
"is_cache_empty={}",
inode->get_filetype_coding(),
inode->get_fuse_ino(),
inode->lookupcnt.load(),
inode->dircachecnt.load(),
inode->forget_expected.load(),
inode->is_cache_empty());
} else {
AZLogDebug("[{}:{}] Inode still present at shutdown: "
"lookupcnt={}, dircachecnt={}, forget_expected={}, "
"is_cache_empty={}",
inode->get_filetype_coding(),
inode->get_fuse_ino(),
inode->lookupcnt.load(),
inode->dircachecnt.load(),
inode->forget_expected.load(),
inode->is_cache_empty());
}
/*
* Fuse wants to treat an unmount as an implicit forget for
* all inodes. Fuse does not gurantee that it will call forget
* for each inode, hence we have to implicity forget all inodes.
*/
if (inode->forget_expected) {
assert(!inode->is_forgotten());
/*
* 'next_it' might get removed as a result of decref() of the
* current inode, if 'it' corresponds to a directory inode and
* 'next_it' corresponds to a file in that directory and
* 'next_it' is present in inode_map only because of the
* dircachecnt held by the readdir cache of the current dir.
* To prevent next_it from being removed, we hold a lookupcnt
* ref on next_inode and then drop that ref after the decref()
* call.
*/
struct nfs_inode *next_inode = nullptr;
if (next_it != end_delete) {
next_inode = next_it->second;
assert(next_inode->magic == NFS_INODE_MAGIC);
assert((next_inode->lookupcnt +
next_inode->dircachecnt) > 0);
next_inode->incref();
}
inode->decref(inode->forget_expected, true /* from_forget */);
if (next_inode) {
/*
* If the following decref() is going to cause next_it to
* be removed, increment it before that.
*/
if (next_inode->lookupcnt == 1 &&
next_inode->dircachecnt == 0) {
++next_it;
}
next_inode->decref();
}
/*
* root_fh is not valid anymore, clear it now.
* We do not expect forget_expected to be non-zero for root
* inode, so we have the assert to confirm.
* XXX If the assert hits, just remove it.
*/
if (inode == root_fh) {
assert(0);
root_fh = nullptr;
}
}
}
/*
* At this point root inode will have just the original reference
* (acquired in nfs_client::init()), drop it now.
* This will also purge the readdir cache for the root directory
* dropping the last dircachecnt ref on all those entries and thus
* causing those inodes to be deleted.
*/
if (root_fh) {
assert(root_fh->lookupcnt == 1);
root_fh->decref(1, false /* from_forget */);
root_fh = nullptr;
}
/*
* Now we shouldn't have any left.
*/
for (auto it : inode_map) {
struct nfs_inode *inode = it.second;
AZLogWarn("[BUG] [{}:{}] Inode still present at shutdown: "
"lookupcnt={}, dircachecnt={}, forget_expected={}, "
"is_cache_empty={}",
inode->get_filetype_coding(),
inode->get_fuse_ino(),
inode->lookupcnt.load(),
inode->dircachecnt.load(),
inode->forget_expected.load(),
inode->is_cache_empty());
}
assert(inode_map.size() == 0);
jukebox_thread.join();
}