Linux Kernel Selftest development
 help / color / mirror / Atom feed
From: Aboorva Devarajan <aboorvad@linux.ibm.com>
To: Andrew Morton <akpm@linux-foundation.org>,
	David Hildenbrand <david@kernel.org>,
	Oscar Salvador <osalvador@suse.de>
Cc: Michal Hocko <mhocko@suse.com>, Lorenzo Stoakes <ljs@kernel.org>,
	"Liam R. Howlett" <liam@infradead.org>,
	Vlastimil Babka <vbabka@kernel.org>,
	Mike Rapoport <rppt@kernel.org>,
	Suren Baghdasaryan <surenb@google.com>,
	Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	linux-mm@kvack.org, linux-kernel@vger.kernel.org,
	linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org,
	"Ritesh Harjani (IBM)" <ritesh.list@gmail.com>,
	Aboorva Devarajan <aboorvad@linux.ibm.com>
Subject: [RFC PATCH 1/2] mm/memory_hotplug: bound offline retry loops with a configurable limit
Date: Wed, 22 Jul 2026 13:18:10 +0530	[thread overview]
Message-ID: <20260722074811.378283-2-aboorvad@linux.ibm.com> (raw)
In-Reply-To: <20260722074811.378283-1-aboorvad@linux.ibm.com>

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


  reply	other threads:[~2026-07-22  7:48 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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)
2026-07-22 12:29   ` David Hildenbrand (Arm)
2026-07-29  2:11     ` Aboorva Devarajan
2026-07-30 14:12       ` David Hildenbrand (Arm)

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260722074811.378283-2-aboorvad@linux.ibm.com \
    --to=aboorvad@linux.ibm.com \
    --cc=akpm@linux-foundation.org \
    --cc=corbet@lwn.net \
    --cc=david@kernel.org \
    --cc=liam@infradead.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=mhocko@suse.com \
    --cc=osalvador@suse.de \
    --cc=ritesh.list@gmail.com \
    --cc=rppt@kernel.org \
    --cc=skhan@linuxfoundation.org \
    --cc=surenb@google.com \
    --cc=vbabka@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox