[PATCH v3 5/5] ceph: narrow mdsc->mutex scope in replay_unsafe_requests

From: Xiubo Li via B4 Relay

Date: Tue Aug 11 2026 - 01:20:03 EST


From: Xiubo Li <xiubo.li@xxxxxxxxx>

Currently replay_unsafe_requests() holds mdsc->mutex across the
entire function, including the __send_request() calls. Since
__send_request() is lockless and the async cap-release helper
schedules deferred work, neither needs the mutex.

Collect the unsafe-list entries and the matching old xarray
entries into local lists under mdsc->mutex, taking a reference on
each, then replay them outside the mutex. Taking a reference
ensures a concurrent reply handler can complete and unregister a
request without invalidating the local list or the iterator.

Signed-off-by: Xiubo Li <xiubo.li@xxxxxxxxx>
---
fs/ceph/mds_client.c | 34 ++++++++++++++++++++++++++++++----
1 file changed, 30 insertions(+), 4 deletions(-)

diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
index 76dfd2c86392..d8eb9154ae26 100644
--- a/fs/ceph/mds_client.c
+++ b/fs/ceph/mds_client.c
@@ -4702,12 +4702,23 @@ static void replay_unsafe_requests(struct ceph_mds_client *mdsc,
{
struct ceph_mds_request *req, *nreq;
unsigned long idx;
+ LIST_HEAD(unsafe_list);
+ LIST_HEAD(old_list);

doutc(mdsc->fsc->client, "mds%d\n", session->s_mds);

+ /*
+ * Collect unsafe and old requests under mdsc->mutex, then
+ * replay them without it: __send_request() is lockless and
+ * ceph_mdsc_release_dir_caps_async() schedules work.
+ */
mutex_lock(&mdsc->mutex);
- list_for_each_entry_safe(req, nreq, &session->s_unsafe, r_unsafe_item)
- __send_request(session, req, true);
+
+ list_for_each_entry_safe(req, nreq, &session->s_unsafe,
+ r_unsafe_item) {
+ ceph_mdsc_get_request(req);
+ list_move(&req->r_unsafe_item, &unsafe_list);
+ }

/*
* also re-send old requests when MDS enters reconnect stage. So that MDS
@@ -4724,11 +4735,26 @@ static void replay_unsafe_requests(struct ceph_mds_client *mdsc,
if (req->r_session->s_mds != session->s_mds)
continue;

- ceph_mdsc_release_dir_caps_async(req);
+ ceph_mdsc_get_request(req);
+ list_add_tail(&req->r_unsafe_item, &old_list);
+ }

+ mutex_unlock(&mdsc->mutex);
+
+ /* replay unsafe requests */
+ list_for_each_entry_safe(req, nreq, &unsafe_list, r_unsafe_item) {
__send_request(session, req, true);
+ list_del_init(&req->r_unsafe_item);
+ ceph_mdsc_put_request(req);
+ }
+
+ /* replay old requests */
+ list_for_each_entry_safe(req, nreq, &old_list, r_unsafe_item) {
+ ceph_mdsc_release_dir_caps_async(req);
+ __send_request(session, req, true);
+ list_del_init(&req->r_unsafe_item);
+ ceph_mdsc_put_request(req);
}
- mutex_unlock(&mdsc->mutex);
}

static int send_reconnect_partial(struct ceph_reconnect_state *recon_state)

--
2.53.0