[PATCH 13/21] pNFS: Discard a GETDEVICEINFO reply that raced a CHANGE notification
Benjamin Coddington <ben.coddington-F/[email protected]>
| Newsgroups | gmane.linux.nfs |
|---|---|
| Message-ID | <c5fb003dffc63416370fbfa25b98e4226acf13b2.1786653063.git.bcodding@hammerspace.com> |
RFC 8881 Section 18.40.4: a GETDEVICEINFO reply in flight while the server changes the device mapping may carry the pre-change mapping; if it is inserted into the cache after the CHANGE notification unhashed the stale entry, the client re-caches stale data. Track a change epoch, bumped when a CHANGE notification is processed before the stale entry is unhashed. nfs4_find_get_deviceid() snapshots the epoch before issuing GETDEVICEINFO and, serialized against the unhash by nfs4_deviceid_lock at insert time, discards the reply and refetches if the epoch moved. A stale insert that instead precedes the unhash is removed by the unhash itself, so both orderings end with the post-change mapping. Assisted-by: Claude:claude-fable-5 Signed-off-by: Benjamin Coddington <bcodding-F/[email protected]> --- fs/nfs/callback_proc.c | 4 ++++ fs/nfs/pnfs.h | 1 + fs/nfs/pnfs_dev.c | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c index 64c994790d3f..1721c6dd2250 100644 --- a/fs/nfs/callback_proc.c +++ b/fs/nfs/callback_proc.c @@ -394,7 +394,11 @@ __be32 nfs4_callback_devicenotify(void *argp, void *resp, * Unhash the cached device first so re-resolution cannot * re-pin the stale node, then re-point any references * pinned under live layouts (RFC 8881 Section 12.2.10). + * The epoch bump lets an in-flight GETDEVICEINFO detect + * that its reply may predate the change. */ + if (dev->cbd_notify_type == NOTIFY_DEVICEID4_CHANGE) + nfs4_deviceid_bump_change_epoch(); nfs4_delete_deviceid(ld, cps->clp, &dev->cbd_dev_id); if (dev->cbd_notify_type == NOTIFY_DEVICEID4_CHANGE) pnfs_layout_reresolve_deviceid_byclid(cps->clp, ld, diff --git a/fs/nfs/pnfs.h b/fs/nfs/pnfs.h index 9ec0ffebe3c4..f3c55c86c256 100644 --- a/fs/nfs/pnfs.h +++ b/fs/nfs/pnfs.h @@ -399,6 +399,7 @@ nfs4_find_get_deviceid(struct nfs_server *server, const struct nfs4_deviceid *id, const struct cred *cred, gfp_t gfp_mask); void nfs4_delete_deviceid(const struct pnfs_layoutdriver_type *, const struct nfs_client *, const struct nfs4_deviceid *); +void nfs4_deviceid_bump_change_epoch(void); void nfs4_init_deviceid_node(struct nfs4_deviceid_node *, struct nfs_server *, const struct nfs4_deviceid *); bool nfs4_put_deviceid_node(struct nfs4_deviceid_node *); diff --git a/fs/nfs/pnfs_dev.c b/fs/nfs/pnfs_dev.c index 274abdd6d5f3..0c39ae8325b3 100644 --- a/fs/nfs/pnfs_dev.c +++ b/fs/nfs/pnfs_dev.c @@ -181,6 +181,22 @@ __nfs4_find_get_deviceid(struct nfs_server *server, return d; } +/* + * Bumped when a CB_NOTIFY_DEVICEID CHANGE is processed. A GETDEVICEINFO + * reply fetched across a change may carry the pre-change mapping; the + * epoch lets nfs4_find_get_deviceid() detect that and refetch. The + * counter is ordered by nfs4_deviceid_lock: the notification handler + * bumps it before unhashing the stale entry under the lock, so an + * insert serialized after the unhash observes the new epoch. + */ +static atomic_t nfs4_deviceid_change_epoch = ATOMIC_INIT(0); + +void +nfs4_deviceid_bump_change_epoch(void) +{ + atomic_inc(&nfs4_deviceid_change_epoch); +} + struct nfs4_deviceid_node * nfs4_find_get_deviceid(struct nfs_server *server, const struct nfs4_deviceid *id, const struct cred *cred, @@ -188,11 +204,14 @@ nfs4_find_get_deviceid(struct nfs_server *server, { long hash = nfs4_deviceid_hash(id); struct nfs4_deviceid_node *d, *new; + int epoch; +retry: d = __nfs4_find_get_deviceid(server, id, hash); if (d) goto found; + epoch = atomic_read(&nfs4_deviceid_change_epoch); new = nfs4_get_device_info(server, id, cred, gfp_mask); if (!new) { trace_nfs4_find_deviceid(server, id, -ENOENT); @@ -200,6 +219,12 @@ nfs4_find_get_deviceid(struct nfs_server *server, } spin_lock(&nfs4_deviceid_lock); + if (atomic_read(&nfs4_deviceid_change_epoch) != epoch) { + /* a mapping changed while we fetched; ours may be stale */ + spin_unlock(&nfs4_deviceid_lock); + server->pnfs_curr_ld->free_deviceid_node(new); + goto retry; + } d = __nfs4_find_get_deviceid(server, id, hash); if (d) { spin_unlock(&nfs4_deviceid_lock); -- 2.53.0