Re: [PATCH 1/5] ceph: convert oldest_tid to atomic64_t

Xiubo Li <[email protected]> Thu, 16 Jul 2026 12:04:02 +0800
Newsgroups org.kernel.vger.ceph-devel,org.kernel.vger.linux-kernel
Message-ID <CAOJNxRJzwSE5ocET1FRqhJ1nZA+5WtuFhDAkVSmu079x8PCXSw@mail.gmail.com>
On Wed, 15 Jul 2026 at 17:55, David Laight <[email protected]> wrote:
>
> On Mon, 13 Jul 2026 17:46:07 +0800
> Xiubo Li via B4 Relay <[email protected]> wrote:
>
> > From: Xiubo Li <[email protected]>
> >
> > The oldest_client_tid sent in the MDS request header is advisory:
> > a stale value is harmless -- at worst the MDS may resend a reply
> > we already have, or skip one we still need (which will just be
> > retried).  With the plain u64 read, however, the compiler is free
> > to split or cache the load, which is undefined behaviour when the
> > write side runs under mdsc->mutex on a different CPU.
> >
> > Convert mdsc->oldest_tid from u64 to atomic64_t so that reads
> > are guaranteed to be single-copy atomic on all architectures.
> > This removes the one remaining reason __prepare_send_request()
> > and __send_request() needed to be called under mdsc->mutex, so
> > drop those comments as well.
> >
> > All write sites (__register_request, __unregister_request) still
> > run under mdsc->mutex, so use atomic64_set() for clarity.
>
> If the read side doesn't hold the lock then it makes little difference
> whether the write side holds it.
> It the write is split (eg on 32bit) then even an atomic read could
> get an invalid value.
>
> On 64bit this change probably has no effect.
> But some 32bit architectures cannot do atomic 64bit read/write so
> they are expensive operations.
> x86-32 now requires the 64bit 'compare exchange' that can be used
> for 64bit load/store so they are only moderately expensive not
> stupidly expensive.
>

Agreed — atomic64_read is unnecessarily expensive on 32-bit for
an advisory value, and on 64-bit a plain u64 load/store is already
single-instruction atomic.  Let me dropped the atomic64_t conversion
in v3.

The read side will use READ_ONCE() and the write side WRITE_ONCE(),
which prevents the compiler from splitting or caching the access.
The value is monotonic and advisory: on 32-bit a torn read is
harmless because at worst the MDS trims its completed_requests
table slightly earlier or later than optimal, which the replay
journal rectifies.

Thanks
Xiubo

>         David
>
> >
> > Signed-off-by: Xiubo Li <[email protected]>
> > ---
> >  fs/ceph/mds_client.c | 21 ++++++++-------------
> >  fs/ceph/mds_client.h |  2 +-
> >  2 files changed, 9 insertions(+), 14 deletions(-)
> >
> > diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
> > index 9f84ef2ac6e4..98d0a5baff70 100644
> > --- a/fs/ceph/mds_client.c
> > +++ b/fs/ceph/mds_client.c
> > @@ -1235,8 +1235,9 @@ static void __register_request(struct ceph_mds_client *mdsc,
> >       if (!req->r_mnt_idmap)
> >               req->r_mnt_idmap = &nop_mnt_idmap;
> >
> > -     if (mdsc->oldest_tid == 0 && req->r_op != CEPH_MDS_OP_SETFILELOCK)
> > -             mdsc->oldest_tid = req->r_tid;
> > +     if (atomic64_read(&mdsc->oldest_tid) == 0 &&
> > +         req->r_op != CEPH_MDS_OP_SETFILELOCK)
> > +             atomic64_set(&mdsc->oldest_tid, req->r_tid);
> >
> >       if (dir) {
> >               struct ceph_inode_info *ci = ceph_inode(dir);
> > @@ -1257,14 +1258,14 @@ static void __unregister_request(struct ceph_mds_client *mdsc,
> >       /* Never leave an unregistered request on an unsafe list! */
> >       list_del_init(&req->r_unsafe_item);
> >
> > -     if (req->r_tid == mdsc->oldest_tid) {
> > +     if (req->r_tid == atomic64_read(&mdsc->oldest_tid)) {
> >               struct rb_node *p = rb_next(&req->r_node);
> > -             mdsc->oldest_tid = 0;
> > +             atomic64_set(&mdsc->oldest_tid, 0);
> >               while (p) {
> >                       struct ceph_mds_request *next_req =
> >                               rb_entry(p, struct ceph_mds_request, r_node);
> >                       if (next_req->r_op != CEPH_MDS_OP_SETFILELOCK) {
> > -                             mdsc->oldest_tid = next_req->r_tid;
> > +                             atomic64_set(&mdsc->oldest_tid, next_req->r_tid);
> >                               break;
> >                       }
> >                       p = rb_next(p);
> > @@ -1693,7 +1694,7 @@ create_session_full_msg(struct ceph_mds_client *mdsc, int op, u64 seq)
> >       ceph_encode_32(&p, 0);
> >
> >       /* version == 7, oldest_client_tid */
> > -     ceph_encode_64(&p, mdsc->oldest_tid);
> > +     ceph_encode_64(&p, atomic64_read(&mdsc->oldest_tid));
> >
> >       msg->front.iov_len = p - msg->front.iov_base;
> >       msg->hdr.front_len = cpu_to_le32(msg->front.iov_len);
> > @@ -2759,7 +2760,7 @@ static struct ceph_mds_request *__get_oldest_req(struct ceph_mds_client *mdsc)
> >
> >  static inline  u64 __get_oldest_tid(struct ceph_mds_client *mdsc)
> >  {
> > -     return mdsc->oldest_tid;
> > +     return atomic64_read(&mdsc->oldest_tid);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_FS_ENCRYPTION)
> > @@ -3438,9 +3439,6 @@ static void complete_request(struct ceph_mds_client *mdsc,
> >       complete_all(&req->r_completion);
> >  }
> >
> > -/*
> > - * called under mdsc->mutex
> > - */
> >  static int __prepare_send_request(struct ceph_mds_session *session,
> >                                 struct ceph_mds_request *req,
> >                                 bool drop_cap_releases)
> > @@ -3555,9 +3553,6 @@ static int __prepare_send_request(struct ceph_mds_session *session,
> >       return 0;
> >  }
> >
> > -/*
> > - * called under mdsc->mutex
> > - */
> >  static int __send_request(struct ceph_mds_session *session,
> >                         struct ceph_mds_request *req,
> >                         bool drop_cap_releases)
> > diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h
> > index 731d6ad04956..3b614b5df18c 100644
> > --- a/fs/ceph/mds_client.h
> > +++ b/fs/ceph/mds_client.h
> > @@ -532,7 +532,7 @@ struct ceph_mds_client {
> >       spinlock_t              snap_empty_lock;  /* protect snap_empty */
> >
> >       u64                    last_tid;      /* most recent mds request */
> > -     u64                    oldest_tid;    /* oldest incomplete mds request,
> > +     atomic64_t             oldest_tid;    /* oldest incomplete mds request,
> >                                                excluding setfilelock requests */
> >       struct rb_root         request_tree;  /* pending mds requests */
> >       struct delayed_work    delayed_work;  /* delayed work */
> >
>