mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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);
}

  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®