* [RFC PATCH 1/2] mm/memory_hotplug: bound offline retry loops with a configurable limit
2026-07-22 7:48 [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit Aboorva Devarajan
@ 2026-07-22 7:48 ` Aboorva Devarajan
2026-07-22 7:48 ` [RFC PATCH 2/2] selftests/mm: add pc-dimm ACPI eject selftest for offline_migrate_max_passes Aboorva Devarajan
2026-07-22 12:08 ` [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit David Hildenbrand (Arm)
2 siblings, 0 replies; 7+ messages in thread
From: Aboorva Devarajan @ 2026-07-22 7:48 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador
Cc: Michal Hocko, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Jonathan Corbet, Shuah Khan,
linux-mm, linux-kernel, linux-doc, linux-kselftest,
Ritesh Harjani (IBM), Aboorva Devarajan
offline_pages() migrates and isolates pages in two nested loops with no
upper bound. A page that can never be migrated or freed (a long-term
pin, or a slab page that raced into the range) keeps the loop spinning
and the offline never returns. This is a known issue, noted in the code
("TODO: fatal migration failures should bail out") and in the admin
guide ("memory offlining might retry for a long time (or even forever),
until aborted by the user").
The only escape is signal_pending(current), which works when userspace
drives the offline but not for in-kernel callers. ACPI DIMM hot-unplug
(kacpi_hotplug_wq) and similar in-kernel hotplug paths run offline_pages()
on ordered workqueues where signal_pending() can never become true, so a
stuck offline wedges the workqueue and blocks all later hotplug events
with no way to abort it.
Add an opt-in backstop: a counter at the top of the inner migration loop
bounds the number of passes over the range. The check sits in the inner
loop because that is where a stuck page spins; since every outer pass
runs the inner loop at least once, this bounds both loops. On the limit
offline_pages() fails with -EBUSY and dump_page()s the stuck page.
The parameter (memory_hotplug.offline_migrate_max_passes) defaults to 0
(unlimited, today's behaviour) and is re-read every pass, so an offline
that is already stuck can be rescued at runtime by writing a non-zero
value. Such callers already handle -EBUSY, so no caller changes are
needed.
Signed-off-by: Aboorva Devarajan <aboorvad@linux.ibm.com>
---
.../admin-guide/kernel-parameters.txt | 14 +++++++
.../admin-guide/mm/memory-hotplug.rst | 26 ++++++++++++-
mm/memory_hotplug.c | 38 +++++++++++++++++++
3 files changed, 77 insertions(+), 1 deletion(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 53f08950a630..90b67c457c2a 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -3980,6 +3980,20 @@ Kernel parameters
Note that even when enabled, there are a few cases where
the feature is not effective.
+ memory_hotplug.offline_migrate_max_passes=
+ [KNL] Maximum number of migration passes
+ for memory offlining.
+ Format: <integer>
+ default: 0 (no limit, historical behaviour)
+ When non-zero, memory offlining gives up with
+ -EBUSY after this many migration passes over
+ the range, instead of retrying forever on a
+ stuck page. Each pass scans the range and
+ migrates what it can.
+ The parameter is re-read on every pass, so an
+ offline request that is already stuck can be
+ rescued at runtime by writing the parameter.
+
memtest= [KNL,X86,ARM,M68K,PPC,RISCV,EARLY] Enable memtest
Format: <integer>
default : 0 <disable>
diff --git a/Documentation/admin-guide/mm/memory-hotplug.rst b/Documentation/admin-guide/mm/memory-hotplug.rst
index 0207f8725142..79b45109c408 100644
--- a/Documentation/admin-guide/mm/memory-hotplug.rst
+++ b/Documentation/admin-guide/mm/memory-hotplug.rst
@@ -224,7 +224,10 @@ increases memory offlining reliability; still, memory offlining can fail in
some corner cases.
Further, memory offlining might retry for a long time (or even forever), until
-aborted by the user.
+aborted by the user. The ``memory_hotplug.offline_migrate_max_passes``
+parameter can be used to bound the number of retry passes, making an offline
+request that cannot make progress fail with ``-EBUSY`` instead of retrying
+indefinitely (see `Module Parameters`_).
Offlining of a memory block can be triggered via::
@@ -547,6 +550,27 @@ The following module parameters are currently defined:
possible.
Parameter availability depends on CONFIG_NUMA.
+``offline_migrate_max_passes`` read-write: Maximum number of migration
+ passes for a single memory offline
+ request. Memory offlining retries to
+ migrate and isolate pages until it succeeds;
+ a page that can never be migrated or freed
+ makes it retry forever. When this parameter
+ is non-zero, an offline request gives up
+ with ``-EBUSY`` after the configured number
+ of migration passes and the memory block
+ stays online.
+
+ The parameter is re-read on every pass, so
+ an offline request that is already stuck
+ retrying can be rescued at runtime by
+ writing a non-zero value, without a reboot.
+
+ The default is "0", meaning no limit (the
+ historical behaviour).
+
+ Parameter availability depends on
+ CONFIG_MEMORY_HOTREMOVE.
================================ ===============================================
ZONE_MOVABLE
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 226ab9cb078a..9067829349a0 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -1785,6 +1785,12 @@ bool mhp_range_allowed(u64 start, u64 size, bool need_mapping)
}
#ifdef CONFIG_MEMORY_HOTREMOVE
+/* Max offline_pages() migration passes before -EBUSY; 0 = retry forever. */
+static unsigned int offline_migrate_max_passes __read_mostly;
+module_param(offline_migrate_max_passes, uint, 0644);
+MODULE_PARM_DESC(offline_migrate_max_passes,
+ "Max migration passes before memory offline gives up (0 = no limit)");
+
/*
* Scan pfn range [start,end) to find movable/migratable pages (LRU and
* hugetlb folio, movable_ops pages). Will skip over most unmovable
@@ -1966,6 +1972,8 @@ int offline_pages(unsigned long start_pfn, unsigned long nr_pages,
struct node_notify node_arg = {
.nid = NUMA_NO_NODE,
};
+ unsigned int max_passes;
+ unsigned int pass = 0;
unsigned long flags;
char *reason;
int ret;
@@ -2062,6 +2070,36 @@ int offline_pages(unsigned long start_pfn, unsigned long nr_pages,
goto failed_removal_isolated;
}
+ /*
+ * A page that can never be migrated (e.g. a
+ * long-term pin) makes this loop retry forever.
+ * Give up after the configured number of passes.
+ * The limit is re-read on every pass so that an
+ * offline request that is already stuck here can
+ * be aborted at runtime by writing the parameter,
+ * which matters for in-kernel callers (e.g. ACPI
+ * DIMM hot-unplug) that run on kworkers and can
+ * never be aborted by a signal.
+ */
+ max_passes = READ_ONCE(offline_migrate_max_passes);
+ if (max_passes && pass++ >= max_passes) {
+ pr_warn("memory offlining [mem %#010llx-%#010llx]: giving up after %u passes\n",
+ (unsigned long long)start_pfn << PAGE_SHIFT,
+ ((unsigned long long)end_pfn << PAGE_SHIFT) - 1,
+ pass - 1);
+ /*
+ * Dump the page we last stopped at as a
+ * diagnostic hint: usually the stuck page,
+ * but just the range start after a restart.
+ */
+ if (pfn >= start_pfn && pfn < end_pfn)
+ dump_page(pfn_to_page(pfn),
+ "memory offline retry limit");
+ ret = -EBUSY;
+ reason = "retry limit exceeded";
+ goto failed_removal_isolated;
+ }
+
cond_resched();
ret = scan_movable_pages(pfn, end_pfn, &pfn);
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [RFC PATCH 2/2] selftests/mm: add pc-dimm ACPI eject selftest for offline_migrate_max_passes
2026-07-22 7:48 [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit Aboorva Devarajan
2026-07-22 7:48 ` [RFC PATCH 1/2] " Aboorva Devarajan
@ 2026-07-22 7:48 ` Aboorva Devarajan
2026-07-22 12:08 ` [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit David Hildenbrand (Arm)
2 siblings, 0 replies; 7+ messages in thread
From: Aboorva Devarajan @ 2026-07-22 7:48 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador
Cc: Michal Hocko, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Jonathan Corbet, Shuah Khan,
linux-mm, linux-kernel, linux-doc, linux-kselftest,
Ritesh Harjani (IBM), Aboorva Devarajan
Add a self-contained VM-based selftest that reproduces the ACPI memory
hot-unplug hang in kworker context and verifies that
memory_hotplug.offline_migrate_max_passes recovers from it.
The test needs a real ACPI eject because the offline that hangs runs on
the kacpi_hotplug_wq kworker (acpi_bus_offline -> device_offline ->
offline_pages) and cannot be aborted by a signal.
It boots the just-built kernel under virtme-ng with a cold-plugged
pc-dimm and runs everything inside the guest:
1. online the DIMM memory blocks as ZONE_MOVABLE;
2. long-term pin one page of the DIMM via vmsplice (a small pin
helper is embedded in the script and built at run time);
3. eject the DIMM via /sys/bus/acpi/devices/PNP0C80:*/eject, which
runs the offline on the kacpi_hotplug_wq kworker, the same path
as a hypervisor-initiated device_del;
4. observe the real hang: with the limit disabled (default), the
block sits in "going-offline" while the kworker spins in
offline_pages();
5. rescue it at runtime: write offline_migrate_max_passes and verify
the kworker gives up with -EBUSY and the block returns to
"online".
Sample output:
TAP version 13
1..3
# DIMM memory blocks: 32 33
# pinned one page in block memory33
# ejecting /sys/bus/acpi/devices/PNP0C80:00
# block memory33 going-offline, holding 10s
# do_migrate_range+0x1eb/0x260
# offline_pages+0x361/0x520
# memory_subsys_offline+0xdb/0x180
# device_offline+0xd0/0x130
# acpi_bus_offline+0x11f/0x190
# acpi_device_hotplug+0x1e8/0x3e0
# acpi_hotplug_work_fn+0x1e/0x30
# process_one_work+0x16a/0x310
# armed offline_migrate_max_passes=50
# memory offlining [mem 0x108000000-0x10fffffff]: giving up after 50 passes
ok 1 ACPI eject kworker hangs unbounded in offline_pages()
ok 2 stuck offline rescued by writing offline_migrate_max_passes
ok 3 memory block online and usable after the bailout
# Totals: pass:3 fail:0 xfail:0 xpass:0 skip:0 error:0
The wrapper is added as TEST_PROGS_EXTENDED (like hwpoison-panic.sh):
installed and kept executable, but not run from a default kselftest
invocation, because it spawns a VM itself. The config fragment gains
the options the guest kernel needs (MEMORY_HOTPLUG, MEMORY_HOTREMOVE,
ACPI_HOTPLUG_MEMORY). When vng, qemu, cc or the kernel knob are
missing it skips cleanly (KSFT_SKIP), so it is safe to run anywhere.
The vmsplice-based pin in this test is a targeted technique to reliably
reproduce the hang path for testing purposes; production scenarios
typically involve slab pages or busy block-device page-cache racing
into the offline range.
Signed-off-by: Aboorva Devarajan <aboorvad@linux.ibm.com>
---
tools/testing/selftests/mm/Makefile | 3 +
tools/testing/selftests/mm/config | 3 +
.../selftests/mm/vmtest_memory_hotplug.sh | 369 ++++++++++++++++++
3 files changed, 375 insertions(+)
create mode 100755 tools/testing/selftests/mm/vmtest_memory_hotplug.sh
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index eae504bd94c8..e113f3ff4655 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -177,6 +177,9 @@ TEST_PROGS += ksft_vmalloc.sh
# Destructive: every successful run panics the kernel. Installed and
# kept executable, but not run from a default kselftest invocation.
TEST_PROGS_EXTENDED += hwpoison-panic.sh
+# Spawns its own VM (virtme-ng); not run from a default kselftest
+# invocation.
+TEST_PROGS_EXTENDED += vmtest_memory_hotplug.sh
TEST_FILES := test_vmalloc.sh
TEST_FILES += test_hmm.sh
diff --git a/tools/testing/selftests/mm/config b/tools/testing/selftests/mm/config
index 06f78bd232e2..d22f63eb9c2c 100644
--- a/tools/testing/selftests/mm/config
+++ b/tools/testing/selftests/mm/config
@@ -14,3 +14,6 @@ CONFIG_UPROBES=y
CONFIG_MEMORY_FAILURE=y
CONFIG_HWPOISON_INJECT=m
CONFIG_PROC_MEM_ALWAYS_FORCE=y
+CONFIG_MEMORY_HOTPLUG=y
+CONFIG_MEMORY_HOTREMOVE=y
+CONFIG_ACPI_HOTPLUG_MEMORY=y
diff --git a/tools/testing/selftests/mm/vmtest_memory_hotplug.sh b/tools/testing/selftests/mm/vmtest_memory_hotplug.sh
new file mode 100755
index 000000000000..c211a0e7caa2
--- /dev/null
+++ b/tools/testing/selftests/mm/vmtest_memory_hotplug.sh
@@ -0,0 +1,369 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test memory_hotplug.offline_migrate_max_passes by reproducing the ACPI
+# memory hot-unplug hang in kworker context and verifying that the retry
+# limit recovers from it.
+#
+# offline_pages() retries page migration in an unbounded loop. A page
+# that can never be migrated (long-term pin, slab, busy metadata) makes
+# the loop spin forever. Userspace-driven offlines can be interrupted by
+# a signal, but an ACPI DIMM eject runs on the kacpi_hotplug_wq kworker
+# where signal_pending() never fires, so the kworker hangs until reboot.
+#
+# Flow:
+# 1. Boot the just-built kernel (virtme-ng) with a cold-plugged pc-dimm.
+# 2. Online the DIMM's blocks as ZONE_MOVABLE.
+# 3. Pin one page of the DIMM (vmsplice long-term pin).
+# 4. Eject the DIMM via sysfs; offline runs on kacpi_hotplug_wq.
+# 5. Observe the hang (going-offline persists).
+# 6. Rescue: write offline_migrate_max_passes at runtime, verify bailout.
+#
+# TAP subtests:
+# 1 ACPI eject kworker hangs unbounded in offline_pages()
+# 2 stuck offline rescued by writing offline_migrate_max_passes
+# 3 memory block online and usable after the bailout
+#
+# Dependencies: virtme-ng (vng), qemu-system-<arch>, cc.
+# SKIPs (never FAILs) when tooling or kernel support is missing.
+
+readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
+readonly KERNEL_CHECKOUT="$(realpath "${SCRIPT_DIR}"/../../../../)"
+
+# shellcheck source=../kselftest/ktap_helpers.sh
+source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
+
+readonly DIMM_SIZE="${DIMM_SIZE:-256M}"
+readonly MAX_PASSES="${MAX_PASSES:-50}"
+readonly HANG_HOLD="${HANG_HOLD:-10}"
+readonly RESCUE_TIMEOUT="${RESCUE_TIMEOUT:-30}"
+readonly BOOT_TIMEOUT="${BOOT_TIMEOUT:-240}"
+
+readonly LOG="$(mktemp /tmp/vmtest_mhp_XXXXXX.log)"
+readonly GUEST_SCRIPT="$(mktemp /tmp/vmtest_mhp_guest_XXXXXX.sh)"
+readonly PIN_SRC="$(mktemp /tmp/vmtest_mhp_pin_XXXXXX.c)"
+# Must live in the kernel tree so the guest sees it through the 9p rootfs.
+readonly PIN_BIN="${SCRIPT_DIR}/.vmtest_pin_park.$$"
+
+VNG_PID=""
+
+cleanup() {
+ [ -n "${VNG_PID}" ] && kill "${VNG_PID}" 2>/dev/null
+ rm -f "${LOG}" "${GUEST_SCRIPT}" "${PIN_SRC}" "${PIN_BIN}"
+}
+
+guest_result() {
+ grep -o "^${1}=[A-Za-z0-9_-]*" "${LOG}" | tail -1 | cut -d= -f2
+}
+
+dump_log_tail() {
+ tail -25 "${LOG}" | while IFS= read -r line; do
+ ktap_print_msg "${line}"
+ done
+}
+
+skip_all_tests() {
+ local i
+
+ for i in 1 2 3; do
+ ktap_test_skip "$1"
+ done
+ ktap_finished
+}
+
+# -- pre-flight checks -------------------------------------------------------
+
+ktap_print_header
+ktap_set_plan 3
+trap cleanup EXIT
+
+arch="$(uname -m)"
+case "${arch}" in
+x86_64|aarch64) ;;
+*) skip_all_tests "pc-dimm ACPI hotplug not available on ${arch}" ;;
+esac
+
+for dep in vng "qemu-system-${arch}" cc; do
+ if ! command -v "${dep}" >/dev/null 2>&1; then
+ skip_all_tests "${dep} not installed"
+ fi
+done
+
+# -- pin helper (compiled on the host, visible to the guest via 9p) -----------
+#
+# Allocate one block's worth of pages (ZONE_MOVABLE serves them from the
+# DIMM), find one that landed in a target block, vmsplice-pin it into a
+# pipe, print PIN_BLK=<N>, and sleep forever.
+
+cat > "${PIN_SRC}" <<'PINEOF'
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+
+static unsigned long memory_block_size(void)
+{
+ char buf[64];
+ ssize_t n;
+ int fd;
+
+ fd = open("/sys/devices/system/memory/block_size_bytes", O_RDONLY);
+ if (fd < 0)
+ return 0;
+ n = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (n <= 0)
+ return 0;
+ buf[n] = '\0';
+ return strtoul(buf, NULL, 16);
+}
+
+static unsigned long vaddr_to_block(int pagemap_fd, char *vaddr,
+ long page_size, unsigned long blk_size)
+{
+ unsigned long vpn = (unsigned long)vaddr / page_size;
+ uint64_t ent;
+
+ if (pread(pagemap_fd, &ent, sizeof(ent), vpn * 8) != sizeof(ent))
+ return -1UL;
+ if (!(ent & (1ULL << 63)))
+ return -1UL;
+ return (ent & ((1ULL << 55) - 1)) * page_size / blk_size;
+}
+
+int main(int argc, char **argv)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ unsigned long blk_size = memory_block_size();
+ unsigned long off, blk = 0;
+ char *area, *page = NULL;
+ int pagemap_fd, pipefd[2], i;
+
+ if (argc < 2 || !blk_size) {
+ fprintf(stderr, "usage: pin_park <memory block>...\n");
+ return 1;
+ }
+
+ area = mmap(NULL, blk_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
+ if (area == MAP_FAILED) {
+ perror("mmap");
+ return 1;
+ }
+ memset(area, 0x42, blk_size);
+
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd < 0) {
+ perror("pagemap");
+ return 1;
+ }
+
+ for (off = 0; off < blk_size && !page; off += page_size) {
+ blk = vaddr_to_block(pagemap_fd, area + off, page_size,
+ blk_size);
+ for (i = 1; i < argc; i++) {
+ if (blk == strtoul(argv[i], NULL, 10)) {
+ page = area + off;
+ break;
+ }
+ }
+ }
+ close(pagemap_fd);
+
+ if (!page) {
+ printf("PIN_NONE\n");
+ fflush(stdout);
+ return 2;
+ }
+
+ if (pipe(pipefd)) {
+ perror("pipe");
+ return 1;
+ }
+
+ struct iovec iov = { .iov_base = page, .iov_len = page_size };
+
+ if (vmsplice(pipefd[1], &iov, 1, 0) != page_size) {
+ perror("vmsplice");
+ return 1;
+ }
+
+ printf("PIN_BLK=%lu\n", blk);
+ fflush(stdout);
+ pause();
+ return 0;
+}
+PINEOF
+
+if ! cc -O2 -o "${PIN_BIN}" "${PIN_SRC}" 2>>"${LOG}"; then
+ skip_all_tests "cannot build the pin helper"
+fi
+
+# -- guest script (runs as init inside the VM) --------------------------------
+
+cat > "${GUEST_SCRIPT}" <<PROLOGUE
+#!/bin/bash
+readonly MAX_PASSES=${MAX_PASSES}
+readonly HANG_HOLD=${HANG_HOLD}
+readonly RESCUE_TIMEOUT=${RESCUE_TIMEOUT}
+readonly PIN_PARK="${PIN_BIN}"
+PROLOGUE
+
+cat >> "${GUEST_SCRIPT}" <<'GUESTEOF'
+set -u
+
+readonly PARAM=/sys/module/memory_hotplug/parameters/offline_migrate_max_passes
+readonly MEM=/sys/devices/system/memory
+
+skip() { echo "VMTEST_SKIP: $*"; echo "VMTEST_DONE"; exit 0; }
+
+block_state() {
+ cat "${MEM}/memory${pinned_blk}/state" 2>/dev/null || echo GONE
+}
+
+[ -f "${PARAM}" ] || skip "kernel lacks offline_migrate_max_passes"
+
+# 1. Online the DIMM's blocks as ZONE_MOVABLE.
+dimm_blocks=""
+for s in "${MEM}"/memory*/state; do
+ [ "$(cat "${s}" 2>/dev/null)" = offline ] || continue
+ if echo online_movable > "${s}" 2>/dev/null; then
+ b=${s%/state}
+ dimm_blocks="${dimm_blocks} ${b##*memory}"
+ fi
+done
+echo "# DIMM memory blocks:${dimm_blocks:- none}"
+[ -n "${dimm_blocks}" ] || skip "no offline memory blocks (pc-dimm missing?)"
+
+# 2. Pin one page inside the DIMM.
+"${PIN_PARK}" ${dimm_blocks} > /tmp/pin.out 2>&1 &
+pin_pid=$!
+for _ in $(seq 1 10); do
+ grep -q "PIN_" /tmp/pin.out 2>/dev/null && break
+ sleep 1
+done
+pinned_blk=$(sed -n 's/^PIN_BLK=//p' /tmp/pin.out)
+[ -n "${pinned_blk}" ] || skip "pin failed: $(cat /tmp/pin.out 2>/dev/null)"
+echo "# pinned one page in block memory${pinned_blk}"
+
+# 3. Eject the DIMM with the retry limit disabled (default = unbounded).
+echo 0 > "${PARAM}"
+dmesg -c > /dev/null 2>&1 || true
+
+ejdev=""
+for dev in /sys/bus/acpi/devices/PNP0C80:*; do
+ [ -e "${dev}/eject" ] && { ejdev="${dev}"; break; }
+done
+[ -n "${ejdev}" ] || skip "no ejectable ACPI memory device (PNP0C80)"
+echo "# ejecting ${ejdev}"
+echo 1 > "${ejdev}/eject" &
+
+# 4. Observe the hang: the block must stay in "going-offline".
+sleep 3
+stuck=0
+if [ "$(block_state)" = going-offline ]; then
+ echo "# block memory${pinned_blk} going-offline, holding ${HANG_HOLD}s"
+ sleep "${HANG_HOLD}"
+ if [ "$(block_state)" = going-offline ]; then
+ stuck=1
+ echo 1 > /proc/sys/kernel/sysrq 2>/dev/null || true
+ echo l > /proc/sysrq-trigger 2>/dev/null || true
+ sleep 1
+ dmesg | grep -B2 -A12 "offline_pages" | tail -20 \
+ | sed 's/^/# /'
+ fi
+fi
+echo "VMTEST_STUCK=${stuck}"
+
+# 5. Rescue: arm the retry limit at runtime and wait for bailout.
+echo "${MAX_PASSES}" > "${PARAM}"
+echo "# armed offline_migrate_max_passes=$(cat "${PARAM}")"
+
+rescued=0
+for _ in $(seq 1 "${RESCUE_TIMEOUT}"); do
+ if dmesg | grep -q "giving up after"; then
+ rescued=1
+ break
+ fi
+ sleep 1
+done
+dmesg | grep "giving up after" | tail -2 | sed 's/^/# /'
+echo "VMTEST_RESCUED=${rescued}"
+
+sleep 2
+echo "VMTEST_BLOCK_STATE=$(block_state)"
+
+kill "${pin_pid}" 2>/dev/null
+echo "VMTEST_DONE"
+GUESTEOF
+
+# -- launch VM ----------------------------------------------------------------
+
+( cd "${KERNEL_CHECKOUT}" && exec vng --force-9p --no-virtme-ng-init \
+ --qemu-opt=-m --qemu-opt="2G,slots=4,maxmem=8G" \
+ --qemu-opt=-object \
+ --qemu-opt="memory-backend-ram,id=mem0,size=${DIMM_SIZE}" \
+ --qemu-opt=-device --qemu-opt="pc-dimm,id=dimm0,memdev=mem0" \
+ bash "${GUEST_SCRIPT}" ) > "${LOG}" 2>&1 &
+VNG_PID=$!
+
+for _ in $(seq 1 "${BOOT_TIMEOUT}"); do
+ grep -q "VMTEST_DONE" "${LOG}" 2>/dev/null && break
+ kill -0 "${VNG_PID}" 2>/dev/null || break
+ sleep 1
+done
+
+if ! grep -q "VMTEST_DONE" "${LOG}" 2>/dev/null; then
+ dump_log_tail
+ skip_all_tests "VM did not finish within ${BOOT_TIMEOUT}s"
+fi
+
+kill "${VNG_PID}" 2>/dev/null
+wait "${VNG_PID}" 2>/dev/null
+VNG_PID=""
+
+# -- report results -----------------------------------------------------------
+
+skip_reason="$(grep "^VMTEST_SKIP: " "${LOG}" | tail -1 | tr -d '\r')"
+if [ -n "${skip_reason}" ]; then
+ skip_all_tests "${skip_reason#VMTEST_SKIP: }"
+fi
+
+grep "^#" "${LOG}" | tr -d '\r' | while IFS= read -r line; do
+ ktap_print_msg "${line#\# }"
+done
+
+stuck="$(guest_result VMTEST_STUCK)"
+rescued="$(guest_result VMTEST_RESCUED)"
+blk_state="$(guest_result VMTEST_BLOCK_STATE)"
+
+if [ "${blk_state:-GONE}" = "GONE" ]; then
+ skip_all_tests "DIMM was ejected despite the pin (pin did not hold)"
+fi
+
+if [ "${stuck:-0}" = 1 ]; then
+ ktap_test_pass "ACPI eject kworker hangs unbounded in offline_pages()"
+else
+ dump_log_tail
+ ktap_test_fail "ACPI eject kworker hangs unbounded in offline_pages()"
+fi
+
+if [ "${rescued:-0}" = 1 ]; then
+ ktap_test_pass "stuck offline rescued by writing offline_migrate_max_passes"
+else
+ dump_log_tail
+ ktap_test_fail "stuck offline rescued by writing offline_migrate_max_passes"
+fi
+
+if [ "${blk_state}" = "online" ]; then
+ ktap_test_pass "memory block online and usable after the bailout"
+else
+ ktap_test_fail "memory block online and usable after the bailout (state=${blk_state})"
+fi
+
+ktap_finished
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit
2026-07-22 7:48 [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit Aboorva Devarajan
2026-07-22 7:48 ` [RFC PATCH 1/2] " Aboorva Devarajan
2026-07-22 7:48 ` [RFC PATCH 2/2] selftests/mm: add pc-dimm ACPI eject selftest for offline_migrate_max_passes Aboorva Devarajan
@ 2026-07-22 12:08 ` David Hildenbrand (Arm)
2026-07-22 12:29 ` David Hildenbrand (Arm)
2 siblings, 1 reply; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-22 12:08 UTC (permalink / raw)
To: Aboorva Devarajan, Andrew Morton, Oscar Salvador
Cc: Michal Hocko, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Jonathan Corbet, Shuah Khan,
linux-mm, linux-kernel, linux-doc, linux-kselftest,
Ritesh Harjani (IBM)
On 7/22/26 09:48, Aboorva Devarajan wrote:
> Memory offlining can loop forever when a memory block holds a page that
> can never be migrated or freed. This series adds an opt-in retry limit
> that lets offline_pages() bail out with -EBUSY instead of retrying
> forever. The default (0) preserves today's behaviour exactly.
>
> Patch 1 is the fix; patch 2 is an in-tree selftest that reproduces the
> hang from an unsignalable kworker and verifies the limit recovers it.
>
> 1. The problem
> ==============
>
> offline_pages() repeats two nested steps until the whole range is
> isolated:
>
> 1. Inner loop: find and migrate movable pages out of the range
> (scan_movable_pages() + do_migrate_range()).
> 2. Outer loop: re-check isolation (test_pages_isolated()); if not
> fully isolated, go back to step 1.
>
> Neither loop has a termination condition. When a page can never be
> migrated, scan_movable_pages() keeps returning the same pfn,
> do_migrate_range() keeps failing on it, and control never even reaches
> the outer isolation re-check - the inner loop spins forever. The admin
> guide acknowledges this:
>
> "Further, memory offlining might retry for a long time (or even
> forever), until aborted by the user."
>
> The single escape in the loop is signal_pending(current):
>
> offline_pages(pfn, end_pfn):
> do { /* outer: isolation */
> do { /* inner: migration */
> if (signal_pending(current)) /* <-- ONLY escape */
> goto failed_removal;
> pfn = scan_movable_pages(pfn, end_pfn);
> do_migrate_range(pfn, end_pfn); /* may never succeed */
> } while (pfn < end_pfn);
> } while (test_pages_isolated(...));
>
>
> 2. Why the kernel itself should be able to bail
> ===============================================
>
> We keep hitting this during memory hot-remove operations: a single
> stuck page can block the whole operation. This was also reported in
> [4] earlier; one example, where offlining kept failing to migrate a
> busy block-device page-cache page (aops:def_blk_aops) in a normal zone:
>
> [10880.889199] page dumped because: migration failure
> [10880.889232] migrating pfn 2a87b failed ret:1
> [10880.889235] page: refcount:3 mapcount:0 mapping:00000000718ec5a6 index:0x857 pfn:0x2a87b
> [10880.889241] aops:def_blk_aops ino:800003 dentry name(?):""
> [10880.889245] flags: 0x33ffffe00004104(referenced|active|private|node=3|zone=0|lastcpupid=0x1fffff)
> ...
> [10880.889291] migrating pfn 2a87b failed ret:1
Hi,
the text reads AI generated. If you did use AI, please disclose it properly.
Quick feedback:
1) Using passes is not really what we want in many cases (e.g., ZONE_MOVABLE
where we might need a couple of seconds/minutes to complete offlining with a lot
of concurrent activity). An actual timeout is also problematic (see below).
2) Having a toggle for all offline_pages() users is questionable. In particular,
user-triggered offlining or offlinig triggered on ZONE_MOVABLE etc should not
obey such timeouts.
I proposed a more restricted approach only for offline_and_remove_memory()
previously [1]
[1] https://lore.kernel.org/all/20230627112220.229240-1-david@redhat.com/
Michal back then commented "I really hate having timeouts back. They just proven
to be hard to get right and it is essentially a policy implemented in the
kernel. They simply do not belong to the kernel space IMHO."
And I agree.
I assume you run into such issues with DIMMs in VMs? virtio-mem handles that
much nicer nowadays, by essentially doing the hard part that should fail easily
through alloc_contig_range().
offline_pages() is just designed to retry forever.
--
Cheers,
David
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit
2026-07-22 12:08 ` [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit David Hildenbrand (Arm)
@ 2026-07-22 12:29 ` David Hildenbrand (Arm)
2026-07-29 2:11 ` Aboorva Devarajan
0 siblings, 1 reply; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-22 12:29 UTC (permalink / raw)
To: Aboorva Devarajan, Andrew Morton, Oscar Salvador
Cc: Michal Hocko, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Jonathan Corbet, Shuah Khan,
linux-mm, linux-kernel, linux-doc, linux-kselftest,
Ritesh Harjani (IBM)
On 7/22/26 14:08, David Hildenbrand (Arm) wrote:
> On 7/22/26 09:48, Aboorva Devarajan wrote:
>> Memory offlining can loop forever when a memory block holds a page that
>> can never be migrated or freed. This series adds an opt-in retry limit
>> that lets offline_pages() bail out with -EBUSY instead of retrying
>> forever. The default (0) preserves today's behaviour exactly.
>>
>> Patch 1 is the fix; patch 2 is an in-tree selftest that reproduces the
>> hang from an unsignalable kworker and verifies the limit recovers it.
>>
>> 1. The problem
>> ==============
>>
>> offline_pages() repeats two nested steps until the whole range is
>> isolated:
>>
>> 1. Inner loop: find and migrate movable pages out of the range
>> (scan_movable_pages() + do_migrate_range()).
>> 2. Outer loop: re-check isolation (test_pages_isolated()); if not
>> fully isolated, go back to step 1.
>>
>> Neither loop has a termination condition. When a page can never be
>> migrated, scan_movable_pages() keeps returning the same pfn,
>> do_migrate_range() keeps failing on it, and control never even reaches
>> the outer isolation re-check - the inner loop spins forever. The admin
>> guide acknowledges this:
>>
>> "Further, memory offlining might retry for a long time (or even
>> forever), until aborted by the user."
>>
>> The single escape in the loop is signal_pending(current):
>>
>> offline_pages(pfn, end_pfn):
>> do { /* outer: isolation */
>> do { /* inner: migration */
>> if (signal_pending(current)) /* <-- ONLY escape */
>> goto failed_removal;
>> pfn = scan_movable_pages(pfn, end_pfn);
>> do_migrate_range(pfn, end_pfn); /* may never succeed */
>> } while (pfn < end_pfn);
>> } while (test_pages_isolated(...));
>>
>>
>> 2. Why the kernel itself should be able to bail
>> ===============================================
>>
>> We keep hitting this during memory hot-remove operations: a single
>> stuck page can block the whole operation. This was also reported in
>> [4] earlier; one example, where offlining kept failing to migrate a
>> busy block-device page-cache page (aops:def_blk_aops) in a normal zone:
>>
>> [10880.889199] page dumped because: migration failure
>> [10880.889232] migrating pfn 2a87b failed ret:1
>> [10880.889235] page: refcount:3 mapcount:0 mapping:00000000718ec5a6 index:0x857 pfn:0x2a87b
>> [10880.889241] aops:def_blk_aops ino:800003 dentry name(?):""
>> [10880.889245] flags: 0x33ffffe00004104(referenced|active|private|node=3|zone=0|lastcpupid=0x1fffff)
>> ...
>> [10880.889291] migrating pfn 2a87b failed ret:1
>
> Hi,
>
> the text reads AI generated. If you did use AI, please disclose it properly.
>
> Quick feedback:
>
> 1) Using passes is not really what we want in many cases (e.g., ZONE_MOVABLE
> where we might need a couple of seconds/minutes to complete offlining with a lot
> of concurrent activity). An actual timeout is also problematic (see below).
>
> 2) Having a toggle for all offline_pages() users is questionable. In particular,
> user-triggered offlining or offlinig triggered on ZONE_MOVABLE etc should not
> obey such timeouts.
>
> I proposed a more restricted approach only for offline_and_remove_memory()
> previously [1]
>
> [1] https://lore.kernel.org/all/20230627112220.229240-1-david@redhat.com/
>
> Michal back then commented "I really hate having timeouts back. They just proven
> to be hard to get right and it is essentially a policy implemented in the
> kernel. They simply do not belong to the kernel space IMHO."
>
> And I agree.
>
> I assume you run into such issues with DIMMs in VMs? virtio-mem handles that
> much nicer nowadays, by essentially doing the hard part that should fail easily
> through alloc_contig_range().
>
> offline_pages() is just designed to retry forever.
>
One thing we can definitely do is to just fail faster if we find an unmovable
page in !ZONE_MOVABLE.
Usually that happens when we race offlining (has_unmovable_pages()) with page
allocation, and failing on unmovable pages is actually perfectly fine.
But for ZONE_MOVABLE we should keep retrying, because some pages might only look
temporarily unmovable.
So that would be the low hanging fruit: on !ZONE_MOVABLE, fail faster.
--
Cheers,
David
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit
2026-07-22 12:29 ` David Hildenbrand (Arm)
@ 2026-07-29 2:11 ` Aboorva Devarajan
2026-07-30 14:12 ` David Hildenbrand (Arm)
0 siblings, 1 reply; 7+ messages in thread
From: Aboorva Devarajan @ 2026-07-29 2:11 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Andrew Morton, Oscar Salvador, Michal Hocko, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Jonathan Corbet, Shuah Khan, linux-mm,
linux-kernel, linux-doc, linux-kselftest, Ritesh Harjani (IBM)
On Wed, 2026-07-22 at 14:29 +0200, David Hildenbrand (Arm) wrote:
> On 7/22/26 14:08, David Hildenbrand (Arm) wrote:
> > On 7/22/26 09:48, Aboorva Devarajan wrote:
> > > Memory offlining can loop forever when a memory block holds a page that
> > > can never be migrated or freed. This series adds an opt-in retry limit
> > > that lets offline_pages() bail out with -EBUSY instead of retrying
> > > forever. The default (0) preserves today's behaviour exactly.
> > >
> > > Patch 1 is the fix; patch 2 is an in-tree selftest that reproduces the
> > > hang from an unsignalable kworker and verifies the limit recovers it.
> > >
> > > 1. The problem
> > > ==============
> > >
> > > offline_pages() repeats two nested steps until the whole range is
> > > isolated:
> > >
> > > 1. Inner loop: find and migrate movable pages out of the range
> > > (scan_movable_pages() + do_migrate_range()).
> > > 2. Outer loop: re-check isolation (test_pages_isolated()); if not
> > > fully isolated, go back to step 1.
> > >
> > > Neither loop has a termination condition. When a page can never be
> > > migrated, scan_movable_pages() keeps returning the same pfn,
> > > do_migrate_range() keeps failing on it, and control never even reaches
> > > the outer isolation re-check - the inner loop spins forever. The admin
> > > guide acknowledges this:
> > >
> > > "Further, memory offlining might retry for a long time (or even
> > > forever), until aborted by the user."
> > >
> > > The single escape in the loop is signal_pending(current):
> > >
> > > offline_pages(pfn, end_pfn):
> > > do { /* outer: isolation */
> > > do { /* inner: migration */
> > > if (signal_pending(current)) /* <-- ONLY escape */
> > > goto failed_removal;
> > > pfn = scan_movable_pages(pfn, end_pfn);
> > > do_migrate_range(pfn, end_pfn); /* may never succeed */
> > > } while (pfn < end_pfn);
> > > } while (test_pages_isolated(...));
> > >
> > >
> > > 2. Why the kernel itself should be able to bail
> > > ===============================================
> > >
> > > We keep hitting this during memory hot-remove operations: a single
> > > stuck page can block the whole operation. This was also reported in
> > > [4] earlier; one example, where offlining kept failing to migrate a
> > > busy block-device page-cache page (aops:def_blk_aops) in a normal zone:
> > >
> > > [10880.889199] page dumped because: migration failure
> > > [10880.889232] migrating pfn 2a87b failed ret:1
> > > [10880.889235] page: refcount:3 mapcount:0 mapping:00000000718ec5a6 index:0x857 pfn:0x2a87b
> > > [10880.889241] aops:def_blk_aops ino:800003 dentry name(?):""
> > > [10880.889245] flags: 0x33ffffe00004104(referenced|active|private|node=3|zone=0|lastcpupid=0x1fffff)
> > > ...
> > > [10880.889291] migrating pfn 2a87b failed ret:1
> >
> > Hi,
> >
Hi David,
Thanks a lot for the review.
> > the text reads AI generated. If you did use AI, please disclose it properly.
yes, I did use LLM to help re-phrase this. I'll make sure to disclose the usage going forward.
> >
> > Quick feedback:
> >
> > 1) Using passes is not really what we want in many cases (e.g., ZONE_MOVABLE
> > where we might need a couple of seconds/minutes to complete offlining with a lot
> > of concurrent activity). An actual timeout is also problematic (see below).
> >
> > 2) Having a toggle for all offline_pages() users is questionable. In particular,
> > user-triggered offlining or offlinig triggered on ZONE_MOVABLE etc should not
> > obey such timeouts.
> >
> > I proposed a more restricted approach only for offline_and_remove_memory()
> > previously [1]
> >
> > [1] https://lore.kernel.org/all/20230627112220.229240-1-david@redhat.com/
> >
> > Michal back then commented "I really hate having timeouts back. They just proven
> > to be hard to get right and it is essentially a policy implemented in the
> > kernel. They simply do not belong to the kernel space IMHO."
> >
> > And I agree.
Thanks for pointing to [1]. On our end, on powerpc (pseries),
drmgr (the userspace tool that triggers memory hotplug) already wraps memory hotplug
operations with a timeout for exactly this reason. However, since we've now seen this
issue occur a couple of times under system load in ZONE_NORMAL, and also as per my understanding
offline_pages() can be triggered from other kworker path where it cannot be interrupted
and could end up retrying indefinitely, So I wanted to get a consensus on how this should
be handled.
> >
> > I assume you run into such issues with DIMMs in VMs? virtio-mem handles that
> > much nicer nowadays, by essentially doing the hard part that should fail easily
> > through alloc_contig_range().
> >
> > offline_pages() is just designed to retry forever.
> >
>
Yes, I do not hit this issue with virtio-mem path, looks like the unsafe unplug mode that
relied on offline_pages() for migration was removed in f504e15b94eb ("virtio-mem:
remove unsafe unplug in Big Block Mode (BBM)"), so now it rather goes through the
alloc_contig_range() path, which gives up with -EBUSY after a few migration attempts
on the same page, so offline_pages() never sees the stuck page.
We originally hit this on a pseries LPAR, during memory hot-remove. offline_pages()
gets stuck on a single page that won't move, with two specific cases as below,
both in ZONE_NORMAL:
Case 1. a slab page that ends up in the isolated range:
[83310.373767] page_type: f5(slab)
[83310.373770] raw: 023ffffe00000000 c0000028e001fa00
[83310.373774] raw: 0000000000000000 0000000001e101e1
[83310.373778] page dumped because: isolation failed
[83310.373788] failed to isolate pfn 4dc68
Case 2. a busy block-device page-cache page that keeps failing to migrate:
[10880.889058] page: refcount:3 mapcount:0 mapping:00000000718ec5a6 index:0x857 pfn:0x2a87b
[10880.889062] memcg:c00000006a67e000
[10880.889064] aops:def_blk_aops ino:800003 dentry name(?):""
[10880.889069] flags: 0x33ffffe00004104(referenced|active|private|node=3|zone=0|lastcpupid=0x1fffff)
[10880.889073] raw: 033ffffe00004104 c0000000b8667960 c0000000b8667960 c0000000130f8dd8
[10880.889077] raw: 0000000000000857 c000000172abe1e0 00000003ffffffff c00000006a67e000
[10880.889081] page dumped because: migration failure
[10880.889114] migrating pfn 2a87b failed ret:1
I can also trigger it in a VM by hot-adding a DIMM, pinning a page and removing the DIMM.
This is the part I wanted to highlight, the ACPI DIMM remove runs offline_pages() from the ACPI kworker,
and as far as I understand, nothing can abort it, there is no signal to deliver, so there is no way
to cancel the work, so it spins until reboot in this case as well?
> One thing we can definitely do is to just fail faster if we find an unmovable
> page in !ZONE_MOVABLE.
>
> Usually that happens when we race offlining (has_unmovable_pages()) with page
> allocation, and failing on unmovable pages is actually perfectly fine.
>
> But for ZONE_MOVABLE we should keep retrying, because some pages might only look
> temporarily unmovable.
>
> So that would be the low hanging fruit: on !ZONE_MOVABLE, fail faster.
This handles scenarios like Case 1. A page can still be allocated from the range after
isolation and become slab. When test_pages_isolated() fails, we can re-check that
page with page_is_unmovable() and return -EBUSY if it's unmovable. I'm currently testing this approach
will send a patch for review.
Case 2 is different. The page is on the LRU, so it is considered movable and the fast-fail condition abovewon't be triggered. Instead page migration keeps failing, causing
offline_pages() to retry indefinitely
and can hang forever. This could be handled with a timeout or a user
policy in the application that
initiated the hotplug operation (signals). However, for the kworker
paths that trigger memory
offlining (ACPI remove, etc.), should this instead be handled by the
driver rather than relying on
offline_pages(), similar to virtio-mem?
Please let me know your comments.
Thanks,
Aboorva
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [RFC PATCH 0/2] mm/memory_hotplug: bound offline retry loops with a configurable limit
2026-07-29 2:11 ` Aboorva Devarajan
@ 2026-07-30 14:12 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-30 14:12 UTC (permalink / raw)
To: Aboorva Devarajan
Cc: Andrew Morton, Oscar Salvador, Michal Hocko, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Jonathan Corbet, Shuah Khan, linux-mm,
linux-kernel, linux-doc, linux-kselftest, Ritesh Harjani (IBM)
>> Usually that happens when we race offlining (has_unmovable_pages()) with page
>> allocation, and failing on unmovable pages is actually perfectly fine.
>>
>> But for ZONE_MOVABLE we should keep retrying, because some pages might only look
>> temporarily unmovable.
>>
>> So that would be the low hanging fruit: on !ZONE_MOVABLE, fail faster.
>
> This handles scenarios like Case 1. A page can still be allocated from the range after
> isolation and become slab. When test_pages_isolated() fails, we can re-check that
> page with page_is_unmovable() and return -EBUSY if it's unmovable. I'm currently testing this approach
> will send a patch for review.
Yes, and that we should handle. It's a known race and we should just fail faster
in that case. !ZONE_MOVABLE.
>
> Case 2 is different. The page is on the LRU, so it is considered movable and the fast-fail condition abovewon't be triggered. Instead page migration keeps failing, causing
> offline_pages() to retry indefinitely
> and can hang forever. This could be handled with a timeout or a user
> policy in the application that
> initiated the hotplug operation (signals). However, for the kworker
> paths that trigger memory
> offlining (ACPI remove, etc.), should this instead be handled by the
> driver rather than relying on
> offline_pages(), similar to virtio-mem?
If it's on ZONE_MOVABLE, then we should figure out why it keeps failing forever
and try fixing that instead.
Above you write: "I can also trigger it in a VM by hot-adding a DIMM, pinning a
page and removing the DIMM."
Anybody who intends to take a longterm pin must be using FOLL_LONGTERM, which
will take care of exactly this condition: migrating memory out of ZONE_MOVABLE
first.
So which interface does you reproducer use to trigger pinning?
--
Cheers,
David
^ permalink raw reply [flat|nested] 7+ messages in thread