From a2d6c487f165348eddfe428eeea24aca1cfacb73 Mon Sep 17 00:00:00 2001 From: tstabrawa <59430211+tstabrawa@users.noreply.github.com> Date: Sun, 19 May 2024 12:59:33 -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 from zpl_readpage_common and define .release_folio, to cause fallback_migrate_folio to wait for us. Signed-off-by: Tim Stabrawa <59430211+tstabrawa@users.noreply.github.com> Closes #15140 --- config/kernel-vfs-invalidate_folio.m4 | 33 ++++++++++ config/kernel-vfs-invalidate_page_has_len.m4 | 33 ++++++++++ config/kernel-vfs-release_folio.m4 | 32 ++++++++++ config/kernel.m4 | 6 ++ module/os/linux/zfs/zpl_file.c | 64 ++++++++++++++++++++ 5 files changed, 168 insertions(+) create mode 100644 config/kernel-vfs-invalidate_folio.m4 create mode 100644 config/kernel-vfs-invalidate_page_has_len.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-invalidate_page_has_len.m4 b/config/kernel-vfs-invalidate_page_has_len.m4 new file mode 100644 index 000000000000..2c30e3367ac7 --- /dev/null +++ b/config/kernel-vfs-invalidate_page_has_len.m4 @@ -0,0 +1,33 @@ +dnl # +dnl # Linux 3.11 has three parameters for invalidate_page +dnl # +AC_DEFUN([ZFS_AC_KERNEL_SRC_VFS_INVALIDATE_PAGE_WITH_LEN], [ + ZFS_LINUX_TEST_SRC([vfs_has_invalidate_page_with_len], [ + #include + + static void + test_invalidate_page(struct page *page, unsigned int offset, + unsigned int len) { + (void) page; (void) offset; (void) len; + return; + } + + static const struct address_space_operations + aops __attribute__ ((unused)) = { + .invalidate_page = test_invalidate_page, + }; + ],[]) +]) + +AC_DEFUN([ZFS_AC_KERNEL_VFS_INVALIDATE_PAGE_WITH_LEN], [ + dnl # + dnl # Linux 3.11 has three parameters for invalidate_page + dnl # + AC_MSG_CHECKING([whether invalidate_page has len]) + ZFS_LINUX_TEST_RESULT([vfs_has_invalidate_page_with_len], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_VFS_INVALIDATE_PAGE_WITH_LEN, 1, [invalidate_page has len]) + ],[ + 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 548905ccd04d..125bd9316a23 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -107,6 +107,9 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [ ZFS_AC_KERNEL_SRC_LSEEK_EXECUTE 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_INVALIDATE_PAGE_WITH_LEN ZFS_AC_KERNEL_SRC_VFS_GETATTR ZFS_AC_KERNEL_SRC_VFS_FSYNC_2ARGS ZFS_AC_KERNEL_SRC_VFS_ITERATE @@ -257,6 +260,9 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [ ZFS_AC_KERNEL_LSEEK_EXECUTE 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_INVALIDATE_PAGE_WITH_LEN ZFS_AC_KERNEL_VFS_GETATTR ZFS_AC_KERNEL_VFS_FSYNC_2ARGS ZFS_AC_KERNEL_VFS_ITERATE diff --git a/module/os/linux/zfs/zpl_file.c b/module/os/linux/zfs/zpl_file.c index 9dec52215c7c..88e35084090b 100644 --- a/module/os/linux/zfs/zpl_file.c +++ b/module/os/linux/zfs/zpl_file.c @@ -659,6 +659,12 @@ zpl_readpage_common(struct page *pp) cookie = spl_fstrans_mark(); int error = -zfs_getpage(pp->mapping->host, pp); spl_fstrans_unmark(cookie); + if (!error && !PagePrivate(pp)) { + /* Set private bit so that page migration will wait for us to + * finish writeback before calling migrate_folio(). */ + SetPagePrivate(pp); + get_page(pp); + } unlock_page(pp); @@ -816,6 +822,54 @@ 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); + } +} +#elif defined(HAVE_VFS_INVALIDATE_PAGE_WITH_LEN) +static void +zpd_invalidate_page(struct page *pp, unsigned int offset, unsigned int len) +{ + if ((offset == 0) && (len == PAGE_SIZE)) + { + zpl_releasepage(pp, 0); + } +} +#else +static void +zpd_invalidate_page(struct page *pp, unsigned int offset) +{ + if (offset == 0) + { + 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 @@ -1304,6 +1358,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 + .invalidate_page = zpl_invalidate_page, +#endif }; #ifdef HAVE_VFS_FILE_OPERATIONS_EXTEND