From 202e6a4b3b1add7c4535584adff862106c86caf4 Mon Sep 17 00:00:00 2001 From: tstabrawa <59430211+tstabrawa@users.noreply.github.com> Date: Sun, 22 Sep 2024 18:02:32 -0500 Subject: [PATCH] Avoid BUG in migrate_folio_extra Linux page migration code won't wait for writeback to complete unless it needs to call release_folio. Call SetPagePrivate wherever PageUptodate is set and define .release_folio, to cause fallback_migrate_folio to wait for us. Signed-off-by: tstabrawa <59430211+tstabrawa@users.noreply.github.com> --- config/kernel-vfs-invalidate_folio.m4 | 33 +++++++++++++++++++ config/kernel-vfs-release_folio.m4 | 32 +++++++++++++++++++ config/kernel.m4 | 4 +++ module/os/linux/zfs/zfs_vnops_os.c | 17 ++++++++++ module/os/linux/zfs/zfs_znode_os.c | 8 +++++ module/os/linux/zfs/zpl_file.c | 46 +++++++++++++++++++++++++++ 6 files changed, 140 insertions(+) create mode 100644 config/kernel-vfs-invalidate_folio.m4 create mode 100644 config/kernel-vfs-release_folio.m4 diff --git a/config/kernel-vfs-invalidate_folio.m4 b/config/kernel-vfs-invalidate_folio.m4 new file mode 100644 index 000000000000..61a5c8478af1 --- /dev/null +++ b/config/kernel-vfs-invalidate_folio.m4 @@ -0,0 +1,33 @@ +dnl # +dnl # Linux 5.18 uses invalidate_folio in lieu of invalidate_page +dnl # +AC_DEFUN([ZFS_AC_KERNEL_SRC_VFS_INVALIDATE_FOLIO], [ + ZFS_LINUX_TEST_SRC([vfs_has_invalidate_folio], [ + #include + + static void + test_invalidate_folio(struct folio *folio, size_t offset, + size_t len) { + (void) folio; (void) offset; (void) len; + return; + } + + static const struct address_space_operations + aops __attribute__ ((unused)) = { + .invalidate_folio = test_invalidate_folio, + }; + ],[]) +]) + +AC_DEFUN([ZFS_AC_KERNEL_VFS_INVALIDATE_FOLIO], [ + dnl # + dnl # Linux 5.18 uses invalidate_folio in lieu of invalidate_page + dnl # + AC_MSG_CHECKING([whether invalidate_folio exists]) + ZFS_LINUX_TEST_RESULT([vfs_has_invalidate_folio], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_VFS_INVALIDATE_FOLIO, 1, [invalidate_folio exists]) + ],[ + AC_MSG_RESULT([no]) + ]) +]) diff --git a/config/kernel-vfs-release_folio.m4 b/config/kernel-vfs-release_folio.m4 new file mode 100644 index 000000000000..f31db5677fd3 --- /dev/null +++ b/config/kernel-vfs-release_folio.m4 @@ -0,0 +1,32 @@ +dnl # +dnl # Linux 5.19 uses release_folio in lieu of releasepage +dnl # +AC_DEFUN([ZFS_AC_KERNEL_SRC_VFS_RELEASE_FOLIO], [ + ZFS_LINUX_TEST_SRC([vfs_has_release_folio], [ + #include + + static bool + test_release_folio(struct folio *folio, gfp_t gfp) { + (void) folio; (void) gfp; + return (0); + } + + static const struct address_space_operations + aops __attribute__ ((unused)) = { + .release_folio = test_release_folio, + }; + ],[]) +]) + +AC_DEFUN([ZFS_AC_KERNEL_VFS_RELEASE_FOLIO], [ + dnl # + dnl # Linux 5.19 uses release_folio in lieu of releasepage + dnl # + AC_MSG_CHECKING([whether release_folio exists]) + ZFS_LINUX_TEST_RESULT([vfs_has_release_folio], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_VFS_RELEASE_FOLIO, 1, [release_folio exists]) + ],[ + AC_MSG_RESULT([no]) + ]) +]) diff --git a/config/kernel.m4 b/config/kernel.m4 index a951ce8489cc..628581c98abc 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -77,6 +77,8 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [ ZFS_AC_KERNEL_SRC_SGET ZFS_AC_KERNEL_SRC_VFS_FILEMAP_DIRTY_FOLIO ZFS_AC_KERNEL_SRC_VFS_READ_FOLIO + ZFS_AC_KERNEL_SRC_VFS_RELEASE_FOLIO + ZFS_AC_KERNEL_SRC_VFS_INVALIDATE_FOLIO ZFS_AC_KERNEL_SRC_VFS_FSYNC_2ARGS ZFS_AC_KERNEL_SRC_VFS_DIRECT_IO ZFS_AC_KERNEL_SRC_VFS_READPAGES @@ -185,6 +187,8 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [ ZFS_AC_KERNEL_SGET ZFS_AC_KERNEL_VFS_FILEMAP_DIRTY_FOLIO ZFS_AC_KERNEL_VFS_READ_FOLIO + ZFS_AC_KERNEL_VFS_RELEASE_FOLIO + ZFS_AC_KERNEL_VFS_INVALIDATE_FOLIO ZFS_AC_KERNEL_VFS_FSYNC_2ARGS ZFS_AC_KERNEL_VFS_DIRECT_IO ZFS_AC_KERNEL_VFS_READPAGES diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index 7d3a7b8d9d38..3dce3660c103 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -260,6 +260,15 @@ update_pages(znode_t *zp, int64_t start, int len, objset_t *os) } else { ClearPageError(pp); SetPageUptodate(pp); + if (!PagePrivate(pp)) { + /* + * Set private bit so page migration + * will wait for us to finish writeback + * before calling migrate_folio(). + */ + SetPagePrivate(pp); + get_page(pp); + } if (mapping_writably_mapped(mp)) flush_dcache_page(pp); @@ -4037,6 +4046,14 @@ zfs_fillpage(struct inode *ip, struct page *pp) } else { ClearPageError(pp); SetPageUptodate(pp); + if (!PagePrivate(pp)) { + /* + * Set private bit so page migration will wait for us to + * finish writeback before calling migrate_folio(). + */ + SetPagePrivate(pp); + get_page(pp); + } } return (error); diff --git a/module/os/linux/zfs/zfs_znode_os.c b/module/os/linux/zfs/zfs_znode_os.c index e135f9044679..cea15ad4bc96 100644 --- a/module/os/linux/zfs/zfs_znode_os.c +++ b/module/os/linux/zfs/zfs_znode_os.c @@ -1576,6 +1576,14 @@ zfs_zero_partial_page(znode_t *zp, uint64_t start, uint64_t len) mark_page_accessed(pp); SetPageUptodate(pp); ClearPageError(pp); + if (!PagePrivate(pp)) { + /* + * Set private bit so page migration will wait for us to + * finish writeback before calling migrate_folio(). + */ + SetPagePrivate(pp); + get_page(pp); + } unlock_page(pp); put_page(pp); } diff --git a/module/os/linux/zfs/zpl_file.c b/module/os/linux/zfs/zpl_file.c index 4d1bf1d5477f..50c63695dcc8 100644 --- a/module/os/linux/zfs/zpl_file.c +++ b/module/os/linux/zfs/zpl_file.c @@ -607,6 +607,42 @@ zpl_writepage(struct page *pp, struct writeback_control *wbc) return (zpl_putpage(pp, wbc, &for_sync)); } +static int +zpl_releasepage(struct page *pp, gfp_t gfp) +{ + if (PagePrivate(pp)) { + ClearPagePrivate(pp); + put_page(pp); + } + return (1); +} + +#ifdef HAVE_VFS_RELEASE_FOLIO +static bool +zpl_release_folio(struct folio *folio, gfp_t gfp) +{ + return (zpl_releasepage(&folio->page, gfp)); +} +#endif + +#ifdef HAVE_VFS_INVALIDATE_FOLIO +static void +zpl_invalidate_folio(struct folio *folio, size_t offset, size_t len) +{ + if ((offset == 0) && (len == PAGE_SIZE)) { + zpl_releasepage(&folio->page, 0); + } +} +#else +static void +zpl_invalidatepage(struct page *pp, unsigned int offset, unsigned int len) +{ + if ((offset == 0) && (len == PAGE_SIZE)) { + zpl_releasepage(pp, 0); + } +} +#endif + /* * The flag combination which matches the behavior of zfs_space() is * FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE. The FALLOC_FL_PUNCH_HOLE @@ -1090,6 +1126,16 @@ const struct address_space_operations zpl_address_space_operations = { #ifdef HAVE_VFS_FILEMAP_DIRTY_FOLIO .dirty_folio = filemap_dirty_folio, #endif +#ifdef HAVE_VFS_RELEASE_FOLIO + .release_folio = zpl_release_folio, +#else + .releasepage = zpl_releasepage, +#endif +#ifdef HAVE_VFS_INVALIDATE_FOLIO + .invalidate_folio = zpl_invalidate_folio, +#else + .invalidatepage = zpl_invalidatepage, +#endif }; const struct file_operations zpl_file_operations = {