* [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans()
@ 2026-09-18 12:28 fdmanana
2026-09-18 12:28 ` [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() fdmanana
` (3 more replies)
0 siblings, 4 replies; 15+ messages in thread
From: fdmanana @ 2026-09-18 12:28 UTC (permalink / raw)
To: linux-btrfs
From: Filipe Manana <fdmanana@suse.com>
Fix a couple issues in record_root_in_trans / btrfs_record_root_in_trans()
detected by Gemini, plus add a lockdep assertion.
Filipe Manana (3):
btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans()
btrfs: fix barrier usage in btrfs_record_root_in_trans()
btrfs: assert reloc mutex is held in record_root_in_trans()
fs/btrfs/transaction.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
--
2.47.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans()
2026-09-18 12:28 [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans() fdmanana
@ 2026-09-18 12:28 ` fdmanana
2026-09-18 17:19 ` Boris Burkov
2026-09-18 21:46 ` Qu Wenruo
2026-09-18 12:28 ` [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans() fdmanana
` (2 subsequent siblings)
3 siblings, 2 replies; 15+ messages in thread
From: fdmanana @ 2026-09-18 12:28 UTC (permalink / raw)
To: linux-btrfs
From: Filipe Manana <fdmanana@suse.com>
If we exit early because the transaction that last used the root already
matches the current transaction, we leave the BTRFS_ROOT_IN_TRANS_SETUP
bit set in the root (which we just set right before the exit). While this
does not cause any functional issue, it makes callers of
btrfs_record_root_in_trans() always lock fs_info->reloc_mutex and call
record_root_in_trans() for nothing, causing unnecessary lock contention.
One caller of btrfs_record_root_in_trans() is start_transaction(), used to
start new transaction or joining an existing one, which is a hot path.
So clear BTRFS_ROOT_IN_TRANS_SETUP on early exit.
Assisted-by: LLM
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
fs/btrfs/transaction.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
index ca114235bbe1..c1555621ae4e 100644
--- a/fs/btrfs/transaction.c
+++ b/fs/btrfs/transaction.c
@@ -432,6 +432,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
spin_lock(&fs_info->fs_roots_radix_lock);
if (btrfs_get_root_last_trans(root) == trans->transid && !force) {
spin_unlock(&fs_info->fs_roots_radix_lock);
+ clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state);
return 0;
}
radix_tree_tag_set(&fs_info->fs_roots_radix,
--
2.47.2
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans()
2026-09-18 12:28 [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans() fdmanana
2026-09-18 12:28 ` [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() fdmanana
@ 2026-09-18 12:28 ` fdmanana
2026-09-18 17:36 ` Boris Burkov
2026-09-18 12:28 ` [PATCH 3/3] btrfs: assert reloc mutex is held in record_root_in_trans() fdmanana
2026-09-18 17:38 ` [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans() Boris Burkov
3 siblings, 1 reply; 15+ messages in thread
From: fdmanana @ 2026-09-18 12:28 UTC (permalink / raw)
To: linux-btrfs
From: Filipe Manana <fdmanana@suse.com>
The barrier usage in btrfs_record_root_in_trans() is wrong, as the writer
side, in record_root_in_trans(), sets BTRFS_ROOT_IN_TRANS_SETUP, does a
write barrier and then sets the root's last transaction. This means the
reader side must check the root's last transaction, issue a read barrier
and then check for BTRFS_ROOT_IN_TRANS_SETUP. However, currently we issue
a read barrier and then check the root's last transaction and the bit
BTRFS_ROOT_IN_TRANS_SETUP, which can be problematic because the CPU is
free to reorder the checks and the following can happen:
1) Before reading the root's last_trans, it checks that
BTRFS_ROOT_IN_TRANS_SETUP is not set.
2) A writer sets BTRFS_ROOT_IN_TRANS_SETUP, does smp_wmb() and updates
the root's last_trans.
3) The reader then sees the root's last_trans matches the current
transaction and falsely concludes the root setup is completes and
returns without waiting for the writer task to complete the setup
(calling btrfs_init_reloc_root()).
So fix the reading ordered as previously described: check the root's
last_trans, issue read barrier and then check BTRFS_ROOT_IN_TRANS_SETUP
(the reverse of what the writer side does).
Assisted-by: LLM
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
fs/btrfs/transaction.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
index c1555621ae4e..13203ea9e116 100644
--- a/fs/btrfs/transaction.c
+++ b/fs/btrfs/transaction.c
@@ -511,10 +511,11 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
* see record_root_in_trans for comments about IN_TRANS_SETUP usage
* and barriers
*/
- smp_rmb();
- if (btrfs_get_root_last_trans(root) == trans->transid &&
- !test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
- return 0;
+ if (btrfs_get_root_last_trans(root) == trans->transid) {
+ smp_rmb();
+ if (!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
+ return 0;
+ }
mutex_lock(&fs_info->reloc_mutex);
ret = record_root_in_trans(trans, root, false);
--
2.47.2
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/3] btrfs: assert reloc mutex is held in record_root_in_trans()
2026-09-18 12:28 [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans() fdmanana
2026-09-18 12:28 ` [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() fdmanana
2026-09-18 12:28 ` [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans() fdmanana
@ 2026-09-18 12:28 ` fdmanana
2026-09-18 21:47 ` Qu Wenruo
2026-09-18 17:38 ` [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans() Boris Burkov
3 siblings, 1 reply; 15+ messages in thread
From: fdmanana @ 2026-09-18 12:28 UTC (permalink / raw)
To: linux-btrfs
From: Filipe Manana <fdmanana@suse.com>
The fs_info->reloc_mutex is supposed to be held when record_root_in_trans()
is called and we mention that in a comment inside the function. Add a
lockdep assertion to check the lock is held.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
fs/btrfs/transaction.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
index 13203ea9e116..0a3913b61f16 100644
--- a/fs/btrfs/transaction.c
+++ b/fs/btrfs/transaction.c
@@ -413,6 +413,8 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info = root->fs_info;
int ret = 0;
+ lockdep_assert_held(&fs_info->reloc_mutex);
+
if ((test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
btrfs_get_root_last_trans(root) < trans->transid) || force) {
WARN_ON(!force && root->commit_root != root->node);
--
2.47.2
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans()
2026-09-18 12:28 ` [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() fdmanana
@ 2026-09-18 17:19 ` Boris Burkov
2026-09-18 17:32 ` Filipe Manana
2026-09-18 21:46 ` Qu Wenruo
1 sibling, 1 reply; 15+ messages in thread
From: Boris Burkov @ 2026-09-18 17:19 UTC (permalink / raw)
To: fdmanana; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 01:28:09PM +0100, fdmanana@kernel.org wrote:
> From: Filipe Manana <fdmanana@suse.com>
>
> If we exit early because the transaction that last used the root already
> matches the current transaction, we leave the BTRFS_ROOT_IN_TRANS_SETUP
> bit set in the root (which we just set right before the exit). While this
> does not cause any functional issue, it makes callers of
> btrfs_record_root_in_trans() always lock fs_info->reloc_mutex and call
I found "always" kind of confusing here. It's until one succeeds and
clears the bit right? It kind of makes it sound like it leaks it
forever.
> record_root_in_trans() for nothing, causing unnecessary lock contention.
> One caller of btrfs_record_root_in_trans() is start_transaction(), used to
> start new transaction or joining an existing one, which is a hot path.
>
> So clear BTRFS_ROOT_IN_TRANS_SETUP on early exit.
>
> Assisted-by: LLM
> Signed-off-by: Filipe Manana <fdmanana@suse.com>
> ---
> fs/btrfs/transaction.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> index ca114235bbe1..c1555621ae4e 100644
> --- a/fs/btrfs/transaction.c
> +++ b/fs/btrfs/transaction.c
> @@ -432,6 +432,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
> spin_lock(&fs_info->fs_roots_radix_lock);
> if (btrfs_get_root_last_trans(root) == trans->transid && !force) {
> spin_unlock(&fs_info->fs_roots_radix_lock);
> + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state);
> return 0;
> }
> radix_tree_tag_set(&fs_info->fs_roots_radix,
> --
> 2.47.2
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans()
2026-09-18 17:19 ` Boris Burkov
@ 2026-09-18 17:32 ` Filipe Manana
2026-09-18 17:48 ` Boris Burkov
0 siblings, 1 reply; 15+ messages in thread
From: Filipe Manana @ 2026-09-18 17:32 UTC (permalink / raw)
To: Boris Burkov; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 6:18 PM Boris Burkov <boris@bur.io> wrote:
>
> On Fri, Sep 18, 2026 at 01:28:09PM +0100, fdmanana@kernel.org wrote:
> > From: Filipe Manana <fdmanana@suse.com>
> >
> > If we exit early because the transaction that last used the root already
> > matches the current transaction, we leave the BTRFS_ROOT_IN_TRANS_SETUP
> > bit set in the root (which we just set right before the exit). While this
> > does not cause any functional issue, it makes callers of
> > btrfs_record_root_in_trans() always lock fs_info->reloc_mutex and call
>
> I found "always" kind of confusing here. It's until one succeeds and
> clears the bit right? It kind of makes it sound like it leaks it
> forever.
Yes, I can reword the sentence to:
"While this does not cause any functional issue, it makes callers of
btrfs_record_root_in_trans() lock fs_info->reloc_mutex and call
record_root_in_trans() for nothing, causing unnecessary lock contention,
until one of them clears the bit in record_root_in_trans()."
Thanks.
>
> > record_root_in_trans() for nothing, causing unnecessary lock contention.
> > One caller of btrfs_record_root_in_trans() is start_transaction(), used to
> > start new transaction or joining an existing one, which is a hot path.
> >
> > So clear BTRFS_ROOT_IN_TRANS_SETUP on early exit.
> >
> > Assisted-by: LLM
> > Signed-off-by: Filipe Manana <fdmanana@suse.com>
> > ---
> > fs/btrfs/transaction.c | 1 +
> > 1 file changed, 1 insertion(+)
> >
> > diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> > index ca114235bbe1..c1555621ae4e 100644
> > --- a/fs/btrfs/transaction.c
> > +++ b/fs/btrfs/transaction.c
> > @@ -432,6 +432,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
> > spin_lock(&fs_info->fs_roots_radix_lock);
> > if (btrfs_get_root_last_trans(root) == trans->transid && !force) {
> > spin_unlock(&fs_info->fs_roots_radix_lock);
> > + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state);
> > return 0;
> > }
> > radix_tree_tag_set(&fs_info->fs_roots_radix,
> > --
> > 2.47.2
> >
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans()
2026-09-18 12:28 ` [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans() fdmanana
@ 2026-09-18 17:36 ` Boris Burkov
2026-09-18 17:43 ` Filipe Manana
0 siblings, 1 reply; 15+ messages in thread
From: Boris Burkov @ 2026-09-18 17:36 UTC (permalink / raw)
To: fdmanana; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 01:28:10PM +0100, fdmanana@kernel.org wrote:
> From: Filipe Manana <fdmanana@suse.com>
>
> The barrier usage in btrfs_record_root_in_trans() is wrong, as the writer
> side, in record_root_in_trans(), sets BTRFS_ROOT_IN_TRANS_SETUP, does a
> write barrier and then sets the root's last transaction. This means the
> reader side must check the root's last transaction, issue a read barrier
> and then check for BTRFS_ROOT_IN_TRANS_SETUP. However, currently we issue
> a read barrier and then check the root's last transaction and the bit
> BTRFS_ROOT_IN_TRANS_SETUP, which can be problematic because the CPU is
> free to reorder the checks and the following can happen:
>
> 1) Before reading the root's last_trans, it checks that
> BTRFS_ROOT_IN_TRANS_SETUP is not set.
>
> 2) A writer sets BTRFS_ROOT_IN_TRANS_SETUP, does smp_wmb() and updates
> the root's last_trans.
>
> 3) The reader then sees the root's last_trans matches the current
> transaction and falsely concludes the root setup is completes and
> returns without waiting for the writer task to complete the setup
> (calling btrfs_init_reloc_root()).
>
> So fix the reading ordered as previously described: check the root's
> last_trans, issue read barrier and then check BTRFS_ROOT_IN_TRANS_SETUP
> (the reverse of what the writer side does).
I think a comment on why we can't use release/acquire (u64) but that the
non-atomicity is ok (we only check equality?) might be nice. Otherwise
we are supposed to have some code like i_size_read(), right?
Not blocking at all, just an observation, since those helpers are
supposed to prevent this kind of bug.
>
> Assisted-by: LLM
> Signed-off-by: Filipe Manana <fdmanana@suse.com>
> ---
> fs/btrfs/transaction.c | 9 +++++----
> 1 file changed, 5 insertions(+), 4 deletions(-)
>
> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> index c1555621ae4e..13203ea9e116 100644
> --- a/fs/btrfs/transaction.c
> +++ b/fs/btrfs/transaction.c
> @@ -511,10 +511,11 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
> * see record_root_in_trans for comments about IN_TRANS_SETUP usage
> * and barriers
> */
> - smp_rmb();
> - if (btrfs_get_root_last_trans(root) == trans->transid &&
> - !test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> - return 0;
> + if (btrfs_get_root_last_trans(root) == trans->transid) {
> + smp_rmb();
> + if (!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> + return 0;
> + }
>
> mutex_lock(&fs_info->reloc_mutex);
> ret = record_root_in_trans(trans, root, false);
> --
> 2.47.2
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans()
2026-09-18 12:28 [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans() fdmanana
` (2 preceding siblings ...)
2026-09-18 12:28 ` [PATCH 3/3] btrfs: assert reloc mutex is held in record_root_in_trans() fdmanana
@ 2026-09-18 17:38 ` Boris Burkov
3 siblings, 0 replies; 15+ messages in thread
From: Boris Burkov @ 2026-09-18 17:38 UTC (permalink / raw)
To: fdmanana; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 01:28:08PM +0100, fdmanana@kernel.org wrote:
> From: Filipe Manana <fdmanana@suse.com>
>
> Fix a couple issues in record_root_in_trans / btrfs_record_root_in_trans()
> detected by Gemini, plus add a lockdep assertion.
Some simple nits/questions on the patches but this LGTM, please feel free to add
Reviewed-by: Boris Burkov <boris@bur.io>
Thanks,
Boris
>
> Filipe Manana (3):
> btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans()
> btrfs: fix barrier usage in btrfs_record_root_in_trans()
> btrfs: assert reloc mutex is held in record_root_in_trans()
>
> fs/btrfs/transaction.c | 12 ++++++++----
> 1 file changed, 8 insertions(+), 4 deletions(-)
>
> --
> 2.47.2
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans()
2026-09-18 17:36 ` Boris Burkov
@ 2026-09-18 17:43 ` Filipe Manana
2026-09-18 17:47 ` Boris Burkov
0 siblings, 1 reply; 15+ messages in thread
From: Filipe Manana @ 2026-09-18 17:43 UTC (permalink / raw)
To: Boris Burkov; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 6:36 PM Boris Burkov <boris@bur.io> wrote:
>
> On Fri, Sep 18, 2026 at 01:28:10PM +0100, fdmanana@kernel.org wrote:
> > From: Filipe Manana <fdmanana@suse.com>
> >
> > The barrier usage in btrfs_record_root_in_trans() is wrong, as the writer
> > side, in record_root_in_trans(), sets BTRFS_ROOT_IN_TRANS_SETUP, does a
> > write barrier and then sets the root's last transaction. This means the
> > reader side must check the root's last transaction, issue a read barrier
> > and then check for BTRFS_ROOT_IN_TRANS_SETUP. However, currently we issue
> > a read barrier and then check the root's last transaction and the bit
> > BTRFS_ROOT_IN_TRANS_SETUP, which can be problematic because the CPU is
> > free to reorder the checks and the following can happen:
> >
> > 1) Before reading the root's last_trans, it checks that
> > BTRFS_ROOT_IN_TRANS_SETUP is not set.
> >
> > 2) A writer sets BTRFS_ROOT_IN_TRANS_SETUP, does smp_wmb() and updates
> > the root's last_trans.
> >
> > 3) The reader then sees the root's last_trans matches the current
> > transaction and falsely concludes the root setup is completes and
> > returns without waiting for the writer task to complete the setup
> > (calling btrfs_init_reloc_root()).
> >
> > So fix the reading ordered as previously described: check the root's
> > last_trans, issue read barrier and then check BTRFS_ROOT_IN_TRANS_SETUP
> > (the reverse of what the writer side does).
>
> I think a comment on why we can't use release/acquire (u64) but that the
> non-atomicity is ok (we only check equality?) might be nice. Otherwise
> we are supposed to have some code like i_size_read(), right?
>
> Not blocking at all, just an observation, since those helpers are
> supposed to prevent this kind of bug.
I'm not sure what you mean. If you are mentioning the helpers for
last_trans use READ/WRITE_ONCE and that that prevents the bug being
fixed here, then that is not correct, because READ/WRITE_ONCE does not
prevent a CPU from reordering intructions (just compiler level
reordering, load/store tearing and a few other things).
>
> >
> > Assisted-by: LLM
> > Signed-off-by: Filipe Manana <fdmanana@suse.com>
> > ---
> > fs/btrfs/transaction.c | 9 +++++----
> > 1 file changed, 5 insertions(+), 4 deletions(-)
> >
> > diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> > index c1555621ae4e..13203ea9e116 100644
> > --- a/fs/btrfs/transaction.c
> > +++ b/fs/btrfs/transaction.c
> > @@ -511,10 +511,11 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
> > * see record_root_in_trans for comments about IN_TRANS_SETUP usage
> > * and barriers
> > */
> > - smp_rmb();
> > - if (btrfs_get_root_last_trans(root) == trans->transid &&
> > - !test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> > - return 0;
> > + if (btrfs_get_root_last_trans(root) == trans->transid) {
> > + smp_rmb();
> > + if (!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> > + return 0;
> > + }
> >
> > mutex_lock(&fs_info->reloc_mutex);
> > ret = record_root_in_trans(trans, root, false);
> > --
> > 2.47.2
> >
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans()
2026-09-18 17:43 ` Filipe Manana
@ 2026-09-18 17:47 ` Boris Burkov
2026-09-18 18:05 ` Filipe Manana
0 siblings, 1 reply; 15+ messages in thread
From: Boris Burkov @ 2026-09-18 17:47 UTC (permalink / raw)
To: Filipe Manana; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 06:43:54PM +0100, Filipe Manana wrote:
> On Fri, Sep 18, 2026 at 6:36 PM Boris Burkov <boris@bur.io> wrote:
> >
> > On Fri, Sep 18, 2026 at 01:28:10PM +0100, fdmanana@kernel.org wrote:
> > > From: Filipe Manana <fdmanana@suse.com>
> > >
> > > The barrier usage in btrfs_record_root_in_trans() is wrong, as the writer
> > > side, in record_root_in_trans(), sets BTRFS_ROOT_IN_TRANS_SETUP, does a
> > > write barrier and then sets the root's last transaction. This means the
> > > reader side must check the root's last transaction, issue a read barrier
> > > and then check for BTRFS_ROOT_IN_TRANS_SETUP. However, currently we issue
> > > a read barrier and then check the root's last transaction and the bit
> > > BTRFS_ROOT_IN_TRANS_SETUP, which can be problematic because the CPU is
> > > free to reorder the checks and the following can happen:
> > >
> > > 1) Before reading the root's last_trans, it checks that
> > > BTRFS_ROOT_IN_TRANS_SETUP is not set.
> > >
> > > 2) A writer sets BTRFS_ROOT_IN_TRANS_SETUP, does smp_wmb() and updates
> > > the root's last_trans.
> > >
> > > 3) The reader then sees the root's last_trans matches the current
> > > transaction and falsely concludes the root setup is completes and
> > > returns without waiting for the writer task to complete the setup
> > > (calling btrfs_init_reloc_root()).
> > >
> > > So fix the reading ordered as previously described: check the root's
> > > last_trans, issue read barrier and then check BTRFS_ROOT_IN_TRANS_SETUP
> > > (the reverse of what the writer side does).
> >
> > I think a comment on why we can't use release/acquire (u64) but that the
> > non-atomicity is ok (we only check equality?) might be nice. Otherwise
> > we are supposed to have some code like i_size_read(), right?
> >
> > Not blocking at all, just an observation, since those helpers are
> > supposed to prevent this kind of bug.
>
> I'm not sure what you mean. If you are mentioning the helpers for
> last_trans use READ/WRITE_ONCE and that that prevents the bug being
> fixed here, then that is not correct, because READ/WRITE_ONCE does not
> prevent a CPU from reordering intructions (just compiler level
> reordering, load/store tearing and a few other things).
>
>
Sorry for being unclear. No, what I mean is I think we should
justify/document why we are not using smp_store_release/smp_load_acquire
since those are exactly this pattern, and if we had used them, it would
have prevented the bug.
> >
> > >
> > > Assisted-by: LLM
> > > Signed-off-by: Filipe Manana <fdmanana@suse.com>
> > > ---
> > > fs/btrfs/transaction.c | 9 +++++----
> > > 1 file changed, 5 insertions(+), 4 deletions(-)
> > >
> > > diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> > > index c1555621ae4e..13203ea9e116 100644
> > > --- a/fs/btrfs/transaction.c
> > > +++ b/fs/btrfs/transaction.c
> > > @@ -511,10 +511,11 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
> > > * see record_root_in_trans for comments about IN_TRANS_SETUP usage
> > > * and barriers
> > > */
> > > - smp_rmb();
> > > - if (btrfs_get_root_last_trans(root) == trans->transid &&
> > > - !test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> > > - return 0;
> > > + if (btrfs_get_root_last_trans(root) == trans->transid) {
> > > + smp_rmb();
> > > + if (!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> > > + return 0;
> > > + }
> > >
> > > mutex_lock(&fs_info->reloc_mutex);
> > > ret = record_root_in_trans(trans, root, false);
> > > --
> > > 2.47.2
> > >
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans()
2026-09-18 17:32 ` Filipe Manana
@ 2026-09-18 17:48 ` Boris Burkov
0 siblings, 0 replies; 15+ messages in thread
From: Boris Burkov @ 2026-09-18 17:48 UTC (permalink / raw)
To: Filipe Manana; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 06:32:05PM +0100, Filipe Manana wrote:
> On Fri, Sep 18, 2026 at 6:18 PM Boris Burkov <boris@bur.io> wrote:
> >
> > On Fri, Sep 18, 2026 at 01:28:09PM +0100, fdmanana@kernel.org wrote:
> > > From: Filipe Manana <fdmanana@suse.com>
> > >
> > > If we exit early because the transaction that last used the root already
> > > matches the current transaction, we leave the BTRFS_ROOT_IN_TRANS_SETUP
> > > bit set in the root (which we just set right before the exit). While this
> > > does not cause any functional issue, it makes callers of
> > > btrfs_record_root_in_trans() always lock fs_info->reloc_mutex and call
> >
> > I found "always" kind of confusing here. It's until one succeeds and
> > clears the bit right? It kind of makes it sound like it leaks it
> > forever.
>
> Yes, I can reword the sentence to:
>
> "While this does not cause any functional issue, it makes callers of
> btrfs_record_root_in_trans() lock fs_info->reloc_mutex and call
> record_root_in_trans() for nothing, causing unnecessary lock contention,
> until one of them clears the bit in record_root_in_trans()."
>
> Thanks.
>
That version sounds great!
> >
> > > record_root_in_trans() for nothing, causing unnecessary lock contention.
> > > One caller of btrfs_record_root_in_trans() is start_transaction(), used to
> > > start new transaction or joining an existing one, which is a hot path.
> > >
> > > So clear BTRFS_ROOT_IN_TRANS_SETUP on early exit.
> > >
> > > Assisted-by: LLM
> > > Signed-off-by: Filipe Manana <fdmanana@suse.com>
> > > ---
> > > fs/btrfs/transaction.c | 1 +
> > > 1 file changed, 1 insertion(+)
> > >
> > > diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> > > index ca114235bbe1..c1555621ae4e 100644
> > > --- a/fs/btrfs/transaction.c
> > > +++ b/fs/btrfs/transaction.c
> > > @@ -432,6 +432,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
> > > spin_lock(&fs_info->fs_roots_radix_lock);
> > > if (btrfs_get_root_last_trans(root) == trans->transid && !force) {
> > > spin_unlock(&fs_info->fs_roots_radix_lock);
> > > + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state);
> > > return 0;
> > > }
> > > radix_tree_tag_set(&fs_info->fs_roots_radix,
> > > --
> > > 2.47.2
> > >
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans()
2026-09-18 17:47 ` Boris Burkov
@ 2026-09-18 18:05 ` Filipe Manana
2026-09-18 18:58 ` Boris Burkov
0 siblings, 1 reply; 15+ messages in thread
From: Filipe Manana @ 2026-09-18 18:05 UTC (permalink / raw)
To: Boris Burkov; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 6:47 PM Boris Burkov <boris@bur.io> wrote:
>
> On Fri, Sep 18, 2026 at 06:43:54PM +0100, Filipe Manana wrote:
> > On Fri, Sep 18, 2026 at 6:36 PM Boris Burkov <boris@bur.io> wrote:
> > >
> > > On Fri, Sep 18, 2026 at 01:28:10PM +0100, fdmanana@kernel.org wrote:
> > > > From: Filipe Manana <fdmanana@suse.com>
> > > >
> > > > The barrier usage in btrfs_record_root_in_trans() is wrong, as the writer
> > > > side, in record_root_in_trans(), sets BTRFS_ROOT_IN_TRANS_SETUP, does a
> > > > write barrier and then sets the root's last transaction. This means the
> > > > reader side must check the root's last transaction, issue a read barrier
> > > > and then check for BTRFS_ROOT_IN_TRANS_SETUP. However, currently we issue
> > > > a read barrier and then check the root's last transaction and the bit
> > > > BTRFS_ROOT_IN_TRANS_SETUP, which can be problematic because the CPU is
> > > > free to reorder the checks and the following can happen:
> > > >
> > > > 1) Before reading the root's last_trans, it checks that
> > > > BTRFS_ROOT_IN_TRANS_SETUP is not set.
> > > >
> > > > 2) A writer sets BTRFS_ROOT_IN_TRANS_SETUP, does smp_wmb() and updates
> > > > the root's last_trans.
> > > >
> > > > 3) The reader then sees the root's last_trans matches the current
> > > > transaction and falsely concludes the root setup is completes and
> > > > returns without waiting for the writer task to complete the setup
> > > > (calling btrfs_init_reloc_root()).
> > > >
> > > > So fix the reading ordered as previously described: check the root's
> > > > last_trans, issue read barrier and then check BTRFS_ROOT_IN_TRANS_SETUP
> > > > (the reverse of what the writer side does).
> > >
> > > I think a comment on why we can't use release/acquire (u64) but that the
> > > non-atomicity is ok (we only check equality?) might be nice. Otherwise
> > > we are supposed to have some code like i_size_read(), right?
> > >
> > > Not blocking at all, just an observation, since those helpers are
> > > supposed to prevent this kind of bug.
> >
> > I'm not sure what you mean. If you are mentioning the helpers for
> > last_trans use READ/WRITE_ONCE and that that prevents the bug being
> > fixed here, then that is not correct, because READ/WRITE_ONCE does not
> > prevent a CPU from reordering intructions (just compiler level
> > reordering, load/store tearing and a few other things).
> >
> >
>
> Sorry for being unclear. No, what I mean is I think we should
> justify/document why we are not using smp_store_release/smp_load_acquire
> since those are exactly this pattern, and if we had used them, it would
> have prevented the bug.
It should work, but I don't see an advantage of one method over the
other (perhaps smp_store_release and and_load_acquire are a bit easier
to read for some).
I have no idea why that code (really old now) was written using smp_wmb/rmb.
Perhaps the macros for smp_store_release and and_load_acquire did not
exist back then, and that explains why we don't use them anywhere in
btrfs and always use smp_wmb/rmb.
>
> > >
> > > >
> > > > Assisted-by: LLM
> > > > Signed-off-by: Filipe Manana <fdmanana@suse.com>
> > > > ---
> > > > fs/btrfs/transaction.c | 9 +++++----
> > > > 1 file changed, 5 insertions(+), 4 deletions(-)
> > > >
> > > > diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> > > > index c1555621ae4e..13203ea9e116 100644
> > > > --- a/fs/btrfs/transaction.c
> > > > +++ b/fs/btrfs/transaction.c
> > > > @@ -511,10 +511,11 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
> > > > * see record_root_in_trans for comments about IN_TRANS_SETUP usage
> > > > * and barriers
> > > > */
> > > > - smp_rmb();
> > > > - if (btrfs_get_root_last_trans(root) == trans->transid &&
> > > > - !test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> > > > - return 0;
> > > > + if (btrfs_get_root_last_trans(root) == trans->transid) {
> > > > + smp_rmb();
> > > > + if (!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> > > > + return 0;
> > > > + }
> > > >
> > > > mutex_lock(&fs_info->reloc_mutex);
> > > > ret = record_root_in_trans(trans, root, false);
> > > > --
> > > > 2.47.2
> > > >
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans()
2026-09-18 18:05 ` Filipe Manana
@ 2026-09-18 18:58 ` Boris Burkov
0 siblings, 0 replies; 15+ messages in thread
From: Boris Burkov @ 2026-09-18 18:58 UTC (permalink / raw)
To: Filipe Manana; +Cc: linux-btrfs
On Fri, Sep 18, 2026 at 07:05:24PM +0100, Filipe Manana wrote:
> On Fri, Sep 18, 2026 at 6:47 PM Boris Burkov <boris@bur.io> wrote:
> >
> > On Fri, Sep 18, 2026 at 06:43:54PM +0100, Filipe Manana wrote:
> > > On Fri, Sep 18, 2026 at 6:36 PM Boris Burkov <boris@bur.io> wrote:
> > > >
> > > > On Fri, Sep 18, 2026 at 01:28:10PM +0100, fdmanana@kernel.org wrote:
> > > > > From: Filipe Manana <fdmanana@suse.com>
> > > > >
> > > > > The barrier usage in btrfs_record_root_in_trans() is wrong, as the writer
> > > > > side, in record_root_in_trans(), sets BTRFS_ROOT_IN_TRANS_SETUP, does a
> > > > > write barrier and then sets the root's last transaction. This means the
> > > > > reader side must check the root's last transaction, issue a read barrier
> > > > > and then check for BTRFS_ROOT_IN_TRANS_SETUP. However, currently we issue
> > > > > a read barrier and then check the root's last transaction and the bit
> > > > > BTRFS_ROOT_IN_TRANS_SETUP, which can be problematic because the CPU is
> > > > > free to reorder the checks and the following can happen:
> > > > >
> > > > > 1) Before reading the root's last_trans, it checks that
> > > > > BTRFS_ROOT_IN_TRANS_SETUP is not set.
> > > > >
> > > > > 2) A writer sets BTRFS_ROOT_IN_TRANS_SETUP, does smp_wmb() and updates
> > > > > the root's last_trans.
> > > > >
> > > > > 3) The reader then sees the root's last_trans matches the current
> > > > > transaction and falsely concludes the root setup is completes and
> > > > > returns without waiting for the writer task to complete the setup
> > > > > (calling btrfs_init_reloc_root()).
> > > > >
> > > > > So fix the reading ordered as previously described: check the root's
> > > > > last_trans, issue read barrier and then check BTRFS_ROOT_IN_TRANS_SETUP
> > > > > (the reverse of what the writer side does).
> > > >
> > > > I think a comment on why we can't use release/acquire (u64) but that the
> > > > non-atomicity is ok (we only check equality?) might be nice. Otherwise
> > > > we are supposed to have some code like i_size_read(), right?
> > > >
> > > > Not blocking at all, just an observation, since those helpers are
> > > > supposed to prevent this kind of bug.
> > >
> > > I'm not sure what you mean. If you are mentioning the helpers for
> > > last_trans use READ/WRITE_ONCE and that that prevents the bug being
> > > fixed here, then that is not correct, because READ/WRITE_ONCE does not
> > > prevent a CPU from reordering intructions (just compiler level
> > > reordering, load/store tearing and a few other things).
> > >
> > >
> >
> > Sorry for being unclear. No, what I mean is I think we should
> > justify/document why we are not using smp_store_release/smp_load_acquire
> > since those are exactly this pattern, and if we had used them, it would
> > have prevented the bug.
>
> It should work, but I don't see an advantage of one method over the
> other (perhaps smp_store_release and and_load_acquire are a bit easier
> to read for some).
>
> I have no idea why that code (really old now) was written using smp_wmb/rmb.
> Perhaps the macros for smp_store_release and and_load_acquire did not
> exist back then, and that explains why we don't use them anywhere in
> btrfs and always use smp_wmb/rmb.
>
>
OK, all good. Thanks for the discussion and the fix, and you don't need
to bother with any additional justification or explanation.
> >
> > > >
> > > > >
> > > > > Assisted-by: LLM
> > > > > Signed-off-by: Filipe Manana <fdmanana@suse.com>
> > > > > ---
> > > > > fs/btrfs/transaction.c | 9 +++++----
> > > > > 1 file changed, 5 insertions(+), 4 deletions(-)
> > > > >
> > > > > diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> > > > > index c1555621ae4e..13203ea9e116 100644
> > > > > --- a/fs/btrfs/transaction.c
> > > > > +++ b/fs/btrfs/transaction.c
> > > > > @@ -511,10 +511,11 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
> > > > > * see record_root_in_trans for comments about IN_TRANS_SETUP usage
> > > > > * and barriers
> > > > > */
> > > > > - smp_rmb();
> > > > > - if (btrfs_get_root_last_trans(root) == trans->transid &&
> > > > > - !test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> > > > > - return 0;
> > > > > + if (btrfs_get_root_last_trans(root) == trans->transid) {
> > > > > + smp_rmb();
> > > > > + if (!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
> > > > > + return 0;
> > > > > + }
> > > > >
> > > > > mutex_lock(&fs_info->reloc_mutex);
> > > > > ret = record_root_in_trans(trans, root, false);
> > > > > --
> > > > > 2.47.2
> > > > >
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans()
2026-09-18 12:28 ` [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() fdmanana
2026-09-18 17:19 ` Boris Burkov
@ 2026-09-18 21:46 ` Qu Wenruo
1 sibling, 0 replies; 15+ messages in thread
From: Qu Wenruo @ 2026-09-18 21:46 UTC (permalink / raw)
To: fdmanana, linux-btrfs
在 2026/9/18 21:58, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
>
> If we exit early because the transaction that last used the root already
> matches the current transaction, we leave the BTRFS_ROOT_IN_TRANS_SETUP
> bit set in the root (which we just set right before the exit). While this
> does not cause any functional issue, it makes callers of
> btrfs_record_root_in_trans() always lock fs_info->reloc_mutex and call
> record_root_in_trans() for nothing, causing unnecessary lock contention.
> One caller of btrfs_record_root_in_trans() is start_transaction(), used to
> start new transaction or joining an existing one, which is a hot path.
>
> So clear BTRFS_ROOT_IN_TRANS_SETUP on early exit.
>
> Assisted-by: LLM
> Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Thanks,
Qu
> ---
> fs/btrfs/transaction.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> index ca114235bbe1..c1555621ae4e 100644
> --- a/fs/btrfs/transaction.c
> +++ b/fs/btrfs/transaction.c
> @@ -432,6 +432,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
> spin_lock(&fs_info->fs_roots_radix_lock);
> if (btrfs_get_root_last_trans(root) == trans->transid && !force) {
> spin_unlock(&fs_info->fs_roots_radix_lock);
> + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state);
> return 0;
> }
> radix_tree_tag_set(&fs_info->fs_roots_radix,
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 3/3] btrfs: assert reloc mutex is held in record_root_in_trans()
2026-09-18 12:28 ` [PATCH 3/3] btrfs: assert reloc mutex is held in record_root_in_trans() fdmanana
@ 2026-09-18 21:47 ` Qu Wenruo
0 siblings, 0 replies; 15+ messages in thread
From: Qu Wenruo @ 2026-09-18 21:47 UTC (permalink / raw)
To: fdmanana, linux-btrfs
在 2026/9/18 21:58, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
>
> The fs_info->reloc_mutex is supposed to be held when record_root_in_trans()
> is called and we mention that in a comment inside the function. Add a
> lockdep assertion to check the lock is held.
>
> Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Thanks,
Qu
> ---
> fs/btrfs/transaction.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> index 13203ea9e116..0a3913b61f16 100644
> --- a/fs/btrfs/transaction.c
> +++ b/fs/btrfs/transaction.c
> @@ -413,6 +413,8 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
> struct btrfs_fs_info *fs_info = root->fs_info;
> int ret = 0;
>
> + lockdep_assert_held(&fs_info->reloc_mutex);
> +
> if ((test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
> btrfs_get_root_last_trans(root) < trans->transid) || force) {
> WARN_ON(!force && root->commit_root != root->node);
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-09-18 21:47 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-18 12:28 [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans() fdmanana
2026-09-18 12:28 ` [PATCH 1/3] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() fdmanana
2026-09-18 17:19 ` Boris Burkov
2026-09-18 17:32 ` Filipe Manana
2026-09-18 17:48 ` Boris Burkov
2026-09-18 21:46 ` Qu Wenruo
2026-09-18 12:28 ` [PATCH 2/3] btrfs: fix barrier usage in btrfs_record_root_in_trans() fdmanana
2026-09-18 17:36 ` Boris Burkov
2026-09-18 17:43 ` Filipe Manana
2026-09-18 17:47 ` Boris Burkov
2026-09-18 18:05 ` Filipe Manana
2026-09-18 18:58 ` Boris Burkov
2026-09-18 12:28 ` [PATCH 3/3] btrfs: assert reloc mutex is held in record_root_in_trans() fdmanana
2026-09-18 21:47 ` Qu Wenruo
2026-09-18 17:38 ` [PATCH 0/3] btrfs: a couple fixes for record_root_in_trans() Boris Burkov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox