NFSv4/pnfs: Re-home the data-server cache onto hash buckets

The per-net data-server cache is a single list, and every
GETDEVICEINFO decode walks all of it looking for a match, so filling
the cache costs O(n^2) in the number of data servers -- which a
striping mount does in one burst, at the same scale the deviceid
cache was just sized for.  Key it by the DS address set instead.

This patch is the mechanical half: nfs4_pnfs_ds.ds_node becomes an
hlist_node, netns init and teardown cover every bucket, and removal
uses hlist_del_init (which needs no bucket reference).  Insertion
still targets bucket 0 and lookup still scans every entry, so
behavior is unchanged; the key comes next.  Splitting it this way
keeps a bisect able to tell a list-conversion bug from a hash-key
bug.

The buckets live in struct nfs_net, so this costs 2KB per network
namespace on 64-bit, paid once nfs.ko is loaded whether or not that
namespace ever mounts NFS.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
Signed-off-by: Anna Schumaker <anna.schumaker@hammerspace.com>
This commit is contained in:
Benjamin Coddington 2026-09-15 08:22:24 -04:00 committed by Anna Schumaker
parent 19932b33af
commit 630295dfc6
4 changed files with 20 additions and 12 deletions

View File

@ -1295,7 +1295,8 @@ void nfs_clients_init(struct net *net)
INIT_LIST_HEAD(&nn->nfs_volume_list);
#if IS_ENABLED(CONFIG_NFS_V4)
idr_init(&nn->cb_ident_idr);
INIT_LIST_HEAD(&nn->nfs4_data_server_cache);
for (int i = 0; i < NFS4_DS_CACHE_HASH_SIZE; i++)
INIT_HLIST_HEAD(&nn->nfs4_data_server_cache[i]);
spin_lock_init(&nn->nfs4_data_server_lock);
#endif /* CONFIG_NFS_V4 */
spin_lock_init(&nn->nfs_client_lock);
@ -1315,7 +1316,8 @@ void nfs_clients_exit(struct net *net)
WARN_ON_ONCE(!list_empty(&nn->nfs_client_list));
WARN_ON_ONCE(!list_empty(&nn->nfs_volume_list));
#if IS_ENABLED(CONFIG_NFS_V4)
WARN_ON_ONCE(!list_empty(&nn->nfs4_data_server_cache));
for (int i = 0; i < NFS4_DS_CACHE_HASH_SIZE; i++)
WARN_ON_ONCE(!hlist_empty(&nn->nfs4_data_server_cache[i]));
#endif /* CONFIG_NFS_V4 */
}

View File

@ -31,7 +31,10 @@ struct nfs_net {
unsigned short nfs_callback_tcpport;
unsigned short nfs_callback_tcpport6;
int cb_users[NFS4_MAX_MINOR_VERSION + 1];
struct list_head nfs4_data_server_cache;
#define NFS4_DS_CACHE_HASH_BITS 8
#define NFS4_DS_CACHE_HASH_SIZE (1 << NFS4_DS_CACHE_HASH_BITS)
/* every entry is still in bucket 0 until the key is added */
struct hlist_head nfs4_data_server_cache[NFS4_DS_CACHE_HASH_SIZE];
spinlock_t nfs4_data_server_lock;
#endif /* CONFIG_NFS_V4 */
struct nfs_netns_client *nfs_client;

View File

@ -57,7 +57,7 @@ struct nfs4_pnfs_ds_addr {
};
struct nfs4_pnfs_ds {
struct list_head ds_node; /* nfs4_pnfs_dev_hlist dev_dslist */
struct hlist_node ds_node; /* nfs_net nfs4_data_server_cache */
char *ds_remotestr; /* comma sep list of addrs */
struct list_head ds_addrs;
const struct net *ds_net;

View File

@ -604,7 +604,7 @@ _same_data_server_addrs_locked(const struct list_head *dsaddrs1,
}
/*
* Lookup DS by addresses and NFS version. nfs4_ds_cache_lock is held
* Lookup DS by addresses and NFS version. nfs4_data_server_lock is held
*/
static struct nfs4_pnfs_ds *
_data_server_lookup_locked(const struct nfs_net *nn,
@ -612,10 +612,13 @@ _data_server_lookup_locked(const struct nfs_net *nn,
{
struct nfs4_pnfs_ds *ds;
list_for_each_entry(ds, &nn->nfs4_data_server_cache, ds_node)
if (ds->ds_version == version &&
_same_data_server_addrs_locked(&ds->ds_addrs, dsaddrs))
return ds;
for (int i = 0; i < NFS4_DS_CACHE_HASH_SIZE; i++)
hlist_for_each_entry(ds, &nn->nfs4_data_server_cache[i],
ds_node)
if (ds->ds_version == version &&
_same_data_server_addrs_locked(&ds->ds_addrs,
dsaddrs))
return ds;
return NULL;
}
@ -666,7 +669,7 @@ void nfs4_pnfs_ds_put(struct nfs4_pnfs_ds *ds)
struct nfs_net *nn = net_generic(ds->ds_net, nfs_net_id);
if (refcount_dec_and_lock(&ds->ds_count, &nn->nfs4_data_server_lock)) {
list_del_init(&ds->ds_node);
hlist_del_init(&ds->ds_node);
spin_unlock(&nn->nfs4_data_server_lock);
destroy_ds(ds);
}
@ -753,11 +756,11 @@ nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, u32 version,
list_splice_init(dsaddrs, &ds->ds_addrs);
ds->ds_remotestr = remotestr;
refcount_set(&ds->ds_count, 1);
INIT_LIST_HEAD(&ds->ds_node);
INIT_HLIST_NODE(&ds->ds_node);
ds->ds_net = net;
ds->ds_clp = NULL;
ds->ds_version = version;
list_add(&ds->ds_node, &nn->nfs4_data_server_cache);
hlist_add_head(&ds->ds_node, &nn->nfs4_data_server_cache[0]);
dprintk("%s add new data server %s\n", __func__,
ds->ds_remotestr);
} else {