Re: [PATCH 2/5] ceph: replace the request_tree rbtree with an xarray keyed by r_tid.

Viacheslav Dubeyko <[email protected]> Tue, 14 Jul 2026 11:35:50 -0700
Newsgroups org.kernel.vger.ceph-devel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
On Mon, 2026-07-13 at 17:46 +0800, Xiubo Li via B4 Relay wrote:
> From: Xiubo Li <[email protected]>
> 
> The xarray gives O(1) lookups by tid (vs O(log N) rbtree) and
> uses internal RCU-based locking, eliminating mdsc->mutex as a
> precondition for tree access and paving the way for lockless
> xa_load() in the future. Iteration is also cleaner: xa_for_each()
> and xa_find() replace the open-coded rb_first()/rb_next() walks.
> 
> Signed-off-by: Xiubo Li <[email protected]>
> ---
>  fs/ceph/debugfs.c    |   6 +--
>  fs/ceph/mds_client.c | 109 ++++++++++++++++++++++-------------------
> ----------
>  fs/ceph/mds_client.h |   3 +-
>  3 files changed, 50 insertions(+), 68 deletions(-)
> 
> diff --git a/fs/ceph/debugfs.c b/fs/ceph/debugfs.c
> index 18eb5da03411..491ead3fe1c6 100644
> --- a/fs/ceph/debugfs.c
> +++ b/fs/ceph/debugfs.c
> @@ -87,12 +87,12 @@ static int mdsc_show(struct seq_file *s, void *p)
>  	struct ceph_fs_client *fsc = s->private;
>  	struct ceph_mds_client *mdsc = fsc->mdsc;
>  	struct ceph_mds_request *req;
> -	struct rb_node *rp;
> +	unsigned long idx;
>  	char *path;
>  
>  	mutex_lock(&mdsc->mutex);
> -	for (rp = rb_first(&mdsc->request_tree); rp; rp =
> rb_next(rp)) {
> -		req = rb_entry(rp, struct ceph_mds_request, r_node);
> +	idx = 0;
> +	xa_for_each(&mdsc->request_tree, idx, req) {
>  
>  		if (req->r_request && req->r_session)
>  			seq_printf(s, "%lld\tmds%d\t", req->r_tid,
> diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
> index 98d0a5baff70..909f909b64ed 100644
> --- a/fs/ceph/mds_client.c
> +++ b/fs/ceph/mds_client.c
> @@ -1183,7 +1183,6 @@ void ceph_mdsc_release_request(struct kref
> *kref)
>  	kmem_cache_free(ceph_mds_request_cachep, req);
>  }
>  
> -DEFINE_RB_FUNCS(request, struct ceph_mds_request, r_tid, r_node)
>  
>  /*
>   * lookup session, bump ref if found.
> @@ -1195,7 +1194,7 @@ lookup_get_request(struct ceph_mds_client
> *mdsc, u64 tid)
>  {
>  	struct ceph_mds_request *req;
>  
> -	req = lookup_request(&mdsc->request_tree, tid);
> +	req = xa_load(&mdsc->request_tree, tid);
>  	if (req)
>  		ceph_mdsc_get_request(req);
>  
> @@ -1229,7 +1228,7 @@ static void __register_request(struct
> ceph_mds_client *mdsc,
>  	}
>  	doutc(cl, "%p tid %lld\n", req, req->r_tid);
>  	ceph_mdsc_get_request(req);
> -	insert_request(&mdsc->request_tree, req);
> +	xa_store(&mdsc->request_tree, req->r_tid, req, GFP_NOFS);

Should we check the return value of xa_store()? What about this?

        err = xa_err(xa_store(&mdsc->request_tree, req->r_tid, req,
GFP_NOFS));
        if (err) {
                ceph_mdsc_put_request(req);
                return err;
        } 

>  
>  	req->r_cred = get_current_cred();
>  	if (!req->r_mnt_idmap)
> @@ -1259,20 +1258,20 @@ static void __unregister_request(struct
> ceph_mds_client *mdsc,
>  	list_del_init(&req->r_unsafe_item);
>  
>  	if (req->r_tid == atomic64_read(&mdsc->oldest_tid)) {
> -		struct rb_node *p = rb_next(&req->r_node);
> +		unsigned long tidx = req->r_tid + 1;
> +		struct ceph_mds_request *next_req;
> +
>  		atomic64_set(&mdsc->oldest_tid, 0);
> -		while (p) {
> -			struct ceph_mds_request *next_req =
> -				rb_entry(p, struct ceph_mds_request,
> r_node);
> +		xa_for_each_start(&mdsc->request_tree, tidx,
> next_req, tidx) {
>  			if (next_req->r_op !=
> CEPH_MDS_OP_SETFILELOCK) {
> -				atomic64_set(&mdsc->oldest_tid,
> next_req->r_tid);
> +				atomic64_set(&mdsc->oldest_tid,
> +					     next_req->r_tid);
>  				break;
>  			}
> -			p = rb_next(p);
>  		}
>  	}
>  
> -	erase_request(&mdsc->request_tree, req);
> +	xa_erase(&mdsc->request_tree, req->r_tid);
>  
>  	if (req->r_unsafe_dir) {
>  		struct ceph_inode_info *ci = ceph_inode(req-
> >r_unsafe_dir);
> @@ -1829,7 +1828,7 @@ static void cleanup_session_requests(struct
> ceph_mds_client *mdsc,
>  {
>  	struct ceph_client *cl = mdsc->fsc->client;
>  	struct ceph_mds_request *req;
> -	struct rb_node *p;
> +	unsigned long idx;
>  
>  	doutc(cl, "mds%d\n", session->s_mds);
>  	mutex_lock(&mdsc->mutex);
> @@ -1845,10 +1844,8 @@ static void cleanup_session_requests(struct
> ceph_mds_client *mdsc,
>  		__unregister_request(mdsc, req);
>  	}
>  	/* zero r_attempts, so kick_requests() will re-send requests
> */
> -	p = rb_first(&mdsc->request_tree);
> -	while (p) {
> -		req = rb_entry(p, struct ceph_mds_request, r_node);
> -		p = rb_next(p);
> +	idx = 0;
> +	xa_for_each(&mdsc->request_tree, idx, req) {
>  		if (req->r_session &&
>  		    req->r_session->s_mds == session->s_mds)
>  			req->r_attempts = 0;
> @@ -2732,7 +2729,6 @@ ceph_mdsc_create_request(struct ceph_mds_client
> *mdsc, int op, int mode)
>  	req->r_fmode = -1;
>  	req->r_feature_needed = -1;
>  	kref_init(&req->r_kref);
> -	RB_CLEAR_NODE(&req->r_node);
>  	INIT_LIST_HEAD(&req->r_wait);
>  	init_completion(&req->r_completion);
>  	init_completion(&req->r_safe_completion);
> @@ -2752,10 +2748,9 @@ ceph_mdsc_create_request(struct
> ceph_mds_client *mdsc, int op, int mode)
>   */
>  static struct ceph_mds_request *__get_oldest_req(struct
> ceph_mds_client *mdsc)
>  {
> -	if (RB_EMPTY_ROOT(&mdsc->request_tree))
> -		return NULL;
> -	return rb_entry(rb_first(&mdsc->request_tree),
> -			struct ceph_mds_request, r_node);
> +	unsigned long idx = 0;
> +
> +	return xa_find(&mdsc->request_tree, &idx, ULONG_MAX,
> XA_PRESENT);
>  }
>  
>  static inline  u64 __get_oldest_tid(struct ceph_mds_client *mdsc)
> @@ -3816,12 +3811,11 @@ static void kick_requests(struct
> ceph_mds_client *mdsc, int mds)
>  {
>  	struct ceph_client *cl = mdsc->fsc->client;
>  	struct ceph_mds_request *req;
> -	struct rb_node *p = rb_first(&mdsc->request_tree);
> +	unsigned long idx;
>  
>  	doutc(cl, "kick_requests mds%d\n", mds);
> -	while (p) {
> -		req = rb_entry(p, struct ceph_mds_request, r_node);
> -		p = rb_next(p);
> +	idx = 0;
> +	xa_for_each(&mdsc->request_tree, idx, req) {
>  		if (test_bit(CEPH_MDS_R_GOT_UNSAFE, &req-
> >r_req_flags))
>  			continue;
>  		if (req->r_attempts > 0)
> @@ -4654,7 +4648,7 @@ static void replay_unsafe_requests(struct
> ceph_mds_client *mdsc,
>  				   struct ceph_mds_session *session)
>  {
>  	struct ceph_mds_request *req, *nreq;
> -	struct rb_node *p;
> +	unsigned long idx;
>  
>  	doutc(mdsc->fsc->client, "mds%d\n", session->s_mds);
>  
> @@ -4666,10 +4660,8 @@ static void replay_unsafe_requests(struct
> ceph_mds_client *mdsc,
>  	 * also re-send old requests when MDS enters reconnect
> stage. So that MDS
>  	 * can process completed request in clientreplay stage.
>  	 */
> -	p = rb_first(&mdsc->request_tree);
> -	while (p) {
> -		req = rb_entry(p, struct ceph_mds_request, r_node);
> -		p = rb_next(p);
> +	idx = 0;
> +	xa_for_each(&mdsc->request_tree, idx, req) {
>  		if (test_bit(CEPH_MDS_R_GOT_UNSAFE, &req-
> >r_req_flags))
>  			continue;
>  		if (req->r_attempts == 0)
> @@ -5514,7 +5506,7 @@ static void ceph_mdsc_reset_workfn(struct
> work_struct *work)
>  	 */
>  	{
>  		struct ceph_mds_request *req;
> -		struct rb_node *rn;
> +		unsigned long idx;
>  		u64 last_tid;
>  
>  		mutex_lock(&mdsc->mutex);
> @@ -5522,14 +5514,12 @@ static void ceph_mdsc_reset_workfn(struct
> work_struct *work)
>  		mutex_unlock(&mdsc->mutex);
>  
>  		mutex_lock(&mdsc->mutex);
> -		rn = rb_first(&mdsc->request_tree);
> -		while (rn) {
> -			req = rb_entry(rn, struct ceph_mds_request,
> r_node);
> -			if (req->r_tid > last_tid)
> -				break;
> +		idx = 0;
> +		while ((req = xa_find(&mdsc->request_tree, &idx,
> last_tid,
> +				      XA_PRESENT))) {
>  			if (req->r_op == CEPH_MDS_OP_SETFILELOCK ||
>  			    !(req->r_op & CEPH_MDS_OP_WRITE)) {
> -				rn = rb_next(rn);
> +				idx++;
>  				continue;
>  			}
>  			ceph_mdsc_get_request(req);
> @@ -5542,7 +5532,7 @@ static void ceph_mdsc_reset_workfn(struct
> work_struct *work)
>  			ceph_mdsc_put_request(req);
>  			if (time_after(jiffies, drain_deadline))
>  				break;
> -			rn = rb_first(&mdsc->request_tree);
> +			idx = 0;  /* restart: tree may have changed
> */
>  		}
>  		mutex_unlock(&mdsc->mutex);
>  
> @@ -6268,7 +6258,7 @@ int ceph_mdsc_init(struct ceph_fs_client *fsc)
>  	mdsc->snap_realms = RB_ROOT;
>  	INIT_LIST_HEAD(&mdsc->snap_empty);
>  	spin_lock_init(&mdsc->snap_empty_lock);
> -	mdsc->request_tree = RB_ROOT;
> +	xa_init(&mdsc->request_tree);
>  	INIT_DELAYED_WORK(&mdsc->delayed_work, delayed_work);
>  	mdsc->last_renew_caps = jiffies;
>  	INIT_LIST_HEAD(&mdsc->cap_delay_list);
> @@ -6590,34 +6580,33 @@ static void
> flush_mdlog_and_wait_mdsc_unsafe_requests(struct ceph_mds_client *md
>  						 u64 want_tid)
>  {
>  	struct ceph_client *cl = mdsc->fsc->client;
> -	struct ceph_mds_request *req = NULL, *nextreq;
> +	struct ceph_mds_request *req;
>  	struct ceph_mds_session *last_session = NULL;
> -	struct rb_node *n;
> +	unsigned long idx;
>  
>  	mutex_lock(&mdsc->mutex);
>  	doutc(cl, "want %lld\n", want_tid);
> -restart:
> -	req = __get_oldest_req(mdsc);
> -	while (req && req->r_tid <= want_tid) {
> -		/* find next request */
> -		n = rb_next(&req->r_node);
> -		if (n)
> -			nextreq = rb_entry(n, struct
> ceph_mds_request, r_node);
> -		else
> -			nextreq = NULL;
> -		if (req->r_op != CEPH_MDS_OP_SETFILELOCK &&
> -		    (req->r_op & CEPH_MDS_OP_WRITE)) {
> +	idx = 0;
> +	while ((req = xa_find(&mdsc->request_tree, &idx, want_tid,
> +			      XA_PRESENT))) {
> +		u64 next_tid = req->r_tid + 1;
> +
> +		if (req->r_op == CEPH_MDS_OP_SETFILELOCK ||
> +		    !(req->r_op & CEPH_MDS_OP_WRITE)) {
> +			idx = next_tid;
> +			continue;
> +		}
> +
> +		{
>  			struct ceph_mds_session *s = req->r_session;
>  
>  			if (!s) {
> -				req = nextreq;
> +				idx = next_tid;
>  				continue;
>  			}
>  
>  			/* write op */
>  			ceph_mdsc_get_request(req);
> -			if (nextreq)
> -				ceph_mdsc_get_request(nextreq);
>  			s = ceph_get_mds_session(s);
>  			mutex_unlock(&mdsc->mutex);
>  
> @@ -6635,16 +6624,9 @@ static void
> flush_mdlog_and_wait_mdsc_unsafe_requests(struct ceph_mds_client *md
>  
>  			mutex_lock(&mdsc->mutex);
>  			ceph_mdsc_put_request(req);
> -			if (!nextreq)
> -				break;  /* next dne before, so we're
> done! */
> -			if (RB_EMPTY_NODE(&nextreq->r_node)) {
> -				/* next request was removed from
> tree */
> -				ceph_mdsc_put_request(nextreq);
> -				goto restart;
> -			}
> -			ceph_mdsc_put_request(nextreq);  /* won't go
> away */
> +			/* restart from the next tid; tree may have
> changed */
> +			idx = next_tid;
>  		}
> -		req = nextreq;
>  	}
>  	mutex_unlock(&mdsc->mutex);
>  	ceph_put_mds_session(last_session);
> @@ -6803,6 +6785,7 @@ static void ceph_mdsc_stop(struct
> ceph_mds_client *mdsc)
>  	if (mdsc->mdsmap)
>  		ceph_mdsmap_destroy(mdsc->mdsmap);
>  	kfree(mdsc->sessions);
> +	xa_destroy(&mdsc->request_tree);
>  	ceph_caps_finalize(mdsc);
>  
>  	if (mdsc->s_cap_auths) {
> diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h
> index 3b614b5df18c..00ca993ed583 100644
> --- a/fs/ceph/mds_client.h
> +++ b/fs/ceph/mds_client.h
> @@ -329,7 +329,6 @@ typedef int (*ceph_mds_request_wait_callback_t)
> (struct ceph_mds_client *mdsc,
>   */
>  struct ceph_mds_request {
>  	u64 r_tid;                   /* transaction id */
> -	struct rb_node r_node;
>  	struct ceph_mds_client *r_mdsc;
>  
>  	struct kref       r_kref;
> @@ -534,7 +533,7 @@ struct ceph_mds_client {
>  	u64                    last_tid;      /* most recent mds
> request */
>  	atomic64_t             oldest_tid;    /* oldest incomplete
> mds request,
>  						 excluding
> setfilelock requests */
> -	struct rb_root         request_tree;  /* pending mds
> requests */
> +	struct xarray           request_tree;  /* pending mds
> requests */

Transaction IDs are u64; xarray indices are unsigned long. req->r_tid,
mdsc->last_tid, and want_tid/last_tid in the drain paths are all u64,
but xa_store(), xa_load(), xa_erase(), and xa_find()'s max parameter
all take unsigned long. CEPH_FS in fs/ceph/Kconfig only depends on INET
— it's not gated on 64BIT — so this driver still builds for 32-bit
targets, where unsigned long truncates r_tid to 32 bits. Are we safe
here?

Thanks,
Slava.

>  	struct delayed_work    delayed_work;  /* delayed work */
>  	unsigned long    last_renew_caps;  /* last time we renewed
> our caps */
>  	struct list_head cap_delay_list;   /* caps with delayed
> release */