All of lore.kernel.org
 help / color / mirror / Atom feed
* Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
@ 2026-10-09 15:34 Naulls, Peter
  2026-10-13 14:41 ` Sebastian Andrzej Siewior
       [not found] ` <SJ0PR17MB4288FC2E7FFB577F5D6C6D9EBB9E2@SJ0PR17MB4288.namprd17.prod.outlook.com>
  0 siblings, 2 replies; 12+ messages in thread
From: Naulls, Peter @ 2026-10-09 15:34 UTC (permalink / raw)
  To: linux-rt-users@vger.kernel.org

Long term kernel hacker, but very infrequent contributor. 

We are using the RT kernel patch on x86 under kernel 6.6.78-rt51. Specifically, this is the kernel from meta-intel in Yocto Scarthgap in the intel-kernel-rt recipe. 

It appears under some high load conditions, and especially triggered by calls to sync(), there is a live lock condition in the btrfs code. I used copilot for this analysis, with apologies due here where relevant. I'm familiar with kernel code, and I'm well aware how complex filesystem interactions can be. 

Here is the lock we observe:


sync (PID 52371)
  btrfs_commit_transaction+0xc48
    [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
  btrfs_sync_fs
  sync_fs_one_sb
  ksys_sync

ordered-I/O completion workers
  join_transaction+0x2b
    [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
  start_transaction
  btrfs_join_transaction
  btrfs_finish_one_ordered
  btrfs_work_helper

journal/fsync callers
  wait_current_trans+0x33
    [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
  start_transaction
  btrfs_start_transaction
  btrfs_sync_file
  vfs_fsync_range
  fsync

btrfs-transaction thread
  transaction_kthread+0x76
    [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
  kthread


The processes are then not killable, indicting a kernel thread problem. 

This was largely found because our userspace code calls sync() excessively (being separately addressed), although that's not the proximate cause. We ended up writing shell script which writes blocks of data to a brtfs partition over several threads and also calls sync and were able to repeat it within 10s of seconds. It is not repeatable in the non-RT kernel.

To add:

I have been extensively through the later kernels in 6.6, and later series and have not found any direct fixe or references for this. I've also been through the latest RT changes and btrfs mailing lists/changes (and this post might be served better there) and cannot find any direct reference to this. The only close match is:

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=99da896614d17e8a84aeb2b2d464ac046cc8633d

This is in related code. I put this patch into our 6.6.78 kernel and it did not resolve the problem. 

With that in mind, I had copilot (again, apologies) have an attempt. It came up with this:

# Experimental mitigation for a suspected PREEMPT_RT Btrfs transaction-waiter
# livelock. wait_for_commit() can reselect a completed transaction that remains
# on trans_list after its waiters are woken. Skip completed entries while still
# waiting for older incomplete transactions. This is a diagnostic hypothesis,
# not a confirmed fix for the observed lockup.
# Review notes: the scan is O(n) under trans_lock. Completion state is published
# locklessly elsewhere; READ_ONCE provides a single load, not acquire ordering.
# A multi-hour run did not reproduce the issue, but the lock owner and root cause
# remain unknown.
diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
index e15e1855fa0a..0f05fabe451c 100644
--- a/fs/btrfs/transaction.c
+++ b/fs/btrfs/transaction.c
@@ -865,6 +865,7 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
                                     const enum btrfs_trans_state min_state)
 {
        struct btrfs_fs_info *fs_info = commit->fs_info;
+       struct btrfs_transaction *next;
        u64 transid = commit->transid;
        bool put = false;

@@ -893,10 +894,16 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
                 */

                spin_lock(&fs_info->trans_lock);
-               commit = list_first_entry_or_null(&fs_info->trans_list,
-                                                 struct btrfs_transaction,
-                                                 list);
-               if (!commit || commit->transid > transid) {
+               commit = NULL;
+               list_for_each_entry(next, &fs_info->trans_list, list) {
+                       if (next->transid > transid)
+                               break;
+                       if (READ_ONCE(next->state) == TRANS_STATE_COMPLETED)
+                               continue;
+                       commit = next;
+                       break;
+               }
+               if (!commit) {
                        spin_unlock(&fs_info->trans_lock);
                        break;
                }


Note the O(N) time here. To that end, this is very unlikely to be the correct fix, but I hope it highlights the core problem. I ran this patch for several hours with no apparent problem (versus seconds of a lockup in previous testing). 

If there's a later kernel patch I've missed, or something else I ought to be looking at or test or need more info, please let me know. 



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-09 15:34 Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78 Naulls, Peter
@ 2026-10-13 14:41 ` Sebastian Andrzej Siewior
  2026-10-13 15:29   ` [EXTERNAL] " Naulls, Peter
       [not found] ` <SJ0PR17MB4288FC2E7FFB577F5D6C6D9EBB9E2@SJ0PR17MB4288.namprd17.prod.outlook.com>
  1 sibling, 1 reply; 12+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-10-13 14:41 UTC (permalink / raw)
  To: Naulls, Peter; +Cc: linux-rt-users@vger.kernel.org

On 2026-10-09 15:34:02 [+0000], Naulls, Peter wrote:
> Long term kernel hacker, but very infrequent contributor. 
Hi Peter,

> We are using the RT kernel patch on x86 under kernel 6.6.78-rt51.
> Specifically, this is the kernel from meta-intel in Yocto Scarthgap in
> the intel-kernel-rt recipe. 
…
> sync (PID 52371)
>   btrfs_commit_transaction+0xc48
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   btrfs_sync_fs
>   sync_fs_one_sb
>   ksys_sync

This sort of sounds like the state is TRANS_STATE_SUPER_COMMITTED but is
not making progress after that. Are there any tasks involved that run at
realtime priority and would block other tasks from making progress?

…
> This is in related code. I put this patch into our 6.6.78 kernel and it did not resolve the problem. 
…
> # Experimental mitigation for a suspected PREEMPT_RT Btrfs transaction-waiter
> # livelock. wait_for_commit() can reselect a completed transaction that remains
> # on trans_list after its waiters are woken. Skip completed entries while still
> # waiting for older incomplete transactions. This is a diagnostic hypothesis,
> # not a confirmed fix for the observed lockup.
> # Review notes: the scan is O(n) under trans_lock. Completion state is published
> # locklessly elsewhere; READ_ONCE provides a single load, not acquire ordering.
> # A multi-hour run did not reproduce the issue, but the lock owner and root cause
> # remain unknown.
> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> index e15e1855fa0a..0f05fabe451c 100644
> --- a/fs/btrfs/transaction.c
> +++ b/fs/btrfs/transaction.c
> @@ -865,6 +865,7 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
>                                      const enum btrfs_trans_state min_state)
>  {
>         struct btrfs_fs_info *fs_info = commit->fs_info;
> +       struct btrfs_transaction *next;
>         u64 transid = commit->transid;
>         bool put = false;
> 
> @@ -893,10 +894,16 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
>                  */
> 
>                 spin_lock(&fs_info->trans_lock);
> -               commit = list_first_entry_or_null(&fs_info->trans_list,
> -                                                 struct btrfs_transaction,
> -                                                 list);
> -               if (!commit || commit->transid > transid) {
> +               commit = NULL;
> +               list_for_each_entry(next, &fs_info->trans_list, list) {
> +                       if (next->transid > transid)
> +                               break;
> +                       if (READ_ONCE(next->state) == TRANS_STATE_COMPLETED)
> +                               continue;
> +                       commit = next;
> +                       break;
> +               }
> +               if (!commit) {
>                         spin_unlock(&fs_info->trans_lock);
>                         break;
>                 }
> 
> 
> Note the O(N) time here. To that end, this is very unlikely to be the
> correct fix, but I hope it highlights the core problem. I ran this
> patch for several hours with no apparent problem (versus seconds of a
> lockup in previous testing). 
> 
> If there's a later kernel patch I've missed, or something else I ought
> to be looking at or test or need more info, please let me know. 

PREEMPT_RT shouldn't do much different than !PREEMPT_RT. Do you have
tasks with different priorities? If that is the case then it might be
that one thread is blocking another thread that should make progress.
Your changes iterates over TRANS_STATE_COMPLETED so it seems that it is
not cleaned up for some reason.

Could you try if you can reproduce this with v7.3-rc7? This one does not
require any additional patches.
I'm not sure if this report would help the btrfs folks to recognize if
this is a solved problem or where the issue might be.

Sebastian

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [EXTERNAL] Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-13 14:41 ` Sebastian Andrzej Siewior
@ 2026-10-13 15:29   ` Naulls, Peter
  2026-10-13 15:52     ` Sebastian Andrzej Siewior
  0 siblings, 1 reply; 12+ messages in thread
From: Naulls, Peter @ 2026-10-13 15:29 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior; +Cc: linux-rt-users@vger.kernel.org

With apologies for Outlook formatting, it's not my first choice. 

From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>

> This sort of sounds like the state is TRANS_STATE_SUPER_COMMITTED but is
> not making progress after that. Are there any tasks involved that run at
> realtime priority and would block other tasks from making progress?

I won't say it's impossible, but it's not likely. We have a monolithic app which is disabled for this test. There 
may be other subsystems writing to the log. To the best of my knowledge though, there's been no adjustment
of priorities (we are experimenting with this, but it's inside the app and wasn't turned on for this test)  vs default 
Linux setup - it is Yocto using a largely standard setup. 

> PREEMPT_RT shouldn't do much different than !PREEMPT_RT. Do you have
> tasks with different priorities? If that is the case then it might be
> that one thread is blocking another thread that should make progress.
> Your changes iterates over TRANS_STATE_COMPLETED so it seems that it is
> not cleaned up for some reason.

As you say; this is not my expertise. 

> Could you try if you can reproduce this with v7.3-rc7? This one does not
> require any additional patches.
> I'm not sure if this report would help the btrfs folks to recognize if
> this is a solved problem or where the issue might be.

Trying a later kernel is not impossible, but it somewhat time consuming, I will have to strip a number of things from the build, 
and Yocto doesn't make this particularly straightforward, and might even compromise the test. We will update the kernel 
in due course, but you'll understand I'd prefer not to commit this time without some degree of confirmation here.  As things
stand right now, it may be that we just run without RT, but it would be good to at least verify this, and have a record for 
other people. 

As for brtfs, yes that was my next follow up, I had been just about to do that before your reply. 

Regards. 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [EXTERNAL] Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-13 15:29   ` [EXTERNAL] " Naulls, Peter
@ 2026-10-13 15:52     ` Sebastian Andrzej Siewior
  0 siblings, 0 replies; 12+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-10-13 15:52 UTC (permalink / raw)
  To: Naulls, Peter; +Cc: linux-rt-users@vger.kernel.org

On 2026-10-13 15:29:39 [+0000], Naulls, Peter wrote:
> With apologies for Outlook formatting, it's not my first choice. 
> 
> From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> 
> > This sort of sounds like the state is TRANS_STATE_SUPER_COMMITTED but is
> > not making progress after that. Are there any tasks involved that run at
> > realtime priority and would block other tasks from making progress?
> 
> I won't say it's impossible, but it's not likely. We have a monolithic app which is disabled for this test. There 
> may be other subsystems writing to the log. To the best of my knowledge though, there's been no adjustment
> of priorities (we are experimenting with this, but it's inside the app and wasn't turned on for this test)  vs default 
> Linux setup - it is Yocto using a largely standard setup. 

in that case ps should report just SCHED_OTHER for most applications and
SCHED_FIFO for the threaded interrupts and a few others.
The tasks that are running during the test-case should be all
SCHED_OTHER.

> > Could you try if you can reproduce this with v7.3-rc7? This one does not
> > require any additional patches.
> > I'm not sure if this report would help the btrfs folks to recognize if
> > this is a solved problem or where the issue might be.
> 
> Trying a later kernel is not impossible, but it somewhat time
> consuming, I will have to strip a number of things from the build, 
> and Yocto doesn't make this particularly straightforward, and might
> even compromise the test. We will update the kernel 
> in due course, but you'll understand I'd prefer not to commit this
> time without some degree of confirmation here.  As things
> stand right now, it may be that we just run without RT, but it would
> be good to at least verify this, and have a record for 
> other people. 

Right. You could try just the bare-kernel and yocto's .config without
anything else. And then just replace the kernel (and modules) on the
target. If this procedure can reproduce the bug on the latest kernel
then it is still around. Otherwise it is either fixed or something
changed and it does not reproduce anymore.

> As for brtfs, yes that was my next follow up, I had been just about to
> do that before your reply. 

I'm not a btrfs guy myself so I kind tell if this gives people enough
clues to as what is going on.

> Regards. 

Sebastian

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
       [not found] ` <SJ0PR17MB4288FC2E7FFB577F5D6C6D9EBB9E2@SJ0PR17MB4288.namprd17.prod.outlook.com>
@ 2026-10-13 17:33   ` Naulls, Peter
  2026-10-14 14:23     ` Daniel Vacek
                       ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: Naulls, Peter @ 2026-10-13 17:33 UTC (permalink / raw)
  To: linux-btrfs@vger.kernel.org

 
Originally posted to RT mailing list, see post and replies there:

https://lore.kernel.org/linux-rt-users/PH0PR17MB4277167A37B20340B231443FBB922@PH0PR17MB4277.namprd17.prod.outlook.com/T/#u

________________________________________
From: Naulls, Peter <PeterNaulls@eaton.com>
Sent: Friday, October 9, 2026 11:34 AM
To: linux-rt-users@vger.kernel.org <linux-rt-users@vger.kernel.org>
Subject: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
 
Long term kernel hacker, but very infrequent contributor. 

We are using the RT kernel patch on x86 under kernel 6.6.78-rt51. Specifically, this is the kernel from meta-intel in Yocto Scarthgap in the intel-kernel-rt recipe. 

It appears under some high load conditions, and especially triggered by calls to sync(), there is a live lock condition in the btrfs code. I used copilot for this analysis, with apologies due here where relevant. I'm familiar with kernel code, and I'm well aware how complex filesystem interactions can be. 

Here is the lock we observe:


sync (PID 52371)
  btrfs_commit_transaction+0xc48
    [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
  btrfs_sync_fs
  sync_fs_one_sb
  ksys_sync

ordered-I/O completion workers
  join_transaction+0x2b
    [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
  start_transaction
  btrfs_join_transaction
  btrfs_finish_one_ordered
  btrfs_work_helper

journal/fsync callers
  wait_current_trans+0x33
    [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
  start_transaction
  btrfs_start_transaction
  btrfs_sync_file
  vfs_fsync_range
  fsync

btrfs-transaction thread
  transaction_kthread+0x76
    [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
  kthread


The processes are then not killable, indicting a kernel thread problem. 

This was largely found because our userspace code calls sync() excessively (being separately addressed), although that's not the proximate cause. We ended up writing shell script which writes blocks of data to a brtfs partition over several threads and also calls sync and were able to repeat it within 10s of seconds. It is not repeatable in the non-RT kernel.

To add:

I have been extensively through the later kernels in 6.6, and later series and have not found any direct fixe or references for this. I've also been through the latest RT changes and btrfs mailing lists/changes (and this post might be served better there) and cannot find any direct reference to this. The only close match is:

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=99da896614d17e8a84aeb2b2d464ac046cc8633d

This is in related code. I put this patch into our 6.6.78 kernel and it did not resolve the problem. 

With that in mind, I had copilot (again, apologies) have an attempt. It came up with this:

# Experimental mitigation for a suspected PREEMPT_RT Btrfs transaction-waiter
# livelock. wait_for_commit() can reselect a completed transaction that remains
# on trans_list after its waiters are woken. Skip completed entries while still
# waiting for older incomplete transactions. This is a diagnostic hypothesis,
# not a confirmed fix for the observed lockup.
# Review notes: the scan is O(n) under trans_lock. Completion state is published
# locklessly elsewhere; READ_ONCE provides a single load, not acquire ordering.
# A multi-hour run did not reproduce the issue, but the lock owner and root cause
# remain unknown.
diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
index e15e1855fa0a..0f05fabe451c 100644
--- a/fs/btrfs/transaction.c
+++ b/fs/btrfs/transaction.c
@@ -865,6 +865,7 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
                                     const enum btrfs_trans_state min_state)
 {
        struct btrfs_fs_info *fs_info = commit->fs_info;
+       struct btrfs_transaction *next;
        u64 transid = commit->transid;
        bool put = false;

@@ -893,10 +894,16 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
                 */

                spin_lock(&fs_info->trans_lock);
-               commit = list_first_entry_or_null(&fs_info->trans_list,
-                                                 struct btrfs_transaction,
-                                                 list);
-               if (!commit || commit->transid > transid) {
+               commit = NULL;
+               list_for_each_entry(next, &fs_info->trans_list, list) {
+                       if (next->transid > transid)
+                               break;
+                       if (READ_ONCE(next->state) == TRANS_STATE_COMPLETED)
+                               continue;
+                       commit = next;
+                       break;
+               }
+               if (!commit) {
                        spin_unlock(&fs_info->trans_lock);
                        break;
                }


Note the O(N) time here. To that end, this is very unlikely to be the correct fix, but I hope it highlights the core problem. I ran this patch for several hours with no apparent problem (versus seconds of a lockup in previous testing).

If there's a later kernel patch I've missed, or something else I ought to be looking at or test or need more info, please let me know.

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-13 17:33   ` Naulls, Peter
@ 2026-10-14 14:23     ` Daniel Vacek
  2026-10-14 14:33     ` Daniel Vacek
  2026-10-21 11:05     ` Daniel Vacek
  2 siblings, 0 replies; 12+ messages in thread
From: Daniel Vacek @ 2026-10-14 14:23 UTC (permalink / raw)
  To: Naulls, Peter; +Cc: linux-btrfs@vger.kernel.org

On Tue, 13 Oct 2026 at 19:46, Naulls, Peter <PeterNaulls@eaton.com> wrote:
>
> Originally posted to RT mailing list, see post and replies there:
>
> https://lore.kernel.org/linux-rt-users/PH0PR17MB4277167A37B20340B231443FBB922@PH0PR17MB4277.namprd17.prod.outlook.com/T/#u
>
> ________________________________________
> From: Naulls, Peter <PeterNaulls@eaton.com>
> Sent: Friday, October 9, 2026 11:34 AM
> To: linux-rt-users@vger.kernel.org <linux-rt-users@vger.kernel.org>
> Subject: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
>
> Long term kernel hacker, but very infrequent contributor.
>
> We are using the RT kernel patch on x86 under kernel 6.6.78-rt51. Specifically, this is the kernel from meta-intel in Yocto Scarthgap in the intel-kernel-rt recipe.
>
> It appears under some high load conditions, and especially triggered by calls to sync(), there is a live lock condition in the btrfs code. I used copilot for this analysis, with apologies due here where relevant. I'm familiar with kernel code, and I'm well aware how complex filesystem interactions can be.
>
> Here is the lock we observe:
>
>
> sync (PID 52371)
>   btrfs_commit_transaction+0xc48
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   btrfs_sync_fs
>   sync_fs_one_sb
>   ksys_sync
>
> ordered-I/O completion workers
>   join_transaction+0x2b
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   start_transaction
>   btrfs_join_transaction
>   btrfs_finish_one_ordered
>   btrfs_work_helper
>
> journal/fsync callers
>   wait_current_trans+0x33
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   start_transaction
>   btrfs_start_transaction
>   btrfs_sync_file
>   vfs_fsync_range
>   fsync
>
> btrfs-transaction thread
>   transaction_kthread+0x76
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   kthread
>
>
> The processes are then not killable, indicting a kernel thread problem.
>
> This was largely found because our userspace code calls sync() excessively (being separately addressed), although that's not the proximate cause. We ended up writing shell script which writes blocks of data to a brtfs partition over several threads and also calls sync and were able to repeat it within 10s of seconds. It is not repeatable in the non-RT kernel.
>
> To add:
>
> I have been extensively through the later kernels in 6.6, and later series and have not found any direct fixe or references for this. I've also been through the latest RT changes and btrfs mailing lists/changes (and this post might be served better there) and cannot find any direct reference to this. The only close match is:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=99da896614d17e8a84aeb2b2d464ac046cc8633d
>
> This is in related code. I put this patch into our 6.6.78 kernel and it did not resolve the problem.
>
> With that in mind, I had copilot (again, apologies) have an attempt. It came up with this:
>
> # Experimental mitigation for a suspected PREEMPT_RT Btrfs transaction-waiter
> # livelock. wait_for_commit() can reselect a completed transaction that remains
> # on trans_list after its waiters are woken. Skip completed entries while still
> # waiting for older incomplete transactions. This is a diagnostic hypothesis,
> # not a confirmed fix for the observed lockup.
> # Review notes: the scan is O(n) under trans_lock. Completion state is published
> # locklessly elsewhere; READ_ONCE provides a single load, not acquire ordering.
> # A multi-hour run did not reproduce the issue, but the lock owner and root cause
> # remain unknown.

Right, the above does not explain why this only happens on RT.

Though the lock owner can be easily found using crash or drgn. One
could also expect it's task->sched_info.last_arrival timestamp would
be about close to those blocked tasks you found.

Would you mind sharing a vmcore (and (a link to) the debug symbols)?
Or eventualy the root access to the reproducer machine? (in case you
have an ephemeral VM)

I'll try to review the related code in the meantime.

--nX

> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> index e15e1855fa0a..0f05fabe451c 100644
> --- a/fs/btrfs/transaction.c
> +++ b/fs/btrfs/transaction.c
> @@ -865,6 +865,7 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
>                                      const enum btrfs_trans_state min_state)
>  {
>         struct btrfs_fs_info *fs_info = commit->fs_info;
> +       struct btrfs_transaction *next;
>         u64 transid = commit->transid;
>         bool put = false;
>
> @@ -893,10 +894,16 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
>                  */
>
>                 spin_lock(&fs_info->trans_lock);
> -               commit = list_first_entry_or_null(&fs_info->trans_list,
> -                                                 struct btrfs_transaction,
> -                                                 list);
> -               if (!commit || commit->transid > transid) {
> +               commit = NULL;
> +               list_for_each_entry(next, &fs_info->trans_list, list) {
> +                       if (next->transid > transid)
> +                               break;
> +                       if (READ_ONCE(next->state) == TRANS_STATE_COMPLETED)
> +                               continue;
> +                       commit = next;
> +                       break;
> +               }
> +               if (!commit) {
>                         spin_unlock(&fs_info->trans_lock);
>                         break;
>                 }
>
>
> Note the O(N) time here. To that end, this is very unlikely to be the correct fix, but I hope it highlights the core problem. I ran this patch for several hours with no apparent problem (versus seconds of a lockup in previous testing).
>
> If there's a later kernel patch I've missed, or something else I ought to be looking at or test or need more info, please let me know.
>

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-13 17:33   ` Naulls, Peter
  2026-10-14 14:23     ` Daniel Vacek
@ 2026-10-14 14:33     ` Daniel Vacek
  2026-10-21  7:41       ` Daniel Vacek
  2026-10-21 11:05     ` Daniel Vacek
  2 siblings, 1 reply; 12+ messages in thread
From: Daniel Vacek @ 2026-10-14 14:33 UTC (permalink / raw)
  To: Naulls, Peter; +Cc: linux-btrfs@vger.kernel.org

On Tue, 13 Oct 2026 at 19:46, Naulls, Peter <PeterNaulls@eaton.com> wrote:
>
> Originally posted to RT mailing list, see post and replies there:
>
> https://lore.kernel.org/linux-rt-users/PH0PR17MB4277167A37B20340B231443FBB922@PH0PR17MB4277.namprd17.prod.outlook.com/T/#u
>
> ________________________________________
> From: Naulls, Peter <PeterNaulls@eaton.com>
> Sent: Friday, October 9, 2026 11:34 AM
> To: linux-rt-users@vger.kernel.org <linux-rt-users@vger.kernel.org>
> Subject: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
>
> Long term kernel hacker, but very infrequent contributor.
>
> We are using the RT kernel patch on x86 under kernel 6.6.78-rt51. Specifically, this is the kernel from meta-intel in Yocto Scarthgap in the intel-kernel-rt recipe.
>
> It appears under some high load conditions, and especially triggered by calls to sync(), there is a live lock condition in the btrfs code. I used copilot for this analysis, with apologies due here where relevant. I'm familiar with kernel code, and I'm well aware how complex filesystem interactions can be.
>
> Here is the lock we observe:
>
>
> sync (PID 52371)
>   btrfs_commit_transaction+0xc48
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   btrfs_sync_fs
>   sync_fs_one_sb
>   ksys_sync
>
> ordered-I/O completion workers
>   join_transaction+0x2b
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   start_transaction
>   btrfs_join_transaction
>   btrfs_finish_one_ordered
>   btrfs_work_helper
>
> journal/fsync callers
>   wait_current_trans+0x33
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   start_transaction
>   btrfs_start_transaction
>   btrfs_sync_file
>   vfs_fsync_range
>   fsync
>
> btrfs-transaction thread
>   transaction_kthread+0x76
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   kthread

Neither of these seems to come from an interrupt. By any chance, have
you seen a "sleeping while atomic" error in dmesg?

--nX

> The processes are then not killable, indicting a kernel thread problem.
>
> This was largely found because our userspace code calls sync() excessively (being separately addressed), although that's not the proximate cause. We ended up writing shell script which writes blocks of data to a brtfs partition over several threads and also calls sync and were able to repeat it within 10s of seconds. It is not repeatable in the non-RT kernel.
>
> To add:
>
> I have been extensively through the later kernels in 6.6, and later series and have not found any direct fixe or references for this. I've also been through the latest RT changes and btrfs mailing lists/changes (and this post might be served better there) and cannot find any direct reference to this. The only close match is:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=99da896614d17e8a84aeb2b2d464ac046cc8633d
>
> This is in related code. I put this patch into our 6.6.78 kernel and it did not resolve the problem.
>
> With that in mind, I had copilot (again, apologies) have an attempt. It came up with this:
>
> # Experimental mitigation for a suspected PREEMPT_RT Btrfs transaction-waiter
> # livelock. wait_for_commit() can reselect a completed transaction that remains
> # on trans_list after its waiters are woken. Skip completed entries while still
> # waiting for older incomplete transactions. This is a diagnostic hypothesis,
> # not a confirmed fix for the observed lockup.
> # Review notes: the scan is O(n) under trans_lock. Completion state is published
> # locklessly elsewhere; READ_ONCE provides a single load, not acquire ordering.
> # A multi-hour run did not reproduce the issue, but the lock owner and root cause
> # remain unknown.
> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> index e15e1855fa0a..0f05fabe451c 100644
> --- a/fs/btrfs/transaction.c
> +++ b/fs/btrfs/transaction.c
> @@ -865,6 +865,7 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
>                                      const enum btrfs_trans_state min_state)
>  {
>         struct btrfs_fs_info *fs_info = commit->fs_info;
> +       struct btrfs_transaction *next;
>         u64 transid = commit->transid;
>         bool put = false;
>
> @@ -893,10 +894,16 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
>                  */
>
>                 spin_lock(&fs_info->trans_lock);
> -               commit = list_first_entry_or_null(&fs_info->trans_list,
> -                                                 struct btrfs_transaction,
> -                                                 list);
> -               if (!commit || commit->transid > transid) {
> +               commit = NULL;
> +               list_for_each_entry(next, &fs_info->trans_list, list) {
> +                       if (next->transid > transid)
> +                               break;
> +                       if (READ_ONCE(next->state) == TRANS_STATE_COMPLETED)
> +                               continue;
> +                       commit = next;
> +                       break;
> +               }
> +               if (!commit) {
>                         spin_unlock(&fs_info->trans_lock);
>                         break;
>                 }
>
>
> Note the O(N) time here. To that end, this is very unlikely to be the correct fix, but I hope it highlights the core problem. I ran this patch for several hours with no apparent problem (versus seconds of a lockup in previous testing).
>
> If there's a later kernel patch I've missed, or something else I ought to be looking at or test or need more info, please let me know.
>

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-14 14:33     ` Daniel Vacek
@ 2026-10-21  7:41       ` Daniel Vacek
  0 siblings, 0 replies; 12+ messages in thread
From: Daniel Vacek @ 2026-10-21  7:41 UTC (permalink / raw)
  To: Naulls, Peter; +Cc: linux-btrfs@vger.kernel.org

On Wed, 14 Oct 2026 at 16:33, Daniel Vacek <neelx@suse.com> wrote:
> On Tue, 13 Oct 2026 at 19:46, Naulls, Peter <PeterNaulls@eaton.com> wrote:
> >
> > Originally posted to RT mailing list, see post and replies there:
> >
> > https://lore.kernel.org/linux-rt-users/PH0PR17MB4277167A37B20340B231443FBB922@PH0PR17MB4277.namprd17.prod.outlook.com/T/#u
> >
> > ________________________________________
> > From: Naulls, Peter <PeterNaulls@eaton.com>
> > Sent: Friday, October 9, 2026 11:34 AM
> > To: linux-rt-users@vger.kernel.org <linux-rt-users@vger.kernel.org>
> > Subject: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
> >
> > Long term kernel hacker, but very infrequent contributor.
> >
> > We are using the RT kernel patch on x86 under kernel 6.6.78-rt51. Specifically, this is the kernel from meta-intel in Yocto Scarthgap in the intel-kernel-rt recipe.
> >
> > It appears under some high load conditions, and especially triggered by calls to sync(), there is a live lock condition in the btrfs code. I used copilot for this analysis, with apologies due here where relevant. I'm familiar with kernel code, and I'm well aware how complex filesystem interactions can be.
> >
> > Here is the lock we observe:
> >
> >
> > sync (PID 52371)
> >   btrfs_commit_transaction+0xc48
> >     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
> >   btrfs_sync_fs
> >   sync_fs_one_sb
> >   ksys_sync
> >
> > ordered-I/O completion workers
> >   join_transaction+0x2b
> >     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
> >   start_transaction
> >   btrfs_join_transaction
> >   btrfs_finish_one_ordered
> >   btrfs_work_helper
> >
> > journal/fsync callers
> >   wait_current_trans+0x33
> >     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
> >   start_transaction
> >   btrfs_start_transaction
> >   btrfs_sync_file
> >   vfs_fsync_range
> >   fsync
> >
> > btrfs-transaction thread
> >   transaction_kthread+0x76
> >     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
> >   kthread
>
> Neither of these seems to come from an interrupt. By any chance, have
> you seen a "sleeping while atomic" error in dmesg?

I reviewed the code from tag
lts-v6.6.78-rt51-preempt-rt-250304T022441Z in
https://github.com/intel/linux-intel-lts/commits/6.6/preempt-rt/ repo,
which I believe is the version you are using. Specifically I audited
all the usages of the `trans_lock`. Though I do not see any code which
could potentially block while holding the lock. There must be
something missing.

Anyways, perhaps you could get a hint from lockdep. Have you tried
enabling it and checking the log while the reproducer hangs?
Possibly, could you share the reproduces shell script you mentioned so
that I can confirm and debug locally, please?

--nX

> --nX
>
> > The processes are then not killable, indicting a kernel thread problem.
> >
> > This was largely found because our userspace code calls sync() excessively (being separately addressed), although that's not the proximate cause. We ended up writing shell script which writes blocks of data to a brtfs partition over several threads and also calls sync and were able to repeat it within 10s of seconds. It is not repeatable in the non-RT kernel.
> >
> > To add:
> >
> > I have been extensively through the later kernels in 6.6, and later series and have not found any direct fixe or references for this. I've also been through the latest RT changes and btrfs mailing lists/changes (and this post might be served better there) and cannot find any direct reference to this. The only close match is:
> >
> > https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=99da896614d17e8a84aeb2b2d464ac046cc8633d
> >
> > This is in related code. I put this patch into our 6.6.78 kernel and it did not resolve the problem.
> >
> > With that in mind, I had copilot (again, apologies) have an attempt. It came up with this:
> >
> > # Experimental mitigation for a suspected PREEMPT_RT Btrfs transaction-waiter
> > # livelock. wait_for_commit() can reselect a completed transaction that remains
> > # on trans_list after its waiters are woken. Skip completed entries while still
> > # waiting for older incomplete transactions. This is a diagnostic hypothesis,
> > # not a confirmed fix for the observed lockup.
> > # Review notes: the scan is O(n) under trans_lock. Completion state is published
> > # locklessly elsewhere; READ_ONCE provides a single load, not acquire ordering.
> > # A multi-hour run did not reproduce the issue, but the lock owner and root cause
> > # remain unknown.
> > diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> > index e15e1855fa0a..0f05fabe451c 100644
> > --- a/fs/btrfs/transaction.c
> > +++ b/fs/btrfs/transaction.c
> > @@ -865,6 +865,7 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
> >                                      const enum btrfs_trans_state min_state)
> >  {
> >         struct btrfs_fs_info *fs_info = commit->fs_info;
> > +       struct btrfs_transaction *next;
> >         u64 transid = commit->transid;
> >         bool put = false;
> >
> > @@ -893,10 +894,16 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
> >                  */
> >
> >                 spin_lock(&fs_info->trans_lock);
> > -               commit = list_first_entry_or_null(&fs_info->trans_list,
> > -                                                 struct btrfs_transaction,
> > -                                                 list);
> > -               if (!commit || commit->transid > transid) {
> > +               commit = NULL;
> > +               list_for_each_entry(next, &fs_info->trans_list, list) {
> > +                       if (next->transid > transid)
> > +                               break;
> > +                       if (READ_ONCE(next->state) == TRANS_STATE_COMPLETED)
> > +                               continue;
> > +                       commit = next;
> > +                       break;
> > +               }
> > +               if (!commit) {
> >                         spin_unlock(&fs_info->trans_lock);
> >                         break;
> >                 }
> >
> >
> > Note the O(N) time here. To that end, this is very unlikely to be the correct fix, but I hope it highlights the core problem. I ran this patch for several hours with no apparent problem (versus seconds of a lockup in previous testing).
> >
> > If there's a later kernel patch I've missed, or something else I ought to be looking at or test or need more info, please let me know.
> >

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-13 17:33   ` Naulls, Peter
  2026-10-14 14:23     ` Daniel Vacek
  2026-10-14 14:33     ` Daniel Vacek
@ 2026-10-21 11:05     ` Daniel Vacek
  2026-10-21 14:46       ` Naulls, Peter
  2 siblings, 1 reply; 12+ messages in thread
From: Daniel Vacek @ 2026-10-21 11:05 UTC (permalink / raw)
  To: Naulls, Peter; +Cc: linux-btrfs@vger.kernel.org

On Tue, 13 Oct 2026 at 19:46, Naulls, Peter <PeterNaulls@eaton.com> wrote:
>
> Originally posted to RT mailing list, see post and replies there:
>
> https://lore.kernel.org/linux-rt-users/PH0PR17MB4277167A37B20340B231443FBB922@PH0PR17MB4277.namprd17.prod.outlook.com/T/#u
>
> ________________________________________
> From: Naulls, Peter <PeterNaulls@eaton.com>
> Sent: Friday, October 9, 2026 11:34 AM
> To: linux-rt-users@vger.kernel.org <linux-rt-users@vger.kernel.org>
> Subject: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
>
> Long term kernel hacker, but very infrequent contributor.
>
> We are using the RT kernel patch on x86 under kernel 6.6.78-rt51. Specifically, this is the kernel from meta-intel in Yocto Scarthgap in the intel-kernel-rt recipe.
>
> It appears under some high load conditions, and especially triggered by calls to sync(), there is a live lock condition in the btrfs code. I used copilot for this analysis, with apologies due here where relevant. I'm familiar with kernel code, and I'm well aware how complex filesystem interactions can be.
>
> Here is the lock we observe:
>
>
> sync (PID 52371)
>   btrfs_commit_transaction+0xc48
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   btrfs_sync_fs
>   sync_fs_one_sb
>   ksys_sync
>
> ordered-I/O completion workers
>   join_transaction+0x2b
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   start_transaction
>   btrfs_join_transaction
>   btrfs_finish_one_ordered
>   btrfs_work_helper
>
> journal/fsync callers
>   wait_current_trans+0x33
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   start_transaction
>   btrfs_start_transaction
>   btrfs_sync_file
>   vfs_fsync_range
>   fsync
>
> btrfs-transaction thread
>   transaction_kthread+0x76
>     [return address immediately after rt_spin_lock(&fs_info->trans_lock)]
>   kthread
>
>
> The processes are then not killable, indicting a kernel thread problem.
>
> This was largely found because our userspace code calls sync() excessively (being separately addressed), although that's not the proximate cause. We ended up writing shell script which writes blocks of data to a brtfs partition over several threads and also calls sync and were able to repeat it within 10s of seconds. It is not repeatable in the non-RT kernel.

Thinking about it again, I don't think this is a livelock situation. I
think it's just a severe lock contention due to the number of
threads/CPUs fighting in a tight loop (due to the
btrfs_commit_transaction() and wait_for_commit() design) and as a
result the overal progress and throughput goes south.

Would you mind testing the below change? It passes my tests though I
was not able to reproduce the exact situation. I still believe it's
going to address it for you and restore the expected performance.

diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
index aa03db69a016..569f5a5c9b91 100644
--- a/fs/btrfs/transaction.c
+++ b/fs/btrfs/transaction.c
@@ -2536,6 +2536,10 @@ int btrfs_commit_transaction(struct
btrfs_trans_handle *trans)
  btrfs_clear_space_info_full(fs_info);

  fs_info->last_trans_committed = cur_trans->transid;
+
+ spin_lock(&fs_info->trans_lock);
+ list_del_init(&cur_trans->list);
+ spin_unlock(&fs_info->trans_lock);
  /*
  * We needn't acquire the lock here because there is no other task
  * which can change it.
@@ -2544,10 +2548,6 @@ int btrfs_commit_transaction(struct
btrfs_trans_handle *trans)
  wake_up(&cur_trans->commit_wait);
  btrfs_trans_state_lockdep_release(fs_info, BTRFS_LOCKDEP_TRANS_COMPLETED);

- spin_lock(&fs_info->trans_lock);
- list_del_init(&cur_trans->list);
- spin_unlock(&fs_info->trans_lock);
-
  btrfs_put_transaction(cur_trans);
  btrfs_put_transaction(cur_trans);


--nX

> To add:
>
> I have been extensively through the later kernels in 6.6, and later series and have not found any direct fixe or references for this. I've also been through the latest RT changes and btrfs mailing lists/changes (and this post might be served better there) and cannot find any direct reference to this. The only close match is:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=99da896614d17e8a84aeb2b2d464ac046cc8633d
>
> This is in related code. I put this patch into our 6.6.78 kernel and it did not resolve the problem.
>
> With that in mind, I had copilot (again, apologies) have an attempt. It came up with this:
>
> # Experimental mitigation for a suspected PREEMPT_RT Btrfs transaction-waiter
> # livelock. wait_for_commit() can reselect a completed transaction that remains
> # on trans_list after its waiters are woken. Skip completed entries while still
> # waiting for older incomplete transactions. This is a diagnostic hypothesis,
> # not a confirmed fix for the observed lockup.
> # Review notes: the scan is O(n) under trans_lock. Completion state is published
> # locklessly elsewhere; READ_ONCE provides a single load, not acquire ordering.
> # A multi-hour run did not reproduce the issue, but the lock owner and root cause
> # remain unknown.
> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> index e15e1855fa0a..0f05fabe451c 100644
> --- a/fs/btrfs/transaction.c
> +++ b/fs/btrfs/transaction.c
> @@ -865,6 +865,7 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
>                                      const enum btrfs_trans_state min_state)
>  {
>         struct btrfs_fs_info *fs_info = commit->fs_info;
> +       struct btrfs_transaction *next;
>         u64 transid = commit->transid;
>         bool put = false;
>
> @@ -893,10 +894,16 @@ static noinline void wait_for_commit(struct btrfs_transaction *commit,
>                  */
>
>                 spin_lock(&fs_info->trans_lock);
> -               commit = list_first_entry_or_null(&fs_info->trans_list,
> -                                                 struct btrfs_transaction,
> -                                                 list);
> -               if (!commit || commit->transid > transid) {
> +               commit = NULL;
> +               list_for_each_entry(next, &fs_info->trans_list, list) {
> +                       if (next->transid > transid)
> +                               break;
> +                       if (READ_ONCE(next->state) == TRANS_STATE_COMPLETED)
> +                               continue;
> +                       commit = next;
> +                       break;
> +               }
> +               if (!commit) {
>                         spin_unlock(&fs_info->trans_lock);
>                         break;
>                 }
>
>
> Note the O(N) time here. To that end, this is very unlikely to be the correct fix, but I hope it highlights the core problem. I ran this patch for several hours with no apparent problem (versus seconds of a lockup in previous testing).
>
> If there's a later kernel patch I've missed, or something else I ought to be looking at or test or need more info, please let me know.
>

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-21 11:05     ` Daniel Vacek
@ 2026-10-21 14:46       ` Naulls, Peter
  2026-10-21 15:50         ` Sebastian Andrzej Siewior
  0 siblings, 1 reply; 12+ messages in thread
From: Naulls, Peter @ 2026-10-21 14:46 UTC (permalink / raw)
  To: Daniel Vacek, Sebastian Andrzej Siewior
  Cc: linux-btrfs@vger.kernel.org, linux-rt-users@vger.kernel.org


Sorry for the delay, I had some other tasks and wanted to do some more rigorous testing on a recent kernel and address pieces from both mailing lists. I'm going to mix up replies here in Outlook, so bear with me. 


From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> Could you try if you can reproduce this with v7.3-rc7? This one does not require any additional patches.

For the testing below, I'm now using 7.3 which was released just a few days ago. I haven't gone back to the 6.6 kernel with the suggested patch. 


> From: Daniel Vacek <neelx@suse.com
> Thinking about it again, I don't think this is a livelock situation. I
> think it's just a severe lock contention due to the number of
> threads/CPUs fighting in a tight loop (due to the
> btrfs_commit_transaction() and wait_for_commit() design) and as a
> result the overall progress and throughput goes south.

Maybe. In the isolated testing I did, it certainly looks like that - the contention eventually clears, but in the meantime there's
very high CPU usage, apparently from the sync command, but traced to the sync() handling in brtfs.  Copilot believes that might not
be the full story, but I'll get to that in a moment. 

> Would you mind testing the below change? It passes my tests though I
> was not able to reproduce the exact situation. I still believe it's
> going to address it for you and restore the expected performance.

In limited testing, this appears to resolve the problem under 7.3, where as I was able to repeat it within a few seconds otherwise. 

[snip]

So to clarify, this is on 7.3 release, on a btrfs loopback mount in /tmp, it doesn't need actual flash backing:

-----
#!/bin/sh
# Create and mount the RAM-backed (tmpfs /tmp) Btrfs loopback test filesystem.
set -eu

image=/tmp/btrfs-loop-test.img
target=/tmp/btrfs-loop-test

[ "$(id -u)" -eq 0 ] || { echo "Must run as root" >&2; exit 1; }
for path in "$image" "$target"; do
    if [ -e "$path" ] || [ -L "$path" ]; then
        echo "$path already exists; reboot or remove it first" >&2
        exit 1
    fi
done

( set -C; : > "$image" )
dd if=/dev/zero of="$image" bs=1048576 count=256 2>/dev/null
mkfs.btrfs -q -n 16384 -s 4096 -d single -m dup -O ^block-group-tree "$image"
mkdir "$target"
mount -t btrfs -o loop,noatime,compress=zstd:3,ssd,discard=async,noacl,space_cache=v2 \
    "$image" "$target"

resolved=$(readlink -f "$target")
awk -v t="$resolved" '$2 == t {print; found=1} END {if (!found) exit 1}' /proc/mounts
df -h "$target"
----

This is the minimal test case I came up with below. 

Typically run with (after creating the mount of course):

/home/root/btrfs-minimal.sh /tmp/btrfs-loop-test 4 1000 4

Failure is typically at iteration 130 with these parameters. It's possible to reproduce with "2 1000 2", although that particular case seems intermittent. 
Observe "sync" in top using CPU. 

----
#!/bin/sh
set -eu

target=${1:?Usage: btrfs-minimal.sh /mount/point [workers] [rounds] [rt_sync_helpers]}
workers=${2:-4}
rounds=${3:-1000}
helpers=${4:-4}
for value in "$workers" "$rounds" "$helpers"; do
    case "$value" in
        ''|*[!0-9]*) echo "Counts must be positive integers" >&2; exit 1 ;;
    esac
    [ "$value" -gt 0 ] || exit 1
done
[ -d "$target" ] && [ "$(id -u)" -eq 0 ] || exit 1
chrt -p $$ >/dev/null
pgrep -V >/dev/null
chrt -r 25 /bin/true
[ -x /usr/bin/sync ] || exit 1

testdir=$(mktemp -d "$target/btrfs-minimal.XXXXXX")
writer_pids=
helper_pids=

cleanup()
{
    trap - EXIT INT TERM
    for pid in $helper_pids; do
        chrt -o -p 0 "$pid" >/dev/null 2>&1 || true
        for child in $(pgrep -P "$pid" 2>/dev/null || true); do
            chrt -o -p 0 "$child" >/dev/null 2>&1 || true
        done
    done
    for pid in $helper_pids $writer_pids; do
        for child in $(pgrep -P "$pid" 2>/dev/null || true); do
            kill "$child" 2>/dev/null || true
        done
        kill "$pid" 2>/dev/null || true
    done
    for pid in $helper_pids $writer_pids; do
        wait "$pid" 2>/dev/null || true
    done
    rm -rf "$testdir"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

printf 'WARNING: global sync can stall any mounted filesystem; recovery may require a reboot.\n'
printf 'Directory: %s; normal writers: %s; rounds: %s; RR/25 sync helpers: %s\n' \
    "$testdir" "$workers" "$rounds" "$helpers"

helper=1
while [ "$helper" -le "$helpers" ]; do
    chrt -r 25 /bin/sh -c 'set -e; while :; do /usr/bin/sync; done' &
    helper_pids="$helper_pids $!"
    helper=$((helper + 1))
done

worker=1
while [ "$worker" -le "$workers" ]; do
    chrt -o 0 /bin/sh -c '
        set -eu
        round=1
        while [ "$round" -le "$2" ]; do
            if ! dd if=/dev/urandom of="$1" bs=262144 count=16 2>/dev/null; then
                printf "Write failed: %s, round %s\n" "$1" "$round" >&2
                exit 1
            fi
            /usr/bin/sync
            if [ $((round % 10)) -eq 0 ]; then
                printf "%s: round %s/%s\n" "$1" "$round" "$2"
            fi
            round=$((round + 1))
        done
    ' writer "$testdir/worker-$worker" "$rounds" &
    writer_pids="$writer_pids $!"
    worker=$((worker + 1))
done

status=0
for pid in $writer_pids; do
    wait "$pid" || status=1
done
---

So yes, your suggested patch appears to resolve it, I ran with 10000 runs and was not able to repeat. I will repeat this on our 6.6 kernel where I think there are some RT differences. 

As to why this needed an RT kernel, from copilot (so handfuls of salt):

"The RT kernel changes how trans_lock behaves. In btrfs it's an ordinary kernel spinlock, and PREEMPT_RT turns kernel spinlocks into priority-ordered sleeping locks. That change turns the loop into a starvation.

Without PREEMPT_RT, trans_lock is a real spinlock:

Tasks waiting for it are served in arrival order, regardless of priority. Once the committer asks for the lock, it gets it after the tasks already ahead of it.
Taking the lock disables preemption, so the committer can't be preempted between getting the lock and removing the transaction from the list.
The SCHED_RR waiters still loop, re-finding the finished transaction, until the committer removes it. But the committer gets through quickly, so the loop is short. The worst case is the contention and lost throughput the maintainer describes.

With PREEMPT_RT, spin_lock() uses an rtmutex. Three things change:

Waiters queue by priority, not arrival. Each looping SCHED_RR/25 waiter goes ahead of the SCHED_OTHER committer.
A higher-priority task can take the lock from a woken waiter. When the lock is released and the committer is woken to take it, an RT waiter that asks before the committer runs can grab it first. The RT waiters ask constantly, because their loop never sleeps: each finds the finished transaction still listed, sees it's complete, and goes back for the lock.
Everything is preemptible. The committer, and anyone else holding the lock, can be preempted by RT tasks at any point.
The RT waiters keep the lock in their own hands, while the one task that could end their loop waits behind them indefinitely. That's the livelock."


And as to why it thinks this might be an incomplete fix:

"The error cleanup path has the same bug and isn't fixed. btrfs_cleanup_one_transaction sets COMPLETED and wakes waiters. btrfs_cleanup_transaction only removes the transaction from the list afterwards. RT waiters in wait_for_commit can loop there exactly as before. It only runs after a filesystem error, so it's rare, but it's the same livelock."

I will now go and repeat this testing and patch on our 6.6 kernel. 


Regards, and thank you. 









^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-21 14:46       ` Naulls, Peter
@ 2026-10-21 15:50         ` Sebastian Andrzej Siewior
  2026-10-21 17:48           ` Naulls, Peter
  0 siblings, 1 reply; 12+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-10-21 15:50 UTC (permalink / raw)
  To: Naulls, Peter
  Cc: Daniel Vacek, linux-btrfs@vger.kernel.org,
	linux-rt-users@vger.kernel.org

nice to see that you have made progress.

On 2026-10-21 14:46:34 [+0000], Naulls, Peter wrote:
> As to why this needed an RT kernel, from copilot (so handfuls of salt):
> 
> "The RT kernel changes how trans_lock behaves. In btrfs it's an
> ordinary kernel spinlock, and PREEMPT_RT turns kernel spinlocks into
> priority-ordered sleeping locks. That change turns the loop into a
> starvation.
> 
> Without PREEMPT_RT, trans_lock is a real spinlock:
> 
> Tasks waiting for it are served in arrival order, regardless of
> priority. Once the committer asks for the lock, it gets it after the
> tasks already ahead of it.
> Taking the lock disables preemption, so the committer can't be
> preempted between getting the lock and removing the transaction from
> the list.
> The SCHED_RR waiters still loop, re-finding the finished transaction,
> until the committer removes it. But the committer gets through
> quickly, so the loop is short. The worst case is the contention and
> lost throughput the maintainer describes.
> 
> With PREEMPT_RT, spin_lock() uses an rtmutex. Three things change:
> 
> Waiters queue by priority, not arrival. Each looping SCHED_RR/25
> waiter goes ahead of the SCHED_OTHER committer.
This is correct.

> A higher-priority task can take the lock from a woken waiter. When the
> lock is released and the committer is woken to take it, an RT waiter
> that asks before the committer runs can grab it first. The RT waiters
> ask constantly, because their loop never sleeps: each finds the
> finished transaction still listed, sees it's complete, and goes back
> for the lock.
> Everything is preemptible. The committer, and anyone else holding the
> lock, can be preempted by RT tasks at any point.
> The RT waiters keep the lock in their own hands, while the one task
> that could end their loop waits behind them indefinitely. That's the
> livelock."

That sounds plausible. I did ask if RT tasks are involved and I think
the answer as no. With just SCHED_OTHER tasks it behaves as spinlock_t
on !PREEMPT_RT (except for the preemption which should have no impact).

Sebastian

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78
  2026-10-21 15:50         ` Sebastian Andrzej Siewior
@ 2026-10-21 17:48           ` Naulls, Peter
  0 siblings, 0 replies; 12+ messages in thread
From: Naulls, Peter @ 2026-10-21 17:48 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: Daniel Vacek, linux-btrfs@vger.kernel.org,
	linux-rt-users@vger.kernel.org


From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>

> That sounds plausible. I did ask if RT tasks are involved and I think
> the answer as no. With just SCHED_OTHER tasks it behaves as spinlock_t
> on !PREEMPT_RT (except for the preemption which should have no impact).

Yes, we are not doing anything special RT wise apart from the kernel enablement. 

I put Daniel's patch into 6.6 and ran it 100000 times against a ramfs btrfs image
and then thousands more times against a flash backed btrfs mount and no problems. 

I'll await the final word from him on this before committing to it for our work. 

As some background, it appears the bug was introduced in early 2022:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5fd76bf31ccfecc06e2e6b29f8c809e934085b99

And is present in all kernels from 5.15 on. 



^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-10-21 17:48 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-09 15:34 Btrfs PREEMPT_RT transaction-waiter livelock: experimental mitigation in kernel 6.6.78 Naulls, Peter
2026-10-13 14:41 ` Sebastian Andrzej Siewior
2026-10-13 15:29   ` [EXTERNAL] " Naulls, Peter
2026-10-13 15:52     ` Sebastian Andrzej Siewior
     [not found] ` <SJ0PR17MB4288FC2E7FFB577F5D6C6D9EBB9E2@SJ0PR17MB4288.namprd17.prod.outlook.com>
2026-10-13 17:33   ` Naulls, Peter
2026-10-14 14:23     ` Daniel Vacek
2026-10-14 14:33     ` Daniel Vacek
2026-10-21  7:41       ` Daniel Vacek
2026-10-21 11:05     ` Daniel Vacek
2026-10-21 14:46       ` Naulls, Peter
2026-10-21 15:50         ` Sebastian Andrzej Siewior
2026-10-21 17:48           ` Naulls, Peter

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.