mm, netfs, fscache: stop read optimisation when folio removed from pagecache
authorDavid Howells <dhowells@redhat.com>
Wed, 28 Jun 2023 10:48:52 +0000 (11:48 +0100)
committerAndrew Morton <akpm@linux-foundation.org>
Fri, 18 Aug 2023 17:12:13 +0000 (10:12 -0700)
Fscache has an optimisation by which reads from the cache are skipped
until we know that (a) there's data there to be read and (b) that data
isn't entirely covered by pages resident in the netfs pagecache.  This is
done with two flags manipulated by fscache_note_page_release():

if (...
    test_bit(FSCACHE_COOKIE_HAVE_DATA, &cookie->flags) &&
    test_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags))
clear_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags);

where the NO_DATA_TO_READ flag causes cachefiles_prepare_read() to
indicate that netfslib should download from the server or clear the page
instead.

The fscache_note_page_release() function is intended to be called from
->releasepage() - but that only gets called if PG_private or PG_private_2
is set - and currently the former is at the discretion of the network
filesystem and the latter is only set whilst a page is being written to
the cache, so sometimes we miss clearing the optimisation.

Fix this by following Willy's suggestion[1] and adding an address_space
flag, AS_RELEASE_ALWAYS, that causes filemap_release_folio() to always call
->release_folio() if it's set, even if PG_private or PG_private_2 aren't
set.

Note that this would require folio_test_private() and page_has_private() to
become more complicated.  To avoid that, in the places[*] where these are
used to conditionalise calls to filemap_release_folio() and
try_to_release_page(), the tests are removed the those functions just
jumped to unconditionally and the test is performed there.

[*] There are some exceptions in vmscan.c where the check guards more than
just a call to the releaser.  I've added a function, folio_needs_release()
to wrap all the checks for that.

AS_RELEASE_ALWAYS should be set if a non-NULL cookie is obtained from
fscache and cleared in ->evict_inode() before truncate_inode_pages_final()
is called.

Additionally, the FSCACHE_COOKIE_NO_DATA_TO_READ flag needs to be cleared
and the optimisation cancelled if a cachefiles object already contains data
when we open it.

[dwysocha@redhat.com: call folio_mapping() inside folio_needs_release()]
Link: https://github.com/DaveWysochanskiRH/kernel/commit/902c990e311120179fa5de99d68364b2947b79ec
Link: https://lkml.kernel.org/r/20230628104852.3391651-3-dhowells@redhat.com
Fixes: 1f67e6d0b188 ("fscache: Provide a function to note the release of a page")
Fixes: 047487c947e8 ("cachefiles: Implement the I/O routines")
Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Dave Wysochanski <dwysocha@redhat.com>
Reported-by: Rohith Surabattula <rohiths.msft@gmail.com>
Suggested-by: Matthew Wilcox <willy@infradead.org>
Tested-by: SeongJae Park <sj@kernel.org>
Cc: Daire Byrne <daire.byrne@gmail.com>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Steve French <sfrench@samba.org>
Cc: Shyam Prasad N <nspmangalore@gmail.com>
Cc: Rohith Surabattula <rohiths.msft@gmail.com>
Cc: Dave Wysochanski <dwysocha@redhat.com>
Cc: Dominique Martinet <asmadeus@codewreck.org>
Cc: Ilya Dryomov <idryomov@gmail.com>
Cc: Andreas Dilger <adilger.kernel@dilger.ca>
Cc: Jingbo Xu <jefflexu@linux.alibaba.com>
Cc: "Theodore Ts'o" <tytso@mit.edu>
Cc: Xiubo Li <xiubli@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
fs/9p/cache.c
fs/afs/internal.h
fs/cachefiles/namei.c
fs/ceph/cache.c
fs/nfs/fscache.c
fs/smb/client/fscache.c
include/linux/pagemap.h
mm/internal.h

index cebba4eaa0b575af6305faa19363d18593133ea4..12c0ae29f1857cb9cceaac3473cb47cef645d4b8 100644 (file)
@@ -68,6 +68,8 @@ void v9fs_cache_inode_get_cookie(struct inode *inode)
                                       &path, sizeof(path),
                                       &version, sizeof(version),
                                       i_size_read(&v9inode->netfs.inode));
+       if (v9inode->netfs.cache)
+               mapping_set_release_always(inode->i_mapping);
 
        p9_debug(P9_DEBUG_FSC, "inode %p get cookie %p\n",
                 inode, v9fs_inode_cookie(v9inode));
index 9d3d64921106de0d68bcc80b2e04b9074a2b593a..da73b97e19a9af4026c91eecdb20ac0f4fe7b228 100644 (file)
@@ -681,6 +681,8 @@ static inline void afs_vnode_set_cache(struct afs_vnode *vnode,
 {
 #ifdef CONFIG_AFS_FSCACHE
        vnode->netfs.cache = cookie;
+       if (cookie)
+               mapping_set_release_always(vnode->netfs.inode.i_mapping);
 #endif
 }
 
index d9d22d0ec38ad2343d0772af744304c409cb3aa3..7bf7a5fcc045f86a98881657e6da2acc06be98dc 100644 (file)
@@ -585,6 +585,8 @@ static bool cachefiles_open_file(struct cachefiles_object *object,
        if (ret < 0)
                goto check_failed;
 
+       clear_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &object->cookie->flags);
+
        object->file = file;
 
        /* Always update the atime on an object we've just looked up (this is
index 177d8e8d73fe424a412be19635e1f99b37b2e1b3..de1dee46d3df72e0a5069a1318a1ad212419ff67 100644 (file)
@@ -36,6 +36,8 @@ void ceph_fscache_register_inode_cookie(struct inode *inode)
                                       &ci->i_vino, sizeof(ci->i_vino),
                                       &ci->i_version, sizeof(ci->i_version),
                                       i_size_read(inode));
+       if (ci->netfs.cache)
+               mapping_set_release_always(inode->i_mapping);
 }
 
 void ceph_fscache_unregister_inode_cookie(struct ceph_inode_info *ci)
index 8c35d88a84b194d700ac0c0610bd67a4ebc9a4da..b05717fe0d4e4f5b505f98e52e25273fe216b325 100644 (file)
@@ -180,6 +180,9 @@ void nfs_fscache_init_inode(struct inode *inode)
                                               &auxdata,      /* aux_data */
                                               sizeof(auxdata),
                                               i_size_read(inode));
+
+       if (netfs_inode(inode)->cache)
+               mapping_set_release_always(inode->i_mapping);
 }
 
 /*
index 8f6909d633da8a913eb6a872af6860fcf3f7754b..3677525ee99311b4005224931ce4a07eef4fa2a1 100644 (file)
@@ -108,6 +108,8 @@ void cifs_fscache_get_inode_cookie(struct inode *inode)
                                       &cifsi->uniqueid, sizeof(cifsi->uniqueid),
                                       &cd, sizeof(cd),
                                       i_size_read(&cifsi->netfs.inode));
+       if (cifsi->netfs.cache)
+               mapping_set_release_always(inode->i_mapping);
 }
 
 void cifs_fscache_unuse_inode_cookie(struct inode *inode, bool update)
index 716953ee1ebdb26e2be6c08f28f4a77b4e40ee54..0ab0f2362b9b7b1b3fafcbd56fc0805844e68297 100644 (file)
@@ -203,6 +203,7 @@ enum mapping_flags {
        /* writeback related tags are not used */
        AS_NO_WRITEBACK_TAGS = 5,
        AS_LARGE_FOLIO_SUPPORT = 6,
+       AS_RELEASE_ALWAYS,      /* Call ->release_folio(), even if no private data */
 };
 
 /**
@@ -273,6 +274,21 @@ static inline int mapping_use_writeback_tags(struct address_space *mapping)
        return !test_bit(AS_NO_WRITEBACK_TAGS, &mapping->flags);
 }
 
+static inline bool mapping_release_always(const struct address_space *mapping)
+{
+       return test_bit(AS_RELEASE_ALWAYS, &mapping->flags);
+}
+
+static inline void mapping_set_release_always(struct address_space *mapping)
+{
+       set_bit(AS_RELEASE_ALWAYS, &mapping->flags);
+}
+
+static inline void mapping_clear_release_always(struct address_space *mapping)
+{
+       clear_bit(AS_RELEASE_ALWAYS, &mapping->flags);
+}
+
 static inline gfp_t mapping_gfp_mask(struct address_space * mapping)
 {
        return mapping->gfp_mask;
index 822b13de378068d83daf81d5d2c9a159a40c6591..483add0bfb289df5763ed468384ec4ed0c6a3251 100644 (file)
@@ -181,7 +181,10 @@ static inline void set_page_refcounted(struct page *page)
  */
 static inline bool folio_needs_release(struct folio *folio)
 {
-       return folio_has_private(folio);
+       struct address_space *mapping = folio_mapping(folio);
+
+       return folio_has_private(folio) ||
+               (mapping && mapping_release_always(mapping));
 }
 
 extern unsigned long highest_memmap_pfn;