[PATCH 3/6] SUNRPC: Defer ip_map sub-object cleanup past RCU grace period
From: Chuck Lever
Date: Fri May 01 2026 - 10:54:14 EST
From: Chuck Lever <chuck.lever@xxxxxxxxxx>
ip_map_put() is already correct in that auth_domain_put() of
im->m_client reaches svcauth_unix_domain_release(), which defers
the actual kfree() of the unix_domain with call_rcu(); the ip_map
itself is freed via kfree_rcu(). Readers in c_show() under
cache_seq_start_rcu() therefore observe both objects until the
next RCU grace period.
This patch is a consistency change rather than a bug fix. The
svc_export and svc_expkey caches were converted to the
queue_rcu_work() pattern in commit 48db892356d6 ("NFSD: Defer
sub-object cleanup in export put callbacks") because path_put()
and auth_domain_put() must run in process context after the RCU
grace period. The next patch routes unix_gid through the same
mechanism. Sending ip_map through sunrpc_cache_queue_release()
unifies all four cache_detail .put callbacks on a single release
path and removes the implicit reliance on every current and
future auth_ops .domain_release implementation deferring its own
kfree() behind call_rcu().
Replace the rcu_head field with an rcu_work, move the kfree() and
auth_domain_put() into a new ip_map_release() taking a
work_struct, and have ip_map_put() invoke INIT_RCU_WORK() and
sunrpc_cache_queue_release() in place of kfree_rcu(). Switch
ip_map_cache_destroy() to sunrpc_cache_destroy_net() so
per-namespace teardown waits for outstanding release work
before freeing the cache_detail.
Assisted-by: Claude:claude-opus-4-7[1m]
Signed-off-by: Chuck Lever <chuck.lever@xxxxxxxxxx>
---
net/sunrpc/svcauth_unix.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index 64a2658faddb..14688813c242 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -103,18 +103,26 @@ struct ip_map {
char m_class[8]; /* e.g. "nfsd" */
struct in6_addr m_addr;
struct unix_domain *m_client;
- struct rcu_head m_rcu;
+ struct rcu_work m_rwork;
};
+static void ip_map_release(struct work_struct *work)
+{
+ struct ip_map *im = container_of(to_rcu_work(work),
+ struct ip_map, m_rwork);
+
+ if (test_bit(CACHE_VALID, &im->h.flags) &&
+ !test_bit(CACHE_NEGATIVE, &im->h.flags))
+ auth_domain_put(&im->m_client->h);
+ kfree(im);
+}
+
static void ip_map_put(struct kref *kref)
{
- struct cache_head *item = container_of(kref, struct cache_head, ref);
- struct ip_map *im = container_of(item, struct ip_map,h);
+ struct ip_map *im = container_of(kref, struct ip_map, h.ref);
- if (test_bit(CACHE_VALID, &item->flags) &&
- !test_bit(CACHE_NEGATIVE, &item->flags))
- auth_domain_put(&im->m_client->h);
- kfree_rcu(im, m_rcu);
+ INIT_RCU_WORK(&im->m_rwork, ip_map_release);
+ sunrpc_cache_queue_release(&im->m_rwork);
}
static inline int hash_ip6(const struct in6_addr *ip)
@@ -1569,6 +1577,5 @@ void ip_map_cache_destroy(struct net *net)
sn->ip_map_cache = NULL;
cache_purge(cd);
- cache_unregister_net(cd, net);
- cache_destroy_net(cd, net);
+ sunrpc_cache_destroy_net(cd, net);
}
--
2.53.0