* [RFC v3 0/2] ext4: fast commit: fix lockdep issues @ 2025-12-24 3:29 Li Chen 2025-12-24 3:29 ` [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep Li Chen 2025-12-24 3:29 ` [RFC v3 2/2] ext4: fast commit: fix s_fc_lock vs i_data_sem inversion Li Chen 0 siblings, 2 replies; 9+ messages in thread From: Li Chen @ 2025-12-24 3:29 UTC (permalink / raw) To: Theodore Ts'o, Andreas Dilger, linux-ext4, linux-kernel Hi, (This RFC v3 series is based on top of the patch posted at https://lore.kernel.org/linux-ext4/20251223131342.287864-1-me@linux.beauty/T/#u) This series fixes two lockdep issues in the ext4 fast commit paths. 1) ext4_fc_track_inode() can return without sleeping when EXT4_STATE_FC_COMMITTING is already clear. The lockdep assertion for i_data_sem should only fire when we actually go to sleep. 2) lockdep reports a possible deadlock due to lock order inversion between s_fc_lock and i_data_sem. The fast commit writer held s_fc_lock while writing the fast commit log. Writing the journal inode mapping can call ext4_map_blocks() and take i_data_sem, while metadata update paths can hold i_data_sem and call ext4_fc_track_inode() which takes s_fc_lock. The fix drops s_fc_lock before the log writing step and uses EXT4_STATE_FC_COMMITTING to keep inode and create dentry state stable until cleanup. Testing: - QEMU VM, ext4 -O fast_commit on virtio-pmem + dax, verified both lockdep report reproduces on an older kernel and is gone with this series. RFC v2 -> RFC v3: - rebase ontop of https://lore.kernel.org/linux-ext4/20251223131342.287864-1-me@linux.beauty/T/#u RFC v1 -> RFC v2: - patch 1: move comments to correct place - patch 2: add it to patchset. - add missing RFC prefix RFC v1: https://lore.kernel.org/linux-ext4/20251222032655.87056-1-me@linux.beauty/T/#u RFC v2: https://lore.kernel.org/linux-ext4/20251222151906.24607-1-me@linux.beauty/T/#t Li Chen (2): ext4: fast_commit: assert i_data_sem only before sleep ext4: fast commit: fix s_fc_lock vs i_data_sem inversion fs/ext4/fast_commit.c | 96 +++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 27 deletions(-) -- 2.52.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep 2025-12-24 3:29 [RFC v3 0/2] ext4: fast commit: fix lockdep issues Li Chen @ 2025-12-24 3:29 ` Li Chen 2026-01-05 12:18 ` Zhang Yi 2025-12-24 3:29 ` [RFC v3 2/2] ext4: fast commit: fix s_fc_lock vs i_data_sem inversion Li Chen 1 sibling, 1 reply; 9+ messages in thread From: Li Chen @ 2025-12-24 3:29 UTC (permalink / raw) To: Theodore Ts'o, Andreas Dilger, linux-ext4, linux-kernel; +Cc: Li Chen ext4_fc_track_inode() can return without sleeping when EXT4_STATE_FC_COMMITTING is already clear. The lockdep assertion for ei->i_data_sem was done unconditionally before the wait loop, which can WARN in call paths that hold i_data_sem even though we never block. Move lockdep_assert_not_held(&ei->i_data_sem) into the actual sleep path, right before schedule(). Signed-off-by: Li Chen <me@linux.beauty> --- fs/ext4/fast_commit.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c index d0926967d086..b0c458082997 100644 --- a/fs/ext4/fast_commit.c +++ b/fs/ext4/fast_commit.c @@ -566,13 +566,6 @@ void ext4_fc_track_inode(handle_t *handle, struct inode *inode) if (ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)) return; - /* - * If we come here, we may sleep while waiting for the inode to - * commit. We shouldn't be holding i_data_sem when we go to sleep since - * the commit path needs to grab the lock while committing the inode. - */ - lockdep_assert_not_held(&ei->i_data_sem); - while (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) { #if (BITS_PER_LONG < 64) DEFINE_WAIT_BIT(wait, &ei->i_state_flags, @@ -586,8 +579,16 @@ void ext4_fc_track_inode(handle_t *handle, struct inode *inode) EXT4_STATE_FC_COMMITTING); #endif prepare_to_wait(wq, &wait.wq_entry, TASK_UNINTERRUPTIBLE); - if (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) + if (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) { + /* + * We might sleep while waiting for the inode to commit. + * We shouldn't be holding i_data_sem when we go to sleep + * since the commit path may grab it while committing this + * inode. + */ + lockdep_assert_not_held(&ei->i_data_sem); schedule(); + } finish_wait(wq, &wait.wq_entry); } -- 2.52.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep 2025-12-24 3:29 ` [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep Li Chen @ 2026-01-05 12:18 ` Zhang Yi 2026-01-06 12:18 ` Li Chen 2026-01-07 14:19 ` Li Chen 0 siblings, 2 replies; 9+ messages in thread From: Zhang Yi @ 2026-01-05 12:18 UTC (permalink / raw) To: Li Chen, linux-ext4; +Cc: Theodore Ts'o, Andreas Dilger, linux-kernel Hi Li, On 12/24/2025 11:29 AM, Li Chen wrote: > ext4_fc_track_inode() can return without sleeping when > EXT4_STATE_FC_COMMITTING is already clear. The lockdep assertion for > ei->i_data_sem was done unconditionally before the wait loop, which can > WARN in call paths that hold i_data_sem even though we never block. Move > lockdep_assert_not_held(&ei->i_data_sem) into the actual sleep path, > right before schedule(). > > Signed-off-by: Li Chen <me@linux.beauty> Thank you for the fix patch! However, the solution does not seem to fix the issue. IIUC, the root cause of this issue is the following race condition (show only one case), and it may cause a real ABBA dead lock issue. ext4_map_blocks() hold i_data_sem // <- A ext4_mb_new_blocks() ext4_dirty_inode() ext4_fc_commit() ext4_fc_perform_commit() set EXT4_STATE_FC_COMMITTING <-B ext4_fc_write_inode_data() ext4_map_blocks() hold i_data_sem // <- A ext4_fc_track_inode() wait EXT4_STATE_FC_COMMITTING <- B jbd2_fc_end_commit() ext4_fc_cleanup() clear EXT4_STATE_FC_COMMITTING() Postponing the lockdep assertion to the point where sleeping is actually necessary does not resolve this deadlock issue, it merely masks the problem, right? I currently don't quite understand why only ext4_fc_track_inode() needs to wait for the inode being fast committed to be completed, instead of adding it to the FC_Q_STAGING list like other tracking operations. So now I don't have a good idea to fix this problem either. Perhaps we need to rethink the necessity of this waiting, or find a way to avoid acquiring i_data_sem during fast commit. Thanks, Yi. > --- > fs/ext4/fast_commit.c | 17 +++++++++-------- > 1 file changed, 9 insertions(+), 8 deletions(-) > > diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c > index d0926967d086..b0c458082997 100644 > --- a/fs/ext4/fast_commit.c > +++ b/fs/ext4/fast_commit.c > @@ -566,13 +566,6 @@ void ext4_fc_track_inode(handle_t *handle, struct inode *inode) > if (ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)) > return; > > - /* > - * If we come here, we may sleep while waiting for the inode to > - * commit. We shouldn't be holding i_data_sem when we go to sleep since > - * the commit path needs to grab the lock while committing the inode. > - */ > - lockdep_assert_not_held(&ei->i_data_sem); > - > while (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) { > #if (BITS_PER_LONG < 64) > DEFINE_WAIT_BIT(wait, &ei->i_state_flags, > @@ -586,8 +579,16 @@ void ext4_fc_track_inode(handle_t *handle, struct inode *inode) > EXT4_STATE_FC_COMMITTING); > #endif > prepare_to_wait(wq, &wait.wq_entry, TASK_UNINTERRUPTIBLE); > - if (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) > + if (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) { > + /* > + * We might sleep while waiting for the inode to commit. > + * We shouldn't be holding i_data_sem when we go to sleep > + * since the commit path may grab it while committing this > + * inode. > + */ > + lockdep_assert_not_held(&ei->i_data_sem); > schedule(); > + } > finish_wait(wq, &wait.wq_entry); > } > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep 2026-01-05 12:18 ` Zhang Yi @ 2026-01-06 12:18 ` Li Chen 2026-01-07 2:00 ` Zhang Yi 2026-01-07 14:19 ` Li Chen 1 sibling, 1 reply; 9+ messages in thread From: Li Chen @ 2026-01-06 12:18 UTC (permalink / raw) To: Zhang Yi; +Cc: linux-ext4, Theodore Ts'o, Andreas Dilger, linux-kernel Hi Zhang Yi, ---- On Mon, 05 Jan 2026 20:18:42 +0800 Zhang Yi <yi.zhang@huaweicloud.com> wrote --- > Hi Li, > > On 12/24/2025 11:29 AM, Li Chen wrote: > > ext4_fc_track_inode() can return without sleeping when > > EXT4_STATE_FC_COMMITTING is already clear. The lockdep assertion for > > ei->i_data_sem was done unconditionally before the wait loop, which can > > WARN in call paths that hold i_data_sem even though we never block. Move > > lockdep_assert_not_held(&ei->i_data_sem) into the actual sleep path, > > right before schedule(). > > > > Signed-off-by: Li Chen <me@linux.beauty> > > Thank you for the fix patch! However, the solution does not seem to fix > the issue. IIUC, the root cause of this issue is the following race > condition (show only one case), and it may cause a real ABBA dead lock > issue. > > ext4_map_blocks() > hold i_data_sem // <- A > ext4_mb_new_blocks() > ext4_dirty_inode() > ext4_fc_commit() > ext4_fc_perform_commit() > set EXT4_STATE_FC_COMMITTING <-B > ext4_fc_write_inode_data() > ext4_map_blocks() > hold i_data_sem // <- A > ext4_fc_track_inode() > wait EXT4_STATE_FC_COMMITTING <- B > jbd2_fc_end_commit() > ext4_fc_cleanup() > clear EXT4_STATE_FC_COMMITTING() > > Postponing the lockdep assertion to the point where sleeping is actually > necessary does not resolve this deadlock issue, it merely masks the > problem, right? > > I currently don't quite understand why only ext4_fc_track_inode() needs > to wait for the inode being fast committed to be completed, instead of > adding it to the FC_Q_STAGING list like other tracking operations. So > now I don't have a good idea to fix this problem either. Perhaps we > need to rethink the necessity of this waiting, or find a way to avoid > acquiring i_data_sem during fast commit. Thanks a lot for your kind review! I'll provide feedback tomorrow. Regards, Li ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep 2026-01-06 12:18 ` Li Chen @ 2026-01-07 2:00 ` Zhang Yi 2026-01-07 14:30 ` Li Chen 0 siblings, 1 reply; 9+ messages in thread From: Zhang Yi @ 2026-01-07 2:00 UTC (permalink / raw) To: Li Chen; +Cc: linux-ext4, Theodore Ts'o, Andreas Dilger, linux-kernel On 1/6/2026 8:18 PM, Li Chen wrote: > Hi Zhang Yi, > > ---- On Mon, 05 Jan 2026 20:18:42 +0800 Zhang Yi <yi.zhang@huaweicloud.com> wrote --- > > Hi Li, > > > > On 12/24/2025 11:29 AM, Li Chen wrote: > > > ext4_fc_track_inode() can return without sleeping when > > > EXT4_STATE_FC_COMMITTING is already clear. The lockdep assertion for > > > ei->i_data_sem was done unconditionally before the wait loop, which can > > > WARN in call paths that hold i_data_sem even though we never block. Move > > > lockdep_assert_not_held(&ei->i_data_sem) into the actual sleep path, > > > right before schedule(). > > > > > > Signed-off-by: Li Chen <me@linux.beauty> > > > > Thank you for the fix patch! However, the solution does not seem to fix > > the issue. IIUC, the root cause of this issue is the following race > > condition (show only one case), and it may cause a real ABBA dead lock > > issue. > > > > ext4_map_blocks() > > hold i_data_sem // <- A > > ext4_mb_new_blocks() > > ext4_dirty_inode() > > ext4_fc_commit() > > ext4_fc_perform_commit() > > set EXT4_STATE_FC_COMMITTING <-B > > ext4_fc_write_inode_data() > > ext4_map_blocks() > > hold i_data_sem // <- A > > ext4_fc_track_inode() > > wait EXT4_STATE_FC_COMMITTING <- B > > jbd2_fc_end_commit() > > ext4_fc_cleanup() > > clear EXT4_STATE_FC_COMMITTING() > > > > Postponing the lockdep assertion to the point where sleeping is actually > > necessary does not resolve this deadlock issue, it merely masks the > > problem, right? > > > > I currently don't quite understand why only ext4_fc_track_inode() needs > > to wait for the inode being fast committed to be completed, instead of > > adding it to the FC_Q_STAGING list like other tracking operations. It seems that the inode metadata of the tracked inode was not recorded during the __track_inode(), so the inode metadata committed at commit time reflects real-time data. However, the current ext4_fc_perform_commit() lacks concurrency control, allowing other processes to simultaneously initiate new handles that modify the inode metadata while the previous metadata is being fast committed. Therefore, to prevent recording newly changed inode metadata during the old commit phase, the ext4_fc_track_inode() function must wait for the ongoing commit process to complete before modifying. > > So > > now I don't have a good idea to fix this problem either. Perhaps we > > need to rethink the necessity of this waiting, or find a way to avoid > > acquiring i_data_sem during fast commit. Ha, the solution seems to have already been listed in the TODOs in fast_commit.c. Change ext4_fc_commit() to lookup logical to physical mapping using extent status tree. This would get rid of the need to call ext4_fc_track_inode() before acquiring i_data_sem. To do that we would need to ensure that modified extents from the extent status tree are not evicted from memory. Alternatively, recording the mapped range of tracking might also be feasible. Thanks, Yi. > > Thanks a lot for your kind review! I'll provide feedback tomorrow. > > Regards, > Li > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep 2026-01-07 2:00 ` Zhang Yi @ 2026-01-07 14:30 ` Li Chen 2026-01-08 3:00 ` Zhang Yi 0 siblings, 1 reply; 9+ messages in thread From: Li Chen @ 2026-01-07 14:30 UTC (permalink / raw) To: Zhang Yi; +Cc: linux-ext4, Theodore Ts'o, Andreas Dilger, linux-kernel Hi Zhang, Thanks a lot for your detailed review! ---- On Wed, 07 Jan 2026 10:00:23 +0800 Zhang Yi <yi.zhang@huaweicloud.com> wrote --- > On 1/6/2026 8:18 PM, Li Chen wrote: > > Hi Zhang Yi, > > > > ---- On Mon, 05 Jan 2026 20:18:42 +0800 Zhang Yi <yi.zhang@huaweicloud.com> wrote --- > > > Hi Li, > > > > > > On 12/24/2025 11:29 AM, Li Chen wrote: > > > > ext4_fc_track_inode() can return without sleeping when > > > > EXT4_STATE_FC_COMMITTING is already clear. The lockdep assertion for > > > > ei->i_data_sem was done unconditionally before the wait loop, which can > > > > WARN in call paths that hold i_data_sem even though we never block. Move > > > > lockdep_assert_not_held(&ei->i_data_sem) into the actual sleep path, > > > > right before schedule(). > > > > > > > > Signed-off-by: Li Chen <me@linux.beauty> > > > > > > Thank you for the fix patch! However, the solution does not seem to fix > > > the issue. IIUC, the root cause of this issue is the following race > > > condition (show only one case), and it may cause a real ABBA dead lock > > > issue. > > > > > > ext4_map_blocks() > > > hold i_data_sem // <- A > > > ext4_mb_new_blocks() > > > ext4_dirty_inode() > > > ext4_fc_commit() > > > ext4_fc_perform_commit() > > > set EXT4_STATE_FC_COMMITTING <-B > > > ext4_fc_write_inode_data() > > > ext4_map_blocks() > > > hold i_data_sem // <- A > > > ext4_fc_track_inode() > > > wait EXT4_STATE_FC_COMMITTING <- B > > > jbd2_fc_end_commit() > > > ext4_fc_cleanup() > > > clear EXT4_STATE_FC_COMMITTING() > > > I think the ABBA reasoning is plausible: if a caller violates the ordering contract and enters ext4_fc_track_inode() while holding i_data_sem, and the recheck still finds EXT4_STATE_FC_COMMITTING set (so we actually schedule()), then we can get A -> wait(B). If the commit task, while holding the inode in COMMITTING, still needs i_data_sem (e.g. via mapping/log writing), that gives B -> wait(A), forming a cycle. > > > Postponing the lockdep assertion to the point where sleeping is actually > > > necessary does not resolve this deadlock issue, it merely masks the > > > problem, right? > > > > > > I currently don't quite understand why only ext4_fc_track_inode() needs > > > to wait for the inode being fast committed to be completed, instead of > > > adding it to the FC_Q_STAGING list like other tracking operations. > > It seems that the inode metadata of the tracked inode was not recorded > during the __track_inode(), so the inode metadata committed at commit > time reflects real-time data. However, the current > ext4_fc_perform_commit() lacks concurrency control, allowing other > processes to simultaneously initiate new handles that modify the inode > metadata while the previous metadata is being fast committed. Therefore, > to prevent recording newly changed inode metadata during the old commit > phase, the ext4_fc_track_inode() function must wait for the ongoing > commit process to complete before modifying. > > > > So > > > now I don't have a good idea to fix this problem either. Perhaps we > > > need to rethink the necessity of this waiting, or find a way to avoid > > > acquiring i_data_sem during fast commit. > > Ha, the solution seems to have already been listed in the TODOs in > fast_commit.c. > > Change ext4_fc_commit() to lookup logical to physical mapping using extent > status tree. This would get rid of the need to call ext4_fc_track_inode() > before acquiring i_data_sem. To do that we would need to ensure that > modified extents from the extent status tree are not evicted from memory. > > Alternatively, recording the mapped range of tracking might also be > feasible. Thanks a lot for your insights! For the next revesion, I plan to follow the "Alternatively" way firstly: record the mapped ranges (and relvant inode metadata) at commit time in a snapshot, when journal updates are locked/handles are drained, and then consume only the snapshot during log writing. This avoids doing logical-to-physical mapping (and thus avoids taking i_data_sem) in the log writing phase, and removes the need for ext4_fc_track_inode() to wait for EXT4_STATE_FC_COMMITTING. I did not pick the extent status tree approach because it would require additional work to guarantee the needed mappings are resident and not evicted under memory pressure, which seems like a larger correctness surface(Please correct me if I'm wrong). If you believe the extent stats tree approach is better, please let me know, and I will do my best to implement it. Thanks again for the guidance. I'll post an RFC v3 later. Regards, Li ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep 2026-01-07 14:30 ` Li Chen @ 2026-01-08 3:00 ` Zhang Yi 0 siblings, 0 replies; 9+ messages in thread From: Zhang Yi @ 2026-01-08 3:00 UTC (permalink / raw) To: Li Chen; +Cc: linux-ext4, Theodore Ts'o, Andreas Dilger, linux-kernel On 1/7/2026 10:30 PM, Li Chen wrote: > Hi Zhang, > > Thanks a lot for your detailed review! > > ---- On Wed, 07 Jan 2026 10:00:23 +0800 Zhang Yi <yi.zhang@huaweicloud.com> wrote --- > > On 1/6/2026 8:18 PM, Li Chen wrote: > > > Hi Zhang Yi, > > > > > > ---- On Mon, 05 Jan 2026 20:18:42 +0800 Zhang Yi <yi.zhang@huaweicloud.com> wrote --- > > > > Hi Li, > > > > > > > > On 12/24/2025 11:29 AM, Li Chen wrote: > > > > > ext4_fc_track_inode() can return without sleeping when > > > > > EXT4_STATE_FC_COMMITTING is already clear. The lockdep assertion for > > > > > ei->i_data_sem was done unconditionally before the wait loop, which can > > > > > WARN in call paths that hold i_data_sem even though we never block. Move > > > > > lockdep_assert_not_held(&ei->i_data_sem) into the actual sleep path, > > > > > right before schedule(). > > > > > > > > > > Signed-off-by: Li Chen <me@linux.beauty> > > > > > > > > Thank you for the fix patch! However, the solution does not seem to fix > > > > the issue. IIUC, the root cause of this issue is the following race > > > > condition (show only one case), and it may cause a real ABBA dead lock > > > > issue. > > > > > > > > ext4_map_blocks() > > > > hold i_data_sem // <- A > > > > ext4_mb_new_blocks() > > > > ext4_dirty_inode() > > > > ext4_fc_commit() > > > > ext4_fc_perform_commit() > > > > set EXT4_STATE_FC_COMMITTING <-B > > > > ext4_fc_write_inode_data() > > > > ext4_map_blocks() > > > > hold i_data_sem // <- A > > > > ext4_fc_track_inode() > > > > wait EXT4_STATE_FC_COMMITTING <- B > > > > jbd2_fc_end_commit() > > > > ext4_fc_cleanup() > > > > clear EXT4_STATE_FC_COMMITTING() > > > > > > I think the ABBA reasoning is plausible: if a caller violates the ordering > contract and enters ext4_fc_track_inode() while holding i_data_sem, and the > recheck still finds EXT4_STATE_FC_COMMITTING set (so we actually schedule()), > then we can get A -> wait(B). If the commit task, while holding the inode > in COMMITTING, still needs i_data_sem (e.g. via mapping/log writing), that > gives B -> wait(A), forming a cycle. > > > > > Postponing the lockdep assertion to the point where sleeping is actually > > > > necessary does not resolve this deadlock issue, it merely masks the > > > > problem, right? > > > > > > > > I currently don't quite understand why only ext4_fc_track_inode() needs > > > > to wait for the inode being fast committed to be completed, instead of > > > > adding it to the FC_Q_STAGING list like other tracking operations. > > > > It seems that the inode metadata of the tracked inode was not recorded > > during the __track_inode(), so the inode metadata committed at commit > > time reflects real-time data. However, the current > > ext4_fc_perform_commit() lacks concurrency control, allowing other > > processes to simultaneously initiate new handles that modify the inode > > metadata while the previous metadata is being fast committed. Therefore, > > to prevent recording newly changed inode metadata during the old commit > > phase, the ext4_fc_track_inode() function must wait for the ongoing > > commit process to complete before modifying. > > > > > > So > > > > now I don't have a good idea to fix this problem either. Perhaps we > > > > need to rethink the necessity of this waiting, or find a way to avoid > > > > acquiring i_data_sem during fast commit. > > > > Ha, the solution seems to have already been listed in the TODOs in > > fast_commit.c. > > > > Change ext4_fc_commit() to lookup logical to physical mapping using extent > > status tree. This would get rid of the need to call ext4_fc_track_inode() > > before acquiring i_data_sem. To do that we would need to ensure that > > modified extents from the extent status tree are not evicted from memory. > > > > Alternatively, recording the mapped range of tracking might also be > > feasible. > > Thanks a lot for your insights! > > For the next revesion, I plan to follow the "Alternatively" way firstly: > record the mapped ranges (and relvant inode metadata) at commit time in a > snapshot, when journal updates are locked/handles are drained, and then > consume only the snapshot during log writing. This avoids doing > logical-to-physical mapping (and thus avoids taking i_data_sem) in the log > writing phase, and removes the need for ext4_fc_track_inode() to wait for > EXT4_STATE_FC_COMMITTING. > > I did not pick the extent status tree approach because it would require > additional work to guarantee the needed mappings are resident and not > evicted under memory pressure, which seems like a larger correctness > surface(Please correct me if I'm wrong). If you believe the extent stats tree > approach is better, please let me know, and I will do my best to implement it. > > Thanks again for the guidance. I'll post an RFC v3 later. Yes, in my opinion, I also prefer the solution of recording the mapped ranges, as it should not incur too much overhead. Please give it a try. Cheers, Yi. > > Regards, > Li > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep 2026-01-05 12:18 ` Zhang Yi 2026-01-06 12:18 ` Li Chen @ 2026-01-07 14:19 ` Li Chen 1 sibling, 0 replies; 9+ messages in thread From: Li Chen @ 2026-01-07 14:19 UTC (permalink / raw) To: Zhang Yi; +Cc: linux-ext4, Theodore Ts'o, Andreas Dilger, linux-kernel Hi Zhang, Thanks a lot for your comments! ---- On Mon, 05 Jan 2026 20:18:42 +0800 Zhang Yi <yi.zhang@huaweicloud.com> wrote --- > Hi Li, > > On 12/24/2025 11:29 AM, Li Chen wrote: > > ext4_fc_track_inode() can return without sleeping when > > EXT4_STATE_FC_COMMITTING is already clear. The lockdep assertion for > > ei->i_data_sem was done unconditionally before the wait loop, which can > > WARN in call paths that hold i_data_sem even though we never block. Move > > lockdep_assert_not_held(&ei->i_data_sem) into the actual sleep path, > > right before schedule(). > > > > Signed-off-by: Li Chen <me@linux.beauty> > > Thank you for the fix patch! However, the solution does not seem to fix > the issue. IIUC, the root cause of this issue is the following race > condition (show only one case), and it may cause a real ABBA dead lock > issue. > > ext4_map_blocks() > hold i_data_sem // <- A > ext4_mb_new_blocks() > ext4_dirty_inode() > ext4_fc_commit() > ext4_fc_perform_commit() > set EXT4_STATE_FC_COMMITTING <-B > ext4_fc_write_inode_data() > ext4_map_blocks() > hold i_data_sem // <- A > ext4_fc_track_inode() > wait EXT4_STATE_FC_COMMITTING <- B > jbd2_fc_end_commit() > ext4_fc_cleanup() > clear EXT4_STATE_FC_COMMITTING() > > Postponing the lockdep assertion to the point where sleeping is actually > necessary does not resolve this deadlock issue, it merely masks the > problem, right? I agree. Moving lockdep_assert_not_held(&ei->i_data_sem) closer to the schedule() site can reduce spurious warnings (since the wait-bit pattern rechecks the bit after prepare_to_wait()), but it does not remove the underlying deadlock risk if we ever end up sleeping there, althoughI still haven't been able to reproduce this ABBA issue. > I currently don't quite understand why only ext4_fc_track_inode() needs > to wait for the inode being fast committed to be completed, instead of > adding it to the FC_Q_STAGING list like other tracking operations. So > now I don't have a good idea to fix this problem either. Perhaps we > need to rethink the necessity of this waiting, or find a way to avoid > acquiring i_data_sem during fast commit. > > Thanks, > Yi. > > > --- > > fs/ext4/fast_commit.c | 17 +++++++++-------- > > 1 file changed, 9 insertions(+), 8 deletions(-) > > > > diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c > > index d0926967d086..b0c458082997 100644 > > --- a/fs/ext4/fast_commit.c > > +++ b/fs/ext4/fast_commit.c > > @@ -566,13 +566,6 @@ void ext4_fc_track_inode(handle_t *handle, struct inode *inode) > > if (ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)) > > return; > > > > - /* > > - * If we come here, we may sleep while waiting for the inode to > > - * commit. We shouldn't be holding i_data_sem when we go to sleep since > > - * the commit path needs to grab the lock while committing the inode. > > - */ > > - lockdep_assert_not_held(&ei->i_data_sem); > > - > > while (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) { > > #if (BITS_PER_LONG < 64) > > DEFINE_WAIT_BIT(wait, &ei->i_state_flags, > > @@ -586,8 +579,16 @@ void ext4_fc_track_inode(handle_t *handle, struct inode *inode) > > EXT4_STATE_FC_COMMITTING); > > #endif > > prepare_to_wait(wq, &wait.wq_entry, TASK_UNINTERRUPTIBLE); > > - if (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) > > + if (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) { > > + /* > > + * We might sleep while waiting for the inode to commit. > > + * We shouldn't be holding i_data_sem when we go to sleep > > + * since the commit path may grab it while committing this > > + * inode. > > + */ > > + lockdep_assert_not_held(&ei->i_data_sem); > > schedule(); > > + } > > finish_wait(wq, &wait.wq_entry); > > } > > > > Regards, Li ^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC v3 2/2] ext4: fast commit: fix s_fc_lock vs i_data_sem inversion 2025-12-24 3:29 [RFC v3 0/2] ext4: fast commit: fix lockdep issues Li Chen 2025-12-24 3:29 ` [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep Li Chen @ 2025-12-24 3:29 ` Li Chen 1 sibling, 0 replies; 9+ messages in thread From: Li Chen @ 2025-12-24 3:29 UTC (permalink / raw) To: Theodore Ts'o, Andreas Dilger, linux-ext4, linux-kernel; +Cc: Li Chen lockdep reports a possible deadlock due to lock order inversion: CPU0 CPU1 ---- ---- lock(&sbi->s_fc_lock); lock(&ei->i_data_sem); lock(&sbi->s_fc_lock); rlock(&ei->i_data_sem); ext4_fc_perform_commit() held s_fc_lock while writing fast commit blocks. This can write the journal inode, whose mapping can call ext4_map_blocks() and take i_data_sem. At the same time, metadata update paths can hold i_data_sem and call ext4_fc_track_inode(), which takes s_fc_lock. Drop s_fc_lock before the log writing step. Keep inode and dentry state stable by using EXT4_STATE_FC_COMMITTING for synchronization: ext4_fc_del() waits for COMMITTING, and inodes referenced only from create dentry updates are also marked COMMITTING and woken up on cleanup. Signed-off-by: Li Chen <me@linux.beauty> --- fs/ext4/fast_commit.c | 79 ++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c index b0c458082997..aa209f1d3d36 100644 --- a/fs/ext4/fast_commit.c +++ b/fs/ext4/fast_commit.c @@ -244,23 +244,26 @@ void ext4_fc_del(struct inode *inode) return; } - /* - * Since ext4_fc_del is called from ext4_evict_inode while having a - * handle open, there is no need for us to wait here even if a fast - * commit is going on. That is because, if this inode is being - * committed, ext4_mark_inode_dirty would have waited for inode commit - * operation to finish before we come here. So, by the time we come - * here, inode's EXT4_STATE_FC_COMMITTING would have been cleared. So, - * we shouldn't see EXT4_STATE_FC_COMMITTING to be set on this inode - * here. - * - * We may come here without any handles open in the "no_delete" case of - * ext4_evict_inode as well. However, if that happens, we first mark the - * file system as fast commit ineligible anyway. So, even in that case, - * it is okay to remove the inode from the fc list. - */ - WARN_ON(ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING) - && !ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)); + /* Don't race with fast commit processing of this inode. */ + while (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) { +#if (BITS_PER_LONG < 64) + DEFINE_WAIT_BIT(wait, &ei->i_state_flags, + EXT4_STATE_FC_COMMITTING); + wq = bit_waitqueue(&ei->i_state_flags, + EXT4_STATE_FC_COMMITTING); +#else + DEFINE_WAIT_BIT(wait, &ei->i_flags, + EXT4_STATE_FC_COMMITTING); + wq = bit_waitqueue(&ei->i_flags, EXT4_STATE_FC_COMMITTING); +#endif + prepare_to_wait(wq, &wait.wq_entry, TASK_UNINTERRUPTIBLE); + if (ext4_test_inode_state(inode, EXT4_STATE_FC_COMMITTING)) { + mutex_unlock(&sbi->s_fc_lock); + schedule(); + mutex_lock(&sbi->s_fc_lock); + } + finish_wait(wq, &wait.wq_entry); + } while (ext4_test_inode_state(inode, EXT4_STATE_FC_FLUSHING_DATA)) { #if (BITS_PER_LONG < 64) DEFINE_WAIT_BIT(wait, &ei->i_state_flags, @@ -1108,6 +1111,27 @@ static int ext4_fc_perform_commit(journal_t *journal) ext4_set_inode_state(&iter->vfs_inode, EXT4_STATE_FC_COMMITTING); } + /* + * Also mark inodes referenced by create dentry updates. These inodes are + * tracked via i_fc_dilist and might not be on s_fc_q[MAIN]. + */ + { + struct ext4_fc_dentry_update *fc_dentry; + struct ext4_inode_info *ei; + + list_for_each_entry(fc_dentry, &sbi->s_fc_dentry_q[FC_Q_MAIN], + fcd_list) { + if (fc_dentry->fcd_op != EXT4_FC_TAG_CREAT) + continue; + if (list_empty(&fc_dentry->fcd_dilist)) + continue; + ei = list_first_entry(&fc_dentry->fcd_dilist, + struct ext4_inode_info, + i_fc_dilist); + ext4_set_inode_state(&ei->vfs_inode, + EXT4_STATE_FC_COMMITTING); + } + } mutex_unlock(&sbi->s_fc_lock); jbd2_journal_unlock_updates(journal); @@ -1137,7 +1161,6 @@ static int ext4_fc_perform_commit(journal_t *journal) } /* Step 6.2: Now write all the dentry updates. */ - mutex_lock(&sbi->s_fc_lock); ret = ext4_fc_commit_dentry_updates(journal, &crc); if (ret) goto out; @@ -1159,7 +1182,6 @@ static int ext4_fc_perform_commit(journal_t *journal) ret = ext4_fc_write_tail(sb, crc); out: - mutex_unlock(&sbi->s_fc_lock); memalloc_nofs_restore(nofs); blk_finish_plug(&plug); return ret; @@ -1342,6 +1364,25 @@ static void ext4_fc_cleanup(journal_t *journal, int full, tid_t tid) struct ext4_fc_dentry_update, fcd_list); list_del_init(&fc_dentry->fcd_list); + if (fc_dentry->fcd_op == EXT4_FC_TAG_CREAT && + !list_empty(&fc_dentry->fcd_dilist)) { + ei = list_first_entry(&fc_dentry->fcd_dilist, + struct ext4_inode_info, + i_fc_dilist); + ext4_clear_inode_state(&ei->vfs_inode, + EXT4_STATE_FC_COMMITTING); + /* + * Make sure clearing of EXT4_STATE_FC_COMMITTING is + * visible before we send the wakeup. Pairs with implicit + * barrier in prepare_to_wait() in ext4_fc_track_inode(). + */ + smp_mb(); +#if (BITS_PER_LONG < 64) + wake_up_bit(&ei->i_state_flags, EXT4_STATE_FC_COMMITTING); +#else + wake_up_bit(&ei->i_flags, EXT4_STATE_FC_COMMITTING); +#endif + } list_del_init(&fc_dentry->fcd_dilist); release_dentry_name_snapshot(&fc_dentry->fcd_name); -- 2.52.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-01-08 3:00 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-12-24 3:29 [RFC v3 0/2] ext4: fast commit: fix lockdep issues Li Chen 2025-12-24 3:29 ` [RFC v3 1/2] ext4: fast_commit: assert i_data_sem only before sleep Li Chen 2026-01-05 12:18 ` Zhang Yi 2026-01-06 12:18 ` Li Chen 2026-01-07 2:00 ` Zhang Yi 2026-01-07 14:30 ` Li Chen 2026-01-08 3:00 ` Zhang Yi 2026-01-07 14:19 ` Li Chen 2025-12-24 3:29 ` [RFC v3 2/2] ext4: fast commit: fix s_fc_lock vs i_data_sem inversion Li Chen
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox