bool readdirectory_cache::remove()

in turbonfs/src/rpc_readdir.cpp [603:763]


bool readdirectory_cache::remove(cookie3 cookie,
                                 const char *filename_hint,
                                 bool acquire_lock)
{
    AZLogDebug("[{}] remove(cookie: {}, filename_hint: {})",
               dir_inode->ino, cookie, filename_hint ? filename_hint : "");

    // Either cookie or filename_hint (not both) must be passed.
    assert((cookie == 0) == (filename_hint != nullptr));

    struct nfs_inode *inode = nullptr;

    /*
     * dnlc_remove will be set for the case when we are deleting a file or
     * directory. The cache is no longer in sync with the directory and  we
     * need to mark the cache as lookuponly.
     */
    const bool is_dnlc_remove = (cookie == 0);

    {
        /*
         * If acquire_lock is true, get exclusive lock on the map for removing
         * the entry from the map. We use a dummy_lock for minimal code changes
         * in the no-lock case.
         * If you call it with acquire_lock=false make sure readdircache_lock_2
         * is held in exclusive mode.
         */
        std::shared_mutex dummy_lock;
        std::unique_lock<std::shared_mutex> lock(
                acquire_lock ? readdircache_lock_2 : dummy_lock);

        if (filename_hint) {
            cookie = filename_to_cookie(filename_hint);
            if (cookie == 0) {
                AZLogDebug("[{}] filename_hint: {}, not found",
                           dir_inode->ino, filename_hint);
                return false;
            }
            AZLogDebug("[{}] filename_hint: {}, found with cookie: {}",
                       dir_inode->ino, filename_hint, cookie);
        }

        const auto it = dir_entries.find(cookie);
        std::shared_ptr<struct directory_entry> dirent =
            (it != dir_entries.end()) ? it->second : nullptr;

        if (!dirent){
            AZLogDebug("[{}] cookie: {}, not found",
                       dir_inode->ino, cookie);
        } else {
            AZLogDebug("[{}] cookie: {}, found, ino: {}",
                       dir_inode->ino, cookie,
                       dirent->nfs_inode ? dirent->nfs_inode->ino : -1);
        }
        /*
         * Given cookie not found in the cache.
         * It should not happen though since the caller would call remove()
         * only after checking.
         */
        if (!dirent) {
            return false;
        }

        assert(dirent->cookie == cookie);

        if (is_dnlc_remove) {
            set_lookuponly();
        }

        /*
         * This directory_entry is being removed from this readdirectory_cache,
         * reduce cache_size. Note that the directory_entry may not be freed
         * just yet as there could be references held to it.
         */
        assert(cache_size >= dirent->get_cache_size());
        cache_size -= dirent->get_cache_size();

        /*
         * Remove the DNLC entry.
         */
        [[maybe_unused]] const int cnt = dnlc_map.erase(dirent->name);
        assert(cnt == 1);

        /*
         * This just removes it from the cache, no destructor is called at
         * this point as there is a ref held on this by the dirent shared_ptr.
         * Also there could be other shared_ptr references to this
         * directory_entry, but no one can take a fresh directory_entry ref
         * after it's removed from dir_entries.
         */
        dir_entries.erase(it);

        inode = dirent->nfs_inode;

        /*
         * READDIR created cache entry, nothing more to do.
         * directory_entry destructor will be called when dirent goes out of
         * scope.
         */
        if (!inode) {
            AZLogDebug("[{}] Removing \"{}\", cookie {}, from readdir cache",
                       dir_inode->get_fuse_ino(),
                       dirent->name,
                       dirent->cookie);
            return true;
        }

        assert(inode->magic == NFS_INODE_MAGIC);

        /*
         * Any inode referenced by a directory_entry added to a
         * readdirectory_cache must have one reference held, by
         * readdirectory_cache::add().
         */
        assert(inode->dircachecnt > 0);

        AZLogDebug("[{}] Removing {} \"{}\" fuse ino {}, cookie {}, from "
                   "readdir cache (lookupcnt={}, dircachecnt={}, "
                   "forget_expected={})",
                   dir_inode->get_fuse_ino(),
                   inode->is_dir() ? "directory" : "file",
                   dirent->name,
                   inode->get_fuse_ino(),
                   dirent->cookie,
                   inode->lookupcnt.load(),
                   inode->dircachecnt.load(),
                   inode->forget_expected.load());

        /*
         * If this is the last dircachecnt on this inode, it means
         * there are no more readdirectory_cache,s referencing this
         * inode. If there are no lookupcnt refs then we can free it.
         * For safely freeing the inode against any races, we need to call
         * decref() and for that we need to make sure we have at least one
         * ref on the inode, so we call incref() before deleting the
         * directory_entry. Later below we call decref() to drop the ref
         * held and if that's the only ref, inode will be deleted.
         *
         * Once dirent goes out of scope ~directory_entry() will be caalled
         * which will drop the inode's original dircachecnt.
         */
        if (inode->dircachecnt == 1) {
            inode->incref();
        } else {
            return true;
        }
    }

    AZLogDebug("[D:{}] inode {} to be freed, after readdir cache remove",
               dir_inode->get_fuse_ino(),
               inode->get_fuse_ino());

    /*
     * Drop the extra ref held above. If it's the last ref the inode will be
     * freed.
     */
    assert(inode->lookupcnt > 0);
    inode->decref();

    return true;
}