Re: [PATCH RFC v3 1/3] mm/zswap: release retired pools via call_rcu() instead of synchronize_rcu()
From: Yosry Ahmed
Date: Tue Aug 25 2026 - 12:56:00 EST
On Fri, Aug 14, 2026 at 11:54 PM Jianyue Wu <wujianyue000@xxxxxxxxx> wrote:
>
> When a pool's last reference is dropped, __zswap_pool_empty() removes it
> from the pool list and schedules __zswap_pool_release(), which calls
> synchronize_rcu() to wait for readers before tearing the pool down.
>
> synchronize_rcu() is a synchronous, potentially long wait. Replace it
> with an asynchronous call_rcu(): __zswap_pool_empty() now hands the pool
> to call_rcu(), and the RCU callback defers the sleepable teardown to the
> release worker (the callback itself runs in softirq context and must not
> block). The grace-period guarantee is unchanged; the retirement path
> just no longer blocks on it.
>
> Suggested-by: Yosry Ahmed <yosry@xxxxxxxxxx>
> Signed-off-by: Jianyue Wu <wujianyue000@xxxxxxxxx>
> ---
> mm/zswap.c | 20 ++++++++++++++++----
> 1 file changed, 16 insertions(+), 4 deletions(-)
>
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 37f34e406c8e..cc4243356e21 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -155,6 +155,7 @@ struct zswap_pool {
> struct crypto_acomp_ctx __percpu *acomp_ctx;
> struct percpu_ref ref;
> struct list_head list;
> + struct rcu_head rcu_head;
> struct work_struct release_work;
> struct hlist_node node;
> char tfm_name[CRYPTO_MAX_ALG_NAME];
> @@ -382,8 +383,6 @@ static void __zswap_pool_release(struct work_struct *work)
> struct zswap_pool *pool = container_of(work, typeof(*pool),
> release_work);
>
> - synchronize_rcu();
> -
> /* nobody should have been able to get a ref... */
> WARN_ON(!percpu_ref_is_zero(&pool->ref));
> percpu_ref_exit(&pool->ref);
> @@ -392,6 +391,20 @@ static void __zswap_pool_release(struct work_struct *work)
> zswap_pool_destroy(pool);
> }
>
> +static void __zswap_pool_release_rcu(struct rcu_head *head)
> +{
> + struct zswap_pool *pool = container_of(head, typeof(*pool), rcu_head);
> +
> + /*
> + * The grace period has elapsed, so no RCU reader can still observe the
> + * pool through the list it was removed from in __zswap_pool_empty().
> + * Hand off to a worker for the sleepable teardown, since this callback
> + * runs in softirq context.
> + */
Oh I didn't realize that call_rcu() cannot be used with blocking
callbacks, sorry about that. What about queue_rcu_work()? Seems like
if we use that we can drop the intermediary callback?
> + INIT_WORK(&pool->release_work, __zswap_pool_release);
> + schedule_work(&pool->release_work);
> +}
> +
> static struct zswap_pool *zswap_pool_current(void);
>
> static void __zswap_pool_empty(struct percpu_ref *ref)
> @@ -406,8 +419,7 @@ static void __zswap_pool_empty(struct percpu_ref *ref)
>
> list_del_rcu(&pool->list);
>
> - INIT_WORK(&pool->release_work, __zswap_pool_release);
> - schedule_work(&pool->release_work);
> + call_rcu(&pool->rcu_head, __zswap_pool_release_rcu);
>
> spin_unlock_bh(&zswap_pools_lock);
> }
>
> --
> 2.43.0
>