ext4: fix fast commit inode enqueueing during a full journal commit
Subsystems (2)
PATCH v5 1/1 ext4: fix fast commit inode enqueueing during a full journal commit
When a full journal commit is on-going, any fast commit has to be enqueued into a different queue: FC_Q_STAGING instead of FC_Q_MAIN. This enqueueing is done only once, i.e. if an inode is already queued in a previous fast commit entry it won't be enqueued again. However, if a full commit starts _after_ the inode is enqueued into FC_Q_MAIN, the next fast commit needs to be done into FC_Q_STAGING. And this is not being done in function ext4_fc_track_template().
This patch fixes the issue by re-enqueuing an inode into the STAGING queue during the fast commit clean-up callback when doing a full commit. However, to prevent a race with a fast-commit, the clean-up callback has to be called with the journal locked.
This bug was found using fstest generic/047. This test creates several 32k bytes files, sync'ing each of them after it's creation, and then shutting down the filesystem. Some data may be loss in this operation; for example a file may have it's size truncated to zero.
---
Hi!
And here's another attempt to fix this bug. The most significant change is that now it doesn't assume a 'special' meaning for a tid of '0'. Which is a wrong assumption as Jan has shown.
I've also added a Suggested-by: tag, although Jan pretty much owns this patch -- I have simply tested it and sent it out!
fs/ext4/fast_commit.c | 15 ++++++++++++++- fs/jbd2/journal.c | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-)
fs/ext4/fast_commit.c +14 -1
@@ -1288,8 +1288,21 @@ static void ext4_fc_cleanup(journal_t *journal, int full, tid_t tid)list_del_init (&iter->i_fc_list);ext4_clear_inode_state (&iter->vfs_inode,EXT4_STATE_FC_COMMITTING ); -if (tid_geq (tid, iter->i_sync_tid)) +if (tid_geq (tid, iter->i_sync_tid)) {ext4_fc_reset_inode (&iter->vfs_inode); + }else if (full) { +/* +* We are called after a full commit, inode has been +* modified while the commit was running. Re-enqueue +* the inode into STAGING, which will then be splice +* back into MAIN. This cannot happen during +* fastcommit because the journal is locked all the +* time in that case (and tid doesn't increase so +* tid check above isn't reliable). +*/ +list_add_tail (&EXT4_I (&iter->vfs_inode)->i_fc_list, + &sbi->s_fc_q[FC_Q_STAGING ]); + }/* Make sure EXT4_STATE_FC_COMMITTING bit is clear */ smp_mb ();#if (BITS_PER_LONG <64 )
fs/jbd2/journal.c +1 -1
@@ -740,9 +740,9 @@ EXPORT_SYMBOL(jbd2_fc_begin_commit);*/ static int __jbd2_fc_end_commit (journal_t *journal,tid_t tid,bool fallback) { -jbd2_journal_unlock_updates (journal);if (journal->j_fc_cleanup_callback) journal->j_fc_cleanup_callback (journal,0 , tid); +jbd2_journal_unlock_updates (journal);write_lock (&journal->j_state_lock); journal->j_flags &= ~JBD2_FAST_COMMIT_ONGOING ;if (fallback)
Quoted 13 lines · Luis Henriques (SUSE)
When a full journal commit is on-going, any fast commit has to be enqueued into a different queue:
FC_Q_STAGINGinstead ofFC_Q_MAIN. This enqueueing is done only once, i.e. if an inode is already queued in a previous fast commit entry it won't be enqueued again. However, if a full commit starts _after_ the inode is enqueued intoFC_Q_MAIN, the next fast commit needs to be done intoFC_Q_STAGING. And this is not being done in functionext4_fc_track_template().This patch fixes the issue by re-enqueuing an inode into the STAGING queue during the fast commit clean-up callback when doing a full commit. However, to prevent a race with a fast-commit, the clean-up callback has to be called with the journal locked.
Quoted 7 lines
This bug was found using fstest generic/047. This test creates several 32k bytes files, sync'ing each of them after it's creation, and then shutting down the filesystem. Some data may be loss in this operation; for example a file may have it's size truncated to zero.
Suggested-by Jan Kara <[email protected]>Signed-off-by Luis Henriques (SUSE) <[email protected]>
Looks good to me. Feel free to add:
Honza
Quoted 57 lines
---
Hi!And here's another attempt to fix this bug. The most significant change is that now it doesn't assume a 'special' meaning for a tid of '0'. Which is a wrong assumption as Jan has shown.
I've also added a Suggested-by: tag, although Jan pretty much owns this patch -- I have simply tested it and sent it out!
fs/ext4/fast_commit.c | 15 ++++++++++++++- fs/jbd2/journal.c | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-)fs/ext4/fast_commit.c +14
-1@@ -1288,8 +1288,21 @@ static void ext4_fc_cleanup(journal_t *journal, int full, tid_t tid)list_del_init (&iter->i_fc_list);ext4_clear_inode_state (&iter->vfs_inode,EXT4_STATE_FC_COMMITTING ); -if (tid_geq (tid, iter->i_sync_tid)) +if (tid_geq (tid, iter->i_sync_tid)) {ext4_fc_reset_inode (&iter->vfs_inode); + }else if (full) { +/* +* We are called after a full commit, inode has been +* modified while the commit was running. Re-enqueue +* the inode into STAGING, which will then be splice +* back into MAIN. This cannot happen during +* fastcommit because the journal is locked all the +* time in that case (and tid doesn't increase so +* tid check above isn't reliable). +*/ +list_add_tail (&EXT4_I (&iter->vfs_inode)->i_fc_list, + &sbi->s_fc_q[FC_Q_STAGING ]); + }/* Make sure EXT4_STATE_FC_COMMITTING bit is clear */ smp_mb ();#if (BITS_PER_LONG <64 )fs/jbd2/journal.c +1
-1@@ -740,9 +740,9 @@ EXPORT_SYMBOL(jbd2_fc_begin_commit);*/ static int __jbd2_fc_end_commit (journal_t *journal,tid_t tid,bool fallback) { -jbd2_journal_unlock_updates (journal);if (journal->j_fc_cleanup_callback) journal->j_fc_cleanup_callback (journal,0 , tid); +jbd2_journal_unlock_updates (journal);write_lock (&journal->j_state_lock); journal->j_flags &= ~JBD2_FAST_COMMIT_ONGOING ;if (fallback)
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 9 lines · Luis Henriques (SUSE)
When a full journal commit is on-going, any fast commit has to be enqueued into a different queue:
FC_Q_STAGINGinstead ofFC_Q_MAIN. This enqueueing is done only once, i.e. if an inode is already queued in a previous fast commit entry it won't be enqueued again. However, if a full commit starts _after_ the inode is enqueued intoFC_Q_MAIN, the next fast commit needs to be done intoFC_Q_STAGING. And this is not being done in functionext4_fc_track_template().[...]
Applied, thanks!
[1/1] ext4: fix fast commit inode enqueueing during a full journal commit
commit: 6db3c1575a750fd417a70e0178bdf6efa0dd5037
Best regards,
Signature · 1 line
Theodore Ts'o <[email protected]>