All of lore.kernel.org
 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: 5+ 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)

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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.