From: sashiko-bot@kernel.org
To: "Kanchana P. Sridhar" <kanchanapsridhar2026@gmail.com>,
hannes@cmpxchg.org, yosry@kernel.org, nphamcs@gmail.com,
chengming.zhou@linux.dev, akpm@linux-foundation.org,
linux-mm@kvack.org, herbert@gondor.apana.org.au,
davem@davemloft.net, linux-crypto@vger.kernel.org
Cc: rientjes@google.com, willy@infradead.org, senozhatsky@chromium.org
Subject: Re: [PATCH v2 8/8] mm: zswap: Refactor and optimize zswap_store_pages() batch operations.
Date: Thu, 29 Oct 2026 03:06:06 +0000 [thread overview]
Message-ID: <sashiko-outbox-188660@kernel.org> (raw)
In-Reply-To: <20261028030503.17923-9-kanchanapsridhar2026@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] mm: zswap: memcg counter corruption in zswap_account_stores()
- [High] mm: zswap: missing zs_free() of prior sub-batches when zswap_compress() fails
- [Low] mm: zswap: unused zswap_pool_get() retained with __maybe_unused
--
commit 5a1c181c752a45f404bc7cfeb7961fd6accb0903
Author: Kanchana P. Sridhar <kanchanapsridhar2026@gmail.com>
mm: zswap: Refactor and optimize zswap_store_pages() batch operations.
This commit refactors zswap_store_pages() into smaller helper functions
(zswap_init_entries(), zswap_tree_stores(), and zswap_account_stores()) to
process batches of pages when storing a large folio. It also batches pool and
obj_cgroup reference counting, memcg charging, and zswap_stored_pages updates
across the batch.
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 4476fb4aeb5fd..2831284cfc903 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -504,12 +504,18 @@ static int __must_check zswap_pool_tryget(struct zswap_pool *pool)
> }
>
> /* The caller must already have a reference. */
> -static void zswap_pool_get(struct zswap_pool *pool)
> +static void __maybe_unused zswap_pool_get(struct zswap_pool *pool)
> {
> percpu_ref_get(&pool->ref);
> }
[Severity: Low]
Since the only call site in zswap_store_pages() is replaced by
zswap_pool_get_many(), zswap_pool_get() no longer has any callers in the
file. Could zswap_pool_get() be removed outright instead of marking it
__maybe_unused?
>
> -static void zswap_pool_put(struct zswap_pool *pool)
> +/* The caller must already have a reference. */
> +static inline void zswap_pool_get_many(struct zswap_pool *pool, u8 nr)
> +{
> + percpu_ref_get_many(&pool->ref, nr);
> +}
[ ... ]
> @@ -1750,24 +1732,30 @@ static bool zswap_store_pages(struct zswap_store_context *zc,
[ ... ]
> +static inline void zswap_account_stores(struct zswap_store_context *zc,
> + struct zswap_pool *pool,
> + struct obj_cgroup *objcg)
> +{
> + size_t total_batch_comp_length = 0;
> + u8 i;
> +
> + for (i = 0; i < zc->nr_pages; ++i) {
> + if (unlikely(zc->entries[i]->length == PAGE_SIZE))
> atomic_long_inc(&zswap_stored_incompressible_pages);
>
> + total_batch_comp_length += zc->entries[i]->length;
> +
> /*
> * We finish by adding the entry to the LRU while it's already
> * in xarray. This is safe because:
> @@ -1778,20 +1766,78 @@ static bool zswap_store_pages(struct zswap_store_context *zc,
> * The publishing order matters to prevent writeback from seeing
> * an incoherent entry.
> */
> - if (likely(entry->length))
> - zswap_lru_add(entry);
> + if (likely(zc->entries[i]->length))
> + zswap_lru_add(zc->entries[i]);
> }
>
> + /*
> + * The entry is successfully compressed and stored in the tree,
> + * and further failures will be cleaned up in zswap_store().
> + * For the batch, grab refs to the pool and objcg, charge zswap memory,
> + * and increment zswap_stored_pages by @zc->nr_pages. The opposite
> + * actions will be performed by zswap_entry_free() when the entry is
> + * removed from the tree.
> + */
> + zswap_pool_get_many(pool, zc->nr_pages);
> + if (objcg) {
> + obj_cgroup_get_many(objcg, zc->nr_pages);
> + obj_cgroup_charge_zswap(objcg, total_batch_comp_length);
> + }
> + atomic_long_add(zc->nr_pages, &zswap_stored_pages);
> +}
[Severity: High]
Does calling obj_cgroup_charge_zswap(objcg, total_batch_comp_length) once per
batch corrupt the MEMCG_ZSWAPPED and MEMCG_ZSWAP_INCOMP counters when
zc->nr_pages > 1?
Looking at obj_cgroup_charge_zswap() in mm/memcontrol.c, it assumes @size
represents a single page:
mm/memcontrol.c:obj_cgroup_charge_zswap() {
...
rcu_read_lock();
memcg = obj_cgroup_memcg(objcg);
mod_memcg_state(memcg, MEMCG_ZSWAP_B, size);
mod_memcg_state(memcg, MEMCG_ZSWAPPED, 1);
if (size == PAGE_SIZE)
mod_memcg_state(memcg, MEMCG_ZSWAP_INCOMP, 1);
rcu_read_unlock();
}
When a batch of zc->nr_pages > 1 pages from a large folio is stored via
zswap_store() -> zswap_store_pages() -> zswap_account_stores(),
MEMCG_ZSWAPPED is only incremented by 1 instead of zc->nr_pages, and
MEMCG_ZSWAP_INCOMP compares the summed total_batch_comp_length against
PAGE_SIZE rather than counting individual incompressible pages.
Later, when each entry in the batch is freed via zswap_entry_free():
mm/zswap.c:zswap_entry_free() {
...
if (entry->objcg) {
obj_cgroup_uncharge_zswap(entry->objcg, entry->length);
obj_cgroup_put(entry->objcg);
}
...
}
obj_cgroup_uncharge_zswap() is called once per entry, decrementing
MEMCG_ZSWAPPED by zc->nr_pages total and decrementing MEMCG_ZSWAP_INCOMP for
each entry whose length == PAGE_SIZE.
This causes both MEMCG_ZSWAPPED and MEMCG_ZSWAP_INCOMP to be miscounted and
underflow, which also breaks zswap_shrinker_count() where MEMCG_ZSWAPPED is
used as nr_stored to scale reclaim targets.
> +
> +/*
> + * Store multiple pages in @folio, starting from the page at index @start up to
> + * the page at index @end-1.
> + */
> +static bool zswap_store_pages(struct zswap_store_context *zc,
> + long start,
> + long end,
> + struct zswap_pool *pool,
> + struct obj_cgroup *objcg)
> +{
> + bool ret;
> + u8 i;
> +
> + zc->nr_pages = end - start;
> + zc->folio_start = start;
> +
> + VM_WARN_ON_ONCE(zc->nr_pages <= 0 || zc->nr_pages > ZSWAP_MAX_BATCH_SIZE);
> +
> + if (unlikely(!zswap_entries_cache_alloc_batch(zc, GFP_KERNEL)))
> + return false;
> +
> + zswap_init_entries(zc, pool, objcg);
> +
> + /*
> + * Locking the comp_ctx mutex once per store batch results in better
> + * performance as compared to locking per compress batch.
> + */
> + zc->comp_ctx = &raw_cpu_ptr(pool->acomp_ctx)->comp;
> + mutex_lock(&zc->comp_ctx->areq.mutex);
> +
> + ret = zswap_compress(zc);
> +
> + mutex_unlock(&zc->comp_ctx->areq.mutex);
> +
> + if (unlikely(!ret))
> + goto compress_failed;
> +
> + ret = zswap_tree_stores(zc);
> +
> + if (unlikely(!ret))
> + goto store_pages_failed;
> +
> + zswap_account_stores(zc, pool, objcg);
> +
> return true;
>
> store_pages_failed:
> - for (i = store_fail_idx; i < zc->nr_pages; ++i)
> + for (i = 0; i < zc->nr_pages; ++i)
> zs_free(pool->zs_pool, zc->entries[i]->handle);
>
> compress_failed:
> - zswap_entries_cache_free_batch(&zc->entries[store_fail_idx],
> - zc->nr_pages - store_fail_idx);
> -
> + zswap_entries_cache_free_batch(zc->entries, zc->nr_pages);
> return false;
> }
[Severity: High]
Can jumping to compress_failed when zswap_compress() returns false leak the
zsmalloc handles allocated in earlier compression sub-batches?
In zswap_compress(), pages in [0, zc->nr_pages) are compressed in sub-batches
of zc->nr_batch_pages (which is 1 for software compressors such as ZSTD or
LZ4 when zc->nr_pages is up to ZSWAP_MAX_BATCH_SIZE):
mm/zswap.c:zswap_compress() {
for (batch_start = 0; batch_start < zc->nr_pages;
batch_start += zc->nr_batch_pages) {
...
err = crypto_wait_req(crypto_acomp_compress(zc->comp_ctx->areq.req),
&zc->comp_ctx->areq.wait);
if (unlikely(err && !zc->wb_enabled))
goto compress_error;
...
ret = zswap_update_entries(zc, batch_start);
if (unlikely(!ret))
return false;
}
}
Each successful call to zswap_update_entries(zc, batch_start) allocates
zs_malloc() handles for zc->entries[batch_start .. batch_start +
zc->nr_batch_pages - 1]. If a subsequent iteration with batch_start > 0 fails
at goto compress_error (when !zc->wb_enabled and a page is incompressible) or
fails in zswap_update_entries() (which only frees handles in the current
sub-batch starting at batch_start):
mm/zswap.c:zswap_update_entries() {
...
if (unlikely(IS_ERR_VALUE(handle))) {
...
for (i = 0; i < comp_batch_iter; ++i)
zs_free(zc->zs_pool, zc->entries[batch_start + i]->handle);
return false;
}
...
}
zswap_compress() returns false while zc->entries[0 .. batch_start - 1]->handle
still hold allocated zsmalloc objects. In zswap_store_pages(), jumping to
compress_failed then frees zc->entries back to the slab cache without calling
zs_free() on those handles.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261028030503.17923-1-kanchanapsridhar2026@gmail.com?part=8
next prev parent reply other threads:[~2026-10-29 3:06 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 3:04 [PATCH v2 0/8] Batched zswap_store() framework with compression batching Kanchana P. Sridhar
2026-10-28 3:04 ` [PATCH v2 1/8] crypto: acomp - Define a unit_size in struct acomp_req to enable batching. mm: zswap: Set the unit size for zswap to PAGE_SIZE Kanchana P. Sridhar
2026-10-28 3:04 ` [PATCH v2 2/8] crypto: acomp - Add bit to indicate segmentation support Kanchana P. Sridhar
2026-10-28 3:04 ` [PATCH v2 3/8] crypto: acomp - Add trivial segmentation wrapper Kanchana P. Sridhar
2026-10-29 3:05 ` sashiko-bot
2026-10-29 5:09 ` Kanchana P. Sridhar
2026-10-29 5:38 ` Herbert Xu
2026-10-29 6:03 ` Kanchana P. Sridhar
2026-10-29 8:22 ` Herbert Xu
2026-10-29 18:11 ` Kanchana P. Sridhar
2026-10-28 3:04 ` [PATCH v2 4/8] crypto: acomp - Add API to get a compression algorithm's batch-size Kanchana P. Sridhar
2026-10-29 3:06 ` sashiko-bot
2026-10-29 5:49 ` Kanchana P. Sridhar
2026-10-28 3:05 ` [PATCH v2 5/8] mm: zswap: Store the folio's node id in the zswap_entry Kanchana P. Sridhar
2026-10-29 3:06 ` sashiko-bot
2026-10-29 23:00 ` Kanchana P. Sridhar
2026-10-29 23:09 ` Yosry Ahmed
2026-10-28 3:05 ` [PATCH v2 6/8] mm: zswap: Store large folios in batches Kanchana P. Sridhar
2026-10-29 3:06 ` sashiko-bot
2026-10-30 0:11 ` Kanchana P. Sridhar
2026-10-30 4:47 ` Herbert Xu
2026-10-30 5:15 ` Kanchana P. Sridhar
2026-10-30 5:51 ` Herbert Xu
2026-10-30 6:18 ` Kanchana P. Sridhar
2026-10-28 3:05 ` [PATCH v2 7/8] mm: zswap: Batched zswap_compress() for storing " Kanchana P. Sridhar
2026-10-29 3:06 ` sashiko-bot
2026-10-30 6:15 ` Kanchana P. Sridhar
2026-10-28 3:05 ` [PATCH v2 8/8] mm: zswap: Refactor and optimize zswap_store_pages() batch operations Kanchana P. Sridhar
2026-10-29 3:06 ` sashiko-bot [this message]
2026-10-30 3:19 ` Kanchana P. Sridhar
2026-10-29 19:12 ` [PATCH v2 0/8] Batched zswap_store() framework with compression batching Yosry Ahmed
2026-10-29 21:09 ` Kanchana P. Sridhar
2026-10-29 22:31 ` 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=sashiko-outbox-188660@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=chengming.zhou@linux.dev \
--cc=davem@davemloft.net \
--cc=hannes@cmpxchg.org \
--cc=herbert@gondor.apana.org.au \
--cc=kanchanapsridhar2026@gmail.com \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=nphamcs@gmail.com \
--cc=rientjes@google.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=senozhatsky@chromium.org \
--cc=willy@infradead.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 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.