bool nfs_inode::release()

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