void nfs_client::shutdown()

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();
}