From: "Kiryl Shutsemau (Meta)" <[email protected]>

Race and functional tests need to drive khugepaged synchronously: set
up a layout, let exactly one full scan pass over it, check the result.
The khugepaged selftest already waits on full_scans advancing by two —
a completion barrier for one pass that started after setup — but it
relies on a short configured scan_sleep_millisecs to make progress.

Lift the pattern into a library helper, khugepaged_full_pass(), and
drive it by the sysfs wake path: any store to scan_sleep_millisecs
wakes the daemon, so the barrier completes promptly regardless of the
configured scan cadence. Wake exactly once per missing pass:
over-waking would queue a straggler pass behind the barrier that
overlaps and perturbs whatever the caller sets up next. One wake
completes one full pass only when the whole mm list fits in a single
scan batch, so callers must pair the helper with a large pages_to_scan.

Settings pushes and pops must not start passes nobody asked for
either, so thp_write_settings() now writes each khugepaged knob only
when it changes. Switch the khugepaged selftest order-parameterized
cases to the helper.

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]>
---
 .../testing/selftests/mm/hugepage_settings.c  | 66 ++++++++++++++++---
 .../testing/selftests/mm/hugepage_settings.h  |  2 +
 tools/testing/selftests/mm/khugepaged.c       | 17 +----
 3 files changed, 61 insertions(+), 24 deletions(-)

diff --git a/tools/testing/selftests/mm/hugepage_settings.c 
b/tools/testing/selftests/mm/hugepage_settings.c
index d7917dce3aba..a26a0cffa9c5 100644
--- a/tools/testing/selftests/mm/hugepage_settings.c
+++ b/tools/testing/selftests/mm/hugepage_settings.c
@@ -183,6 +183,17 @@ void thp_read_settings(struct thp_settings *settings)
        }
 }
 
+/*
+ * Write only on change: any store to a khugepaged sysfs knob wakes the
+ * daemon, and settings pushes/pops must not start scan passes nobody
+ * asked for — khugepaged_full_pass() is the only sanctioned wake.
+ */
+static void thp_update_num(const char *name, unsigned long num)
+{
+       if (thp_read_num(name) != num)
+               thp_write_num(name, num);
+}
+
 void thp_write_settings(struct thp_settings *settings)
 {
        struct khugepaged_settings *khugepaged = &settings->khugepaged;
@@ -198,15 +209,15 @@ void thp_write_settings(struct thp_settings *settings)
                        shmem_enabled_strings[settings->shmem_enabled]);
        thp_write_num("use_zero_page", settings->use_zero_page);
 
-       thp_write_num("khugepaged/defrag", khugepaged->defrag);
-       thp_write_num("khugepaged/alloc_sleep_millisecs",
-                       khugepaged->alloc_sleep_millisecs);
-       thp_write_num("khugepaged/scan_sleep_millisecs",
-                       khugepaged->scan_sleep_millisecs);
-       thp_write_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none);
-       thp_write_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap);
-       thp_write_num("khugepaged/max_ptes_shared", 
khugepaged->max_ptes_shared);
-       thp_write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);
+       thp_update_num("khugepaged/defrag", khugepaged->defrag);
+       thp_update_num("khugepaged/alloc_sleep_millisecs",
+                      khugepaged->alloc_sleep_millisecs);
+       thp_update_num("khugepaged/scan_sleep_millisecs",
+                      khugepaged->scan_sleep_millisecs);
+       thp_update_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none);
+       thp_update_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap);
+       thp_update_num("khugepaged/max_ptes_shared", 
khugepaged->max_ptes_shared);
+       thp_update_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);
 
        if (dev_queue_read_ahead_path[0])
                write_num(dev_queue_read_ahead_path, settings->read_ahead_kb);
@@ -230,6 +241,43 @@ void thp_write_settings(struct thp_settings *settings)
        }
 }
 
+/*
+ * Completion barrier for khugepaged: wait until a full scan pass that
+ * started after this call has finished. full_scans must advance by two;
+ * a +1 step may complete a pass that examined this mm before the
+ * caller's setup was in place.
+ *
+ * Any store to scan_sleep_millisecs wakes the daemon, so the barrier
+ * works regardless of the configured scan cadence. It wakes exactly
+ * once per missing pass — over-waking would queue a straggler pass
+ * behind the barrier, perturbing whatever the caller sets up next.
+ * One wake completes one full pass only if the whole mm list fits in
+ * one scan batch, so callers must pair this with a large
+ * pages_to_scan.
+ */
+bool khugepaged_full_pass(unsigned int timeout_s)
+{
+       unsigned long deadline_ms = timeout_s * 1000UL;
+       unsigned long sleep_ms =
+               thp_read_num("khugepaged/scan_sleep_millisecs");
+       unsigned long elapsed_ms = 0;
+       int pass;
+
+       for (pass = 0; pass < 2; pass++) {
+               unsigned long target =
+                       thp_read_num("khugepaged/full_scans") + 1;
+
+               thp_write_num("khugepaged/scan_sleep_millisecs", sleep_ms);
+               while (thp_read_num("khugepaged/full_scans") < target) {
+                       if (elapsed_ms >= deadline_ms)
+                               return false;
+                       usleep(10 * 1000);
+                       elapsed_ms += 10;
+               }
+       }
+       return true;
+}
+
 struct thp_settings *thp_current_settings(void)
 {
        if (!settings_index) {
diff --git a/tools/testing/selftests/mm/hugepage_settings.h 
b/tools/testing/selftests/mm/hugepage_settings.h
index 726c73c43c05..8de446affeec 100644
--- a/tools/testing/selftests/mm/hugepage_settings.h
+++ b/tools/testing/selftests/mm/hugepage_settings.h
@@ -83,6 +83,8 @@ static inline void thp_save_settings(void)
        hugepage_save_settings(/* thp = */ true, /* hugetlb = */ false);
 }
 
+bool khugepaged_full_pass(unsigned int timeout_s);
+
 void thp_set_read_ahead_path(char *path);
 unsigned long thp_supported_orders(void);
 unsigned long thp_shmem_supported_orders(void);
diff --git a/tools/testing/selftests/mm/khugepaged.c 
b/tools/testing/selftests/mm/khugepaged.c
index 971e97a7330a..65fafab06410 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -1126,23 +1126,10 @@ static bool range_not_collapsed(void *p, size_t len)
        return true;
 }
 
-/*
- * Completion barrier: one full khugepaged pass that started after this
- * call. Waiting for full_scans to advance by two guarantees it; a +1
- * step might complete a pass that scanned our mm before the setup.
- */
 static bool khugepaged_wait_full_pass(void)
 {
-       int full_scans = thp_read_num("khugepaged/full_scans") + 2;
-       int timeout = 60; /* 30 seconds */
-
-       while (timeout--) {
-               if (thp_read_num("khugepaged/full_scans") >= full_scans)
-                       return true;
-               printf(".");
-               usleep(TICK);
-       }
-       return false;
+       /* Wait up to 30 seconds for the pass to complete. */
+       return khugepaged_full_pass(30);
 }
 
 /*
-- 
2.54.0


Reply via email to