Re: [PATCH v4 2/5] mm/zswap: Factor writeback loop out of shrink_worker()
From: Hao Jia
Date: Thu Jun 25 2026 - 07:28:33 EST
On 2026/6/25 01:00, Yosry Ahmed wrote:
On Wed, Jun 24, 2026 at 4:55 AM Hao Jia <jiahao.kernel@xxxxxxxxx> wrote:
On 2026/6/23 07:36, Yosry Ahmed wrote:
Perhaps something like this?
struct zswap_shrink_state {
int attempts;
int failures;
bool stop;
};
static bool zswap_shrink_no_candidate(struct zswap_shrink_state *s)
{
if (!s->attempts && ++s->failures == MAX_RECLAIM_RETRIES)
return true;
s->attempts = 0;
return false;
}
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.
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;
cond_resched();
memcg = zswap_iter_global();
if (!memcg) {
if (zswap_shrink_no_candidate(&s))
break;
continue;
}
zswap_shrink_one(memcg, &s);
/* Drop the extra reference taken by the iterator. */
mem_cgroup_put(memcg);
if (s.stop)
break;
}
}
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?
/* Track progress of a memcg-tree writeback walk. */
struct zswap_shrink_state {
int attempts;
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.
*
* 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,
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();
ret = zswap_shrink_one(memcg, &s);
/* drop the extra reference taken by zswap_iter_global() */
mem_cgroup_put(memcg);
if (ret == -EAGAIN)
break;
}
}