From: Benjamin Coddington <ben.coddington@hammerspace.com>
To: Trond Myklebust <trondmy@kernel.org>, Anna Schumaker <anna@kernel.org>
Cc: linux-nfs@vger.kernel.org,
Jonathan Curley <jcurley@purestorage.com>,
Mike Snitzer <snitzer@kernel.org>,
Jeff Layton <jlayton@kernel.org>,
Junrui Luo <moonafterrain@outlook.com>
Subject: [PATCH v4 23/24] NFSv4/pnfs: Key the data-server cache by its address set and version
Date: Tue, 15 Sep 2026 08:22:25 -0400 [thread overview]
Message-ID: <e97aee2e9ea53df9d7ae784033597d20b7a5eb69.1789474702.git.bcodding@hammerspace.com> (raw)
In-Reply-To: <cover.1789474702.git.bcodding@hammerspace.com>
Populating the data-server cache was quadratic: every GETDEVICEINFO
decode scanned the whole per-net cache under one spinlock. At the
anticipated scale of 1024 data servers a striping mount pays that in a
burst at first access, and again on notification-driven re-resolution.
Hash each DS to a bucket keyed on the whole cache key -- per-address
jhash over exactly the fields same_sockaddr() compares, combined by
addition so multipath ordering cannot change the bucket, then the NFS
version folded in -- with the existing comparator as the in-bucket
tiebreaker. Lookup and insert now touch one bucket; teardown is
unchanged (hlist_del_init needs no bucket).
Keying on the whole set requires the comparator to test set equality,
so it is tightened from the subset test it did before -- a subset
match would hash to a different bucket and simply never be found.
That test was also wrong in a way worth naming. It answered "is
dsaddrs1 a subset of dsaddrs2", and the caller passes the cached list
first, so a cached data server whose address set was contained in an
incoming one was returned for that incoming set. The aliasing was
therefore one-directional: cache {A,B} first and an incoming {A} did
not match, but cache {A} first and an incoming {A,B} did.
The consequence was mild, which is why it went unnoticed: every
address on one device's multipath list names the same data server, so
a merged entry's addresses are all paths that server also advertised.
The effect is lost path diversity and a truncated ds_remotestr (and
the netaddr flexfiles reports in layoutstats), not I/O sent to the
wrong server.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
fs/nfs/pnfs_nfs.c | 68 ++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 58 insertions(+), 10 deletions(-)
diff --git a/fs/nfs/pnfs_nfs.c b/fs/nfs/pnfs_nfs.c
index f88a9784988a..c9bbcb765f4a 100644
--- a/fs/nfs/pnfs_nfs.c
+++ b/fs/nfs/pnfs_nfs.c
@@ -15,6 +15,8 @@
#include "nfs4session.h"
#include "internal.h"
+#include <linux/hash.h>
+#include <linux/jhash.h>
#include "pnfs.h"
#include "netns.h"
#include "nfs4trace.h"
@@ -577,8 +579,8 @@ same_sockaddr(struct sockaddr *addr1, struct sockaddr *addr2)
}
/*
- * Checks if 'dsaddrs1' contains a subset of 'dsaddrs2'. If it does,
- * declare a match.
+ * Checks if 'dsaddrs1' and 'dsaddrs2' hold the same set of addresses.
+ * If they do, declare a match.
*/
static bool
_same_data_server_addrs_locked(const struct list_head *dsaddrs1,
@@ -588,6 +590,10 @@ _same_data_server_addrs_locked(const struct list_head *dsaddrs1,
struct sockaddr *sa1, *sa2;
bool match = false;
+ if (list_count_nodes((struct list_head *)dsaddrs1) !=
+ list_count_nodes((struct list_head *)dsaddrs2))
+ return false;
+
list_for_each_entry(da1, dsaddrs1, da_node) {
sa1 = (struct sockaddr *)&da1->da_addr;
match = false;
@@ -603,6 +609,47 @@ _same_data_server_addrs_locked(const struct list_head *dsaddrs1,
return match;
}
+/* Hash family, address bytes, and port - as same_sockaddr() */
+static u32
+nfs4_ds_addr_hash(const struct sockaddr *sa)
+{
+ u32 h = sa->sa_family;
+
+ switch (sa->sa_family) {
+ case AF_INET: {
+ const struct sockaddr_in *a = (const struct sockaddr_in *)sa;
+
+ h = jhash(&a->sin_addr.s_addr, sizeof(a->sin_addr.s_addr), h);
+ h = jhash(&a->sin_port, sizeof(a->sin_port), h);
+ break;
+ }
+ case AF_INET6: {
+ const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)sa;
+
+ h = jhash(&a->sin6_addr, sizeof(a->sin6_addr), h);
+ h = jhash(&a->sin6_port, sizeof(a->sin6_port), h);
+ break;
+ }
+ }
+ return h;
+}
+
+/*
+ * Bucket index for a DS cache key. Per-address hashes combine by
+ * addition so the multipath list order cannot change the bucket,
+ * matching the order-independent set comparison above.
+ */
+static u32
+nfs4_ds_cache_hash(const struct list_head *dsaddrs, u32 version)
+{
+ const struct nfs4_pnfs_ds_addr *da;
+ u32 h = 0;
+
+ list_for_each_entry(da, dsaddrs, da_node)
+ h += nfs4_ds_addr_hash((const struct sockaddr *)&da->da_addr);
+ return hash_32(jhash_1word(version, h), NFS4_DS_CACHE_HASH_BITS);
+}
+
/*
* Lookup DS by addresses and NFS version. nfs4_data_server_lock is held
*/
@@ -611,14 +658,12 @@ _data_server_lookup_locked(const struct nfs_net *nn,
const struct list_head *dsaddrs, u32 version)
{
struct nfs4_pnfs_ds *ds;
+ u32 bucket = nfs4_ds_cache_hash(dsaddrs, version);
- 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;
+ hlist_for_each_entry(ds, &nn->nfs4_data_server_cache[bucket], ds_node)
+ if (ds->ds_version == version &&
+ _same_data_server_addrs_locked(&ds->ds_addrs, dsaddrs))
+ return ds;
return NULL;
}
@@ -735,6 +780,7 @@ nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, u32 version,
{
struct nfs_net *nn = net_generic(net, nfs_net_id);
struct nfs4_pnfs_ds *tmp_ds, *ds = NULL;
+ struct hlist_head *bucket;
char *remotestr;
if (list_empty(dsaddrs)) {
@@ -748,6 +794,8 @@ nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, u32 version,
/* this is only used for debugging, so it's ok if its NULL */
remotestr = nfs4_pnfs_remotestr(dsaddrs, gfp_flags);
+ /* @dsaddrs is empty after the splice below. */
+ bucket = &nn->nfs4_data_server_cache[nfs4_ds_cache_hash(dsaddrs, version)];
spin_lock(&nn->nfs4_data_server_lock);
tmp_ds = _data_server_lookup_locked(nn, dsaddrs, version);
@@ -760,7 +808,7 @@ nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, u32 version,
ds->ds_net = net;
ds->ds_clp = NULL;
ds->ds_version = version;
- hlist_add_head(&ds->ds_node, &nn->nfs4_data_server_cache[0]);
+ hlist_add_head(&ds->ds_node, bucket);
dprintk("%s add new data server %s\n", __func__,
ds->ds_remotestr);
} else {
--
2.53.0
next prev parent reply other threads:[~2026-09-15 12:22 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-15 12:22 [PATCH v4 00/24] NFS: flexfiles device notifications and caching for wide striped layouts Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 01/24] NFSv4/pnfs: Free the netid when draining a data-server address list Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 02/24] NFSv4/flexfiles: Use the full 64-bit stripe_unit Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 03/24] NFSv4/pnfs: bound the CB_NOTIFY_DEVICEID array count before allocating Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 04/24] pNFS: Fix CB_NOTIFY_DEVICEID CHANGE to consume ndc_immediate Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 05/24] NFSv4/flexfiles: Use the full 64-bit offset for read DS selection Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 06/24] NFSv4/flexfiles: Bound page coalescing on the absolute stripe offset Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 07/24] NFSv4/filelayout: Anchor page coalescing on pattern_offset Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 08/24] NFSv4/flexfiles: Reference the device node across DS setup Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 09/24] NFSv4/flexfiles: Carry the device node reference across each I/O Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 10/24] NFSv4/flexfiles: Hold a device node reference for layoutstats encoding Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 11/24] NFSv4/flexfiles: Make the pinned device node pointer RCU-managed Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 12/24] pNFS: Add a reresolve_deviceid layout driver hook Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 13/24] NFSv4/flexfiles: Implement in-place device re-resolve on CHANGE Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 14/24] NFSv4: Dispatch CB_NOTIFY_DEVICEID CHANGE to an in-place refresh Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 15/24] NFSv4/flexfiles: Honor ndc_immediate on CB_NOTIFY_DEVICEID CHANGE Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 16/24] pNFS: Discard a GETDEVICEINFO reply that raced a CHANGE notification Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 17/24] pNFS: Add deviceid reference query and collection walkers Benjamin Coddington
2026-09-15 17:29 ` Anna Schumaker
2026-09-15 12:22 ` [PATCH v4 18/24] NFSv4/pnfs: Recover revoked layouts on a deleted deviceID Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 19/24] NFSv4/pnfs: Confirm a deviceID delete via GETDEVICEINFO Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 20/24] NFSv4/pnfs: Dispatch CB_NOTIFY_DEVICEID DELETE to race recovery Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 21/24] NFSv4/pnfs: Grow the deviceid cache hash table Benjamin Coddington
2026-09-15 12:22 ` [PATCH v4 22/24] NFSv4/pnfs: Re-home the data-server cache onto hash buckets Benjamin Coddington
2026-09-15 12:22 ` Benjamin Coddington [this message]
2026-09-15 12:22 ` [PATCH v4 24/24] NFSv4/flexfiles: Add a dataserver_nconnect cap Benjamin Coddington
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=e97aee2e9ea53df9d7ae784033597d20b7a5eb69.1789474702.git.bcodding@hammerspace.com \
--to=ben.coddington@hammerspace.com \
--cc=anna@kernel.org \
--cc=jcurley@purestorage.com \
--cc=jlayton@kernel.org \
--cc=linux-nfs@vger.kernel.org \
--cc=moonafterrain@outlook.com \
--cc=snitzer@kernel.org \
--cc=trondmy@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox