From: Hao Jia <jiahao.kernel@gmail.com>
To: Yosry Ahmed <yosry@kernel.org>, nphamcs@gmail.com
Cc: akpm@linux-foundation.org, tj@kernel.org, hannes@cmpxchg.org,
shakeel.butt@linux.dev, mhocko@kernel.org, mkoutny@suse.com,
chengming.zhou@linux.dev, muchun.song@linux.dev,
roman.gushchin@linux.dev, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
Hao Jia <jiahao1@lixiang.com>
Subject: Re: [PATCH v4 2/5] mm/zswap: Factor writeback loop out of shrink_worker()
Date: Fri, 26 Jun 2026 17:34:05 +0800 [thread overview]
Message-ID: <f1cd1ec9-48b0-1b03-0514-6c9958f3c77f@gmail.com> (raw)
In-Reply-To: <CAO9r8zOYgjbuG5i+LrCcMK764nVpOS+muo-5Q45ZFdiVus-dTA@mail.gmail.com>
On 2026/6/26 01:59, Yosry Ahmed wrote:
>>>> static long zswap_shrink_one(struct mem_cgroup *memcg,
>>>> struct zswap_shrink_state *s)
>>>> {
>>>> long shrunk;
>>>>
>>>> shrunk = shrink_memcg(memcg, NR_ZSWAP_WB_BATCH);
>>>> if (shrunk == -ENOENT)
>>>> return 0;
>>>>
>>>> s->attempts++;
>>>> if (shrunk <= 0 && ++s->failures == MAX_RECLAIM_RETRIES)
>>>> s->stop = true;
>>>
>>> Do we need 'stop' or can we just return a value here to indicate that
>>> we should stop (e.g. -EBUSY)?
>>>
>>
>> Perhaps we could return -EAGAIN instead of -EBUSY? This would align with
>> the semantics of the memory.reclaim interface, which returns -EAGAIN
>> when it reclaims fewer bytes than requested.
>
> Hmm but -EAGAIN tells the caller to try again, while here -EAGAIN
> tells the caller *not* to try again because we exhausted all retries?
>
Okay, let's go with -EBUSY.
>>>
>>> I think splitting the shrink/retry logic over 2 functions makes it
>>> more difficult to follow, so yeah I think fold
>>> zswap_shrink_no_candidate() into zswap_shrink_one(). Then the callers
>>> only need to iterate memcgs (depending on the context) and call
>>> zswap_shrink_one() for each of them.
>>
>> So, something like this?
>
> Yeah, something like this :)
>
>> /* Track progress of a memcg-tree writeback walk. */
>> struct zswap_shrink_state {
>> int attempts;
>
> While at it, I think "attempts" is really the number of scans, right?
> Should we rename it? Maybe "scans" or similar?
>
>> int failures;
>> };
>>
>> /*
>> * Take one step of a memcg-tree writeback walk driven by the caller's
>> * iterator, and fold the result into @s, the retry bookkeeping shared
>> * across steps. @memcg is the iterator's current memcg, or NULL once
>> * it has wrapped around after a full pass over the tree.
>> *
>> * The function returns -EAGAIN to signal the caller to abort the walk
>> * after encountering the following conditions MAX_RECLAIM_RETRIES times:
>> * - No writeback-candidate memcgs were found in a memcg tree walk.
>> * - Shrinking a writeback-candidate memcg failed.
>
> Orthogonal to this patch, but I wonder if this can be simplified. I
> wonder if these two conditions can be replaced with "shrinking a memcg
> that has zswap entries failed". The "no writeback-candidate memcgs in
> the tree" case seems like we should abort right away instead of
> retrying?
>
> Nhat, WDYT?
>
Perhaps something like the following is what you had in mind? I've
drafted the implementation below to make it easier for Nhat to compare
with the previous behavior.
>> *
>> * Return: The number of compressed bytes written back (>= 0), or -EAGAIN
>> * once the retry budget is exhausted and the caller should abort the walk.
>> */
>> static long zswap_shrink_one(struct mem_cgroup *memcg,
>
> Nit: zswap_shrink_one_memcg()
>
> BTW, the existing writeback logic has been broken for a while now when
> memcg is disabled. I think we constantly hit the !memcg case and run
> out of retries. Not sure if your patch changes this in any way, or if
> you want to fix that while you're at it :)
Yes, I'd be happy to do that. However, would it be better to submit a
separate fix patch or combine it with this one?
>
>> struct zswap_shrink_state *s)
>> {
>> long shrunk;
>>
>> /*
>> * If the iterator has completed a full pass, update the shrink state
>> * and check whether we should keep going.
>> */
>> if (!memcg) {
>> /*
>> * Continue shrinking without incrementing failures if we found
>> * candidate memcgs in the last tree walk.
>> */
>> if (!s->attempts && ++s->failures == MAX_RECLAIM_RETRIES)
>> return -EAGAIN;
>> s->attempts = 0;
>> return 0;
>> }
>>
>> shrunk = shrink_memcg(memcg, NR_ZSWAP_WB_BATCH);
>>
>> /*
>> * There are no writeback-candidate pages in the memcg. This is not an
>> * issue as long as we can find another memcg with pages in zswap. Skip
>> * this without incrementing attempts and failures.
>> */
>> if (shrunk == -ENOENT)
>> return 0;
>> s->attempts++;
>>
>> if (shrunk <= 0 && ++s->failures == MAX_RECLAIM_RETRIES)
>> return -EAGAIN;
>>
>> return shrunk;
>> }
>>
>> static void shrink_worker(struct work_struct *w)
>> {
>> struct zswap_shrink_state s = {};
>> unsigned long thr;
>>
>> /* Reclaim down to the accept threshold */
>> thr = zswap_accept_thr_pages();
>>
>> while (zswap_total_pages() > thr) {
>> struct mem_cgroup *memcg;
>> long ret;
>>
>> cond_resched();
>>
>> memcg = zswap_iter_global();
>
> Do we still need this helper? Or should we just keep the memcg
> iteration open-coded?
Done.
>
>> ret = zswap_shrink_one(memcg, &s);
>> /* drop the extra reference taken by zswap_iter_global() */
>> mem_cgroup_put(memcg);
>> if (ret == -EAGAIN)
>> break;
>> }
>> }
/* Track progress of a memcg-tree writeback walk. */
struct zswap_shrink_state {
int scans;
int failures;
};
/*
* Take one step of a memcg-tree writeback walk driven by the caller's
* iterator, and fold the result into @s, the retry bookkeeping shared
* across steps. @memcg is the iterator's current memcg, or NULL once
* it has wrapped around after a full pass over the tree.
*
* The function returns -EBUSY to signal the caller to abort the walk when
* either of the following occurs:
* - A full pass over the tree found no writeback-candidate memcg.
* - Shrinking a writeback-candidate memcg failed MAX_RECLAIM_RETRIES
times.
*
* When memory cgroup is disabled, the iterator always yields NULL. All
* zswap entries then live on the root list_lru, so NULL is treated as the
* root memcg and shrunk directly rather than as a completed tree pass.
*
* Return: The number of compressed bytes written back (>= 0), or -EBUSY
* when the caller should abort the walk.
*/
static long zswap_shrink_one_memcg(struct mem_cgroup *memcg,
struct zswap_shrink_state *s)
{
bool disabled = mem_cgroup_disabled();
long shrunk;
/*
* If the iterator has completed a full pass, update the shrink state
* and check whether we should keep going.
* With memcg disabled the iterator always yields NULL, so fall through
* and shrink the root memcg directly instead.
*/
if (!memcg && !disabled) {
/*
* Abort if no writeback-candidate memcgs in the last tree walk.
* Otherwise reset the scans count and continue.
*/
if (!s->scans)
return -EBUSY;
s->scans = 0;
return 0;
}
shrunk = shrink_memcg(memcg, NR_ZSWAP_WB_BATCH);
/*
* There are no writeback-candidate pages in the memcg. With memcg
* enabled this is not an issue as long as we can find another memcg
* with pages in zswap, so skip without counting it as a candidate.
* With memcg disabled the root LRU is the only target, so we should
* abort if it has no writeback-candidate pages.
*/
if (shrunk == -ENOENT)
return disabled ? -EBUSY : 0;
s->scans++;
if (shrunk <= 0 && ++s->failures == MAX_RECLAIM_RETRIES)
return -EBUSY;
return shrunk;
}
static void shrink_worker(struct work_struct *w)
{
struct zswap_shrink_state s = {};
unsigned long thr;
/* Reclaim down to the accept threshold */
thr = zswap_accept_thr_pages();
/*
* Global reclaim will select cgroup in a round-robin fashion from all
* online memcgs, but memcgs that have no pages in zswap and
* writeback-disabled memcgs (memory.zswap.writeback=0) are not
* candidates for shrinking.
*
* We save iteration cursor memcg into zswap_next_shrink,
* which can be modified by the offline memcg cleaner
* zswap_memcg_offline_cleanup().
*
* Since the offline cleaner is called only once, we cannot leave an
* offline memcg reference in zswap_next_shrink.
* We can rely on the cleaner only if we get online memcg under lock.
*
* If we get an offline memcg, we cannot determine if the cleaner has
* already been called or will be called later. We must put back the
* reference before returning from this function. Otherwise, the
* offline memcg left in zswap_next_shrink will hold the reference
* until the next run of shrink_worker().
*/
while (zswap_total_pages() > thr) {
struct mem_cgroup *memcg;
long ret;
cond_resched();
/*
* Start shrinking from the next memcg after zswap_next_shrink.
* When the offline cleaner has already advanced the cursor,
* advancing the cursor here overlooks one memcg, but this
* should be negligibly rare.
*
* If we get an online memcg, keep the extra reference in case
* the original one obtained by mem_cgroup_iter() is dropped by
* zswap_memcg_offline_cleanup() while we are shrinking the
* memcg.
*/
spin_lock(&zswap_shrink_lock);
do {
memcg = mem_cgroup_iter(NULL, zswap_next_shrink, NULL);
zswap_next_shrink = memcg;
} while (memcg && !mem_cgroup_tryget_online(memcg));
spin_unlock(&zswap_shrink_lock);
ret = zswap_shrink_one_memcg(memcg, &s);
/* drop the extra reference taken above */
mem_cgroup_put(memcg);
if (ret == -EBUSY)
break;
}
}
Thanks,
Hao
next prev parent reply other threads:[~2026-06-26 9:34 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-18 4:48 [PATCH v4 0/5] mm/zswap: Implement per-cgroup proactive writeback Hao Jia
2026-06-18 4:48 ` [PATCH v4 1/5] mm/zswap: Extend shrink_memcg() writeback capability Hao Jia
2026-06-22 23:33 ` Yosry Ahmed
2026-06-23 13:22 ` Hao Jia
2026-06-23 18:17 ` Yosry Ahmed
2026-06-24 11:58 ` Hao Jia
2026-06-24 16:57 ` Yosry Ahmed
2026-06-25 11:31 ` Hao Jia
2026-06-18 4:48 ` [PATCH v4 2/5] mm/zswap: Factor writeback loop out of shrink_worker() Hao Jia
2026-06-22 23:36 ` Yosry Ahmed
2026-06-24 11:55 ` Hao Jia
2026-06-24 17:00 ` Yosry Ahmed
2026-06-25 11:28 ` Hao Jia
2026-06-25 17:59 ` Yosry Ahmed
2026-06-26 9:34 ` Hao Jia [this message]
2026-06-26 17:09 ` Yosry Ahmed
2026-06-29 11:36 ` Hao Jia
2026-06-18 4:48 ` [PATCH v4 3/5] mm/zswap: Implement proactive writeback Hao Jia
2026-06-22 23:40 ` Yosry Ahmed
2026-06-18 4:48 ` [PATCH v4 4/5] mm/zswap: Add per-memcg stat for " Hao Jia
2026-06-22 23:42 ` Yosry Ahmed
2026-06-18 4:48 ` [PATCH v4 5/5] selftests/cgroup: Add tests for zswap " Hao Jia
2026-06-21 4:20 ` [PATCH v4 0/5] mm/zswap: Implement per-cgroup " Muchun Song
2026-06-22 6:08 ` Hao Jia
2026-06-22 10:04 ` Youngjun Park
2026-06-22 21:29 ` Yosry Ahmed
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=f1cd1ec9-48b0-1b03-0514-6c9958f3c77f@gmail.com \
--to=jiahao.kernel@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=chengming.zhou@linux.dev \
--cc=hannes@cmpxchg.org \
--cc=jiahao1@lixiang.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=mhocko@kernel.org \
--cc=mkoutny@suse.com \
--cc=muchun.song@linux.dev \
--cc=nphamcs@gmail.com \
--cc=roman.gushchin@linux.dev \
--cc=shakeel.butt@linux.dev \
--cc=tj@kernel.org \
--cc=yosry@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