Re: [PATCH v6 5/5] selftests/mm: add hard memory failure anonymous HugeTLB test
From: Jiaqi Yan
Date: Fri Jul 24 2026 - 23:06:50 EST
On Sun, Jul 5, 2026 at 11:07 AM Jiaqi Yan <jiaqiyan@xxxxxxxxxx> wrote:
>
> Add a new testcase to validate memory failure recovery for HWPoison
> anonymous HugeTLB page, including proper SIGBUS delivery, releasing
> a HugeTLB page containing one HWPoison page to buddy allocator, and
> isolation of the raw HWPoison page.
>
> The test uses HugeTLB's default hugesize.
>
> Although can be added in future, this patch does not support testing
> the MADV_SOFT variant.
>
> Signed-off-by: Jiaqi Yan <jiaqiyan@xxxxxxxxxx>
> ---
> tools/testing/selftests/mm/memory-failure.c | 70 ++++++++++++++++++++-
> 1 file changed, 67 insertions(+), 3 deletions(-)
>
> diff --git a/tools/testing/selftests/mm/memory-failure.c b/tools/testing/selftests/mm/memory-failure.c
> index 032ed952057c..78c105ce2af1 100644
> --- a/tools/testing/selftests/mm/memory-failure.c
> +++ b/tools/testing/selftests/mm/memory-failure.c
> @@ -18,6 +18,7 @@
> #include <linux/magic.h>
> #include <errno.h>
>
> +#include "hugepage_settings.h"
> #include "vm_util.h"
>
> enum inject_type {
> @@ -27,6 +28,7 @@ enum inject_type {
>
> enum result_type {
> MADV_HARD_ANON,
> + MADV_HARD_ANON_HUGETLB,
> MADV_HARD_CLEAN_PAGECACHE,
> MADV_HARD_DIRTY_PAGECACHE,
> MADV_SOFT_ANON,
> @@ -47,6 +49,8 @@ FIXTURE(memory_failure)
> int pagemap_fd;
> int kpageflags_fd;
> bool triggered;
> + /* Number of initial HugeTLB pages with default page size. */
> + unsigned long nr_hugetlb_pages;
> };
>
> FIXTURE_VARIANT(memory_failure)
> @@ -157,6 +161,7 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
> void *vaddr, enum result_type type, int setjmp)
> {
> unsigned long size;
> + unsigned long nr_hugetlb_pages;
> uint64_t pfn_flags;
>
> switch (type) {
> @@ -174,6 +179,7 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
> ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
> break;
> case MADV_HARD_ANON:
> + case MADV_HARD_ANON_HUGETLB:
> case MADV_HARD_DIRTY_PAGECACHE:
> /* The SIGBUS signal should have been received. */
> ASSERT_EQ(setjmp, 1);
> @@ -184,8 +190,15 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
> ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
> ASSERT_EQ(siginfo.si_addr, vaddr);
>
> - /* XXX Check backing pte is hwpoison entry when supported. */
> - ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
> + if (type != MADV_HARD_ANON_HUGETLB)
> + /*
> + * Check backing pte is hwpoison entry when supported.
> + * Although try_to_unmap_one() also installs hwpoison
> + * entry for HugeTLB, pagemap_hugetlb_range() doesn't
> + * parse swap entries at all.
> + */
> + ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
> +
> break;
> default:
> SKIP(return, "unexpected inject type %d.\n", type);
> @@ -193,7 +206,19 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
>
> /* Check if the value of HardwareCorrupted has increased. */
> ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
> - ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
> +
> + if (type == MADV_HARD_ANON_HUGETLB) {
> + /*
> + * Only one page is hardware corrupted; the rest should all be
> + * released to buddy allocator.
> + */
> + ASSERT_EQ(size, self->corrupted_size + getpagesize() / 1024);
> + /* HugeTLB should have lost the HWPoison HugeTLB page. */
> + nr_hugetlb_pages = hugetlb_nr_default_pages();
> + ASSERT_EQ(nr_hugetlb_pages + 1, self->nr_hugetlb_pages);
> + } else {
> + ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
> + }
>
> /* Check if HWPoison flag is set. */
> ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
> @@ -247,6 +272,45 @@ TEST_F(memory_failure, anon)
> ASSERT_EQ(munmap(addr, self->page_size), 0);
> }
>
> +TEST_F(memory_failure, anon_hugetlb)
> +{
> + char *addr;
> + int ret;
> + const unsigned long nr_alloc_hugetlb_pages = 4;
> + unsigned long alloc_size;
> +
> + if (variant->type == MADV_SOFT)
> + SKIP(return, "Soft offline test is not implemented");
> +
> + /* HugeTLB settings will be automatically restored when test exits. */
> + hugetlb_setup_default(nr_alloc_hugetlb_pages);
>From Sashiko [1]
> Will the sysfs hugepage settings actually be restored when the test exits?
> The anon_hugetlb test calls hugetlb_setup_default() which registers the
> hugepage_restore_settings_atexit handler via atexit().
> However, this test is implemented using the kselftest_harness.h TEST_F()
> macro, which forks a child process to run the test case. When the test
> completes, the child process terminates using _exit() or abort(), both of
> which bypass atexit() handlers.
> As a result, the cleanup handler is never executed, permanently leaking the
> modified nr_hugepages sysfs setting on the host machine.
This is true, and I double checked with this test.
> Could this cleanup be moved to a FIXTURE_TEARDOWN() block so the kselftest
> harness guarantees it will execute?
However, this suggestion doesn't fix the problem. The harness runs
FIXTURE_TEARDOWN() inside the forked child process too, so it will be completely
bypassed in any of the following cases:
- An assertion fails
- The test crashes with a segmentation fault
- The test process is killed by a signal
I believe the correct fix is to use HUGETLB_SETUP_DEFAULT_PAGES() like
[2] did, and I have validated:
$ echo 16 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
$ ./memory-failure -r memory_failure.madv_hard.anon_hugetlb
# [INFO] detected hugetlb page size: 2048 KiB
# [INFO] detected hugetlb page size: 32768 KiB
# [INFO] detected hugetlb page size: 64 KiB
# [INFO] detected hugetlb page size: 1048576 KiB
TAP version 13
1..1
# Starting 1 tests from 1 test cases.
# RUN memory_failure.madv_hard.anon_hugetlb ...
# OK memory_failure.madv_hard.anon_hugetlb
ok 1 memory_failure.madv_hard.anon_hugetlb
# PASSED: 1 / 1 tests passed.
# Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0
$ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
16
[1] https://sashiko.dev/#/patchset/20260705180714.3708947-1-jiaqiyan@xxxxxxxxxx
[2] https://patchew.org/linux/20260511162840.375890-1-rppt@xxxxxxxxxx/20260511162840.375890-46-rppt@xxxxxxxxxx/
> +
> + alloc_size = default_huge_page_size() * nr_alloc_hugetlb_pages;
> + self->page_size = default_huge_page_size();
> + self->nr_hugetlb_pages = hugetlb_nr_default_pages();
> +
> + addr = mmap(0, alloc_size, PROT_READ | PROT_WRITE,
> + MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0);
> + if (addr == MAP_FAILED)
> + SKIP(return, "mmap failed, not enough memory or hugetlb not supported.\n");
> + memset(addr, 0xce, alloc_size);
> +
> + prepare(_metadata, self, addr);
> +
> + ret = sigsetjmp(signal_jmp_buf, 1);
> + if (!self->triggered) {
> + self->triggered = true;
> + ASSERT_EQ(variant->inject(self, addr), 0);
> + FORCE_READ(*addr);
> + }
> +
> + check(_metadata, self, addr, MADV_HARD_ANON_HUGETLB, ret);
> +
> + cleanup(_metadata, self, addr);
> +
> + ASSERT_EQ(munmap(addr, alloc_size), 0);
> +}
> +
> static int prepare_file(const char *fname, unsigned long size)
> {
> int fd;
> --
> 2.55.0.rc0.799.gd6f94ed593-goog
>