From: Qiliang Yuan <qiliang.yuan@linux.dev>
To: Vlastimil Babka <vbabka@kernel.org>
Cc: Qiliang Yuan <qiliang.yuan@linux.dev>,
Andrew Morton <akpm@linux-foundation.org>,
Kairui Song <kasong@tencent.com>, Qi Zheng <qi.zheng@linux.dev>,
Shakeel Butt <shakeel.butt@linux.dev>,
Barry Song <baohua@kernel.org>,
Axel Rasmussen <axelrasmussen@google.com>,
Yuanchu Xie <yuanchu@google.com>, Wei Xu <weixugc@google.com>,
Baoquan He <baoquan.he@linux.dev>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>,
Steven Rostedt <rostedt@goodmis.org>,
Masami Hiramatsu <mhiramat@kernel.org>,
Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
Brendan Jackman <brendan.jackman@linux.dev>,
Johannes Weiner <hannes@cmpxchg.org>, Zi Yan <ziy@nvidia.com>,
linux-mm@kvack.org, linux-kernel@vger.kernel.org,
linux-trace-kernel@vger.kernel.org
Subject: Re: [PATCH v2 2/2] mm/compaction: defer failed async direct compaction
Date: Tue, 10 Nov 2026 09:42:56 -0500 [thread overview]
Message-ID: <20261110144256.3841504-1-qiliang.yuan@linux.dev> (raw)
In-Reply-To: <20261102-bug-mm-thp-async-compact-defer-v2-2-b407e974db54@gmail.com>
On Mon, 02 Nov 2026 01:01:08 -0500, Qiliang Yuan wrote:
> @@ -2600,9 +2600,12 @@ compact_zone(struct compact_control *cc, struct capture_control *capc)
>
> /*
> * Clear pageblock skip if there were failures recently and compaction
> - * is about to be retried after being deferred.
> + * is about to be retried after being deferred. Leave this to sync
> + * compaction: async compaction relies on the skip hints, and clearing
> + * them on its retries would rescan the whole zone each time.
> */
> - if (compaction_restarting(cc->zone, cc->order))
> + if (cc->mode != MIGRATE_ASYNC &&
> + compaction_restarting(cc->zone, cc->order))
> __reset_isolation_suitable(cc->zone);
As was pointed out to me off-list, this test removes a recovery path.
I'm not asking to reconsider the series, only leaving the analysis and
a reproducer here in case they help when you look at the issue.
The async run a THP fault does on the local node is never deferred:
try_to_compact_pages() calls defer_compaction() only for sync
priorities, because the deferral state is shared and deferring it
after an async failure would defer sync compaction too. The async path
checks that same sync state in compaction_deferred(), so as long as
sync isn't deferred, every fault scans the zone again. Patch 1 splits
the state so that async can be deferred on its own.
Skip hints are cleared in two places: in compact_zone(), when
compaction_restarting() sees the sync deferral at its maximum, and by
kswapd before it sleeps. Madvised THP faults don't wake kswapd, and
with proactive compaction off nothing else resets the hints, so once
the pinned pages become movable again, the reset in compact_zone() is
what lets local THP attempts use those pageblocks. After sync reaches
the maximum deferral, that reset comes from an async retry, which is
what the test in this patch blocks.
I reproduced it on the same QEMU guest as the series: 32 vCPUs,
node 0 with 16 CPUs and 24 GiB, node 1 with 16 CPUs and 8 GiB,
compaction_proactiveness set to 0 and defrag left at madvise:
1. Fill node 0 with anonymous memory, free every other 4K page and
long-term pin the rest through io_uring fixed buffers.
2. With defrag set to always, fault in a 1 GiB MADV_HUGEPAGE buffer
bound to node 0, so THP faults there run sync direct compaction
and fail until sync compaction reaches the maximum deferral.
3. Close the io_uring rings. The pins are gone, the memory stays in
use and is movable again.
4. Populate a 4 GiB MADV_HUGEPAGE buffer from a CPU of node 0.
Compactions that succeeded in step 4, one boot per number:
mainline 1184, 963
v2 8, 0, 516, 400, 534, 521, 495
v2 without the test 1025, 994, 906, 957
v2 recovered in five of seven boots without resetting any skip hint
(no call to __reset_isolation_suitable() in the two boots I traced):
with async compaction deferred, it scans few pageblocks while they are
pinned and leaves most of them unmarked. In the other two boots the
marked pageblocks kept it from recovering at all. Without the test,
the reset after the maximum sync deferral recovers every boot, as on
mainline, and the steady pinned case stays as cheap as with v2 (7.8
to 20 ms in compaction against 310 to 332 ms on mainline).
The reproducer is below, run as CPUS=0-15 sh thp-recover.sh.
Thanks,
Qiliang
---8<--- thp-recover.sh
#!/bin/sh
# Run as root on a two-node machine; CPUS lists the CPUs of node 0.
CPUS=${CPUS:-0}
D=/sys/kernel/mm/transparent_hugepage/defrag
echo 0 > /proc/sys/vm/compaction_proactiveness
echo madvise > $D
echo 1 > /proc/sys/vm/drop_caches
M=/sys/devices/system/node/node0/meminfo
free=$(awk '/MemFree/ { print int($4 / 1024) }' $M)
./thp-recover hog $((free - 300)) > hog.log &
hog=$!
until grep -q ready hog.log; do sleep 1; done
echo "steady, pinned:"
for i in 1 2 3; do taskset -c $CPUS ./thp-recover fault 4096; done
# Fail sync compaction on node 0 until it reaches the maximum deferral
echo always > $D
echo "bound to node 0:"
taskset -c $CPUS ./thp-recover bfault 1024
echo madvise > $D
kill -USR1 $hog
until grep -q unpinned hog.log; do sleep 1; done
echo "after dropping the pins:"
for i in 1 2 3 4 5; do taskset -c $CPUS ./thp-recover fault 4096; done
kill $hog
---8<--- thp-recover.c
// SPDX-License-Identifier: GPL-2.0
/*
* thp-recover hog <mb> fill node 0, free every other page and long-term
* pin the rest; SIGUSR1 drops the pins and keeps
* the memory in use
* thp-recover fault <mb> populate a MADV_HUGEPAGE buffer and print how
* much of it got THPs and on which node
* thp-recover bfault <mb> the same, bound to node 0
*/
#define _GNU_SOURCE
#include <linux/io_uring.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <time.h>
#include <unistd.h>
#define MB (1UL << 20)
#define MPOL_BIND 2
#define RING_BUFS 16384
static volatile sig_atomic_t unpin;
static void on_usr1(int sig)
{
unpin = sig;
}
static void bind_node0(void)
{
unsigned long mask = 1;
if (syscall(__NR_set_mempolicy, MPOL_BIND, &mask, 64))
perror("set_mempolicy"), exit(1);
}
static long vmstat(const char *key)
{
char name[64];
long v, ret = 0;
FILE *f = fopen("/proc/vmstat", "r");
while (f && fscanf(f, "%63s %ld", name, &v) == 2)
if (!strcmp(name, key))
ret = v;
if (f)
fclose(f);
return ret;
}
static long thp_mb(void)
{
const char *key = "AnonHugePages:";
char line[256];
long kb = 0;
FILE *f = fopen("/proc/self/smaps_rollup", "r");
while (f && fgets(line, sizeof(line), f))
if (!strncmp(line, key, strlen(key)))
kb = atol(line + strlen(key));
if (f)
fclose(f);
return kb / 1024;
}
static int hog(size_t size)
{
size_t npages = size / 8192, done = 0, i, off;
struct iovec *iov = calloc(RING_BUFS, sizeof(*iov));
int fds[4096], nr = 0, r;
char *buf;
bind_node0();
buf = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
madvise(buf, size, MADV_NOHUGEPAGE);
memset(buf, 1, size);
/* Plenty of free memory, but no free 2M block */
for (off = 4096; off < size; off += 8192)
madvise(buf + off, 4096, MADV_DONTNEED);
/* Long-term pin the pages left, as RDMA registration does */
while (done < npages) {
struct io_uring_params p = { 0 };
size_t n = npages - done > RING_BUFS ? RING_BUFS : npages - done;
int fd = syscall(__NR_io_uring_setup, 4, &p);
for (i = 0; i < n; i++) {
iov[i].iov_base = buf + (done + i) * 8192;
iov[i].iov_len = 4096;
}
if (fd < 0 || syscall(__NR_io_uring_register, fd,
IORING_REGISTER_BUFFERS, iov, n))
perror("pin"), exit(1);
fds[nr++] = fd;
done += n;
}
printf("ready\n");
fflush(stdout);
signal(SIGUSR1, on_usr1);
while (!unpin)
pause();
for (r = 0; r < nr; r++)
close(fds[r]);
printf("unpinned\n");
fflush(stdout);
for (;;)
pause();
}
static int fault(size_t size)
{
size_t n = size / (2 * MB), i, node[2] = { 0 };
long stall = vmstat("compact_stall"), ok = vmstat("compact_success");
void **pages = malloc(n * sizeof(*pages));
int *status = malloc(n * sizeof(*status));
struct timespec t0, t1;
double ms;
char *buf;
buf = mmap(NULL, size + 2 * MB, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
buf = (char *)(((unsigned long)buf + 2 * MB - 1) & ~(2 * MB - 1));
madvise(buf, size, MADV_HUGEPAGE);
clock_gettime(CLOCK_MONOTONIC, &t0);
for (i = 0; i < size; i += 4096)
buf[i] = 1;
clock_gettime(CLOCK_MONOTONIC, &t1);
ms = (t1.tv_sec - t0.tv_sec) * 1e3 + (t1.tv_nsec - t0.tv_nsec) / 1e6;
for (i = 0; i < n; i++)
pages[i] = buf + i * 2 * MB;
syscall(__NR_move_pages, 0, n, pages, NULL, status, 0);
for (i = 0; i < n; i++)
if (status[i] == 0 || status[i] == 1)
node[status[i]]++;
printf("%zu MB in %.0f ms, %ld MB in THPs, 2M ranges on node 0/1: %zu/%zu, ",
size / MB, ms, thp_mb(), node[0], node[1]);
printf("compactions: %ld, succeeded: %ld\n",
vmstat("compact_stall") - stall, vmstat("compact_success") - ok);
return 0;
}
int main(int argc, char **argv)
{
size_t size;
if (argc != 3) {
fprintf(stderr, "usage: %s hog|fault|bfault <mb>\n", argv[0]);
return 1;
}
size = strtoul(argv[2], NULL, 0) * MB;
if (!strcmp(argv[1], "hog"))
return hog(size);
if (!strcmp(argv[1], "bfault"))
bind_node0();
return fault(size);
}
next prev parent reply other threads:[~2026-11-10 14:43 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 6:01 [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Qiliang Yuan
2026-11-02 6:01 ` [PATCH v2 1/2] mm/compaction: keep compaction deferral state per migration mode Qiliang Yuan
2026-11-02 6:01 ` [PATCH v2 2/2] mm/compaction: defer failed async direct compaction Qiliang Yuan
2026-11-10 14:42 ` Qiliang Yuan [this message]
2026-11-02 9:23 ` [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Lorenzo Stoakes (ARM)
2026-11-10 13:58 ` Qiliang Yuan
2026-11-10 14:13 ` Vlastimil Babka (SUSE)
2026-11-10 15:39 ` Qiliang Yuan
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=20261110144256.3841504-1-qiliang.yuan@linux.dev \
--to=qiliang.yuan@linux.dev \
--cc=akpm@linux-foundation.org \
--cc=axelrasmussen@google.com \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=baoquan.he@linux.dev \
--cc=brendan.jackman@linux.dev \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=kasong@tencent.com \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=mhiramat@kernel.org \
--cc=mhocko@suse.com \
--cc=qi.zheng@linux.dev \
--cc=rostedt@goodmis.org \
--cc=rppt@kernel.org \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=weixugc@google.com \
--cc=yuanchu@google.com \
--cc=ziy@nvidia.com \
/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
all inboxes | Powered by JetHome®