JBD ordered mode rewrite
Jan Kara
maintainerRFC 1/1 JBD ordered mode rewrite
Hi,
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate of data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.
Honza
Signature · 5 lines
Jan Kara <[email protected]> SUSE Labs, CR --- Signed-off-by: Jan Kara <[email protected]>
fs/buffer.c +2 -1
@@ -1675,7 +1675,8 @@ static int __block_write_full_page(struct inode *inode, struct page *page,*/ clear_buffer_dirty (bh);set_buffer_uptodate (bh); - }else if (!buffer_mapped (bh) &&buffer_dirty (bh)) { + }else if (!buffer_mapped (bh) &&buffer_dirty (bh) + && !wbc->skip_unmapped) {WARN_ON (bh->b_size != blocksize); err =get_block (inode, block, bh,1 );if (err)
fs/ext3/ialloc.c +1 -0
@@ -588,6 +588,7 @@ got: ei->i_extra_isize = (EXT3_INODE_SIZE (inode->i_sb) >EXT3_GOOD_OLD_INODE_SIZE ) ?sizeof (struct ext3_inode ) -EXT3_GOOD_OLD_INODE_SIZE :0 ; +journal_init_jbd_inode (&ei->jinode, inode); ret = inode;if (DQUOT_ALLOC_INODE (inode)) {
fs/ext3/inode.c +58 -60
210 lines not on this page · raw message
fs/ext3/super.c +2 -0
@@ -520,6 +520,8 @@ static void ext3_clear_inode(struct inode *inode)EXT3_I (inode)->i_block_alloc_info =NULL ;if (unlikely (rsv))kfree (rsv); +journal_release_jbd_inode (EXT3_SB (inode->i_sb)->s_journal, + &EXT3_I (inode)->jinode); }static inline void ext3_show_quota_options (struct seq_file *seq,struct super_block *sb)
fs/jbd/checkpoint.c +0 -1
@@ -682,7 +682,6 @@ void __journal_drop_transaction(journal_t *journal, transaction_t *transaction)J_ASSERT (transaction->t_state ==T_FINISHED );J_ASSERT (transaction->t_buffers ==NULL ); -J_ASSERT (transaction->t_sync_datalist ==NULL );J_ASSERT (transaction->t_forget ==NULL );J_ASSERT (transaction->t_iobuf_list ==NULL );J_ASSERT (transaction->t_shadow_list ==NULL );
fs/jbd/commit.c +87 -170
344 lines not on this page · raw message
fs/jbd/journal.c +45 -0
51 lines not on this page · raw message
fs/jbd/transaction.c +75 -213
357 lines not on this page · raw message
fs/mpage.c +4 -1
@@ -527,7 +527,10 @@ static int __mpage_writepage(struct page *page, struct writeback_control *wbc, map_bh.b_state =0 ; map_bh.b_size =1 << blkbits; -if (mpd->get_block (inode, block_in_file, &map_bh,1 )) +if (mpd->get_block (inode, block_in_file, &map_bh, + !wbc->skip_unmapped)) + goto confused; +if (!buffer_mapped (&map_bh)) goto confused;if (buffer_new (&map_bh)) unmap_underlying_metadata(map_bh.b_bdev,
include/linux/ext3_fs.h +1 -0
@@ -826,6 +826,7 @@ int ext3_get_blocks_handle(handle_t *handle, struct inode *inode,extern struct inode *ext3_iget (struct super_block *,unsigned long );extern int ext3_write_inode (struct inode *,int );extern int ext3_setattr (struct dentry *,struct iattr *); +extern void ext3_drop_inode (struct inode *);extern void ext3_delete_inode (struct inode *);extern int ext3_sync_inode (handle_t *,struct inode *);extern void ext3_discard_reservation (struct inode *);
include/linux/ext3_fs_i.h +1 -0
@@ -142,6 +142,7 @@ struct ext3_inode_info {*/ struct mutex truncate_mutex;struct inode vfs_inode; + struct jbd_inode jinode; };#endif /* _LINUX_EXT3_FS_I */
include/linux/jbd.h +49 -21
106 lines not on this page · raw message
include/linux/writeback.h +1 -0
@@ -63,6 +63,7 @@ struct writeback_control { unsigned for_writepages:1; /* This is a writepages() call */ unsigned range_cyclic:1; /* range_start is cyclic */ unsigned more_io:1; /* more io to be dispatched */ + unsigned skip_unmapped:1; /* A kjournald commit */ };/*
Quoted 17 lines · Jan Kara
Hi,
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate of data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.
Quoted 1 line
Honza
Just one nit, doesn't compile properly when jbd/ext3 are modules :). Thanks much,
Josef
Jan Kara
maintainerQuoted 18 lines · Josef Bacik
Quoted 17 lines · Jan Kara
Hi,
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate of data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.
Quoted 4 lines
Quoted 1 line
Honza
Just one nit, doesn't compile properly when jbd/ext3 are modules :). Thanks much,
Thanks for spotting this. Will fix :).
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Andrew Morton
maintainerQuoted 6 lines · On Thu, 6 Mar 2008 18:42:09 +0100 Jan Kara <[email protected]> wrote:
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate of
Quoted 8 lines
data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.
Thanks for plugging away with this.
Please change your patch preparation tools to always always include a diffstat, OK?
fs/buffer.c | 3 fs/ext3/ialloc.c | 1 fs/ext3/inode.c | 118 +++++++++--------- fs/ext3/super.c | 2 fs/jbd/checkpoint.c | 1 fs/jbd/commit.c | 257 +++++++++++++---------------------------- fs/jbd/journal.c | 45 +++++++ fs/jbd/transaction.c | 288 +++++++++++----------------------------------- fs/mpage.c | 5 include/linux/ext3_fs.h | 1 include/linux/ext3_fs_i.h | 1 include/linux/jbd.h | 70 +++++++---- include/linux/writeback.h | 2 13 files changed, 326 insertions(+), 468 deletions(-)
Would it make sense to turn this patch into a patch series sometime?
Jan Kara
maintainerQuoted 18 lines · Andrew Morton
Quoted 6 lines · On Thu, 6 Mar 2008 18:42:09 +0100 Jan Kara <[email protected]> wrote:
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate ofQuoted 8 lines
data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.
Quoted 4 lines
Thanks for plugging away with this.
Please change your patch preparation tools to always always include a diffstat, OK?
Hmm, I mostly submit patches by hand but I'll try to not forget generate
patches with git-diff --stat ;).
Quoted 15 lines
fs/buffer.c | 3 fs/ext3/ialloc.c | 1 fs/ext3/inode.c | 118 +++++++++--------- fs/ext3/super.c | 2 fs/jbd/checkpoint.c | 1 fs/jbd/commit.c | 257 +++++++++++++---------------------------- fs/jbd/journal.c | 45 +++++++ fs/jbd/transaction.c | 288 +++++++++++----------------------------------- fs/mpage.c | 5 include/linux/ext3_fs.h | 1 include/linux/ext3_fs_i.h | 1 include/linux/jbd.h | 70 +++++++---- include/linux/writeback.h | 2 13 files changed, 326 insertions(+), 468 deletions(-)
Quoted 1 line
Would it make sense to turn this patch into a patch series sometime?
We can definitely split out ext3 and JBD changes (although ext3 would not
compile after JBD changes). I'll have a look at what Mingming suggests - whether we could make both modes coexist reasonably easily. In that case also patches could change smaller chunks.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 7 lines · Jan Kara
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate of data being committed happens.
There's a lot of JBD code which gets removed by this patch - cool :)
Quoted 2 lines
The patch has survived some light testing but it still has some potential of eating your data so beware :)
Looking through the patch, I don't see how you solve the page lock / transaction ordering issues. I see that you avoid starting a handle in ->writepage during transaction commit, but what about another process which starts a handle under page lock and needs to wait for transactions to be written out before continuing?
Quoted 7 lines
I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish...
Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.
My attempt at helpful review follows.
Quoted 26 lines
fs/ext3/inode.c +1
-9@@ -1465,15 +1444,11 @@ static int journal_dirty_data_fn(handle_t *handle, struct buffer_head *bh)* We don't honour synchronous mounts for writepage(). That would be * disastrous. Any write() or metadata operation will sync the fs for * us. -* -* AKPM2: if all the page's buffers are mapped to disk and !data=journal, -* we don't need to open a transaction here. */ static int ext3_ordered_writepage (struct page *page,struct writeback_control *wbc) {struct inode *inode = page->mapping->host; -struct buffer_head *page_bufs;handle_t *handle =NULL ;int ret =0 ;int err;@@ -1487,46 +1462,49 @@ static int ext3_ordered_writepage(struct page *page,if (ext3_journal_current_handle ()) goto out_fail; - handle =ext3_journal_start (inode,ext3_writepage_trans_blocks (inode)); - -if (IS_ERR (handle)) { - ret =PTR_ERR (handle); - goto out_fail; +/*
fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1475
Quoted 10 lines
fs/ext3/inode.c +10
-0+* Now there are two different reasons why we can be called: +* 1) write out during commit +* 2) fsync / writeout to free memory +* +* In the first case, we just need to write the buffer to disk, in the +* second case we may need to do hole filling and attach the inode to +* the transaction. Note that even in the first case, we may get an +* unmapped buffer (hole fill with data via mmap) but we don't have to +* write it - actually, we can't because from a transaction commit we +* cannot start a new transaction or we could deadlock.
What happens to the data if we get here under the 1st case with a hole? Do we eventually fill the hole (with correct data) via some mechanism I don't see here?
<snip>
Quoted 9 lines
fs/jbd/transaction.c +9
-0+/* +* This function must be called when inode is journaled in ordered mode +* before truncation happens. It starts writeout of truncated part in +* case it is in the committing transaction so that we stand to ordered +* mode consistency guarantees. +*/ +int journal_begin_ordered_truncate (struct jbd_inode *inode,loff_t new_size) +{ +journal_t *journal;
fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1960
Quoted 10 lines
fs/jbd/transaction.c +10
-0+transaction_t *commit_trans; +int ret =0 ; + +if (!inode->i_transaction && !inode->i_next_transaction) + goto out; + journal = inode->i_transaction->t_journal; +spin_lock (&journal->j_state_lock); + commit_trans = journal->j_committing_transaction; +spin_unlock (&journal->j_state_lock); +if (inode->i_transaction == commit_trans) {
AFAICT, this is called in ext3 before a handle is started for truncate. Is it possible for the current running transaction to become the new committing transaction shortly after the spinlock is dropped, but before the truncate transaction starts? Could this lead to ordered data not being written out if the inode is part of the current running transaction but not part of the committing transaction?
Quoted 29 lines
fs/jbd/transaction.c +8
-0+ ret =__filemap_fdatawrite_range (inode->i_vfs_inode->i_mapping, + new_size,LLONG_MAX ,WB_SYNC_ALL ); +if (ret) +journal_abort (journal, ret); + } +out: +return ret; +}fs/mpage.c +4
-1@@ -527,7 +527,10 @@ static int __mpage_writepage(struct page *page, struct writeback_control *wbc, map_bh.b_state =0 ; map_bh.b_size =1 << blkbits; -if (mpd->get_block (inode, block_in_file, &map_bh,1 )) +if (mpd->get_block (inode, block_in_file, &map_bh, + !wbc->skip_unmapped)) + goto confused; +if (!buffer_mapped (&map_bh)) goto confused;if (buffer_new (&map_bh)) unmap_underlying_metadata(map_bh.b_bdev,include/linux/ext3_fs.h +0
-0
include/linux/ext3_fs.h int ext3_get_blocks_handle(handle_t *handle, struct inode *inode, · line 829
Quoted 4 lines
include/linux/ext3_fs.h +1
-0@@ -826,6 +826,7 @@ int ext3_get_blocks_handle(handle_t *handle, struct inode *inode,extern struct inode *ext3_iget (struct super_block *,unsigned long );extern int ext3_write_inode (struct inode *,int );extern int ext3_setattr (struct dentry *,struct iattr *); +extern void ext3_drop_inode (struct inode *);
Not sure what this is here for...
Thanks,
--Mark
--
Mark Fasheh
Principal Software Developer, Oracle
[email protected]
Jan Kara
maintainerQuoted 12 lines · Mark Fasheh
Quoted 7 lines · Jan Kara
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate of data being committed happens.There's a lot of JBD code which gets removed by this patch - cool :)
Quoted 8 lines
Quoted 2 lines
The patch has survived some light testing but it still has some potential of eating your data so beware :)
Looking through the patch, I don't see how you solve the page lock / transaction ordering issues. I see that you avoid starting a handle in ->writepage during transaction commit, but what about another process which starts a handle under page lock and needs to wait for transactions to be written out before continuing?
Ho, hum, drat. I think you're right. And I don't see a way around this
besides reversing the order of page_lock and transaction start in the whole ext3/4 :(... I'll think how we could solve this problem but I'm afraid the rewrite is the easiest one.
Quoted 43 lines
Quoted 7 lines
I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish...
Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.My attempt at helpful review follows.
Quoted 32 lines
fs/ext3/inode.c +7
-9@@ -1465,15 +1444,11 @@ static int journal_dirty_data_fn(handle_t *handle, struct buffer_head *bh)* We don't honour synchronous mounts for writepage(). That would be * disastrous. Any write() or metadata operation will sync the fs for * us. -* -* AKPM2: if all the page's buffers are mapped to disk and !data=journal, -* we don't need to open a transaction here. */ static int ext3_ordered_writepage (struct page *page,struct writeback_control *wbc) {struct inode *inode = page->mapping->host; -struct buffer_head *page_bufs;handle_t *handle =NULL ;int ret =0 ;int err;@@ -1487,46 +1462,49 @@ static int ext3_ordered_writepage(struct page *page,if (ext3_journal_current_handle ()) goto out_fail; - handle =ext3_journal_start (inode,ext3_writepage_trans_blocks (inode)); - -if (IS_ERR (handle)) { - ret =PTR_ERR (handle); - goto out_fail; +/* +* Now there are two different reasons why we can be called: +* 1) write out during commit +* 2) fsync / writeout to free memory +* +* In the first case, we just need to write the buffer to disk, in the +* second case we may need to do hole filling and attach the inode to
Quoted 8 lines
fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1475
Quoted 4 lines
fs/ext3/inode.c +4
-0+* the transaction. Note that even in the first case, we may get an +* unmapped buffer (hole fill with data via mmap) but we don't have to +* write it - actually, we can't because from a transaction commit we +* cannot start a new transaction or we could deadlock. What happens to the data if we get here under the 1st case with a hole? Do we eventually fill the hole (with correct data) via some mechanism I don't see here?
Yes, eventually pdflush will find the page, call writepage() and that
will fill the hole.
Quoted 21 lines
<snip>
Quoted 18 lines
fs/jbd/transaction.c +18
-0+/* +* This function must be called when inode is journaled in ordered mode +* before truncation happens. It starts writeout of truncated part in +* case it is in the committing transaction so that we stand to ordered +* mode consistency guarantees. +*/ +int journal_begin_ordered_truncate (struct jbd_inode *inode,loff_t new_size) +{ +journal_t *journal; +transaction_t *commit_trans; +int ret =0 ; + +if (!inode->i_transaction && !inode->i_next_transaction) + goto out; + journal = inode->i_transaction->t_journal; +spin_lock (&journal->j_state_lock); + commit_trans = journal->j_committing_transaction; +spin_unlock (&journal->j_state_lock);
Quoted 8 lines
fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1960
Quoted 1 line
fs/jbd/transaction.c +1
-0+if (inode->i_transaction == commit_trans) {AFAICT, this is called in ext3 before a handle is started for truncate. Is it possible for the current running transaction to become the new committing transaction shortly after the spinlock is dropped, but before the truncate transaction starts? Could this lead to ordered data not being written out if the inode is part of the current running transaction but not part of the committing transaction?
Good catch. Actually, the problem isn't injournal_begin_ordered_truncate() but you are right that we should call this function only after we add the inode to the orphan list. That way we know that if the current running transaction commits, the inode will be truncated at least during journal replay and thus we are safe from the races you describe above. Fixed.
Quoted 27 lines
Quoted 27 lines
fs/jbd/transaction.c +8
-0+ ret =__filemap_fdatawrite_range (inode->i_vfs_inode->i_mapping, + new_size,LLONG_MAX ,WB_SYNC_ALL ); +if (ret) +journal_abort (journal, ret); + } +out: +return ret; +}fs/mpage.c +4
-1@@ -527,7 +527,10 @@ static int __mpage_writepage(struct page *page, struct writeback_control *wbc, map_bh.b_state =0 ; map_bh.b_size =1 << blkbits; -if (mpd->get_block (inode, block_in_file, &map_bh,1 )) +if (mpd->get_block (inode, block_in_file, &map_bh, + !wbc->skip_unmapped)) + goto confused; +if (!buffer_mapped (&map_bh)) goto confused;if (buffer_new (&map_bh)) unmap_underlying_metadata(map_bh.b_bdev,include/linux/ext3_fs.h +0
-1--- a/include/linux/ext3_fs.h
Quoted 8 lines
include/linux/ext3_fs.h int ext3_get_blocks_handle(handle_t *handle, struct inode *inode, · line 829
Quoted 6 lines
+++
b/include/linux/ext3_fs.hinclude/linux/ext3_fs.h +1
-0@@ -826,6 +826,7 @@ int ext3_get_blocks_handle(handle_t *handle, struct inode *inode,extern struct inode *ext3_iget (struct super_block *,unsigned long );extern int ext3_write_inode (struct inode *,int );extern int ext3_setattr (struct dentry *,struct iattr *); +extern void ext3_drop_inode (struct inode *);Not sure what this is here for...
Removed - it was left there from the previous version of the patch.
Thanks for a really helpful review.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 2 lines · Jan Kara
Hi,
Hi Jan,
Quoted 5 lines
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit,
Quoted 8 lines
simplification of some JBD code, no more anonymous pages when truncate of data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
I know ext4 is keep changing that it's a bit hard to create patch against ext4, but I feel features like especially rewrite the default ordered mode should done in ext4/jbd2. I could port to current ext4 and JBD2 if you agrees with this.
Also, would it make sense to create a new ordered mode writepage routines, and keep the old ordered mode code there for a while, to allow easy comparison? This could a good transition for people to start experiment this ordered mode without worrying about put data in danger by default.
Quoted 1 line
Any comments or testing most welcom
[...]
Quoted 30 lines
/*
* Note that we always start a transaction even if we're not journalling
* data. This is to preserve ordering: any hole instantiation withinfs/ext3/inode.c +2
-9@@ -1465,15 +1444,11 @@ static int journal_dirty_data_fn(handle_t *handle, struct buffer_head *bh)* We don't honour synchronous mounts for writepage(). That would be * disastrous. Any write() or metadata operation will sync the fs for * us. -* -* AKPM2: if all the page's buffers are mapped to disk and !data=journal, -* we don't need to open a transaction here. */ static int ext3_ordered_writepage (struct page *page,struct writeback_control *wbc) {struct inode *inode = page->mapping->host; -struct buffer_head *page_bufs;handle_t *handle =NULL ;int ret =0 ;int err;@@ -1487,46 +1462,49 @@ static int ext3_ordered_writepage(struct page *page,if (ext3_journal_current_handle ()) goto out_fail; - handle =ext3_journal_start (inode,ext3_writepage_trans_blocks (inode)); - -if (IS_ERR (handle)) { - ret =PTR_ERR (handle); - goto out_fail; +/* +* Now there are two different reasons why we can be called:
fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1476
Quoted 10 lines
fs/ext3/inode.c +10
-0+* 1) write out during commit +* 2) fsync / writeout to free memory +* +* In the first case, we just need to write the buffer to disk, in the +* second case we may need to do hole filling and attach the inode to +* the transaction. Note that even in the first case, we may get an +* unmapped buffer (hole fill with data via mmap) but we don't have to +* write it - actually, we can't because from a transaction commit we +* cannot start a new transaction or we could deadlock. +*/
Any thoughts how to handle the unmapped page under case 1)? Right now I see it fails. Your comments here saying that we still have the issue that "can't start a new transaction while commiting", but likely, with delayed allocation, starting a new transaction could to happen a lot to do defered block allocation.
I really hope this new mode could be easy to add delayed allocation support. Any thoughts that we could workaround the locking in the JBD2 layer?
fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1487
Quoted 11 lines
fs/ext3/inode.c +9
-0+if (!wbc->skip_unmapped) { + handle =ext3_journal_start (inode, +ext3_writepage_trans_blocks (inode)); +if (IS_ERR (handle)) { + ret =PTR_ERR (handle); + goto out_fail; + } } +else if (!PageMappedToDisk (page)) + goto out_fail;
Thanks & Regards,
Mingming
Jan Kara
maintainerHi Mingming,
Quoted 15 lines · Mingming Cao
Quoted 5 lines · Jan Kara
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit,Quoted 8 lines
simplification of some JBD code, no more anonymous pages when truncate of data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Quoted 4 lines
I know ext4 is keep changing that it's a bit hard to create patch against ext4, but I feel features like especially rewrite the default ordered mode should done in ext4/jbd2. I could port to current ext4 and JBD2 if you agrees with this.
I definitely agree with you :). This was just the first version of the
patch, more like a proof-of-concept, and it was easier to code against jbd/ext3 for me :) But when I have something more definitive, I'll port it against jbd2/ext4.
Quoted 5 lines
Also, would it make sense to create a new ordered mode writepage routines, and keep the old ordered mode code there for a while, to allow easy comparison? This could a good transition for people to start experiment this ordered mode without worrying about put data in danger by default.
This is an interesting idea. I'll have a look how hard would it be to do
that. It would be also good because we could separate the patch into several chunks and it would still compile / work between them.
Quoted 50 lines
Quoted 1 line
Any comments or testing most welcom
[...]
Quoted 30 lines
/*
* Note that we always start a transaction even if we're not journalling
* data. This is to preserve ordering: any hole instantiation withinfs/ext3/inode.c +2
-9@@ -1465,15 +1444,11 @@ static int journal_dirty_data_fn(handle_t *handle, struct buffer_head *bh)* We don't honour synchronous mounts for writepage(). That would be * disastrous. Any write() or metadata operation will sync the fs for * us. -* -* AKPM2: if all the page's buffers are mapped to disk and !data=journal, -* we don't need to open a transaction here. */ static int ext3_ordered_writepage (struct page *page,struct writeback_control *wbc) {struct inode *inode = page->mapping->host; -struct buffer_head *page_bufs;handle_t *handle =NULL ;int ret =0 ;int err;@@ -1487,46 +1462,49 @@ static int ext3_ordered_writepage(struct page *page,if (ext3_journal_current_handle ()) goto out_fail; - handle =ext3_journal_start (inode,ext3_writepage_trans_blocks (inode)); - -if (IS_ERR (handle)) { - ret =PTR_ERR (handle); - goto out_fail; +/* +* Now there are two different reasons why we can be called: fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1476
Quoted 10 lines
fs/ext3/inode.c +10
-0+* 1) write out during commit +* 2) fsync / writeout to free memory +* +* In the first case, we just need to write the buffer to disk, in the +* second case we may need to do hole filling and attach the inode to +* the transaction. Note that even in the first case, we may get an +* unmapped buffer (hole fill with data via mmap) but we don't have to +* write it - actually, we can't because from a transaction commit we +* cannot start a new transaction or we could deadlock. +*/ Any thoughts how to handle the unmapped page under case 1)? Right now I see it fails. Your comments here saying that we still have the issue that "can't start a new transaction while commiting", but likely, with delayed allocation, starting a new transaction could to happen a lot to do defered block allocation.
Quoted 3 lines
I really hope this new mode could be easy to add delayed allocation support. Any thoughts that we could workaround the locking in the JBD2 layer?
"can't start a new transaction while commiting" is a fundamental problem
that you cannot add to a journal when you are trying to clean it up. It's not just a locking problem :). As Mark pointed out, there's another problem with the fact that we cannot afford to take page_lock while committing because that's essentially a clasical lock inversion between a transaction start and a page lock. How to solve that one, I don't know yet.
To your question about delayed allocation - currently, we just skip those
buffers so as you write in some other email, you basically get the guarantees of writeback mode. Actually, I guess the result is the same with the old way of ordered-mode handling since you cannot afford to do the block allocation from the commit code as well... We could get around this limitation (actually easier than in the old code) if we were sure we have enough credits in the transaction to really do the allocation - we would do the allocation in the writepage() and attach changed buffers to the committing transaction.
Quoted 11 lines
fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1487
Quoted 11 lines
fs/ext3/inode.c +9
-0+if (!wbc->skip_unmapped) { + handle =ext3_journal_start (inode, +ext3_writepage_trans_blocks (inode)); +if (IS_ERR (handle)) { + ret =PTR_ERR (handle); + goto out_fail; + } } +else if (!PageMappedToDisk (page)) + goto out_fail;
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 6 lines · Jan Kara
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate of
Quoted 8 lines
data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.
Looks like a very good patch - thanks for your effort in moving this beyond the "hand-waving" stage that it's been in for the past few years.
I'm looking at what implications this has for delayed allocation in ext4, because the vast majority of file data will be unmapped in that case and a journal commit in ordered mode will no longer cause the data to be flushed to disk.
I _think_ is OK, because the pdflushd will now be totally in charge of flushing the dirty pages to disk, instead of this previously being done by ordered mode in the journal. I know there have been some bugs in this area in the past, but I guess it isn't much different than running in writeback mode. That said, I don't know how many users run in writeback mode unless they are running a database, and the database is doing a lot of explicit fsync of file data so there may still be bugs lurking...
Some care is still needed here because with ext4 delayed allocation the common case will be unmapped buffers, while the ext3 implementation will only have this in very rare cases (unmapped mmap writes), but it looks like the right mechanism is already in place for both.
I'll just put a bunch of comments inline, not necessarily problems, just walking through the code to ensure I understand what is going on...
fs/buffer.c static int __block_write_full_page(struct inode *inode, struct page *page, · line 1679
Quoted 7 lines
fs/buffer.c +2
-1@@ -1675,7 +1675,8 @@ static int __block_write_full_page(struct inode *inode, struct page *page,*/ clear_buffer_dirty (bh);set_buffer_uptodate (bh); - }else if (!buffer_mapped (bh) &&buffer_dirty (bh)) { + }else if (!buffer_mapped (bh) &&buffer_dirty (bh) + && !wbc->skip_unmapped) {
(comment) This is skipping writeout of unmapped pages during journal commit - good, otherwise we would deadlock trying to add block mappings...
fs/ext3/inode.c void ext3_delete_inode (struct inode * inode) · line 188
Quoted 6 lines
fs/ext3/inode.c +2
-0@@ -183,6 +184,8 @@ void ext3_delete_inode (struct inode * inode) {handle_t *handle; +if (ext3_should_order_data (inode)) +ext3_begin_ordered_truncate (inode,0 );
(comment) This is flushing out pages allocated in the previous transaction to keep consistent semantics in case of a crash loses the delete... There is some possibility for optimization here - comments below at journal_begin_ordered_truncate().
fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1469
Quoted 9 lines
fs/ext3/inode.c +7
-0@@ -1487,46 +1462,49 @@ static int ext3_ordered_writepage(struct page *page, +if (!wbc->skip_unmapped) { + handle =ext3_journal_start (inode, +ext3_writepage_trans_blocks (inode)); +if (IS_ERR (handle)) { + ret =PTR_ERR (handle); + goto out_fail; + } }
I guess the common case here for delalloc is that if someone cares to fsync the file then that inode would be added to the journal list and the pages would be mapped here in the process context and flushed with the journal commit. This is ideal because it avoids syncing out all of the dirty pages for inodes that were NOT fsync'd and fixes the "one process doing fsync kills performance for streaming writes" problem in ordered mode that was recently discussed on the list.
We in fact win twice because fsync_page_range() will potentially only map and flush the range of pages that the application cares about and does not necessarily have to write out all pages (though that will still happen in ordered mode if the pages had previously been mapped).
fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1486
Quoted 2 lines
fs/ext3/inode.c +2
-0+else if (!PageMappedToDisk (page)) + goto out_fail;
(style) } else if (...) {
goto out_fail;
}fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1492
Quoted 5 lines
fs/ext3/inode.c +1
-0+/* This can go as soon as someone cares about that ;) */ if (!page_has_buffers (page)) {create_empty_buffers (page, inode->i_sb->s_blocksize, (1 << BH_Dirty)|(1 << BH_Uptodate)); }
Is there any reason to keep this in the non-data-journal case? Adding buffer heads to every page is a non-trivial amount of RAM usage.
fs/ext3/inode.c int ext3_write_inode(struct inode *inode, int wait) · line 2976
Quoted 6 lines
fs/ext3/inode.c +1
-1@@ -2989,7 +2973,14 @@ int ext3_write_inode(struct inode *inode, int wait)* be freed, so we have a strong guarantee that no future commit will * leave these blocks visible to the user.) * -* Called with inode->sem down. +* Another thing we have to asure is that if we are in ordered mode
s/asure/assure/
fs/ext3/inode.c int ext3_write_inode(struct inode *inode, int wait) · line 2978
Quoted 2 lines
fs/ext3/inode.c +2
-0+* and inode is still attached to the committing transaction, we must +* we start writeout of all the dirty buffers which are being truncated.
s/buffers/pages/
fs/ext3/inode.c int ext3_setattr(struct dentry *dentry, struct iattr *attr) · line 3028
Quoted 7 lines
fs/ext3/inode.c +3
-0@@ -3032,6 +3023,13 @@ int ext3_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_valid & ATTR_SIZE && attr->ia_size < inode->i_size) {handle_t *handle; +if (ext3_should_order_data (inode)) { + error =ext3_begin_ordered_truncate (inode, + attr->ia_size);
(style) align "attr->ia_size" with previous '('.
fs/ext3/super.c static void ext3_clear_inode(struct inode *inode) · line 524
Quoted 6 lines
fs/ext3/super.c +2
-0@@ -520,6 +520,8 @@ static void ext3_clear_inode(struct inode *inode)EXT3_I (inode)->i_block_alloc_info =NULL ;if (unlikely (rsv))kfree (rsv); +journal_release_jbd_inode (EXT3_SB (inode->i_sb)->s_journal, + &EXT3_I (inode)->jinode);
(style) align with '(' on previous line.
fs/jbd/commit.c static void journal_end_buffer_io_sync(struct buffer_head *bh, int uptodate) · line 41
Quoted 5 lines
fs/jbd/commit.c +2
-2/* -* When an ext3-ordered file is truncated, it is possible that many pages are -* not sucessfully freed, because they are attached to a committing transaction. +* When an ext3 file is truncated, it is possible that some pages are not +* sucessfully freed, because they are attached to a committing transaction.
s/sucessfully/successfully/
fs/jbd/commit.c nope: · line 93
Quoted 9 lines
fs/jbd/commit.c +0
-9-static int inverted_lock (journal_t *journal,struct buffer_head *bh) -{ -if (!jbd_trylock_bh_state (bh)) { -spin_unlock (&journal->j_list_lock); -schedule (); -return 0 ; - } -return 1 ; -}
Always nice to see unpleasant code like this be removed.
fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal, · line 170
Quoted 9 lines
fs/jbd/commit.c +8
-0+static int journal_submit_data_buffers (journal_t *journal, +transaction_t *commit_transaction) { +/* +* We are in a committing transaction. Therefore no new inode +* can be added to our inode list. We use JI_COMMIT_RUNNING +* flag to protect inode we currently operate on from being +* released while we write out pages. +*/
Should we J_ASSERT() this is true? Probably better to put this comment before the function instead of inside it.
Quoted 6 lines
fs/jbd/commit.c +6
-0+spin_lock (&journal->j_list_lock); +list_for_each_entry (jinode, &commit_transaction->t_inode_list, i_list) { + mapping = jinode->i_vfs_inode->i_mapping; +if (!mapping_cap_writeback_dirty (mapping)) +continue ; + wbc.nr_to_write = mapping->nrpages *2 ;
fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal, · line 186
Quoted 10 lines
fs/jbd/commit.c +9
-0+ jinode->i_flags |=JI_COMMIT_RUNNING ; +spin_unlock (&journal->j_list_lock); + err =do_writepages (jinode->i_vfs_inode->i_mapping, &wbc); +if (!ret) + ret = err; +spin_lock (&journal->j_list_lock); + jinode->i_flags &= ~JI_COMMIT_RUNNING ; +wake_up_bit (&jinode->i_flags, __JI_COMMIT_RUNNING); } +spin_unlock (&journal->j_list_lock);
Hmm, this is one area I'm a bit worried about. In the old code the number of buffers to sync for a transaction are bounded because they can only be added to the transaction while it is open. With this new inode-list code there could be a process busily adding new dirty + mapped pages to the inode(s) in the running transaction, while we are blocked here writing out the pages in kjournald trying to commit the previous transaction.
At some point we will eventually no longer be able to add new pages to the running transaction (because it is full or was closed due to timeout) and we won't be able to start a new transaction because the committing transaction has not yet committed. At that point we would be able to finish the commit of the previous transaction, but not until we had reached the point of blocking all operations in the filesystem waiting for the new transaction to open. This would essentially lead to livelock of the system, with stop-start-stop cycles for the transactions and IO.
What would be needed is some kind of mechanism to separate the dirty pages on the inode into "dirty from current transaction" and "dirty from the previous transaction". That seems non-trivial, however. I guess we could also force any process doing writing to flush out all of the dirty and mapped pages on the inode if it detects the inode is in two transactions.
This would at least parallelize the IO submission and also throttle the addition of new pages until the dirty ones on the committing transaction had been flushed but may essentially mean sync IO performance for streaming writes on large files without delalloc (which would not add new pages that are mapped normally).
fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal,
Quoted 6 lines
fs/jbd/commit.c +5
-0+static int journal_finish_data_buffers (journal_t *journal, +transaction_t *commit_transaction) { +/* Now refile inode to proper lists */ +list_for_each_entry_safe (jinode, next_i, + &commit_transaction->t_inode_list, i_list) {
(style) align with '(' on previous line
fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal, · line 225
Quoted 9 lines
fs/jbd/commit.c +8
-0+list_del (&jinode->i_list); +if (jinode->i_next_transaction) { + jinode->i_transaction = jinode->i_next_transaction; + jinode->i_next_transaction =NULL ; +list_add (&jinode->i_list, + &jinode->i_transaction->t_inode_list); } +else + jinode->i_transaction =NULL ;
(style) } else {
jinode->i_transaction = NULL;
}fs/jbd/commit.c start_journal_io: · line 568
Quoted 6 lines
fs/jbd/commit.c +1
-1@@ -655,7 +565,14 @@ start_journal_io:so we incur less scheduling load. */ -jbd_debug (3 ,"JBD: commit phase 4\n" ); +jbd_debug (3 ,"JBD: commit phase 3\n" );
Rather than numbering these phases, which has little meaning and often means they just get renumbered like here, it is probably more useful to give them descriptive names like "JBD: commit wait for data buffers", etc.
fs/jbd/transaction.c __blist_del_buffer(struct journal_head **list, struct journal_head *jh) · line 1332
Quoted 11 lines
fs/jbd/transaction.c +3
-4@@ -1504,10 +1327,10 @@ __blist_del_buffer(struct journal_head **list, struct journal_head *jh)* Remove a buffer from the appropriate transaction list. * * Note that this function can *change* the value of -* bh->b_transaction->t_sync_datalist, t_buffers, t_forget, -* t_iobuf_list, t_shadow_list, t_log_list or t_reserved_list. If the caller -* is holding onto a copy of one of thee pointers, it could go bad. -* Generally the caller needs to re-read the pointer from the transaction_t. +* bh->b_transaction->t_buffers, t_forget, t_iobuf_list, t_shadow_list, +* t_log_list or t_reserved_list. If the caller is holding onto a copy of one +* of thee pointers, it could go bad. Generally the caller needs to re-read
s/thee/these/
fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1912
Quoted 10 lines
fs/jbd/transaction.c +10
-0+/* +* File inode in the inode list of the handle's transaction +*/ +int journal_file_inode (handle_t *handle,struct jbd_inode *jinode) +{ +transaction_t *transaction = handle->h_transaction; +journal_t *journal = transaction->t_journal; + +if (is_handle_aborted (handle)) +return 0 ;
Should we be returning an error back to the caller at this point?
fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1921
Quoted 8 lines
fs/jbd/transaction.c +8
-0+jbd_debug (4 ,"Adding inode %lu, tid:%d\n" , jinode->i_vfs_inode->i_ino, + transaction->t_tid); + +spin_lock (&journal->j_list_lock); + +if (jinode->i_transaction == transaction || + jinode->i_next_transaction == transaction) + goto done;
Is it possible/safe to do the above check outside of the j_list_lock as an optimization? We will be calling this function for every page that is dirtied and it will likely be quite highly contended. It used to be that we HAD to get this lock because we were almost certainly adding the new buffers to the transaction list, but in this updated code the common case is that the inode will already be on the list...
Quoted 14 lines
fs/jbd/transaction.c +14
-0+/* +* This function must be called when inode is journaled in ordered mode +* before truncation happens. It starts writeout of truncated part in +* case it is in the committing transaction so that we stand to ordered +* mode consistency guarantees. +*/ +int journal_begin_ordered_truncate (struct jbd_inode *inode,loff_t new_size) +{ +journal_t *journal; +transaction_t *commit_trans; +int ret =0 ; + +if (!inode->i_transaction && !inode->i_next_transaction) + goto out;
fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1965
Quoted 10 lines
fs/jbd/transaction.c +10
-0+ journal = inode->i_transaction->t_journal; +spin_lock (&journal->j_state_lock); + commit_trans = journal->j_committing_transaction; +spin_unlock (&journal->j_state_lock); +if (inode->i_transaction == commit_trans) { + ret =__filemap_fdatawrite_range (inode->i_vfs_inode->i_mapping, + new_size,LLONG_MAX ,WB_SYNC_ALL ); +if (ret) +journal_abort (journal, ret); + }
Is there any way here to entirely avoid writing blocks which were just allocated during the current transaction (e.g. temp files during compile or whatever)? One possibility is to store the transaction number when the inode was created (or first dirtied?) in ext3_inode_info and if that is equal to the committing transaction then we don't need to write anything at all?
Cheers, Andreas
--
Andreas Dilger
Sr. Staff Engineer, Lustre Group
Sun Microsystems of Canada, Inc.
Quoted 19 lines · Andreas Dilger
Quoted 6 lines · Jan Kara
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate ofQuoted 8 lines
data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.Looks like a very good patch - thanks for your effort in moving this beyond the "hand-waving" stage that it's been in for the past few years.
Quoted 8 lines
I'm looking at what implications this has for delayed allocation in ext4, because the vast majority of file data will be unmapped in that case and a journal commit in ordered mode will no longer cause the data to be flushed to disk.
I _think_ is OK, because the pdflushd will now be totally in charge of flushing the dirty pages to disk, instead of this previously being done by ordered mode in the journal.
I missed something here, just trying to understand: if a journal commit in ordered mode will no longer cause the data to be flushed to disk, how could we ensure the ordering? Are you suggesting with delayed allocation the journalling mode falls back to writeback mode?
Quoted 306 lines
I know there have been some bugs in this
area in the past, but I guess it isn't much different than running in writeback mode. That said, I don't know how many users run in writeback mode unless they are running a database, and the database is doing a lot of explicit fsync of file data so there may still be bugs lurking...Some care is still needed here because with ext4 delayed allocation the common case will be unmapped buffers, while the ext3 implementation will only have this in very rare cases (unmapped mmap writes), but it looks like the right mechanism is already in place for both.
I'll just put a bunch of comments inline, not necessarily problems, just walking through the code to ensure I understand what is going on...
fs/buffer.c static int __block_write_full_page(struct inode *inode, struct page *page, · line 1679
Quoted 7 lines
fs/buffer.c +2
-1@@ -1675,7 +1675,8 @@ static int __block_write_full_page(struct inode *inode, struct page *page,*/ clear_buffer_dirty (bh);set_buffer_uptodate (bh); - }else if (!buffer_mapped (bh) &&buffer_dirty (bh)) { + }else if (!buffer_mapped (bh) &&buffer_dirty (bh) + && !wbc->skip_unmapped) {(comment) This is skipping writeout of unmapped pages during journal commit - good, otherwise we would deadlock trying to add block mappings...
fs/ext3/inode.c void ext3_delete_inode (struct inode * inode) · line 188
Quoted 6 lines
fs/ext3/inode.c +2
-0@@ -183,6 +184,8 @@ void ext3_delete_inode (struct inode * inode) {handle_t *handle; +if (ext3_should_order_data (inode)) +ext3_begin_ordered_truncate (inode,0 );(comment) This is flushing out pages allocated in the previous transaction to keep consistent semantics in case of a crash loses the delete... There is some possibility for optimization here - comments below at
journal_begin_ordered_truncate().fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1469
Quoted 9 lines
fs/ext3/inode.c +7
-0@@ -1487,46 +1462,49 @@ static int ext3_ordered_writepage(struct page *page, +if (!wbc->skip_unmapped) { + handle =ext3_journal_start (inode, +ext3_writepage_trans_blocks (inode)); +if (IS_ERR (handle)) { + ret =PTR_ERR (handle); + goto out_fail; + } }I guess the common case here for delalloc is that if someone cares to fsync the file then that inode would be added to the journal list and the pages would be mapped here in the process context and flushed with the journal commit. This is ideal because it avoids syncing out all of the dirty pages for inodes that were NOT fsync'd and fixes the "one process doing fsync kills performance for streaming writes" problem in ordered mode that was recently discussed on the list.
We in fact win twice because
fsync_page_range()will potentially only map and flush the range of pages that the application cares about and does not necessarily have to write out all pages (though that will still happen in ordered mode if the pages had previously been mapped).fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1486
Quoted 2 lines
fs/ext3/inode.c +2
-0+else if (!PageMappedToDisk (page)) + goto out_fail;(style) } else if (...) { goto out_fail; }fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1492
Quoted 5 lines
fs/ext3/inode.c +1
-0+/* This can go as soon as someone cares about that ;) */ if (!page_has_buffers (page)) {create_empty_buffers (page, inode->i_sb->s_blocksize, (1 << BH_Dirty)|(1 << BH_Uptodate)); }Is there any reason to keep this in the non-data-journal case? Adding buffer heads to every page is a non-trivial amount of RAM usage.
fs/ext3/inode.c int ext3_write_inode(struct inode *inode, int wait) · line 2976
Quoted 6 lines
fs/ext3/inode.c +1
-1@@ -2989,7 +2973,14 @@ int ext3_write_inode(struct inode *inode, int wait)* be freed, so we have a strong guarantee that no future commit will * leave these blocks visible to the user.) * -* Called with inode->sem down. +* Another thing we have to asure is that if we are in ordered mode s/asure/assure/
fs/ext3/inode.c int ext3_write_inode(struct inode *inode, int wait) · line 2978
Quoted 2 lines
fs/ext3/inode.c +2
-0+* and inode is still attached to the committing transaction, we must +* we start writeout of all the dirty buffers which are being truncated. s/buffers/pages/
fs/ext3/inode.c int ext3_setattr(struct dentry *dentry, struct iattr *attr) · line 3028
Quoted 7 lines
fs/ext3/inode.c +3
-0@@ -3032,6 +3023,13 @@ int ext3_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_valid & ATTR_SIZE && attr->ia_size < inode->i_size) {handle_t *handle; +if (ext3_should_order_data (inode)) { + error =ext3_begin_ordered_truncate (inode, + attr->ia_size);(style) align "
attr->ia_size" with previous '('.fs/ext3/super.c static void ext3_clear_inode(struct inode *inode) · line 524
Quoted 6 lines
fs/ext3/super.c +2
-0@@ -520,6 +520,8 @@ static void ext3_clear_inode(struct inode *inode)EXT3_I (inode)->i_block_alloc_info =NULL ;if (unlikely (rsv))kfree (rsv); +journal_release_jbd_inode (EXT3_SB (inode->i_sb)->s_journal, + &EXT3_I (inode)->jinode);(style) align with '(' on previous line.
fs/jbd/commit.c static void journal_end_buffer_io_sync(struct buffer_head *bh, int uptodate) · line 41
Quoted 5 lines
fs/jbd/commit.c +2
-2/* -* When an ext3-ordered file is truncated, it is possible that many pages are -* not sucessfully freed, because they are attached to a committing transaction. +* When an ext3 file is truncated, it is possible that some pages are not +* sucessfully freed, because they are attached to a committing transaction. s/sucessfully/successfully/
fs/jbd/commit.c nope: · line 93
Quoted 9 lines
fs/jbd/commit.c +0
-9-static int inverted_lock (journal_t *journal,struct buffer_head *bh) -{ -if (!jbd_trylock_bh_state (bh)) { -spin_unlock (&journal->j_list_lock); -schedule (); -return 0 ; - } -return 1 ; -}Always nice to see unpleasant code like this be removed.
fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal, · line 170
Quoted 9 lines
fs/jbd/commit.c +8
-0+static int journal_submit_data_buffers (journal_t *journal, +transaction_t *commit_transaction) { +/* +* We are in a committing transaction. Therefore no new inode +* can be added to our inode list. We use JI_COMMIT_RUNNING +* flag to protect inode we currently operate on from being +* released while we write out pages. +*/ Should we
J_ASSERT()this is true? Probably better to put this comment before the function instead of inside it.Quoted 6 lines
fs/jbd/commit.c +6
-0+spin_lock (&journal->j_list_lock); +list_for_each_entry (jinode, &commit_transaction->t_inode_list, i_list) { + mapping = jinode->i_vfs_inode->i_mapping; +if (!mapping_cap_writeback_dirty (mapping)) +continue ; + wbc.nr_to_write = mapping->nrpages *2 ;fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal, · line 186
Quoted 10 lines
fs/jbd/commit.c +9
-0+ jinode->i_flags |=JI_COMMIT_RUNNING ; +spin_unlock (&journal->j_list_lock); + err =do_writepages (jinode->i_vfs_inode->i_mapping, &wbc); +if (!ret) + ret = err; +spin_lock (&journal->j_list_lock); + jinode->i_flags &= ~JI_COMMIT_RUNNING ; +wake_up_bit (&jinode->i_flags, __JI_COMMIT_RUNNING); } +spin_unlock (&journal->j_list_lock);Hmm, this is one area I'm a bit worried about. In the old code the number of buffers to sync for a transaction are bounded because they can only be added to the transaction while it is open. With this new inode-list code there could be a process busily adding new dirty + mapped pages to the inode(s) in the running transaction, while we are blocked here writing out the pages in kjournald trying to commit the previous transaction.
At some point we will eventually no longer be able to add new pages to the running transaction (because it is full or was closed due to timeout) and we won't be able to start a new transaction because the committing transaction has not yet committed. At that point we would be able to finish the commit of the previous transaction, but not until we had reached the point of blocking all operations in the filesystem waiting for the new transaction to open. This would essentially lead to livelock of the system, with stop-start-stop cycles for the transactions and IO.
What would be needed is some kind of mechanism to separate the dirty pages on the inode into "dirty from current transaction" and "dirty from the previous transaction". That seems non-trivial, however. I guess we could also force any process doing writing to flush out all of the dirty and mapped pages on the inode if it detects the inode is in two transactions.
This would at least parallelize the IO submission and also throttle the addition of new pages until the dirty ones on the committing transaction had been flushed but may essentially mean sync IO performance for streaming writes on large files without delalloc (which would not add new pages that are mapped normally).
fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal,
Quoted 6 lines
fs/jbd/commit.c +5
-0+static int journal_finish_data_buffers (journal_t *journal, +transaction_t *commit_transaction) { +/* Now refile inode to proper lists */ +list_for_each_entry_safe (jinode, next_i, + &commit_transaction->t_inode_list, i_list) {(style) align with '(' on previous line
fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal, · line 225
Quoted 9 lines
fs/jbd/commit.c +8
-0+list_del (&jinode->i_list); +if (jinode->i_next_transaction) { + jinode->i_transaction = jinode->i_next_transaction; + jinode->i_next_transaction =NULL ; +list_add (&jinode->i_list, + &jinode->i_transaction->t_inode_list); } +else + jinode->i_transaction =NULL ;(style) } else { jinode->i_transaction = NULL; }fs/jbd/commit.c start_journal_io: · line 568
Quoted 6 lines
fs/jbd/commit.c +1
-1@@ -655,7 +565,14 @@ start_journal_io:so we incur less scheduling load. */ -jbd_debug (3 ,"JBD: commit phase 4\n" ); +jbd_debug (3 ,"JBD: commit phase 3\n" );Rather than numbering these phases, which has little meaning and often means they just get renumbered like here, it is probably more useful to give them descriptive names like "JBD: commit wait for data buffers", etc.
fs/jbd/transaction.c __blist_del_buffer(struct journal_head **list, struct journal_head *jh) · line 1332
Quoted 11 lines
fs/jbd/transaction.c +3
-4@@ -1504,10 +1327,10 @@ __blist_del_buffer(struct journal_head **list, struct journal_head *jh)* Remove a buffer from the appropriate transaction list. * * Note that this function can *change* the value of -* bh->b_transaction->t_sync_datalist, t_buffers, t_forget, -* t_iobuf_list, t_shadow_list, t_log_list or t_reserved_list. If the caller -* is holding onto a copy of one of thee pointers, it could go bad. -* Generally the caller needs to re-read the pointer from the transaction_t. +* bh->b_transaction->t_buffers, t_forget, t_iobuf_list, t_shadow_list, +* t_log_list or t_reserved_list. If the caller is holding onto a copy of one +* of thee pointers, it could go bad. Generally the caller needs to re-read s/thee/these/
fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1912
Quoted 10 lines
fs/jbd/transaction.c +10
-0+/* +* File inode in the inode list of the handle's transaction +*/ +int journal_file_inode (handle_t *handle,struct jbd_inode *jinode) +{ +transaction_t *transaction = handle->h_transaction; +journal_t *journal = transaction->t_journal; + +if (is_handle_aborted (handle)) +return 0 ;Should we be returning an error back to the caller at this point?
fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1921
Quoted 8 lines
fs/jbd/transaction.c +8
-0+jbd_debug (4 ,"Adding inode %lu, tid:%d\n" , jinode->i_vfs_inode->i_ino, + transaction->t_tid); + +spin_lock (&journal->j_list_lock); + +if (jinode->i_transaction == transaction || + jinode->i_next_transaction == transaction) + goto done;Is it possible/safe to do the above check outside of the
j_list_lockas an optimization? We will be calling this function for every page that is dirtied and it will likely be quite highly contended. It used to be that we HAD to get this lock because we were almost certainly adding the new buffers to the transaction list, but in this updated code the common case is that the inode will already be on the list...Quoted 14 lines
fs/jbd/transaction.c +14
-0+/* +* This function must be called when inode is journaled in ordered mode +* before truncation happens. It starts writeout of truncated part in +* case it is in the committing transaction so that we stand to ordered +* mode consistency guarantees. +*/ +int journal_begin_ordered_truncate (struct jbd_inode *inode,loff_t new_size) +{ +journal_t *journal; +transaction_t *commit_trans; +int ret =0 ; + +if (!inode->i_transaction && !inode->i_next_transaction) + goto out;fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1965
Quoted 10 lines
fs/jbd/transaction.c +10
-0+ journal = inode->i_transaction->t_journal; +spin_lock (&journal->j_state_lock); + commit_trans = journal->j_committing_transaction; +spin_unlock (&journal->j_state_lock); +if (inode->i_transaction == commit_trans) { + ret =__filemap_fdatawrite_range (inode->i_vfs_inode->i_mapping, + new_size,LLONG_MAX ,WB_SYNC_ALL ); +if (ret) +journal_abort (journal, ret); + }Is there any way here to entirely avoid writing blocks which were just allocated during the current transaction (e.g. temp files during compile or whatever)? One possibility is to store the transaction number when the inode was created (or first dirtied?) in
ext3_inode_infoand if that is equal to the committing transaction then we don't need to write anything at all?Cheers, Andreas
--
Andreas Dilger
Sr. Staff Engineer, Lustre Group
Sun Microsystems of Canada, Inc.--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 4 lines · Andreas Dilger
I'm looking at what implications this has for delayed allocation in ext4, because the vast majority of file data will be unmapped in that case and a journal commit in ordered mode will no longer cause the data to be flushed to disk.
The buffers shouldn't be unmapped. They are accounted for and doing the delalloc conversion is easier than really allocating blocks for truely unmapped blocks. You should probably reuse BH_Delay for that as it has all the right handling in buffer.c in place due to XFS.
Also on any filesystem with ->page_mkwrite implemented unmapped buffers should be entirely gone because we now have the proper early reservation / allocation infrastructure in place.
Jan Kara
maintainerQuoted 24 lines · Andreas Dilger
Quoted 6 lines · Jan Kara
Below is my rewrite of ordered mode in JBD. Now we don't have a list of
data buffers that need syncing on transaction commit but a list of inodes that need writeout during commit. This brings all sorts of advantages such as possibility to get rid of journal heads and buffer heads for data buffers in ordered mode, better ordering of writes on transaction commit, simplification of some JBD code, no more anonymous pages when truncate ofQuoted 8 lines
data being committed happens. The patch has survived some light testing but it still has some potential of eating your data so beware :) I've run dbench to see whether we didn't decrease performance by different handling of truncate and the throughput I'm getting on my machine is the same (OK, is lower by 0.5%) if I disable the code in truncate waiting for commit to finish... Also the throughput of dbench is about 2% better with my patch than with current JBD.
Any comments or testing most welcome.Looks like a very good patch - thanks for your effort in moving this beyond the "hand-waving" stage that it's been in for the past few years.
I'm looking at what implications this has for delayed allocation in ext4, because the vast majority of file data will be unmapped in that case and a journal commit in ordered mode will no longer cause the data to be flushed to disk.
Quoted 7 lines
I _think_ is OK, because the pdflushd will now be totally in charge of flushing the dirty pages to disk, instead of this previously being done by ordered mode in the journal. I know there have been some bugs in this area in the past, but I guess it isn't much different than running in writeback mode. That said, I don't know how many users run in writeback mode unless they are running a database, and the database is doing a lot of explicit fsync of file data so there may still be bugs lurking...
Yes, they basically get guarantees as in writeback mode. As I wrote to
Mingming, I'm not sure how that is handled with current ordered-mode handling because you cannot afford to do block allocation from the commit code anyway. We could possibly do the allocation if we were sure that we have enough credits in the transaction (but that currently isn't the case since we really count the number of buffers dirtied on the account of transaction handle and after the handle is dropped, we give back unused credits to the transaction).
Quoted 61 lines
Some care is still needed here because with ext4 delayed allocation the common case will be unmapped buffers, while the ext3 implementation will only have this in very rare cases (unmapped mmap writes), but it looks like the right mechanism is already in place for both.
I'll just put a bunch of comments inline, not necessarily problems, just walking through the code to ensure I understand what is going on...
fs/buffer.c static int __block_write_full_page(struct inode *inode, struct page *page, · line 1679
Quoted 7 lines
fs/buffer.c +2
-1@@ -1675,7 +1675,8 @@ static int __block_write_full_page(struct inode *inode, struct page *page,*/ clear_buffer_dirty (bh);set_buffer_uptodate (bh); - }else if (!buffer_mapped (bh) &&buffer_dirty (bh)) { + }else if (!buffer_mapped (bh) &&buffer_dirty (bh) + && !wbc->skip_unmapped) {(comment) This is skipping writeout of unmapped pages during journal commit - good, otherwise we would deadlock trying to add block mappings...
fs/ext3/inode.c void ext3_delete_inode (struct inode * inode) · line 188
Quoted 6 lines
fs/ext3/inode.c +2
-0@@ -183,6 +184,8 @@ void ext3_delete_inode (struct inode * inode) {handle_t *handle; +if (ext3_should_order_data (inode)) +ext3_begin_ordered_truncate (inode,0 );(comment) This is flushing out pages allocated in the previous transaction to keep consistent semantics in case of a crash loses the delete... There is some possibility for optimization here - comments below at
journal_begin_ordered_truncate().fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1469
Quoted 9 lines
fs/ext3/inode.c +7
-0@@ -1487,46 +1462,49 @@ static int ext3_ordered_writepage(struct page *page, +if (!wbc->skip_unmapped) { + handle =ext3_journal_start (inode, +ext3_writepage_trans_blocks (inode)); +if (IS_ERR (handle)) { + ret =PTR_ERR (handle); + goto out_fail; + } }I guess the common case here for delalloc is that if someone cares to fsync the file then that inode would be added to the journal list and the pages would be mapped here in the process context and flushed with the journal commit. This is ideal because it avoids syncing out all of the dirty pages for inodes that were NOT fsync'd and fixes the "one process doing fsync kills performance for streaming writes" problem in ordered mode that was recently discussed on the list.
We in fact win twice because
fsync_page_range()will potentially only map and flush the range of pages that the application cares about and does not necessarily have to write out all pages (though that will still happen in ordered mode if the pages had previously been mapped).fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1486
Quoted 2 lines
fs/ext3/inode.c +2
-0+else if (!PageMappedToDisk (page)) + goto out_fail;(style) } else if (...) { goto out_fail; }
Quoted 8 lines
fs/ext3/inode.c static int ext3_ordered_writepage(struct page *page, · line 1492
Quoted 5 lines
fs/ext3/inode.c +1
-0+/* This can go as soon as someone cares about that ;) */ if (!page_has_buffers (page)) {create_empty_buffers (page, inode->i_sb->s_blocksize, (1 << BH_Dirty)|(1 << BH_Uptodate)); }Is there any reason to keep this in the non-data-journal case? Adding buffer heads to every page is a non-trivial amount of RAM usage.
No. But I think that it's better to do nobh ordered mode in a separate
patch. This one is complicated enough...
Quoted 4 lines
Quoted 4 lines
fs/jbd/commit.c +3
-0+static int journal_submit_data_buffers (journal_t *journal, +transaction_t *commit_transaction) { +/*
Quoted 8 lines
fs/jbd/commit.c static int journal_write_commit_record(journal_t *journal, · line 170
Quoted 5 lines
fs/jbd/commit.c +5
-0+* We are in a committing transaction. Therefore no new inode +* can be added to our inode list. We use JI_COMMIT_RUNNING +* flag to protect inode we currently operate on from being +* released while we write out pages. +*/ Should we
J_ASSERT()this is true? Probably better to put this comment before the function instead of inside it.
Comment moved. How would you like to assert for inode being freed? Maybe
we could check jinode->i_transaction == commit_transaction. That should be good enough.
Quoted 15 lines
Quoted 15 lines
fs/jbd/commit.c +14
-0+spin_lock (&journal->j_list_lock); +list_for_each_entry (jinode, &commit_transaction->t_inode_list, i_list) { + mapping = jinode->i_vfs_inode->i_mapping; +if (!mapping_cap_writeback_dirty (mapping)) +continue ; + wbc.nr_to_write = mapping->nrpages *2 ; + jinode->i_flags |=JI_COMMIT_RUNNING ; +spin_unlock (&journal->j_list_lock); + err =do_writepages (jinode->i_vfs_inode->i_mapping, &wbc); +if (!ret) + ret = err; +spin_lock (&journal->j_list_lock); + jinode->i_flags &= ~JI_COMMIT_RUNNING ; +wake_up_bit (&jinode->i_flags, __JI_COMMIT_RUNNING); }
Quoted 8 lines
Review static int ext3_ordered_writepage(struct page *page, · line 1501
Quoted 1 line
(quoted diff) +1
-0+ spin_unlock(&journal->j_list_lock);Hmm, this is one area I'm a bit worried about. In the old code the number of buffers to sync for a transaction are bounded because they can only be added to the transaction while it is open. With this new inode-list code there could be a process busily adding new dirty + mapped pages to the inode(s) in the running transaction, while we are blocked here writing out the pages in kjournald trying to commit the previous transaction.
Correct me if I'm wrong but as far as I look into the write-out code, we
do just one-sweep scan of mapping when writing out dirty pages. So we could possibly write out more than we need but at most the whole file. Oh, ok, someone could be extending the file as well but we could fix that by setting .range_end to the current i_size - that's actually a nice optimization anyway so I've done that.
Quoted 25 lines
At some point we will eventually no longer be able to add new pages to the running transaction (because it is full or was closed due to timeout) and we won't be able to start a new transaction because the committing transaction has not yet committed. At that point we would be able to finish the commit of the previous transaction, but not until we had reached the point of blocking all operations in the filesystem waiting for the new transaction to open. This would essentially lead to livelock of the system, with stop-start-stop cycles for the transactions and IO.
What would be needed is some kind of mechanism to separate the dirty pages on the inode into "dirty from current transaction" and "dirty from the previous transaction". That seems non-trivial, however. I guess we could also force any process doing writing to flush out all of the dirty and mapped pages on the inode if it detects the inode is in two transactions.
This would at least parallelize the IO submission and also throttle the addition of new pages until the dirty ones on the committing transaction had been flushed but may essentially mean sync IO performance for streaming writes on large files without delalloc (which would not add new pages that are mapped normally).
Quoted 4 lines
fs/jbd/commit.c +0
-0@@ -655,7 +565,14 @@ start_journal_io:so we incur less scheduling load. */
Quoted 6 lines
fs/jbd/commit.c start_journal_io: · line 568
Quoted 2 lines
fs/jbd/commit.c +1
-1-jbd_debug (3 ,"JBD: commit phase 4\n" ); +jbd_debug (3 ,"JBD: commit phase 3\n" );Rather than numbering these phases, which has little meaning and often means they just get renumbered like here, it is probably more useful to give them descriptive names like "JBD: commit wait for data buffers", etc.
Yes, but I guess that's for a separate patch. I'll add it to my todo :).
Quoted 4 lines
Quoted 4 lines
fs/jbd/transaction.c +4
-0+/* +* File inode in the inode list of the handle's transaction +*/ +int journal_file_inode (handle_t *handle,struct jbd_inode *jinode)
Quoted 8 lines
fs/jbd/transaction.c void journal_refile_buffer(journal_t *journal, struct journal_head *jh) · line 1912
Quoted 6 lines
fs/jbd/transaction.c +6
-0+{ +transaction_t *transaction = handle->h_transaction; +journal_t *journal = transaction->t_journal; + +if (is_handle_aborted (handle)) +return 0 ;Should we be returning an error back to the caller at this point?
Yes, of course.
Quoted 7 lines
Quoted 7 lines
fs/jbd/transaction.c +7
-0+jbd_debug (4 ,"Adding inode %lu, tid:%d\n" , jinode->i_vfs_inode->i_ino, + transaction->t_tid); + +spin_lock (&journal->j_list_lock); + +if (jinode->i_transaction == transaction || + jinode->i_next_transaction == transaction)
Quoted 8 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ goto done;Is it possible/safe to do the above check outside of the
j_list_lockas an optimization? We will be calling this function for every page that is dirtied and it will likely be quite highly contended. It used to be that we HAD to get this lock because we were almost certainly adding the new buffers to the transaction list, but in this updated code the common case is that the inode will already be on the list...
Probably we could do that - there are two places where we remove inode
from a transaction list (and these are the only ones interesting for races). One is the commit code and the other one is journal_release_jbd_inode(). We are safe from the second one because we obviously hold a reference to the inode. The first one is more subtle but if jinode->i_transaction == transaction, it is filed on the running transaction's list from which we have handle and so commit code cannot touch it. If jinode->i_next_transaction == transaction, then commit code can change inode under us but it will file the inode to the running transaction's list anyway so there's nothing to lose.
Changed the code and added a big comment...
Quoted 23 lines
Quoted 23 lines
fs/jbd/transaction.c +23
-0+/* +* This function must be called when inode is journaled in ordered mode +* before truncation happens. It starts writeout of truncated part in +* case it is in the committing transaction so that we stand to ordered +* mode consistency guarantees. +*/ +int journal_begin_ordered_truncate (struct jbd_inode *inode,loff_t new_size) +{ +journal_t *journal; +transaction_t *commit_trans; +int ret =0 ; + +if (!inode->i_transaction && !inode->i_next_transaction) + goto out; + journal = inode->i_transaction->t_journal; +spin_lock (&journal->j_state_lock); + commit_trans = journal->j_committing_transaction; +spin_unlock (&journal->j_state_lock); +if (inode->i_transaction == commit_trans) { + ret =__filemap_fdatawrite_range (inode->i_vfs_inode->i_mapping, + new_size,LLONG_MAX ,WB_SYNC_ALL ); +if (ret) +journal_abort (journal, ret);
Quoted 8 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ }Is there any way here to entirely avoid writing blocks which were just allocated during the current transaction (e.g. temp files during compile or whatever)? One possibility is to store the transaction number when the inode was created (or first dirtied?) in
ext3_inode_infoand if that is equal to the committing transaction then we don't need to write anything at all?
Note that we write out blocks only if we truncate inode that is being
currently committed. I.e., it has been created in the previous transaction and is truncated in the current one. Or did you mean something else?
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 14 lines · Jan Kara
Quoted 5 lines · Andreas Dilger
I'm looking at what implications this has for delayed allocation in ext4, because the vast majority of file data will be unmapped in that case and a journal commit in ordered mode will no longer cause the data to be flushed to disk.
Quoted 7 lines
I _think_ is OK, because the pdflushd will now be totally in charge of flushing the dirty pages to disk, instead of this previously being done by ordered mode in the journal. I know there have been some bugs in this area in the past, but I guess it isn't much different than running in writeback mode. That said, I don't know how many users run in writeback mode unless they are running a database, and the database is doing a lot of explicit fsync of file data so there may still be bugs lurking...
Quoted 8 lines
Yes, they basically get guarantees as in writeback mode. As I wrote to
Mingming, I'm not sure how that is handled with current ordered-mode handling because you cannot afford to do block allocation from the commit code anyway. We could possibly do the allocation if we were sure that we have enough credits in the transaction (but that currently isn't the case since we really count the number of buffers dirtied on the account of transaction handle and after the handle is dropped, we give back unused credits to the transaction).
Actually, I think this is the same semantics as the existing ordered mode, as long as the on-disk inode is only changed as part of the transaction that is allocating the pages (which is needed for correctness in any case).
The noticable difference will be that the new ordered mode will not force out pending dirty pages, if it is run with delalloc. This would be a big improvement, because users of fsync() on a filesystem with another streaming IO process have terrible latency problems with the current code. So not only does your patch reduce complexity, it allows ext4 delalloc to work in ordered mode, and improves overall responsiveness as well.
I think in ext4 it makes sense to always be running in delalloc mode once your patch is landed, as delalloc has improved performance greatly, and one of the few reasons we haven't submitted it upstream was the lack of ordered mode support.
As far as patching ext3/jbd vs. ext4/jbd2, I think it makes a lot more sense to test this patch with ext4 first in -mm because people will not expect as much stability with ext4 vs. ext3 and this will allow some time to get testing on your changes, which affect a very core part of ext3/jbd.
Quoted 7 lines
Quoted 6 lines
Hmm, this is one area I'm a bit worried about. In the old code the number of buffers to sync for a transaction are bounded because they can only be added to the transaction while it is open. With this new inode-list code there could be a process busily adding new dirty + mapped pages to the inode(s) in the running transaction, while we are blocked here writing out the pages in kjournald trying to commit the previous transaction.
Quoted 6 lines
Correct me if I'm wrong but as far as I look into the write-out code, we
do just one-sweep scan of mapping when writing out dirty pages. So we could possibly write out more than we need but at most the whole file. Oh, ok, someone could be extending the file as well but we could fix that by setting .range_end to the currenti_size- that's actually a nice optimization anyway so I've done that.
Yes, the continually-growing file livelock is a problem that hit fsync in the past, and we need to avoid it here also. Using .range_end is a good way to avoid it, though it isn't 100% foolproof due to usage like:
ftruncate(fd, LARGE_SIZE); write(fd, buf, LARGE_SIZE);
It will eventually end, but not for a long time.
Quoted 13 lines
Quoted 4 lines
Quoted 4 lines
fs/jbd/transaction.c +4
-0+spin_lock (&journal->j_list_lock); + +if (jinode->i_transaction == transaction || + jinode->i_next_transaction == transaction)Quoted 8 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ goto done;Is it possible/safe to do the above check outside of the
j_list_lockas an optimization? We will be calling this function for every page that is dirtied and it will likely be quite highly contended. It used to be that we HAD to get this lock because we were almost certainly adding the new buffers to the transaction list, but in this updated code the common case is that the inode will already be on the list...
Quoted 11 lines
Probably we could do that - there are two places where we remove inode
from a transaction list (and these are the only ones interesting for races). One is the commit code and the other one isjournal_release_jbd_inode(). We are safe from the second one because we obviously hold a reference to the inode. The first one is more subtle but ifjinode->i_transaction== transaction, it is filed on the running transaction's list from which we have handle and so commit code cannot touch it. Ifjinode->i_next_transaction== transaction, then commit code can change inode under us but it will file the inode to the running transaction's list anyway so there's nothing to lose.
Changed the code and added a big comment...
Great, thanks. Avoiding lock contention is a big issue on many-way SMP systems that are becoming much more common these days.
Quoted 27 lines
Quoted 18 lines
Quoted 18 lines
fs/jbd/transaction.c +18
-0+int journal_begin_ordered_truncate (struct jbd_inode *inode,loff_t new_size) +{ +journal_t *journal; +transaction_t *commit_trans; +int ret =0 ; + +if (!inode->i_transaction && !inode->i_next_transaction) + goto out; + + journal = inode->i_transaction->t_journal; +spin_lock (&journal->j_state_lock); + commit_trans = journal->j_committing_transaction; +spin_unlock (&journal->j_state_lock); +if (inode->i_transaction == commit_trans) { + ret =__filemap_fdatawrite_range (inode->i_vfs_inode->i_mapping, + new_size,LLONG_MAX ,WB_SYNC_ALL ); +if (ret) +journal_abort (journal, ret);Quoted 8 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ }Is there any way here to entirely avoid writing blocks which were just allocated during the current transaction (e.g. temp files during compile or whatever)? One possibility is to store the transaction number when the inode was created (or first dirtied?) in
ext3_inode_infoand if that is equal to the committing transaction then we don't need to write anything at all?
Quoted 3 lines
Note that we write out blocks only if we truncate inode that is being
currently committed. I.e., it has been created in the previous transaction and is truncated in the current one. Or did you mean something else?
OK, it seems very few files will span the truncate boundary (for compiles likely only num_cpus) and if they can be truncated within the same transaction without an IO that is fine.
Cheers, Andreas
--
Andreas Dilger
Sr. Staff Engineer, Lustre Group
Sun Microsystems of Canada, Inc.
Possible race between direct IO and JBD?
In reply to Jan KaraHi,
While looking at a bug related to direct IO returns to EIO, after looking at the code, I found there is a window that try_to_free_buffers() from direct IO could race with JBD, which holds the reference to the data buffers before journal_commit_transaction() ensures the data buffers has reached to the disk.
A little more detail: to prepare for direct IO, generic_file_direct_IO() calls invalidate_inode_pages2_range() to invalidate the pages in the cache before performaning direct IO. invalidate_inode_pages2_range() tries to free the buffers via try_to free_buffers(), but sometimes it can't, due to the buffers is possible still on some transaction's t_sync_datalist or t_locked_list waiting for journal_commit_transaction() to process it.
Currently Direct IO simply returns EIO if try_to_free_buffers() finds the buffer is busy, as it has no clue that JBD is referencing it.
Is this a known issue and expected behavior? Any thoughts?
Mingming
Andrew Morton
maintainerRe: Possible race between direct IO and JBD?
Quoted 16 lines · On Fri, 25 Apr 2008 16:38:23 -0700 Mingming Cao <[email protected]> wrote:
Hi,
While looking at a bug related to direct IO returns to EIO, after looking at the code, I found there is a window that
try_to_free_buffers()from direct IO could race with JBD, which holds the reference to the data buffers beforejournal_commit_transaction()ensures the data buffers has reached to the disk.A little more detail: to prepare for direct IO,
generic_file_direct_IO()callsinvalidate_inode_pages2_range()to invalidate the pages in the cache before performaning direct IO.invalidate_inode_pages2_range()tries to free the buffers viatry_tofree_buffers(), but sometimes it can't, due to the buffers is possible still on some transaction'st_sync_datalistort_locked_listwaiting forjournal_commit_transaction()to process it.
Quoted 4 lines
Currently Direct IO simply returns EIO if
try_to_free_buffers()finds the buffer is busy, as it has no clue that JBD is referencing it.Is this a known issue and expected behavior? Any thoughts?
Something like that might be possible, although people used to test buffered-vs-direct fairly heavily.
generic_file_direct_IO() will run filemap_write_and_wait()->filemap_fdatawrite() under i_mutex, and this should run commits, write back dirty pages, etc.
There might remain races though, perhaps with page faults.
Jan Kara
maintainerRe: Possible race between direct IO and JBD?
In reply to Mingming CaoHi,
Quoted 14 lines · Mingming Cao
While looking at a bug related to direct IO returns to EIO, after looking at the code, I found there is a window that
try_to_free_buffers()from direct IO could race with JBD, which holds the reference to the data buffers beforejournal_commit_transaction()ensures the data buffers has reached to the disk.A little more detail: to prepare for direct IO,
generic_file_direct_IO()callsinvalidate_inode_pages2_range()to invalidate the pages in the cache before performaning direct IO.invalidate_inode_pages2_range()tries to free the buffers viatry_tofree_buffers(), but sometimes it can't, due to the buffers is possible still on some transaction'st_sync_datalistort_locked_listwaiting forjournal_commit_transaction()to process it.
Quoted 4 lines
Currently Direct IO simply returns EIO if
try_to_free_buffers()finds the buffer is busy, as it has no clue that JBD is referencing it.Is this a known issue and expected behavior? Any thoughts?
Are you seeing this in data=ordered mode? As Andrew pointed out we dofilemap_write_and_wait() so all the relevant data buffers of the inode should be already on disk. In __journal_try_to_free_buffer() we check whether the buffer is already-written-out data buffer and unfile and free it in that case. It shouldn't happen that a data buffer has b_next_transaction set so really the only idea why try_to_free_buffers() could fail is that somebody manages to write to a page via mmap before invalidate_inode_pages2_range() gets to it. Under which kind of load do you observe the problem? Do you know exactly because of which condition does journal_try_to_free_buffers() fail?
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: Possible race between direct IO and JBD?
Quoted 21 lines · Jan Kara
Hi,
Quoted 14 lines · Mingming Cao
While looking at a bug related to direct IO returns to EIO, after looking at the code, I found there is a window that
try_to_free_buffers()from direct IO could race with JBD, which holds the reference to the data buffers beforejournal_commit_transaction()ensures the data buffers has reached to the disk.A little more detail: to prepare for direct IO,
generic_file_direct_IO()callsinvalidate_inode_pages2_range()to invalidate the pages in the cache before performaning direct IO.invalidate_inode_pages2_range()tries to free the buffers viatry_tofree_buffers(), but sometimes it can't, due to the buffers is possible still on some transaction'st_sync_datalistort_locked_listwaiting forjournal_commit_transaction()to process it.Quoted 4 lines
Currently Direct IO simply returns EIO if
try_to_free_buffers()finds the buffer is busy, as it has no clue that JBD is referencing it.Is this a known issue and expected behavior? Any thoughts?
Quoted 11 lines
Are you seeing this in data=ordered mode? As Andrew pointed out we do
filemap_write_and_wait()so all the relevant data buffers of the inode should be already on disk. In__journal_try_to_free_buffer()we check whether the buffer is already-written-out data buffer and unfile and free it in that case. It shouldn't happen that a data buffer hasb_next_transactionset so really the only idea whytry_to_free_buffers()could fail is that somebody manages to write to a page via mmap beforeinvalidate_inode_pages2_range()gets to it. Under which kind of load do you observe the problem? Do you know exactly because of which condition doesjournal_try_to_free_buffers()fail?
Thank you for your reply.
What we are noticing is invalidate_inode_pages2_range() fails with -EIO (from try_to_free_buffers() since b_count > 0).
I don't think the file is being updated through mmap(). Previous writepage() added these buffers to t_sync_data list (data=ordered). filemap_write_and_wait() waits for pagewrite back to be cleared. So, buffers are no longer dirty, but still on the t_sync_data and kjournald didn't get chance to process them yet :(
Since we have elevated b_count on these buffers, try_to_free_buffers() fails. How can we make filemap_write_and_wait() to wait for kjournald to unfile these buffers ?
Does this makes sense ? Am I missing something here ?
Thanks,
Badari
Jan Kara
maintainerRe: Possible race between direct IO and JBD?
Quoted 46 lines · Badari Pulavarty
Quoted 21 lines · Jan Kara
Hi,
Quoted 14 lines · Mingming Cao
While looking at a bug related to direct IO returns to EIO, after looking at the code, I found there is a window that
try_to_free_buffers()from direct IO could race with JBD, which holds the reference to the data buffers beforejournal_commit_transaction()ensures the data buffers has reached to the disk.A little more detail: to prepare for direct IO,
generic_file_direct_IO()callsinvalidate_inode_pages2_range()to invalidate the pages in the cache before performaning direct IO.invalidate_inode_pages2_range()tries to free the buffers viatry_tofree_buffers(), but sometimes it can't, due to the buffers is possible still on some transaction'st_sync_datalistort_locked_listwaiting forjournal_commit_transaction()to process it.Quoted 4 lines
Currently Direct IO simply returns EIO if
try_to_free_buffers()finds the buffer is busy, as it has no clue that JBD is referencing it.Is this a known issue and expected behavior? Any thoughts?
Quoted 11 lines
Are you seeing this in data=ordered mode? As Andrew pointed out we do
filemap_write_and_wait()so all the relevant data buffers of the inode should be already on disk. In__journal_try_to_free_buffer()we check whether the buffer is already-written-out data buffer and unfile and free it in that case. It shouldn't happen that a data buffer hasb_next_transactionset so really the only idea whytry_to_free_buffers()could fail is that somebody manages to write to a page via mmap beforeinvalidate_inode_pages2_range()gets to it. Under which kind of load do you observe the problem? Do you know exactly because of which condition doesjournal_try_to_free_buffers()fail?Thank you for your reply.
What we are noticing is
invalidate_inode_pages2_range()fails with-EIO(fromtry_to_free_buffers()sinceb_count> 0).I don't think the file is being updated through
mmap(). Previouswritepage()added these buffers tot_sync_datalist (data=ordered).filemap_write_and_wait()waits for pagewrite back to be cleared. So, buffers are no longer dirty, but still on thet_sync_dataand kjournald didn't get chance to process them yet :(
Quoted 3 lines
Since we have elevated
b_counton these buffers,try_to_free_buffers()fails. How can we makefilemap_write_and_wait()to wait for kjournald to unfile these buffers ?
Hmm, I don't get one thing:
The call chain is invalidate_inode_pages2_range() ->invalidate_complete_page2() -> try_to_release_page() -> ext3_releasepage() -> journal_try_to_free_buffers() -> __journal_try_to_free_buffer() and this function should remove the buffer from the committing transaction. So who's holding the reference to those buffers? Or is it that __journal_try_to_free_buffer() fails to remove the buffer from the committing transaction? Why?
Hmm, maybe I have one idea - in theory we could call__journal_try_to_free_buffer() exactly at the moment commit code inspects the buffer. Then we'd release the buffer from the transaction but try_to_free_buffers() would fail because of elevated b_count exactly as you described. Could you maybe verify this? Not that I'd know how to easily fix this ;)...
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: Possible race between direct IO and JBD?
Quoted 47 lines · Jan Kara
Quoted 46 lines · Badari Pulavarty
Quoted 21 lines · Jan Kara
Hi,
Quoted 14 lines · Mingming Cao
While looking at a bug related to direct IO returns to EIO, after looking at the code, I found there is a window that
try_to_free_buffers()from direct IO could race with JBD, which holds the reference to the data buffers beforejournal_commit_transaction()ensures the data buffers has reached to the disk.A little more detail: to prepare for direct IO,
generic_file_direct_IO()callsinvalidate_inode_pages2_range()to invalidate the pages in the cache before performaning direct IO.invalidate_inode_pages2_range()tries to free the buffers viatry_tofree_buffers(), but sometimes it can't, due to the buffers is possible still on some transaction'st_sync_datalistort_locked_listwaiting forjournal_commit_transaction()to process it.Quoted 4 lines
Currently Direct IO simply returns EIO if
try_to_free_buffers()finds the buffer is busy, as it has no clue that JBD is referencing it.Is this a known issue and expected behavior? Any thoughts?
Quoted 11 lines
Are you seeing this in data=ordered mode? As Andrew pointed out we do
filemap_write_and_wait()so all the relevant data buffers of the inode should be already on disk. In__journal_try_to_free_buffer()we check whether the buffer is already-written-out data buffer and unfile and free it in that case. It shouldn't happen that a data buffer hasb_next_transactionset so really the only idea whytry_to_free_buffers()could fail is that somebody manages to write to a page via mmap beforeinvalidate_inode_pages2_range()gets to it. Under which kind of load do you observe the problem? Do you know exactly because of which condition doesjournal_try_to_free_buffers()fail?Thank you for your reply.
What we are noticing is
invalidate_inode_pages2_range()fails with-EIO(fromtry_to_free_buffers()sinceb_count> 0).I don't think the file is being updated through
mmap(). Previouswritepage()added these buffers tot_sync_datalist (data=ordered).filemap_write_and_wait()waits for pagewrite back to be cleared. So, buffers are no longer dirty, but still on thet_sync_dataand kjournald didn't get chance to process them yet :(
Quoted 8 lines
Quoted 3 lines
Since we have elevated
b_counton these buffers,try_to_free_buffers()fails. How can we makefilemap_write_and_wait()to wait for kjournald to unfile these buffers ?Hmm, I don't get one thing:
The call chain isinvalidate_inode_pages2_range()->invalidate_complete_page2()->try_to_release_page()->ext3_releasepage()->journal_try_to_free_buffers()->__journal_try_to_free_buffer()and this function should remove the buffer from the committing transaction.
Thanks, yes I noticed that after you pointing this out.
But __journal_try_to_free_buffer() only unfile the buffer from t_sync_datalist or t_locked list, the journal head is not removed in journal_remove_journal_head() there, at that time, journal_remove_journal_head() just check if counter b_jcount is 0. But before calling __journal_try_to_free_buffer(), since journal_try_to_free_buffers() already increase the b_jcount in journal_grab_journal_head(), so the journal head is not removed in __journal_try_to_free_buffer-> journal_remove_journal_head()
Quoted 2 lines
So who's
holding the reference to those buffers?
Looking at the code, it seems the it's the journal_put_journal_head(jh) who remove the journal head and decrease the bh
journal_try_to_free_buffers()
{
...
jh = journal_grab_journal_head(bh);
if (!jh)
continue;
jbd_lock_bh_state(bh);
__journal_try_to_free_buffer(journal, bh);
journal_put_journal_head(jh);
jbd_unlock_bh_state(bh);...
}
so when journal_put_journal_head()-> __journal_remove_journal_head(),
now the b_jcount is zero, but isjh->b_transaction is NULL? So it seems possible that bh ref count is non zero when exit from journal_put_journal_head() if jh_b_transaction is not cleared.
I miss where jh->b_transaction is clear to NULL?
void journal_put_journal_head(struct journal_head *jh)
{
struct buffer_head *bh = jh2bh(jh);
jbd_lock_bh_journal_head(bh);
J_ASSERT_JH(jh, jh->b_jcount > 0);
--jh->b_jcount;
if (!jh->b_jcount && !jh->b_transaction) {
__journal_remove_journal_head(bh);
__brelse(bh);
}
jbd_unlock_bh_journal_head(bh);
}Mingming
Quoted 11 lines
Or is it that
__journal_try_to_free_buffer()fails to remove the buffer from the committing transaction? Why?
Hmm, maybe I have one idea - in theory we could call__journal_try_to_free_buffer()exactly at the moment commit code inspects the buffer. Then we'd release the buffer from the transaction buttry_to_free_buffers()would fail because of elevatedb_countexactly as you described. Could you maybe verify this? Not that I'd know how to easily fix this ;)...Honza
Jan Kara
maintainerRe: Possible race between direct IO and JBD?
Quoted 89 lines · Mingming Cao
Quoted 47 lines · Jan Kara
Quoted 46 lines · Badari Pulavarty
Quoted 21 lines · Jan Kara
Hi,
Quoted 14 lines · Mingming Cao
While looking at a bug related to direct IO returns to EIO, after looking at the code, I found there is a window that
try_to_free_buffers()from direct IO could race with JBD, which holds the reference to the data buffers beforejournal_commit_transaction()ensures the data buffers has reached to the disk.A little more detail: to prepare for direct IO,
generic_file_direct_IO()callsinvalidate_inode_pages2_range()to invalidate the pages in the cache before performaning direct IO.invalidate_inode_pages2_range()tries to free the buffers viatry_tofree_buffers(), but sometimes it can't, due to the buffers is possible still on some transaction'st_sync_datalistort_locked_listwaiting forjournal_commit_transaction()to process it.Quoted 4 lines
Currently Direct IO simply returns EIO if
try_to_free_buffers()finds the buffer is busy, as it has no clue that JBD is referencing it.Is this a known issue and expected behavior? Any thoughts?
Quoted 11 lines
Are you seeing this in data=ordered mode? As Andrew pointed out we do
filemap_write_and_wait()so all the relevant data buffers of the inode should be already on disk. In__journal_try_to_free_buffer()we check whether the buffer is already-written-out data buffer and unfile and free it in that case. It shouldn't happen that a data buffer hasb_next_transactionset so really the only idea whytry_to_free_buffers()could fail is that somebody manages to write to a page via mmap beforeinvalidate_inode_pages2_range()gets to it. Under which kind of load do you observe the problem? Do you know exactly because of which condition doesjournal_try_to_free_buffers()fail?Thank you for your reply.
What we are noticing is
invalidate_inode_pages2_range()fails with-EIO(fromtry_to_free_buffers()sinceb_count> 0).I don't think the file is being updated through
mmap(). Previouswritepage()added these buffers tot_sync_datalist (data=ordered).filemap_write_and_wait()waits for pagewrite back to be cleared. So, buffers are no longer dirty, but still on thet_sync_dataand kjournald didn't get chance to process them yet :(Quoted 8 lines
Quoted 3 lines
Since we have elevated
b_counton these buffers,try_to_free_buffers()fails. How can we makefilemap_write_and_wait()to wait for kjournald to unfile these buffers ?Hmm, I don't get one thing:
The call chain isinvalidate_inode_pages2_range()->invalidate_complete_page2()->try_to_release_page()->ext3_releasepage()->journal_try_to_free_buffers()->__journal_try_to_free_buffer()and this function should remove the buffer from the committing transaction.Thanks, yes I noticed that after you pointing this out.
But
__journal_try_to_free_buffer()only unfile the buffer fromt_sync_datalistort_lockedlist, the journal head is not removed injournal_remove_journal_head()there, at that time,journal_remove_journal_head()just check if counterb_jcountis 0. But before calling__journal_try_to_free_buffer(), sincejournal_try_to_free_buffers()already increase theb_jcountinjournal_grab_journal_head(), so the journal head is not removed in __journal_try_to_free_buffer->journal_remove_journal_head()Quoted 2 lines
So who's
holding the reference to those buffers?Looking at the code, it seems the it's the
journal_put_journal_head(jh)who remove the journal head and decrease the bh
journal_try_to_free_buffers()
{
...jh = journal_grab_journal_head(bh); if (!jh) continue; jbd_lock_bh_state(bh); __journal_try_to_free_buffer(journal, bh); journal_put_journal_head(jh); jbd_unlock_bh_state(bh);...
Quoted 8 lines
}
so when journal_put_journal_head()->__journal_remove_journal_head(),
now theb_jcountis zero, but isjh->b_transactionis NULL? So it seems possible that bh ref count is non zero when exit fromjournal_put_journal_head()ifjh_b_transactionis not cleared.I miss where
jh->b_transactionis clear to NULL?
__journal_unfile_buffer() called from __journal_try_to_free_buffer() setsjh->b_transaction to NULL. So as soon as journal_put_journal_head() is called, it results in freeing of journal head and releasing buffer reference. So really the only possible race I see is what I describe below...
Quoted 6 lines
Quoted 6 lines
Hmm, maybe I have one idea - in theory we could call
__journal_try_to_free_buffer()exactly at the moment commit code inspects the buffer. Then we'd release the buffer from the transaction buttry_to_free_buffers()would fail because of elevatedb_countexactly as you described. Could you maybe verify this? Not that I'd know how to easily fix this ;)...
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: Possible race between direct IO and JBD?
Quoted 97 lines · Jan Kara
Quoted 96 lines · Mingming Cao
Quoted 47 lines · Jan Kara
Quoted 46 lines · Badari Pulavarty
Quoted 21 lines · Jan Kara
Hi,
Quoted 14 lines · Mingming Cao
While looking at a bug related to direct IO returns to EIO, after looking at the code, I found there is a window that
try_to_free_buffers()from direct IO could race with JBD, which holds the reference to the data buffers beforejournal_commit_transaction()ensures the data buffers has reached to the disk.A little more detail: to prepare for direct IO,
generic_file_direct_IO()callsinvalidate_inode_pages2_range()to invalidate the pages in the cache before performaning direct IO.invalidate_inode_pages2_range()tries to free the buffers viatry_tofree_buffers(), but sometimes it can't, due to the buffers is possible still on some transaction'st_sync_datalistort_locked_listwaiting forjournal_commit_transaction()to process it.Quoted 4 lines
Currently Direct IO simply returns EIO if
try_to_free_buffers()finds the buffer is busy, as it has no clue that JBD is referencing it.Is this a known issue and expected behavior? Any thoughts?
Quoted 11 lines
Are you seeing this in data=ordered mode? As Andrew pointed out we do
filemap_write_and_wait()so all the relevant data buffers of the inode should be already on disk. In__journal_try_to_free_buffer()we check whether the buffer is already-written-out data buffer and unfile and free it in that case. It shouldn't happen that a data buffer hasb_next_transactionset so really the only idea whytry_to_free_buffers()could fail is that somebody manages to write to a page via mmap beforeinvalidate_inode_pages2_range()gets to it. Under which kind of load do you observe the problem? Do you know exactly because of which condition doesjournal_try_to_free_buffers()fail?Thank you for your reply.
What we are noticing is
invalidate_inode_pages2_range()fails with-EIO(fromtry_to_free_buffers()sinceb_count> 0).I don't think the file is being updated through
mmap(). Previouswritepage()added these buffers tot_sync_datalist (data=ordered).filemap_write_and_wait()waits for pagewrite back to be cleared. So, buffers are no longer dirty, but still on thet_sync_dataand kjournald didn't get chance to process them yet :(Quoted 8 lines
Quoted 3 lines
Since we have elevated
b_counton these buffers,try_to_free_buffers()fails. How can we makefilemap_write_and_wait()to wait for kjournald to unfile these buffers ?Hmm, I don't get one thing:
The call chain isinvalidate_inode_pages2_range()->invalidate_complete_page2()->try_to_release_page()->ext3_releasepage()->journal_try_to_free_buffers()->__journal_try_to_free_buffer()and this function should remove the buffer from the committing transaction.Thanks, yes I noticed that after you pointing this out.
But
__journal_try_to_free_buffer()only unfile the buffer fromt_sync_datalistort_lockedlist, the journal head is not removed injournal_remove_journal_head()there, at that time,journal_remove_journal_head()just check if counterb_jcountis 0. But before calling__journal_try_to_free_buffer(), sincejournal_try_to_free_buffers()already increase theb_jcountinjournal_grab_journal_head(), so the journal head is not removed in __journal_try_to_free_buffer->journal_remove_journal_head()Quoted 2 lines
So who's
holding the reference to those buffers?Looking at the code, it seems the it's the
journal_put_journal_head(jh)who remove the journal head and decrease the bh
journal_try_to_free_buffers()
{
...jh = journal_grab_journal_head(bh); if (!jh) continue; jbd_lock_bh_state(bh); __journal_try_to_free_buffer(journal, bh); journal_put_journal_head(jh); jbd_unlock_bh_state(bh);...
}
so when journal_put_journal_head()->__journal_remove_journal_head(),
now theb_jcountis zero, but isjh->b_transactionis NULL? So it seems possible that bh ref count is non zero when exit fromjournal_put_journal_head()ifjh_b_transactionis not cleared.
Quoted 5 lines
Quoted 1 line
I miss where
jh->b_transactionis clear to NULL?
__journal_unfile_buffer()called from__journal_try_to_free_buffer()setsjh->b_transactionto NULL. So as soon asjournal_put_journal_head()is called, it results in freeing of journal head and releasing buffer reference.
Thanks, I saw this piece of code after I post it.
Quoted 10 lines
So really the only possible race I see is what I describe below...
Quoted 6 lines
Quoted 6 lines
Hmm, maybe I have one idea - in theory we could call
__journal_try_to_free_buffer()exactly at the moment commit code inspects the buffer. Then we'd release the buffer from the transaction buttry_to_free_buffers()would fail because of elevatedb_countexactly as you described. Could you maybe verify this? Not that I'd know how to easily fix this ;)...
here are some details:
The customer workload involves direct IO and buffered IO. The saw EIO gets returned without any log messages. Initial probing via SystemTap shows:
drop_buffers returns 0try_to_free_buffers returns 0try_to_release_page returns 0drop_buffers returns 0try_to_free_buffers returns 0try_to_release_page returns 0
drop_buffers returns 0try_to_free_buffers returns 0try_to_release_page returns 0invalidate_inode_pages2_range returns -5 (EIO)
drop_buffers returns 0try_to_free_buffers returns 0try_to_release_page returns 0
Which indicating that the EIO is from the invalidate_inode_pages2_range(), which tries to free buffers but failed.
We will try to add more debug information. Thanks for the suggestions.
However, Since ext3 has releasepge method defined, so thetry_to_free_buffer() failure should from
try_to_release_page()->ext3_releasepage()->journal_try_to_free_buffers()->try_to_free_buffer(), instead of try_to_release_page() calling try_to_free_buffer() directly.
If journal_try_to_free_buffers() calls try to free_buffer(), that means the journal head is already successfully removed by journal_remove_journal_head(), so buffer_jbd() safty checking after it is false as expected. Otherwise try_to_free_buffer() won't be called. In that case, I am not sure if it is possible to have race with commit code?? we seems have j_list_lock protected when __journal_try_to_free_buffer() is trying to take the buffer off the list.
There are many other try_to_release_page() failure before the DIO EIO, not sure where those coming from.
Fortunately Badari is able to reproduce this problem via simple buffered write and direct write to the same file on 2.6.25-git12. We could add more debug info there to see if we could get the counter and the jh values out when try to free a busy buffer.
Quoted 1 line
Honza
PATCH 1/1 jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
In reply to Jan KaraHi Andrew & Jan,
I was able to reproduce the customer problem involving DIO (invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with -EIO.
After a long debug, found 2 cases how this could happen. These are race conditions with journal_try_to_free_buffers() and journal_commit_transaction().
1) journal_submit_data_buffers() tries to get bh_state lock. If try lock fails, it drops the j_list_lock and sleeps for bh_state lock, while holding a reference on the buffer.
In the meanwhile, journal_try_to_free_buffers() can clean up the journal head could call try_to_free_buffers(). try_to_free_buffers() would fail due to the reference held by journal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).
2) When the buffer is on t_locked_list waiting for IO to finish, we hold a reference and give up the cpu, if we can't get bh_state lock. This causes try_to_free_buffers() to fail.
Fix is to drop the reference on the buffer if we can't get bh_state lock, give up the cpu and re-try the whole operation - instead of waiting for the vh_state lock.
Does this look like a resonable fix ?
Thanks,
Badari
1) journal_submit_data_buffers() tries to get bh_state lock. If try lock fails, it drops the j_list_lock and sleeps for bh_state lock, while holding a reference on the buffer head. In the meanwhile, journal_try_to_free_buffers() can clean up the journal head could call try_to_free_buffers(). try_to_free_buffers() would fail due to the reference held by journal_submit_data_buffers()
- which inturn causes failues for DIO (invalidate_inode_pages2()).
2) When the buffer is on t_locked_list waiting for IO to finish, we hold a reference and give up the cpu, if we can't get bh_state lock. This causes try_to_free_buffers() to fail.
Fix is to drop the reference on the buffer, give up the cpu and re-try the whole operation.
--- fs/jbd/commit.c | 20 +++++++++++++------- fs/jbd2/commit.c | 20 +++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-)
fs/jbd/commit.c +13 -7
@@ -79,12 +79,16 @@ nope:/* * Try to acquire jbd_lock_bh_state() against the buffer, when j_list_lock is -* held. For ranking reasons we must trylock. If we lose, schedule away and +* held. For ranking reasons we must trylock. If we lose, unlock the buffer +* if needed, drop the reference on the buffer, schedule away and * return 0. j_list_lock is dropped in this case. */ -static int inverted_lock (journal_t *journal,struct buffer_head *bh) +static int inverted_lock (journal_t *journal,struct buffer_head *bh,int locked) {if (!jbd_trylock_bh_state (bh)) { +if (locked) +unlock_buffer (bh); +put_bh (bh);spin_unlock (&journal->j_list_lock);schedule ();return 0 ;
@@ -218,10 +222,13 @@ write_out_data: } locked =1 ; } -/* We have to get bh_state lock. Again out of order, sigh. */ -if (!inverted_lock (journal, bh)) { -jbd_lock_bh_state (bh); +/* +* We have to get bh_state lock. If the try lock fails, give up +* cpu and retry the whole operation. +*/ +if (!inverted_lock (journal, bh, locked)) {spin_lock (&journal->j_list_lock); +continue ; }/* Someone already cleaned up the buffer? */ if (!buffer_jbd(bh)
@@ -430,8 +437,7 @@ void journal_commit_transaction(journal_ err = -EIO ;spin_lock (&journal->j_list_lock); } -if (!inverted_lock (journal, bh)) { -put_bh (bh); +if (!inverted_lock (journal, bh,0 )) {spin_lock (&journal->j_list_lock);continue ; }
fs/jbd2/commit.c +13 -7
@@ -81,12 +81,16 @@ nope:/* * Try to acquire jbd_lock_bh_state() against the buffer, when j_list_lock is -* held. For ranking reasons we must trylock. If we lose, schedule away and +* held. For ranking reasons we must trylock. If we lose, unlock the buffer +* if needed, drop the reference on the buffer, schedule away and * return 0. j_list_lock is dropped in this case. */ -static int inverted_lock (journal_t *journal,struct buffer_head *bh) +static int inverted_lock (journal_t *journal,struct buffer_head *bh,int locked) {if (!jbd_trylock_bh_state (bh)) { +if (locked) +unlock_buffer (bh); +put_bh (bh);spin_unlock (&journal->j_list_lock);schedule ();return 0 ;
@@ -217,8 +221,7 @@ static int journal_wait_on_locked_list(j ret = -EIO ;spin_lock (&journal->j_list_lock); } -if (!inverted_lock (journal, bh)) { -put_bh (bh); +if (!inverted_lock (journal, bh,0 )) {spin_lock (&journal->j_list_lock);continue ; }
@@ -296,10 +299,13 @@ write_out_data: } locked =1 ; } -/* We have to get bh_state lock. Again out of order, sigh. */ -if (!inverted_lock (journal, bh)) { -jbd_lock_bh_state (bh); +/* +* We have to get bh_state lock. If the try lock fails, give up +* cpu and retry the whole operation. +*/ +if (!inverted_lock (journal, bh, locked)) {spin_lock (&journal->j_list_lock); +continue ; }/* Someone already cleaned up the buffer? */ if (!buffer_jbd(bh)
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 19 lines · Badari Pulavarty
Hi Andrew & Jan,
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).
Quoted 4 lines
2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.
Besides these two, I think there are two more race conditions with journal_try_to_free_buffers() inside journal_commit_transaction()->journal_submit_data_buffers()
3) when journal_submit_data_buffers() saw the buffer is dirty but failed to lock the buffer bh1, journal_submit_data_buffers() released the j_list_lock and submit other buffers collected from previous check, with the reference to bh1 still hold. During this time journal_try_to_free_buffers() could clean up the journal head of bh1 and remove it from the t_syncdata_list. Then try_to_free_buffers() would fail because the reference held by journal_submit_data_buffers()
...
if (buffer_dirty(bh)) {
if (test_set_buffer_locked(bh)) {
BUFFER_TRACE(bh, "needs blocking lock");
spin_unlock(&journal->j_list_lock);
<-- here release the j_list_lock without put(bh)
journal_try_to_free_buffers() could come in and remove this bh from t_syncdata_list
/* Write out all data to prevent deadlocks */
journal_do_submit_data(wbuf, bufs);
bufs = 0;
lock_buffer(bh);
spin_lock(&journal->j_list_lock);
<-- here continue the check without validate if the bh still on t_sycdata_list
}
locked = 1;
}4) when journal_commit_transaction() go through the t_locked_list and wait for the buffer to be unlocked, it still holds the reference to the buffer, released the j_list_lock and gives the journal_try_to_free_buffers() a chance to come in remove this buffer from t_locked_list, but journal_commit_transaction() continues as if the buffer still on the locked list.
while (commit_transaction->t_locked_list) {
struct buffer_head *bh;
jh = commit_transaction->t_locked_list->b_tprev;
bh = jh2bh(jh);
get_bh(bh);
if (buffer_locked(bh)) {
spin_unlock(&journal->j_list_lock);
wait_on_buffer(bh);
if (unlikely(!buffer_uptodate(bh)))
err = -EIO;
spin_lock(&journal->j_list_lock);
}Mingming
Jan Kara
maintainerRe: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
In reply to Badari PulavartyQuoted 23 lines · Badari Pulavarty
Hi Andrew & Jan,
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
journal_try_to_free_buffers()andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.
Quoted 5 lines
Fix is to drop the reference on the buffer if we can't get
bh_statelock, give up the cpu and re-try the whole operation - instead of waiting for thevh_statelock.Does this look like a resonable fix ?
As Mingming pointed out there are few other places where we could hold
the bh reference. Note also that we accumulate references to buffers in the wbuf[] list and we need that for submit_bh() which consumes one bh reference. Generally, it seems to me as a too fragile and impractical rule "nobody can hold bh reference when not holding page lock" which is basically what it comes down to if you really want to be sure that journal_try_to_free_buffers() succeeds. And also note that in principle there are other places which hold references to buffers without holding the page lock - for example writepage() in ordered mode (although this one is in practice hardly triggerable). So how we could fix at least the races with commit code is to implement launder_page() callback for ext3/4 which would wait for the previous transaction commit in case the page has buffers that are part of that commit (I don't want this logic in journal_try_to_free_buffers() as that is called also on memory-reclaim path, but journal_launder_page() is fine with me). This would be correct but could considerably slow down O_DIRECT writes in cases they're mixed with buffered writes so I'm not sure if this is acceptable.
OTOH with the ordered mode rewrite patch, the problem with commit code
also goes away since there we don't need extra references to data buffers (we use just filemap_fdatawrite).
Quoted 12 lines
1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer head. In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which inturn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.
Quoted 5 lines
Fix is to drop the reference on the buffer, give up the cpu and re-try the whole operation.
Signed-off-by Badari Pulavarty <[email protected]>Reviewed-by Mingming Cao <[email protected]>
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 24 lines · Jan Kara
Quoted 23 lines · Badari Pulavarty
Hi Andrew & Jan,
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
journal_try_to_free_buffers()andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.
Quoted 7 lines
Quoted 5 lines
Fix is to drop the reference on the buffer if we can't get
bh_statelock, give up the cpu and re-try the whole operation - instead of waiting for thevh_statelock.Does this look like a resonable fix ?
As Mingming pointed out there are few other places where we could hold
the bh reference.
Actually there is one more place that journal_try_to_free_buffers (calling from DIO path) could race with journal_submit_data_buffers(), the DIO and buffered IO test still returns EIO with the fix which should fixed the first 3 race cases.
I could not figure out how this could happen with Badari's fix to inverted_lock().
This time the debug shows that the one who release put_bh() after the journal_try_to_free_buffers() failed is from this code path:
journal_submit_data_buffers() {
...
} else if (!locked && buffer_locked(bh)) {
__journal_file_buffer(jh, commit_transaction,
BJ_Locked);
jbd_unlock_bh_state(bh);
put_bh(bh);}
But when we get here we should already making sure the bh is on the t_syncdata_list with badari's fix...
Quoted 3 lines
Note also that we accumulate references to buffers in the wbuf[] list and we need that for
submit_bh()which consumes one bh reference.
It seems to me it's safe in this case. When journal_try_to_free_buffers() called from DIO path, filemap_fdatawrite_and_wait() already making sure that the IO submitted by kjournald is already finished by waiting for buffer unlocked.
Quoted 37 lines
Generally, it seems to me as a too fragile and impractical
rule "nobody can hold bh reference when not holding page lock" which is basically what it comes down to if you really want to be sure thatjournal_try_to_free_buffers()succeeds. And also note that in principle there are other places which hold references to buffers without holding the page lock - for examplewritepage()in ordered mode (although this one is in practice hardly triggerable). So how we could fix at least the races with commit code is to implementlaunder_page()callback for ext3/4 which would wait for the previous transaction commit in case the page has buffers that are part of that commit (I don't want this logic injournal_try_to_free_buffers()as that is called also on memory-reclaim path, butjournal_launder_page()is fine with me). This would be correct but could considerably slow downO_DIRECTwrites in cases they're mixed with buffered writes so I'm not sure if this is acceptable.
OTOH with the ordered mode rewrite patch, the problem with commit code
also goes away since there we don't need extra references to data buffers (we use justfilemap_fdatawrite).Quoted 12 lines
1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer head. In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which inturn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.Quoted 5 lines
Fix is to drop the reference on the buffer, give up the cpu and re-try the whole operation.
Signed-off-by Badari Pulavarty <[email protected]>Reviewed-by Mingming Cao <[email protected]>Honza
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
In reply to Jan KaraQuoted 38 lines · Jan Kara
Quoted 23 lines · Badari Pulavarty
Hi Andrew & Jan,
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
journal_try_to_free_buffers()andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.Quoted 5 lines
Fix is to drop the reference on the buffer if we can't get
bh_statelock, give up the cpu and re-try the whole operation - instead of waiting for thevh_statelock.Does this look like a resonable fix ?
As Mingming pointed out there are few other places where we could hold
the bh reference. Note also that we accumulate references to buffers in the wbuf[] list and we need that forsubmit_bh()which consumes one bh reference. Generally, it seems to me as a too fragile and impractical rule "nobody can hold bh reference when not holding page lock" which is basically what it comes down to if you really want to be sure thatjournal_try_to_free_buffers()succeeds. And also note that in principle there are other places which hold references to buffers without holding the page lock - for examplewritepage()in ordered mode (although this one is
Quoted 8 lines
in practice hardly triggerable). So how we could fix at least the races with commit code is to implement
launder_page()callback for ext3/4 which would wait for the previous transaction commit in case the page has buffers that are part of that commit (I don't want this logic injournal_try_to_free_buffers()as that is called also on memory-reclaim path, butjournal_launder_page()is fine with me). This would be correct but could considerably slow downO_DIRECTwrites in cases they're mixed with buffered writes so I'm not sure if this is acceptable.
Yes. I have been discussing all of these with Mingming. I agree with you that it looks silly to update all the places to not hold a ref on the buffer while waiting for spinlocks.
Adding a launder_page() seems to be the right approach. I wouldn't worry about making O_DIRECT slower. Currently, its failing with -EIO in this case anyway.
Unfortunately, this happens at a customer site and I can't effort to give them a complete re-write type patch - I need to get this into older distro release :( Thats why I am trying to fix the cases. I am finding more and more of these ..
Thanks,
Badari
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
In reply to Jan KaraQuoted 29 lines · Jan Kara
Quoted 23 lines · Badari Pulavarty
Hi Andrew & Jan,
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
journal_try_to_free_buffers()andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.Quoted 5 lines
Fix is to drop the reference on the buffer if we can't get
bh_statelock, give up the cpu and re-try the whole operation - instead of waiting for thevh_statelock.Does this look like a resonable fix ?
Quoted 15 lines
As Mingming pointed out there are few other places where we could hold
the bh reference. Note also that we accumulate references to buffers in the wbuf[] list and we need that forsubmit_bh()which consumes one bh reference. Generally, it seems to me as a too fragile and impractical rule "nobody can hold bh reference when not holding page lock" which is basically what it comes down to if you really want to be sure thatjournal_try_to_free_buffers()succeeds. And also note that in principle there are other places which hold references to buffers without holding the page lock - for examplewritepage()in ordered mode (although this one is in practice hardly triggerable). So how we could fix at least the races with commit code is to implementlaunder_page()callback for ext3/4 which would wait for the previous transaction commit in case the page has buffers that are part of that commit (I don't want this logic injournal_try_to_free_buffers()as that is called also on memory-reclaim path, butjournal_launder_page()is fine with me).
I am not sure how we are going to gurantee that by the time journal_try_to_free_buffers() get called, the page has buffers are not as part of the current transaction commit(which could be different than the one we waited in ext3_launder_page())?
It seems more realistic to fix the races one by one to me.
There is still a window that journal_submit_data_buffers() already removed the jh from the bh (when found the buffers are already being synced), but still keep a reference to the buffer head. journal_try_to_free_buffers() could be called. In that case try_to_free_buffers() will be called since there is no jh related to this buffer, and failed due to journal_submit_data_buffers() hasn't finish the cleanup business yet.
For this new race, we could just grab the j_list_lock when re-try try_to_free_buffers() to force waiting for journal_commit_transaction() to finish it flush work. But not sure if this is acceptable approach?
Patch like this? Comments?
Mingming
---------------------------------------------------------------------
There are a few cases direct IO could race with kjournal flushing data buffers which could result direct IO return EIO error.
1) journal_submit_data_buffers() tries to get bh_state lock. If try lock fails, it drops the j_list_lock and sleeps for bh_state lock, while holding a reference on the buffer.
In the meanwhile, journal_try_to_free_buffers() can clean up the journal head could call try_to_free_buffers(). try_to_free_buffers() would fail due to the reference held by journal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).
2) When the buffer is on t_locked_list waiting for IO to finish, we hold a reference and give up the cpu, if we can't get bh_state lock. This causes try_to_free_buffers() to fail.
3) when journal_submit_data_buffers() saw the buffer is dirty but failed to lock the buffer bh1, journal_submit_data_buffers() released the j_list_lock and submit other buffers collected from previous check, with the reference to bh1 still hold. During this time journal_try_to_free_buffers() could clean up the journal head of bh1 and remove it from the t_syncdata_list. Then try_to_free_buffers() would fail because the reference held by journal_submit_data_buffers()
4) journal_submit_data_buffers() already removed the jh from the bh (when found the buffers are already being synced), but still keep a reference to the buffer head. journal_try_to_free_buffers() could be called. In that case try_to_free_buffers() will be called since there is no jh related to this buffer, and failed due to journal_submit_data_buffers() hasn't finish the cleanup business yet.
Fix for first three races is to drop the reference on the buffer head
when release the j_list_lock,
give up the cpu and re-try the whole operation.
This patch also fixes the race that data buffers has been flushed to disk and journal head is cleard
by journal_submit_data_buffers() but did not get a chance to release
buffer head reference before the journal_try_to_free_buffers() kicked in.
--- fs/jbd/commit.c | 21 ++++++++++++++++----- fs/jbd/transaction.c | 13 +++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-)
fs/jbd/commit.c +16 -8
@@ -79,12 +79,16 @@ nope:/* * Try to acquire jbd_lock_bh_state() against the buffer, when j_list_lock is -* held. For ranking reasons we must trylock. If we lose, schedule away and +* held. For ranking reasons we must trylock. If we lose, unlock the buffer +* if needed, drop the reference on the buffer, schedule away and * return 0. j_list_lock is dropped in this case. */ -static int inverted_lock (journal_t *journal,struct buffer_head *bh) +static int inverted_lock (journal_t *journal,struct buffer_head *bh,int locked) {if (!jbd_trylock_bh_state (bh)) { +if (locked) +unlock_buffer (bh); +put_bh (bh);spin_unlock (&journal->j_list_lock);schedule ();return 0 ;
@@ -209,19 +213,24 @@ write_out_data:if (buffer_dirty (bh)) {if (test_set_buffer_locked (bh)) {BUFFER_TRACE (bh,"needs blocking lock" ); +put_bh (bh);spin_unlock (&journal->j_list_lock);/* Write out all data to prevent deadlocks */ journal_do_submit_data (wbuf, bufs); bufs =0 ; -lock_buffer (bh);spin_lock (&journal->j_list_lock); +continue ; } locked =1 ; } -/* We have to get bh_state lock. Again out of order, sigh. */ -if (!inverted_lock (journal, bh)) { -jbd_lock_bh_state (bh); +/* +* We have to get bh_state lock. If the try lock fails, +* release the ref on the buffer, give up cpu and retry the +* whole operation. +*/ +if (!inverted_lock (journal, bh, locked)) {spin_lock (&journal->j_list_lock); +continue ; }/* Someone already cleaned up the buffer? */ if (!buffer_jbd(bh)
@@ -430,8 +439,7 @@ void journal_commit_transaction(journal_ err = -EIO ;spin_lock (&journal->j_list_lock); } -if (!inverted_lock (journal, bh)) { -put_bh (bh); +if (!inverted_lock (journal, bh,0 )) {spin_lock (&journal->j_list_lock);continue ; }
fs/jbd/transaction.c +13 -0
@@ -1714,6 +1714,19 @@ int journal_try_to_free_buffers(journal_ goto busy; }while ((bh = bh->b_this_page) != head); ret =try_to_free_buffers (page); +if (ret ==0 ) { +/* +* it is possible that journal_submit_data_buffers() +* still holds the bh ref even if clears the jh +* after journal_remove_journal_head, +* which leads to try_to_free_buffers() failed +* let's wait for journal_submit_data_buffers() +* to finishing remove the bh from the sync_data_list +*/ +spin_lock (&journal->j_list_lock); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_list_lock); + } busy:return ret; }
PATCH 1/1 JBD:need hold j_state_lock to updates to transaction t_state to T_COMMIT
Updating the current transaction's t_state is being protected by j_state_lock. We need to do the same when updating the t_state to T_COMMIT.
--- fs/jbd/commit.c | 2 ++ 1 file changed, 2 insertions(+)
fs/jbd/commit.c +2 -0
@@ -478,7 +478,9 @@ void journal_commit_transaction(journal_* transaction! Now comes the tricky part: we need to write out * metadata. Loop over the transaction's entire buffer list: */ +spin_lock (&journal->j_state_lock); commit_transaction->t_state =T_COMMIT ; +spin_unlock (&journal->j_state_lock);J_ASSERT (commit_transaction->t_nr_buffers <= commit_transaction->t_outstanding_credits);
Re: [PATCH] JBD:need hold j_state_lock to updates to transaction t_state to T_COMMIT
Quoted 4 lines · Mingming Cao
Updating the current transaction's
t_stateis being protected byj_state_lock. We need to do the same when updating thet_statetoT_COMMIT.Signed-off-by Mingming Cao <[email protected]>
Thanks for the fix. You can add
(and also to the JBD2 patch)
Honza
Quoted 20 lines
--- fs/jbd/commit.c | 2 ++ 1 file changed, 2 insertions(+)Index:
linux-2.6.26-rc1/fs/jbd/commit.c
===================================================================fs/jbd/commit.c +2
-0@@ -478,7 +478,9 @@ void journal_commit_transaction(journal_* transaction! Now comes the tricky part: we need to write out * metadata. Loop over the transaction's entire buffer list: */ +spin_lock (&journal->j_state_lock); commit_transaction->t_state =T_COMMIT ; +spin_unlock (&journal->j_state_lock);J_ASSERT (commit_transaction->t_nr_buffers <= commit_transaction->t_outstanding_credits);
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Jan Kara
maintainerRe: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
In reply to Mingming CaoHello,
Quoted 42 lines · Mingming Cao
Quoted 26 lines
Quoted 21 lines
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
journal_try_to_free_buffers()andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.Quoted 5 lines
Fix is to drop the reference on the buffer if we can't get
bh_statelock, give up the cpu and re-try the whole operation - instead of waiting for thevh_statelock.Does this look like a resonable fix ?
Quoted 15 lines
As Mingming pointed out there are few other places where we could hold
the bh reference. Note also that we accumulate references to buffers in the wbuf[] list and we need that forsubmit_bh()which consumes one bh reference. Generally, it seems to me as a too fragile and impractical rule "nobody can hold bh reference when not holding page lock" which is basically what it comes down to if you really want to be sure thatjournal_try_to_free_buffers()succeeds. And also note that in principle there are other places which hold references to buffers without holding the page lock - for examplewritepage()in ordered mode (although this one is in practice hardly triggerable). So how we could fix at least the races with commit code is to implementlaunder_page()callback for ext3/4 which would wait for the previous transaction commit in case the page has buffers that are part of that commit (I don't want this logic injournal_try_to_free_buffers()as that is called also on memory-reclaim path, butjournal_launder_page()is fine with me).
Quoted 4 lines
I am not sure how we are going to gurantee that by the time
journal_try_to_free_buffers()get called, the page has buffers are not as part of the current transactioncommit(which could be different than the one we waited in ext3_launder_page())?
Hmm, you are right. It is not enough to just wait in ext3_launder_page()
because we don't have a transaction for direct_IO started yet. But if we actually released buffers from the page there, it should be fine.
Quoted 1 line
It seems more realistic to fix the races one by one to me.
Not to me, really. The scheme for buffer references you are trying to
impose is awkward to say the least. First, it is completely counter-intuitive (at least to me ;), second, it is impractical as well. For example in your scheme, you have no sensible way of locking ordered data mode buffer - you cannot just do: get the reference and do lock_buffer() because that violates your requirements. The only reasonable way you could do that is to lock the page to make sure buffer won't go away from you - but you cannot currently do that in journal commit code because of lock ordering. So the only way I can see which is left is: get some jbd spin lock to serialize with journal_try_to_free_buffers(), get the buffer reference, try to lock buffer, if it fails, drop everything and restart. And this is IMO no-go...
And BTW even if you fix such races, I think you'll still have races like:
CPU1: CPU2:
filemap_write_and_wait()
dirty a page
msync() (dirties buffers)
invalidate_inode_page2_range() -> -EIO
The code could historically always return EIO when mixing buffered and
unbuffered accesses and the question is, under which circumstances is this acceptable? I agree that the current state when if you do "buffered write, DIO write" in sequence and you'll possibly get EIO is bad and we should fix it. But I'm not sure we should fix the EIO return under all possible circumstances at all costs...
Quoted 87 lines
There is still a window that
journal_submit_data_buffers()already removed the jh from the bh (when found the buffers are already being synced), but still keep a reference to the buffer head.journal_try_to_free_buffers()could be called. In that casetry_to_free_buffers()will be called since there is no jh related to this buffer, and failed due tojournal_submit_data_buffers()hasn't finish the cleanup business yet.For this new race, we could just grab the
j_list_lockwhen re-trytry_to_free_buffers()to force waiting forjournal_commit_transaction()to finish it flush work. But not sure if this is acceptable approach?Patch like this? Comments?
---------------------------------------------------------------------
There are a few cases direct IO could race with kjournal flushing data buffers which could result direct IO return EIO error.1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.3) when
journal_submit_data_buffers()saw the buffer is dirty but failed to lock the buffer bh1,journal_submit_data_buffers()released thej_list_lockand submit other buffers collected from previous check, with the reference to bh1 still hold. During this timejournal_try_to_free_buffers()could clean up the journal head of bh1 and remove it from thet_syncdata_list. Thentry_to_free_buffers()would fail because the reference held byjournal_submit_data_buffers()4)
journal_submit_data_buffers()already removed the jh from the bh (when found the buffers are already being synced), but still keep a reference to the buffer head.journal_try_to_free_buffers()could be called. In that casetry_to_free_buffers()will be called since there is no jh related to this buffer, and failed due tojournal_submit_data_buffers()hasn't finish the cleanup business yet.Fix for first three races is to drop the reference on the buffer head
when release thej_list_lock,
give up the cpu and re-try the whole operation.This patch also fixes the race that data buffers has been flushed to disk and journal head is cleard
byjournal_submit_data_buffers()but did not get a chance to release
buffer head reference before thejournal_try_to_free_buffers()kicked in.Signed-off-by Badari Pulavarty <[email protected]>Signed-off-by Mingming Cao <[email protected]>--- fs/jbd/commit.c | 21 ++++++++++++++++----- fs/jbd/transaction.c | 13 +++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-)Index:
linux-2.6.26-rc1/fs/jbd/commit.c
===================================================================fs/jbd/commit.c +6
-2@@ -79,12 +79,16 @@ nope:/* * Try to acquire jbd_lock_bh_state() against the buffer, when j_list_lock is -* held. For ranking reasons we must trylock. If we lose, schedule away and +* held. For ranking reasons we must trylock. If we lose, unlock the buffer +* if needed, drop the reference on the buffer, schedule away and * return 0. j_list_lock is dropped in this case. */ -static int inverted_lock (journal_t *journal,struct buffer_head *bh) +static int inverted_lock (journal_t *journal,struct buffer_head *bh,int locked) {if (!jbd_trylock_bh_state (bh)) { +if (locked) +unlock_buffer (bh); +put_bh (bh);spin_unlock (&journal->j_list_lock);schedule ();return 0 ;@@ -209,19 +213,24 @@ write_out_data:if (buffer_dirty (bh)) {
fs/jbd/commit.c write_out_data: · line 222
Quoted 10 lines
fs/jbd/commit.c +2
-1if (test_set_buffer_locked (bh)) {BUFFER_TRACE (bh,"needs blocking lock" ); +put_bh (bh);spin_unlock (&journal->j_list_lock);/* Write out all data to prevent deadlocks */ journal_do_submit_data (wbuf, bufs); bufs =0 ; -lock_buffer (bh);spin_lock (&journal->j_list_lock); +continue ;
^^^ Here you can see what I wrote above. Basically you just busy-loop
wait for buffer lock. You should at least put schedule() there so that you don't lockup the CPU but it's ugly anyway.
Quoted 5 lines
fs/jbd/commit.c +0
-2} locked =1 ; } -/* We have to get bh_state lock. Again out of order, sigh. */ -if (!inverted_lock (journal, bh)) {
fs/jbd/commit.c write_out_data: · line 234
Quoted 10 lines
fs/jbd/commit.c +7
-1-jbd_lock_bh_state (bh); +/* +* We have to get bh_state lock. If the try lock fails, +* release the ref on the buffer, give up cpu and retry the +* whole operation. +*/ +if (!inverted_lock (journal, bh, locked)) {spin_lock (&journal->j_list_lock); +continue ; }
^^^ And here you add a place where we are not guaranteed to make any
progress... If someone intensively spins on that buffer, commit code could cycle here forever (or at least for quite a long time).
Quoted 26 lines
fs/jbd/commit.c +1
-2/* Someone already cleaned up the buffer? */ if (!buffer_jbd(bh)@@ -430,8 +439,7 @@ void journal_commit_transaction(journal_ err = -EIO ;spin_lock (&journal->j_list_lock); } -if (!inverted_lock (journal, bh)) { -put_bh (bh); +if (!inverted_lock (journal, bh,0 )) {spin_lock (&journal->j_list_lock);continue ; }Index:
linux-2.6.26-rc1/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +6
-0@@ -1714,6 +1714,19 @@ int journal_try_to_free_buffers(journal_ goto busy; }while ((bh = bh->b_this_page) != head); ret =try_to_free_buffers (page); +if (ret ==0 ) { +/* +* it is possible that journal_submit_data_buffers() +* still holds the bh ref even if clears the jh +* after journal_remove_journal_head, +* which leads to try_to_free_buffers() failed
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1732
Quoted 10 lines
fs/jbd/transaction.c +7
-0+* let's wait for journal_submit_data_buffers() +* to finishing remove the bh from the sync_data_list +*/ +spin_lock (&journal->j_list_lock); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_list_lock); + } busy:return ret; }
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 45 lines · Jan Kara
Hello,
Quoted 42 lines · Mingming Cao
Quoted 41 lines
Quoted 21 lines
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
journal_try_to_free_buffers()andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.Quoted 5 lines
Fix is to drop the reference on the buffer if we can't get
bh_statelock, give up the cpu and re-try the whole operation - instead of waiting for thevh_statelock.Does this look like a resonable fix ?
As Mingming pointed out there are few other places where we could hold
the bh reference. Note also that we accumulate references to buffers in the wbuf[] list and we need that forsubmit_bh()which consumes one bh reference. Generally, it seems to me as a too fragile and impractical rule "nobody can hold bh reference when not holding page lock" which is basically what it comes down to if you really want to be sure thatjournal_try_to_free_buffers()succeeds. And also note that in principle there are other places which hold references to buffers without holding the page lock - for examplewritepage()in ordered mode (although this one is in practice hardly triggerable). So how we could fix at least the races with commit code is to implementlaunder_page()callback for ext3/4 which would wait for the previous transaction commit in case the page has buffers that are part of that commit (I don't want this logic injournal_try_to_free_buffers()as that is called also on memory-reclaim path, butjournal_launder_page()is fine with me).
Quoted 8 lines
Quoted 4 lines
I am not sure how we are going to gurantee that by the time
journal_try_to_free_buffers()get called, the page has buffers are not as part of the current transactioncommit(which could be different than the one we waited in ext3_launder_page())?Hmm, you are right. It is not enough to just wait in
ext3_launder_page()
because we don't have a transaction fordirect_IOstarted yet. But if we actually released buffers from the page there, it should be fine.
Do you mean calling journal_try_to_free_buffers() inside ext3_launder_page()? I think we still need some lock to serialize launder_page() with kjournald commit code(not sure if is that Okay?), otherwise there is always a window that by the time try_to_free_buffers() get called, the current transaction has be changed...
Quoted 4 lines
Quoted 1 line
It seems more realistic to fix the races one by one to me.
Not to me, really. The scheme for buffer references you are trying to
impose is awkward to say the least. First, it is completely counter-intuitive (at least to me ;), second, it is impractical as well.
Sigh...I am not very happy with the solution either, but I could not see a decent solution that could fix this problem. Currently we constantly hit EIO error only in 10 minutes with the simple parallel buffered IO and direct IO:(...
Quoted 7 lines
For example in your scheme, you have no sensible way of locking ordered data mode buffer - you cannot just do: get the reference and do
lock_buffer()because that violates your requirements. The only reasonable way you could do that is to lock the page to make sure buffer won't go away from you - but you cannot currently do that in journal commit code because of lock ordering. So the only way I can see which is left is: get some jbd spin lock to serialize withjournal_try_to_free_buffers(), get the buffer
Quoted 9 lines
reference, try to lock buffer, if it fails, drop everything and restart. And this is IMO no-go...
And BTW even if you fix such races, I think you'll still have races like:
CPU1: CPU2:
filemap_write_and_wait()
dirty a page
msync()(dirties buffers)
invalidate_inode_page2_range()->-EIO
I could see this is possible with mapped IO. But for buffered IO, since direct IO is holding a i_mutex, this case should not happen,right?
Quoted 99 lines
The code could historically always return EIO when mixing buffered and
unbuffered accesses and the question is, under which circumstances is this acceptable? I agree that the current state when if you do "buffered write, DIO write" in sequence and you'll possibly get EIO is bad and we should fix it. But I'm not sure we should fix the EIO return under all possible circumstances at all costs...Quoted 92 lines
There is still a window that
journal_submit_data_buffers()already removed the jh from the bh (when found the buffers are already being synced), but still keep a reference to the buffer head.journal_try_to_free_buffers()could be called. In that casetry_to_free_buffers()will be called since there is no jh related to this buffer, and failed due tojournal_submit_data_buffers()hasn't finish the cleanup business yet.For this new race, we could just grab the
j_list_lockwhen re-trytry_to_free_buffers()to force waiting forjournal_commit_transaction()to finish it flush work. But not sure if this is acceptable approach?Patch like this? Comments?
---------------------------------------------------------------------
There are a few cases direct IO could race with kjournal flushing data buffers which could result direct IO return EIO error.1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.3) when
journal_submit_data_buffers()saw the buffer is dirty but failed to lock the buffer bh1,journal_submit_data_buffers()released thej_list_lockand submit other buffers collected from previous check, with the reference to bh1 still hold. During this timejournal_try_to_free_buffers()could clean up the journal head of bh1 and remove it from thet_syncdata_list. Thentry_to_free_buffers()would fail because the reference held byjournal_submit_data_buffers()4)
journal_submit_data_buffers()already removed the jh from the bh (when found the buffers are already being synced), but still keep a reference to the buffer head.journal_try_to_free_buffers()could be called. In that casetry_to_free_buffers()will be called since there is no jh related to this buffer, and failed due tojournal_submit_data_buffers()hasn't finish the cleanup business yet.Fix for first three races is to drop the reference on the buffer head
when release thej_list_lock,
give up the cpu and re-try the whole operation.This patch also fixes the race that data buffers has been flushed to disk and journal head is cleard
byjournal_submit_data_buffers()but did not get a chance to release
buffer head reference before thejournal_try_to_free_buffers()kicked in.Signed-off-by Badari Pulavarty <[email protected]>Signed-off-by Mingming Cao <[email protected]>--- fs/jbd/commit.c | 21 ++++++++++++++++----- fs/jbd/transaction.c | 13 +++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-)Index:
linux-2.6.26-rc1/fs/jbd/commit.c
===================================================================fs/jbd/commit.c +7
-2@@ -79,12 +79,16 @@ nope:/* * Try to acquire jbd_lock_bh_state() against the buffer, when j_list_lock is -* held. For ranking reasons we must trylock. If we lose, schedule away and +* held. For ranking reasons we must trylock. If we lose, unlock the buffer +* if needed, drop the reference on the buffer, schedule away and * return 0. j_list_lock is dropped in this case. */ -static int inverted_lock (journal_t *journal,struct buffer_head *bh) +static int inverted_lock (journal_t *journal,struct buffer_head *bh,int locked) {if (!jbd_trylock_bh_state (bh)) { +if (locked) +unlock_buffer (bh); +put_bh (bh);spin_unlock (&journal->j_list_lock);schedule ();return 0 ;@@ -209,19 +213,24 @@ write_out_data:if (buffer_dirty (bh)) {if (test_set_buffer_locked (bh)) {BUFFER_TRACE (bh,"needs blocking lock" ); +put_bh (bh);spin_unlock (&journal->j_list_lock);/* Write out all data to prevent deadlocks */
Quoted 9 lines
fs/jbd/commit.c write_out_data: · line 222
Quoted 5 lines
fs/jbd/commit.c +1
-1journal_do_submit_data (wbuf, bufs); bufs =0 ; -lock_buffer (bh);spin_lock (&journal->j_list_lock); +continue ;^^^ Here you can see what I wrote above. Basically you just busy-loop
wait for buffer lock. You should at least putschedule()there so that you don't lockup the CPU but it's ugly anyway.
Yup.
The conflict is that if we still held the bh refrence after released the j_list_lock, journal_try_to_free_buffers() could came in returns EIO to direct IO since buffer is busy(); but if we release the bh reference after released the j_list_lock, it made possible that journal_try_to_free_buffers() to free that buffer, so we can't do lock_buffer() here anymore so we have to loop here. This is a trade off ...
On the other hand, the journal_submit_data_bufferes() continue process this buffer after regrab the j_list_lock even if it's has been removed from the t_syncdata_list by __journal_try_to_free_buffers(). IMO this is not the optimized way.
Quoted 57 lines
Quoted 5 lines
fs/jbd/commit.c +0
-2} locked =1 ; } -/* We have to get bh_state lock. Again out of order, sigh. */ -if (!inverted_lock (journal, bh)) {fs/jbd/commit.c write_out_data: · line 234
Quoted 10 lines
fs/jbd/commit.c +7
-1-jbd_lock_bh_state (bh); +/* +* We have to get bh_state lock. If the try lock fails, +* release the ref on the buffer, give up cpu and retry the +* whole operation. +*/ +if (!inverted_lock (journal, bh, locked)) {spin_lock (&journal->j_list_lock); +continue ; }^^^ And here you add a place where we are not guaranteed to make any
progress... If someone intensively spins on that buffer, commit code could cycle here forever (or at least for quite a long time).Quoted 26 lines
fs/jbd/commit.c +1
-2/* Someone already cleaned up the buffer? */ if (!buffer_jbd(bh)@@ -430,8 +439,7 @@ void journal_commit_transaction(journal_ err = -EIO ;spin_lock (&journal->j_list_lock); } -if (!inverted_lock (journal, bh)) { -put_bh (bh); +if (!inverted_lock (journal, bh,0 )) {spin_lock (&journal->j_list_lock);continue ; }Index:
linux-2.6.26-rc1/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +6
-0@@ -1714,6 +1714,19 @@ int journal_try_to_free_buffers(journal_ goto busy; }while ((bh = bh->b_this_page) != head); ret =try_to_free_buffers (page); +if (ret ==0 ) { +/* +* it is possible that journal_submit_data_buffers() +* still holds the bh ref even if clears the jh +* after journal_remove_journal_head, +* which leads to try_to_free_buffers() failed fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1732
Quoted 10 lines
fs/jbd/transaction.c +7
-0+* let's wait for journal_submit_data_buffers() +* to finishing remove the bh from the sync_data_list +*/ +spin_lock (&journal->j_list_lock); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_list_lock); + } busy:return ret; }Honza
Jan Kara
maintainerRe: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 53 lines · Mingming Cao
Quoted 43 lines · Jan Kara
Quoted 42 lines · Mingming Cao
Quoted 41 lines
Quoted 21 lines
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
journal_try_to_free_buffers()andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.Quoted 5 lines
Fix is to drop the reference on the buffer if we can't get
bh_statelock, give up the cpu and re-try the whole operation - instead of waiting for thevh_statelock.Does this look like a resonable fix ?
As Mingming pointed out there are few other places where we could hold
the bh reference. Note also that we accumulate references to buffers in the wbuf[] list and we need that forsubmit_bh()which consumes one bh reference. Generally, it seems to me as a too fragile and impractical rule "nobody can hold bh reference when not holding page lock" which is basically what it comes down to if you really want to be sure thatjournal_try_to_free_buffers()succeeds. And also note that in principle there are other places which hold references to buffers without holding the page lock - for examplewritepage()in ordered mode (although this one is in practice hardly triggerable). So how we could fix at least the races with commit code is to implementlaunder_page()callback for ext3/4 which would wait for the previous transaction commit in case the page has buffers that are part of that commit (I don't want this logic injournal_try_to_free_buffers()as that is called also on memory-reclaim path, butjournal_launder_page()is fine with me).Quoted 8 lines
Quoted 4 lines
I am not sure how we are going to gurantee that by the time
journal_try_to_free_buffers()get called, the page has buffers are not as part of the current transactioncommit(which could be different than the one we waited in ext3_launder_page())?Hmm, you are right. It is not enough to just wait in
ext3_launder_page()
because we don't have a transaction fordirect_IOstarted yet. But if we actually released buffers from the page there, it should be fine.
Quoted 6 lines
Do you mean calling
journal_try_to_free_buffers()insideext3_launder_page()? I think we still need some lock to serializelaunder_page()with kjournald commit code(not sure if is that Okay?), otherwise there is always a window that by the timetry_to_free_buffers()get called, the current transaction has be changed...
Once we succeed with removing journal heads from data buffers injournal_try_to_free_buffers(), they can be added only if someone dirties the page via mmap and writepage is called (which I'd currently neglect as too rare and not common usage). So after that moment, once commit code drops references to buffer heads, we can happily remove those buffers.
Quoted 9 lines
Quoted 4 lines
Quoted 1 line
It seems more realistic to fix the races one by one to me.
Not to me, really. The scheme for buffer references you are trying to
impose is awkward to say the least. First, it is completely counter-intuitive (at least to me ;), second, it is impractical as well.Sigh...I am not very happy with the solution either, but I could not see a decent solution that could fix this problem. Currently we constantly hit EIO error only in 10 minutes with the simple parallel buffered IO and direct IO:(...
Yes, buffered and unbuffered writes should happily live together...
Quoted 17 lines
Quoted 7 lines
For example in your scheme, you have no sensible way of locking ordered data mode buffer - you cannot just do: get the reference and do
lock_buffer()because that violates your requirements. The only reasonable way you could do that is to lock the page to make sure buffer won't go away from you - but you cannot currently do that in journal commit code because of lock ordering. So the only way I can see which is left is: get some jbd spin lock to serialize withjournal_try_to_free_buffers(), get the bufferQuoted 9 lines
reference, try to lock buffer, if it fails, drop everything and restart. And this is IMO no-go...
And BTW even if you fix such races, I think you'll still have races like:
CPU1: CPU2:
filemap_write_and_wait()
dirty a page
msync()(dirties buffers)
invalidate_inode_page2_range()->-EIO
Quoted 2 lines
I could see this is possible with mapped IO. But for buffered IO, since direct IO is holding a
i_mutex, this case should not happen,right?
Yes.
Quoted 26 lines
Quoted 7 lines
Quoted 7 lines
fs/jbd/commit.c +1
-0@@ -209,19 +213,24 @@ write_out_data:if (buffer_dirty (bh)) {if (test_set_buffer_locked (bh)) {BUFFER_TRACE (bh,"needs blocking lock" ); +put_bh (bh);spin_unlock (&journal->j_list_lock);/* Write out all data to prevent deadlocks */ Quoted 9 lines
fs/jbd/commit.c write_out_data: · line 222
Quoted 5 lines
fs/jbd/commit.c +1
-1journal_do_submit_data (wbuf, bufs); bufs =0 ; -lock_buffer (bh);spin_lock (&journal->j_list_lock); +continue ;^^^ Here you can see what I wrote above. Basically you just busy-loop
wait for buffer lock. You should at least putschedule()there so that you don't lockup the CPU but it's ugly anyway.Yup.
The conflict is that if we still held the bh refrence after released the
j_list_lock,journal_try_to_free_buffers()could came in returns EIO to direct IO since buffer isbusy(); but if we release the bh reference after released thej_list_lock, it made possible thatjournal_try_to_free_buffers()to free that buffer, so we can't dolock_buffer()here anymore so we have to loop here. This is a trade off ...
Quoted 4 lines
On the other hand, the
journal_submit_data_bufferes()continue process this buffer after regrab thej_list_lockeven if it's has been removed from thet_syncdata_listby__journal_try_to_free_buffers(). IMO this is not the optimized way.
Well, you are going to hit this path for example when the buffer is under
IO just now. So it happens quite often e.g. when the machine is under memory pressure and writes-out dirty data more aggressively.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
In reply to Jan KaraQuoted 45 lines · Jan Kara
Hello,
Quoted 42 lines · Mingming Cao
Quoted 41 lines
Quoted 21 lines
I was able to reproduce the customer problem involving DIO (
invalidate_inode_pages2) problem by writing simple testcase to keep writing to a file using buffered writes and DIO writes forever in a loop. I see DIO writes fail with-EIO.After a long debug, found 2 cases how this could happen. These are race conditions with
journal_try_to_free_buffers()andjournal_commit_transaction().1)
journal_submit_data_buffers()tries to getbh_statelock. If try lock fails, it drops thej_list_lockand sleeps forbh_statelock, while holding a reference on the buffer.
In the meanwhile,journal_try_to_free_buffers()can clean up the journal head could calltry_to_free_buffers().try_to_free_buffers()would fail due to the reference held byjournal_submit_data_buffers()
- which in turn causes failues for DIO (invalidate_inode_pages2()).2) When the buffer is on
t_locked_listwaiting for IO to finish, we hold a reference and give up the cpu, if we can't getbh_statelock. This causestry_to_free_buffers()to fail.Quoted 5 lines
Fix is to drop the reference on the buffer if we can't get
bh_statelock, give up the cpu and re-try the whole operation - instead of waiting for thevh_statelock.Does this look like a resonable fix ?
As Mingming pointed out there are few other places where we could hold
the bh reference. Note also that we accumulate references to buffers in the wbuf[] list and we need that forsubmit_bh()which consumes one bh reference. Generally, it seems to me as a too fragile and impractical rule "nobody can hold bh reference when not holding page lock" which is basically what it comes down to if you really want to be sure thatjournal_try_to_free_buffers()succeeds. And also note that in principle there are other places which hold references to buffers without holding the page lock - for examplewritepage()in ordered mode (although this one is in practice hardly triggerable). So how we could fix at least the races with commit code is to implementlaunder_page()callback for ext3/4 which would wait for the previous transaction commit in case the page has buffers that are part of that commit (I don't want this logic injournal_try_to_free_buffers()as that is called also on memory-reclaim path, butjournal_launder_page()is fine with me).
Quoted 7 lines
Quoted 4 lines
I am not sure how we are going to gurantee that by the time
journal_try_to_free_buffers()get called, the page has buffers are not as part of the current transactioncommit(which could be different than the one we waited in ext3_launder_page())?Hmm, you are right. It is not enough to just wait in
ext3_launder_page()
because we don't have a transaction fordirect_IOstarted yet. But if we actually released buffers from the page there, it should be fine.
Does this match what you are thinking? It certainly slow down the DIO path, but the positive side is it doesn't disturb the other code path... thanks for your feedback!
--------------------------------------------
An unexpected EIO error gets returned when writing to a file using buffered writes and DIO writes at the same time.
We found there are a number of places where journal_try_to_free_buffers() could race with journal_commit_transaction(), the later still
helds the reference to the buffers on the t_syncdata_list or t_locked_list , while journal_try_to_free_buffers() tries to free them, which resulting an EIO error returns back to the dio caller.
The logic fix is to retry freeing if journal_try_to_free_buffers() to failed to free those data buffers while journal_commit_transaction() is still reference those buffers.
This is done via implement ext3 launder_page() callback, instead of inside journal_try_to_free_buffers() itself, so that it doesn't affecting other code path calling journal_try_to_free_buffers and only dio path get affected.
fs/ext3/inode.c +20 -0
@@ -1766,6 +1766,23 @@ static int ext3_journalled_set_page_dirtreturn __set_page_dirty_nobuffers (page); } +static int ext3_launder_page (struct page *page) +{ +int ret; +int retry =5 ; + +while (retry --) { + ret =ext3_releasepage (page,GFP_KERNEL ); +if (ret ==1 ) +break ; +else +schedule (); + } + +return ret; +} + +static const struct address_space_operations ext3_ordered_aops = { .readpage = ext3_readpage, .readpages = ext3_readpages,
@@ -1778,6 +1795,7 @@ static const struct address_space_operat .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .launder_page = ext3_launder_page, };static const struct address_space_operations ext3_writeback_aops = {
@@ -1792,6 +1810,7 @@ static const struct address_space_operat .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .launder_page = ext3_launder_page, };static const struct address_space_operations ext3_journalled_aops = {
@@ -1805,6 +1824,7 @@ static const struct address_space_operat .bmap = ext3_bmap, .invalidatepage = ext3_invalidatepage, .releasepage = ext3_releasepage, + .launder_page = ext3_launder_page, };void ext3_set_aops (struct inode *inode)
Jan Kara
maintainerRe: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 39 lines · Mingming Cao
On Mon, 2008-05-12 at 17:54 +0200, Jan Kara wrote:
Does this match what you are thinking? It certainly slow down the DIO path, but the positive side is it doesn't disturb the other code path... thanks for your feedback!--------------------------------------------
An unexpected EIO error gets returned when writing to a file using buffered writes and DIO writes at the same time.
We found there are a number of places where
journal_try_to_free_buffers()could race withjournal_commit_transaction(), the later still
helds the reference to the buffers on thet_syncdata_listort_locked_list, whilejournal_try_to_free_buffers()tries to free them, which resulting an EIO error returns back to the dio caller.The logic fix is to retry freeing if
journal_try_to_free_buffers()to failed to free those data buffers whilejournal_commit_transaction()is still reference those buffers.
This is done via implement ext3launder_page()callback, instead of insidejournal_try_to_free_buffers()itself, so that it doesn't affecting other code path callingjournal_try_to_free_buffersand only dio path get affected.Signed-off-by Mingming Cao <[email protected]>Index:
linux-2.6.26-rc1/fs/ext3/inode.c
===================================================================fs/ext3/inode.c +7
-0@@ -1766,6 +1766,23 @@ static int ext3_journalled_set_page_dirtreturn __set_page_dirty_nobuffers (page); } +static int ext3_launder_page (struct page *page) +{ +int ret; +int retry =5 ; + +while (retry --) { + ret =ext3_releasepage (page,GFP_KERNEL );
fs/ext3/inode.c static int ext3_journalled_set_page_dirt · line 1785
Quoted 10 lines
fs/ext3/inode.c +10
-0+if (ret ==1 ) +break ; +else +schedule (); + } + +return ret; +} + +
Yes, I meant something like this. We could be more clever and do:
head = bh = page_buffers(page);
do {
wait_on_buffer(bh);
bh = bh->b_this_page;
} while (bh != head);
/*
* Now commit code should have been able to proceed and release
* those buffers
*/
schedule();or we could do simple:
log_wait_commit(...);
That would impose larger perf. penalty but on the other hand you shouldn't hit this path too often. But maybe the code above would be fine and would handle most cases. Also please add a big comment to that function to explain why this magic is needed.
Quoted 20 lines
fs/ext3/inode.c +2
-0static const struct address_space_operations ext3_ordered_aops = { .readpage = ext3_readpage, .readpages = ext3_readpages,@@ -1778,6 +1795,7 @@ static const struct address_space_operat .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .launder_page = ext3_launder_page, };static const struct address_space_operations ext3_writeback_aops = {@@ -1792,6 +1810,7 @@ static const struct address_space_operat .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .launder_page = ext3_launder_page, };static const struct address_space_operations ext3_journalled_aops = {
fs/ext3/inode.c static const struct address_space_operat · line 1830
Quoted 7 lines
fs/ext3/inode.c +1
-0@@ -1805,6 +1824,7 @@ static const struct address_space_operat .bmap = ext3_bmap, .invalidatepage = ext3_invalidatepage, .releasepage = ext3_releasepage, + .launder_page = ext3_launder_page, };void ext3_set_aops (struct inode *inode)
Actually, we need .launder_page callback only in data=order mode.
data=writeback mode doesn't need it at all (journal code doesn't touch data buffers there) and for data=journal mode DIO could have never worked reasonably when mixed with buffered IO and it would have to do a different and much more expensive trickery (like flushing the journal, or at least forcing current transaction to commit).
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 28 lines · Jan Kara
Quoted 15 lines · Mingming Cao
Index:
linux-2.6.26-rc1/fs/ext3/inode.c
===================================================================fs/ext3/inode.c +7
-0@@ -1766,6 +1766,23 @@ static int ext3_journalled_set_page_dirtreturn __set_page_dirty_nobuffers (page); } +static int ext3_launder_page (struct page *page) +{ +int ret; +int retry =5 ; + +while (retry --) { + ret =ext3_releasepage (page,GFP_KERNEL );fs/ext3/inode.c static int ext3_journalled_set_page_dirt · line 1785
Quoted 10 lines
fs/ext3/inode.c +10
-0+if (ret ==1 ) +break ; +else +schedule (); + } + +return ret; +} + +Yes, I meant something like this. We could be more clever and do:
Quoted 11 lines
head = bh = page_buffers(page);
do {
wait_on_buffer(bh);
bh = bh->b_this_page;
} while (bh != head);
/*
* Now commit code should have been able to proceed and release
* those buffers
*/
schedule();Thanks.
We could recheck if buffer_busy() before calling wait_on_buffer(bh) to wait for buffer unlocked. This will handles the mapped IO re-dirty race case, but still need the schedule() and retry to handle the buffered IO race.
Quoted 6 lines
or we could do simple:
log_wait_commit(...);That would impose larger perf. penalty but on the other hand you shouldn't hit this path too often.
My concern with doing log_wait_commit() here is the perf penalty. In the case the buffers is at the end of the queue to commit, we have to wait for all other previous transactions to finish committing before we could continue...
Quoted 4 lines
But maybe the code above would be fine and would
handle most cases. Also please add a big comment to that function to explain why this magic is needed.
Will do.
Quoted 26 lines
Quoted 26 lines
fs/ext3/inode.c +3
-0static const struct address_space_operations ext3_ordered_aops = { .readpage = ext3_readpage, .readpages = ext3_readpages,@@ -1778,6 +1795,7 @@ static const struct address_space_operat .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .launder_page = ext3_launder_page, };static const struct address_space_operations ext3_writeback_aops = {@@ -1792,6 +1810,7 @@ static const struct address_space_operat .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .launder_page = ext3_launder_page, };static const struct address_space_operations ext3_journalled_aops = {@@ -1805,6 +1824,7 @@ static const struct address_space_operat .bmap = ext3_bmap, .invalidatepage = ext3_invalidatepage, .releasepage = ext3_releasepage, + .launder_page = ext3_launder_page, };
Quoted 8 lines
fs/ext3/inode.c static const struct address_space_operat · line 1830
Quoted 1 line
fs/ext3/inode.c +0
-0void ext3_set_aops (struct inode *inode)Actually, we need .launder_page callback only in data=order mode.
data=writeback mode doesn't need it at all (journal code doesn't touch data buffers there) and for data=journal mode DIO could have never worked reasonably when mixed with buffered IO and it would have to do a different and much more expensive trickery (like flushing the journal, or at least forcing current transaction to commit).
You are right, thanks for pointing this out.
Will post an updated patch.
Mingming
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
In reply to Jan KaraQuoted 52 lines · Jan Kara
Quoted 39 lines · Mingming Cao
On Mon, 2008-05-12 at 17:54 +0200, Jan Kara wrote:
Does this match what you are thinking? It certainly slow down the DIO path, but the positive side is it doesn't disturb the other code path... thanks for your feedback!--------------------------------------------
An unexpected EIO error gets returned when writing to a file using buffered writes and DIO writes at the same time.
We found there are a number of places where
journal_try_to_free_buffers()could race withjournal_commit_transaction(), the later still
helds the reference to the buffers on thet_syncdata_listort_locked_list, whilejournal_try_to_free_buffers()tries to free them, which resulting an EIO error returns back to the dio caller.The logic fix is to retry freeing if
journal_try_to_free_buffers()to failed to free those data buffers whilejournal_commit_transaction()is still reference those buffers.
This is done via implement ext3launder_page()callback, instead of insidejournal_try_to_free_buffers()itself, so that it doesn't affecting other code path callingjournal_try_to_free_buffersand only dio path get affected.Signed-off-by Mingming Cao <[email protected]>Index:
linux-2.6.26-rc1/fs/ext3/inode.c
===================================================================fs/ext3/inode.c +7
-0@@ -1766,6 +1766,23 @@ static int ext3_journalled_set_page_dirtreturn __set_page_dirty_nobuffers (page); } +static int ext3_launder_page (struct page *page) +{ +int ret; +int retry =5 ; + +while (retry --) { + ret =ext3_releasepage (page,GFP_KERNEL );fs/ext3/inode.c static int ext3_journalled_set_page_dirt · line 1785
Quoted 10 lines
fs/ext3/inode.c +10
-0+if (ret ==1 ) +break ; +else +schedule (); + } + +return ret; +} + +Yes, I meant something like this. We could be more clever and do:
Quoted 11 lines
head = bh = page_buffers(page);
do {
wait_on_buffer(bh);
bh = bh->b_this_page;
} while (bh != head);
/*
* Now commit code should have been able to proceed and release
* those buffers
*/
schedule();Bummer, we can't free buffers in ext3_launder_page() before calling try_to_free_page, as later invalidate_complete_page2()->try_to_free_page() expecting the page buffers are still here, and will return EIO if it launder_page() has already freed those buffers.:(
Doing wait_on_buffer() alone in launder_page() is not enough as it doesn't wait for buffer reference drop to 0.
Quoted 5 lines
or we could do simple:
log_wait_commit(...);That would impose larger perf. penalty but on the other hand you shouldn't hit this path too often.
Guess this is the last it worth give a try to see how bad it is, here is a draft patch. Do you see any obvious problems?
Mingming
fs/ext3/inode.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+)
fs/ext3/inode.c +48 -0
@@ -1766,6 +1766,53 @@ static int ext3_journalled_set_page_dirtreturn __set_page_dirty_nobuffers (page); } +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* helds the reference to the buffers on the t_syncdata_list or t_locked_list +* while journal_try_to_free_buffers() tries to free them, +* which resulting an EIO error returns back to the generic_file_direct_IO() +* +* It is also possible that mapped IO could re-dirty the page and buffers +* after direct IO has waited dirty pages have been flush to disk. +* When the journal_try_to_free_buffers() is called from the direct IO +* path, it may failed to free the buffers as the buffer may be locked again +* +* To fix this problem, we add a ext3_launder_page() callback which +* is only called on direct IO path, to wait for the buffer unlocked +* and waiting for the jbd finish commit those data buffers. +* +* This will impact the direct IO perf, but this alows direct IO and +* buffered IO could be performed concurrently. +* +* returns 0 in sucess case, and non zero in failure case. +*/ +static int ext3_launder_page (struct page *page) +{ +int ret =0 ; +struct buffer_head *head; +struct buffer_head *bh; +journal_t *journal =EXT3_JOURNAL (page->mapping->host); + + head = bh =page_buffers (page); +do { +/* +* it's possible mapped IO re-dirty and locked the +* buffer again when we came here +*/ +if (buffer_locked (bh)) { +get_bh (bh); +wait_on_buffer (bh); +put_bh (bh); + } + +journal_wait_commit_buffer (journal, bh); + bh = bh->b_this_page; + }while (bh != head); + +return ret; +} +static const struct address_space_operations ext3_ordered_aops = { .readpage = ext3_readpage, .readpages = ext3_readpages,
@@ -1778,6 +1825,7 @@ static const struct address_space_operat .releasepage = ext3_releasepage, .direct_IO = ext3_direct_IO, .migratepage = buffer_migrate_page, + .launder_page = ext3_launder_page, };static const struct address_space_operations ext3_writeback_aops = {
fs/jbd/journal.c +41 -0
53 lines not on this page · raw message
include/linux/jbd.h +1 -0
@@ -974,6 +974,7 @@ int __log_start_commit(journal_t *journaint journal_start_commit (journal_t *journal,tid_t *tid);int journal_force_commit_nested (journal_t *journal);int log_wait_commit (journal_t *journal,tid_t tid); +int journal_wait_commit_buffer (journal_t *journal,struct buffer_head *bh);int log_do_checkpoint (journal_t *journal);void __log_wait_for_space (journal_t *journal);
Jan Kara
maintainerRe: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 65 lines · Mingming Cao
Quoted 52 lines · Jan Kara
Quoted 39 lines · Mingming Cao
On Mon, 2008-05-12 at 17:54 +0200, Jan Kara wrote:
Does this match what you are thinking? It certainly slow down the DIO path, but the positive side is it doesn't disturb the other code path... thanks for your feedback!--------------------------------------------
An unexpected EIO error gets returned when writing to a file using buffered writes and DIO writes at the same time.
We found there are a number of places where
journal_try_to_free_buffers()could race withjournal_commit_transaction(), the later still
helds the reference to the buffers on thet_syncdata_listort_locked_list, whilejournal_try_to_free_buffers()tries to free them, which resulting an EIO error returns back to the dio caller.The logic fix is to retry freeing if
journal_try_to_free_buffers()to failed to free those data buffers whilejournal_commit_transaction()is still reference those buffers.
This is done via implement ext3launder_page()callback, instead of insidejournal_try_to_free_buffers()itself, so that it doesn't affecting other code path callingjournal_try_to_free_buffersand only dio path get affected.Signed-off-by Mingming Cao <[email protected]>Index:
linux-2.6.26-rc1/fs/ext3/inode.c
===================================================================fs/ext3/inode.c +7
-0@@ -1766,6 +1766,23 @@ static int ext3_journalled_set_page_dirtreturn __set_page_dirty_nobuffers (page); } +static int ext3_launder_page (struct page *page) +{ +int ret; +int retry =5 ; + +while (retry --) { + ret =ext3_releasepage (page,GFP_KERNEL );fs/ext3/inode.c static int ext3_journalled_set_page_dirt · line 1785
Quoted 10 lines
fs/ext3/inode.c +10
-0+if (ret ==1 ) +break ; +else +schedule (); + } + +return ret; +} + +Yes, I meant something like this. We could be more clever and do:
Quoted 11 lines
head = bh = page_buffers(page); do { wait_on_buffer(bh); bh = bh->b_this_page; } while (bh != head); /* * Now commit code should have been able to proceed and release * those buffers */ schedule();
Quoted 5 lines
Bummer, we can't free buffers in
ext3_launder_page()before callingtry_to_free_page, as later invalidate_complete_page2()->try_to_free_page() expecting the page buffers are still here, and will return EIO if itlaunder_page()has already freed those buffers.:(
Are you sure? Because if bufferes are released in ext3_launder_page(),PagePrivate() has been set to 0 and we should directly fall through to releasing the page without ever calling try_to_release_page()... So I'd want to find out why PagePrivate is still set in invalidate_complete_page2().
Quoted 2 lines
Doing
wait_on_buffer()alone inlaunder_page()is not enough as it doesn't wait for buffer reference drop to 0.
Yes, this would not be enough.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 71 lines · Jan Kara
Quoted 65 lines · Mingming Cao
Quoted 52 lines · Jan Kara
Quoted 39 lines · Mingming Cao
On Mon, 2008-05-12 at 17:54 +0200, Jan Kara wrote:
Does this match what you are thinking? It certainly slow down the DIO path, but the positive side is it doesn't disturb the other code path... thanks for your feedback!--------------------------------------------
An unexpected EIO error gets returned when writing to a file using buffered writes and DIO writes at the same time.
We found there are a number of places where
journal_try_to_free_buffers()could race withjournal_commit_transaction(), the later still
helds the reference to the buffers on thet_syncdata_listort_locked_list, whilejournal_try_to_free_buffers()tries to free them, which resulting an EIO error returns back to the dio caller.The logic fix is to retry freeing if
journal_try_to_free_buffers()to failed to free those data buffers whilejournal_commit_transaction()is still reference those buffers.
This is done via implement ext3launder_page()callback, instead of insidejournal_try_to_free_buffers()itself, so that it doesn't affecting other code path callingjournal_try_to_free_buffersand only dio path get affected.Signed-off-by Mingming Cao <[email protected]>Index:
linux-2.6.26-rc1/fs/ext3/inode.c
===================================================================fs/ext3/inode.c +7
-0@@ -1766,6 +1766,23 @@ static int ext3_journalled_set_page_dirtreturn __set_page_dirty_nobuffers (page); } +static int ext3_launder_page (struct page *page) +{ +int ret; +int retry =5 ; + +while (retry --) { + ret =ext3_releasepage (page,GFP_KERNEL );fs/ext3/inode.c static int ext3_journalled_set_page_dirt · line 1785
Quoted 10 lines
fs/ext3/inode.c +10
-0+if (ret ==1 ) +break ; +else +schedule (); + } + +return ret; +} + +Yes, I meant something like this. We could be more clever and do:
Quoted 11 lines
head = bh = page_buffers(page); do { wait_on_buffer(bh); bh = bh->b_this_page; } while (bh != head); /* * Now commit code should have been able to proceed and release * those buffers */ schedule();Quoted 5 lines
Bummer, we can't free buffers in
ext3_launder_page()before callingtry_to_free_page, as later invalidate_complete_page2()->try_to_free_page() expecting the page buffers are still here, and will return EIO if itlaunder_page()has already freed those buffers.:(
Quoted 6 lines
Are you sure? Because if bufferes are released in
ext3_launder_page(),PagePrivate()has been set to 0 and we should directly fall through to releasing the page without ever callingtry_to_release_page()... So I'd want to find out why PagePrivate is still set ininvalidate_complete_page2().
You are right. PagePrivate() is being set to 0 in drop_buffers().
The problem is do_launder_page() returns successfully if the page is not dirty (our case), so ext3_launder_page() is not even get called. This also explains why the log_wait_commit() approach doesn't work for me:(
Have to think other ways...could we pass some flag to journal_try_to_free_buffers(), and ask journal_try_to_free_buffers() wait for jbd commit to finish flushing the data, if the request is from directo IO?
Mingming
Jan Kara
maintainerRe: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 12 lines · Mingming Cao
Quoted 11 lines
Quoted 5 lines
Bummer, we can't free buffers in
ext3_launder_page()before callingtry_to_free_page, as later invalidate_complete_page2()->try_to_free_page() expecting the page buffers are still here, and will return EIO if itlaunder_page()has already freed those buffers.:(Are you sure? Because if bufferes are released in
ext3_launder_page(),PagePrivate()has been set to 0 and we should directly fall through to releasing the page without ever callingtry_to_release_page()... So I'd want to find out why PagePrivate is still set ininvalidate_complete_page2().
Quoted 5 lines
You are right.
PagePrivate()is being set to 0 indrop_buffers().The problem is
do_launder_page()returns successfully if the page is not dirty (our case), soext3_launder_page()is not even get called. This also explains why thelog_wait_commit()approach doesn't work for me:(
I didn't realize PageDirty() is going to be already cleared by previous
writes... :(
Quoted 4 lines
Have to think other ways...could we pass some flag to
journal_try_to_free_buffers(), and askjournal_try_to_free_buffers()wait for jbd commit to finish flushing the data, if the request is from directo IO?
Well, we could do that but we'd have to change try_to_release_page() call
to accept an extra argument which would consequently mean changing all the filesystems... But yes, it probably makes sence because it is really different whether we should just release the page because of memory pressure or because direct IO needs to write to that area of the file. So adding the parameter to releasepage() callback is probably a reasonable thing to do.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: [PATCH] jbd_commit_transaction() races with journal_try_to_drop_buffers() causing DIO failures
Quoted 21 lines · Jan Kara
Quoted 12 lines · Mingming Cao
Quoted 11 lines
Quoted 5 lines
Bummer, we can't free buffers in
ext3_launder_page()before callingtry_to_free_page, as later invalidate_complete_page2()->try_to_free_page() expecting the page buffers are still here, and will return EIO if itlaunder_page()has already freed those buffers.:(Are you sure? Because if bufferes are released in
ext3_launder_page(),PagePrivate()has been set to 0 and we should directly fall through to releasing the page without ever callingtry_to_release_page()... So I'd want to find out why PagePrivate is still set ininvalidate_complete_page2().Quoted 5 lines
You are right.
PagePrivate()is being set to 0 indrop_buffers().The problem is
do_launder_page()returns successfully if the page is not dirty (our case), soext3_launder_page()is not even get called. This also explains why thelog_wait_commit()approach doesn't work for me:(I didn't realize
PageDirty()is going to be already cleared by previous
writes... :(
Quoted 7 lines
Quoted 4 lines
Have to think other ways...could we pass some flag to
journal_try_to_free_buffers(), and askjournal_try_to_free_buffers()wait for jbd commit to finish flushing the data, if the request is from directo IO?Well, we could do that but we'd have to change
try_to_release_page()call
to accept an extra argument which would consequently mean changing all the filesystems...
Actually there is an argument gfp_mask passed to try_to_release_page() which we could pass a special flag from direct IO that could be parsed as direct IO request. This would avoid changing all the filesystems and the address space operation interface. In fact, I don't see in-kernel tree fs releasepage() cal back functions is using this gfp_mask, but btrfs is using it.
Quoted 6 lines
But yes, it probably makes sence because it is really different whether we should just release the page because of memory pressure or because direct IO needs to write to that area of the file. So adding the parameter to
releasepage()callback is probably a reasonable thing to do.
Will send a patch shortly, with that patch the test fine for about 18 hours.
PATCH 1/1 Fix DIO EIO error caused by race between jbd_commit_transaction() and journal_try_to_drop_buffers()
In reply to Jan KaraThis patch fixed a few races between direct IO and kjournld commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause journal_try_to_free_buffers() fail to free the data buffers, when jbd is committing the transaction that has those data buffers on its t_syncdata_list or t_locked_list. journal_commit_transaction() still holds the reference to those buffers
before data reach to disk and buffers are removed from thet_syncdata_list of t_locked_list. This prevent the concurrent journal_try_to_free_buffers() to free those buffers at the same time, and cause EIO error returns back to direct IO.
With this patch, in case of direct IO and when try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.
--- fs/jbd/commit.c | 1 + fs/jbd/journal.c | 1 + fs/jbd/transaction.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/jbd.h | 3 +++ 4 files changed, 51 insertions(+)
include/linux/jbd.h +3 -0
@@ -667,6 +667,9 @@ struct journal_s*/ wait_queue_head_t j_wait_transaction_locked; +/* Wait queu for waiting for data buffers to flushed to disk*/ +wait_queue_head_t j_wait_data_flushed; +/* Wait queue for waiting for checkpointing to complete */ wait_queue_head_t j_wait_logspace;
fs/jbd/commit.c +1 -0
@@ -462,6 +462,7 @@ void journal_commit_transaction(journal_* clean by now, so check that it is in fact empty. */ J_ASSERT (commit_transaction->t_sync_datalist ==NULL ); +wake_up (&journal->j_wait_data_flushed) jbd_debug (3, "JBD: commit phase 3\n");
fs/jbd/journal.c +1 -0
@@ -660,6 +660,7 @@ static journal_t * journal_init_common ( goto fail;init_waitqueue_head (&journal->j_wait_transaction_locked); +init_waitqueue_head (&journal->j_wait_data_flushed);init_waitqueue_head (&journal->j_wait_logspace);init_waitqueue_head (&journal->j_wait_done_commit);init_waitqueue_head (&journal->j_wait_checkpoint);
fs/jbd/transaction.c +64 -2
96 lines not on this page · raw message
mm/truncate.c +2 -1
@@ -346,7 +346,8 @@ invalidate_complete_page2(struct addressif (page->mapping != mapping)return 0 ; -if (PagePrivate (page) && !try_to_release_page (page,GFP_KERNEL )) +if (PagePrivate (page) && + !try_to_release_page (page,GFP_KERNEL |__GFP_REPEAT))return 0 ;write_lock_irq (&mapping->tree_lock);
Re: [PATCH] Fix DIO EIO error caused by race between jbd_commit_transaction() and journal_try_to_drop_buffers()
Quoted 99 lines · Mingming Cao
This patch fixed a few races between direct IO and kjournld commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers
before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, and cause EIO error returns back to direct IO.With this patch, in case of direct IO and when
try_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/commit.c | 1 + fs/jbd/journal.c | 1 + fs/jbd/transaction.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/jbd.h | 3 +++ 4 files changed, 51 insertions(+)Index:
linux-2.6.26-rc1/include/linux/jbd.h
===================================================================include/linux/jbd.h +3
-0@@ -667,6 +667,9 @@ struct journal_s*/ wait_queue_head_t j_wait_transaction_locked; +/* Wait queu for waiting for data buffers to flushed to disk*/ +wait_queue_head_t j_wait_data_flushed; +/* Wait queue for waiting for checkpointing to complete */ wait_queue_head_t j_wait_logspace;Index:
linux-2.6.26-rc1/fs/jbd/commit.c
===================================================================fs/jbd/commit.c +1
-0@@ -462,6 +462,7 @@ void journal_commit_transaction(journal_* clean by now, so check that it is in fact empty. */ J_ASSERT (commit_transaction->t_sync_datalist ==NULL ); +wake_up (&journal->j_wait_data_flushed) jbd_debug (3, "JBD: commit phase 3\n");Index:
linux-2.6.26-rc1/fs/jbd/journal.c
===================================================================fs/jbd/journal.c +1
-0@@ -660,6 +660,7 @@ static journal_t * journal_init_common ( goto fail;init_waitqueue_head (&journal->j_wait_transaction_locked); +init_waitqueue_head (&journal->j_wait_data_flushed);init_waitqueue_head (&journal->j_wait_logspace);init_waitqueue_head (&journal->j_wait_done_commit);init_waitqueue_head (&journal->j_wait_checkpoint);Index:
linux-2.6.26-rc1/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +25
-0@@ -1648,12 +1648,47 @@ out:return ; } +/* +* Journal_try_to_free_buffers() will call this function to w +* waiting for the current transaction finishing syncing data buffers, before +* try to free that buffer. +* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + +/* +* If the current transaction is flushing and waiting for data buffers +* (t_state is T_FLUSH), wait for the j_wait_data_flushed event +*/ +if (transaction->t_state ==T_FLUSH ) { +DEFINE_WAIT (wait); +
fs/jbd/transaction.c out: · line 1685
Quoted 10 lines
fs/jbd/transaction.c +9
-0+prepare_to_wait (&journal->j_wait_data_flushed, + &wait,TASK_UNINTERRUPTIBLE ); +spin_unlock (&journal->j_state_lock); +schedule (); +finish_wait (&journal->j_wait_data_flushed, &wait); +spin_lock (&journal->j_state_lock); + } +return ; +}
Got a couple of whitespace problems above it looks like. Thanks,
Josef
Mingming Cao
Reviewed-by Badari PulavartyRe: [PATCH] Fix DIO EIO error caused by race between jbd_commit_transaction() and journal_try_to_drop_buffers()
Quoted 3 lines · Josef Bacik
Got a couple of whitespace problems above it looks like. Thanks,
Thanks for catching this, below is updated patch, fixed the whitespace and comments.
---------------------------------------------------
JBD: fix journal_try_to_free_buffers race withjournal_commit_transaction
From: Mingming Cao <[email protected]>
This patch fixed a few races between direct IO and kjournld commit transaction.
An unexpected EIO error gets returned to direct IO caller when it failed to
free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specificly, those races could cause journal_try_to_free_buffers() fail to free the data buffers, when jbd is committing the transaction that has
those data buffers on its t_syncdata_list or t_locked_list. journal_commit_transaction() still holds the reference to those buffers
before data reach to disk and buffers are removed from thet_syncdata_list of t_locked_list. This prevent the concurrent journal_try_to_free_buffers() to free those buffers at the same time, but cause
EIO error returns back to direct IO.
With this patch, in case of direct IO and when try_to_free_buffers() failed,
let's waiting for journal_commit_transaction() to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.
--- fs/jbd/commit.c | 1 + fs/jbd/journal.c | 1 + fs/jbd/transaction.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/jbd.h | 3 +++ 4 files changed, 51 insertions(+)
Index: linux-2.6.26-rc1/include/linux/jbd.h
===================================================================
--- linux-2.6.26-rc1.orig/include/linux/jbd.h 2008-05-14
16:36:41.000000000 -0700
+++ linux-2.6.26-rc1/include/linux/jbd.h 2008-05-15 14:12:10.000000000
-0700
@@ -667,6 +667,9 @@ struct journal_s
*/
wait_queue_head_t j_wait_transaction_locked;
+ /* Wait queu for waiting for data buffers to flushed to disk*/
+ wait_queue_head_t j_wait_data_flushed;
+
/* Wait queue for waiting for checkpointing to complete */
wait_queue_head_t j_wait_logspace;
Index: linux-2.6.26-rc1/fs/jbd/commit.c
===================================================================
--- linux-2.6.26-rc1.orig/fs/jbd/commit.c 2008-05-03 11:59:44.000000000
-0700
+++ linux-2.6.26-rc1/fs/jbd/commit.c 2008-05-15 14:12:46.000000000 -0700
@@ -462,6 +462,7 @@ void journal_commit_transaction(journal_
* clean by now, so check that it is in fact empty. */
J_ASSERT (commit_transaction->t_sync_datalist == NULL);
+ wake_up(&journal->j_wait_data_flushed)
jbd_debug (3, "JBD: commit phase 3\n");
Index: linux-2.6.26-rc1/fs/jbd/journal.c
===================================================================
--- linux-2.6.26-rc1.orig/fs/jbd/journal.c 2008-05-14 16:36:41.000000000
-0700
+++ linux-2.6.26-rc1/fs/jbd/journal.c 2008-05-15 14:13:02.000000000
-0700
@@ -660,6 +660,7 @@ static journal_t * journal_init_common (
goto fail;
init_waitqueue_head(&journal->j_wait_transaction_locked);
+ init_waitqueue_head(&journal->j_wait_data_flushed);
init_waitqueue_head(&journal->j_wait_logspace); init_waitqueue_head(&journal->j_wait_done_commit); init_waitqueue_head(&journal->j_wait_checkpoint);
Index: linux-2.6.26-rc1/fs/jbd/transaction.c
===================================================================
--- linux-2.6.26-rc1.orig/fs/jbd/transaction.c 2008-05-03
11:59:44.000000000 -0700
+++ linux-2.6.26-rc1/fs/jbd/transaction.c 2008-05-16 09:27:21.000000000
-0700
@@ -1648,12 +1648,49 @@ out:
return;
}
+/*
+ * journal_try_to_free_buffers() could race with journal_commit_transaction()
+ * The later might still hold the reference count to the buffers when inspecting
+ * them on t_syncdata_list or t_locked_list.
+ *
+ * Journal_try_to_free_buffers() will call this function to
+ * wait for the current transaction finishing syncing data buffers, before
+ * try to free that buffer.
+ *
+ * Called with journal->j_state_lock hold.
+ */
+static void journal_wait_for_transaction_sync_data(journal_t *journal) +{
+ transaction_t *transaction = NULL;
+
+ transaction = journal->j_committing_transaction;
+
+ if (!transaction)
+ return;
+
+ /*
+ * If the current transaction is flushing and waiting for data buffers
+ * (t_state is T_FLUSH), wait for the j_wait_data_flushed event
+ */
+ if (transaction->t_state == T_FLUSH) {
+ DEFINE_WAIT(wait);
+
+ prepare_to_wait(&journal->j_wait_data_flushed,
+ &wait, TASK_UNINTERRUPTIBLE);
+ spin_unlock(&journal->j_state_lock);
+ schedule();
+ finish_wait(&journal->j_wait_data_flushed, &wait);
+ spin_lock(&journal->j_state_lock);
+ }
+ return;
+}
/**
* int journal_try_to_free_buffers() - try to free page buffers.
* @journal: journal for operation
* @page: to try and free
- * @unused_gfp_mask: unused
+ * @gfp_mask: unused for allocation purpose. Here is used
+ * as a flag to tell if direct IO is attemping to free buffers.
*
*
* For all the buffers on this page,
@@ -1682,13 +1719,16 @@ out:
* journal_try_to_free_buffer() is changing its state. But that
* cannot happen because we never reallocate freed data as metadata
* while the data is part of a transaction. Yes?
+ *
+ * Return 0 on failure, 1 on success
*/
int journal_try_to_free_buffers(journal_t *journal,
- struct page *page, gfp_t unused_gfp_mask)
+ struct page *page, gfp_t gfp_mask)
{
struct buffer_head *head;
struct buffer_head *bh;
int ret = 0;
+ int dio = gfp_mask & __GFP_REPEAT;
J_ASSERT(PageLocked(page));
@@ -1713,7 +1753,31 @@ int journal_try_to_free_buffers(journal_
if (buffer_jbd(bh))
goto busy;
} while ((bh = bh->b_this_page) != head);
+
ret = try_to_free_buffers(page);
+
+ /*
+ * In the case of concurrent direct IO and buffered IO,
+ * There are a number of places where we
+ * could race with journal_commit_transaction(), the later still
+ * helds the reference to the buffers to free while processing them.
+ * try_to_free_buffers() failed to free those buffers,
+ * resulting in an unexpected EIO error
+ * returns back to the generic_file_direct_IO()
+ *
+ * So let's wait for the current transaction finished flush
+ * dirty data buffers before we try to free those buffers
+ * again. This wait is needed by direct IO code path only,
+ * gfp_mask __GFP_REPEAT is passed from the direct IO code
+ * path to flag if we need to wait and retry free buffers.
+ */
+ if (ret == 0 && dio) {
+ spin_lock(&journal->j_state_lock);
+ journal_wait_for_transaction_sync_data(journal);
+ ret = try_to_free_buffers(page);
+ spin_unlock(&journal->j_state_lock);
+ }
+
busy:
return ret;
}
Index: linux-2.6.26-rc1/mm/truncate.c
===================================================================
--- linux-2.6.26-rc1.orig/mm/truncate.c 2008-05-03 11:59:44.000000000
-0700
+++ linux-2.6.26-rc1/mm/truncate.c 2008-05-15 13:13:21.000000000 -0700
@@ -346,7 +346,8 @@ invalidate_complete_page2(struct address
if (page->mapping != mapping)
return 0;
- if (PagePrivate(page) && !try_to_release_page(page, GFP_KERNEL))
+ if (PagePrivate(page) &&
+ !try_to_release_page(page,GFP_KERNEL|__GFP_REPEAT))
return 0;
write_lock_irq(&mapping->tree_lock);
Quoted 6 lines
Josef
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH] Fix DIO EIO error caused by race between jbd_commit_transaction() and journal_try_to_drop_buffers()
Quoted 108 lines · Mingming Cao
Quoted 3 lines · Josef Bacik
Got a couple of whitespace problems above it looks like. Thanks,
Thanks for catching this, below is updated patch, fixed the whitespace and comments.
---------------------------------------------------
JBD: fixjournal_try_to_free_buffersrace withjournal_commit_transactionFrom: Mingming Cao <[email protected]>
This patch fixed a few races between direct IO and kjournld commit transaction.
An unexpected EIO error gets returned to direct IO caller when it failed to
free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same fileMore specificly, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has
those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers
before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, but cause
EIO error returns back to direct IO.With this patch, in case of direct IO and when
try_to_free_buffers()failed,
let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/commit.c | 1 + fs/jbd/journal.c | 1 + fs/jbd/transaction.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/jbd.h | 3 +++ 4 files changed, 51 insertions(+)Index:
linux-2.6.26-rc1/include/linux/jbd.h
===================================================================
---linux-2.6.26-rc1.orig/include/linux/jbd.h2008-05-14
16:36:41.000000000 -0700
+++linux-2.6.26-rc1/include/linux/jbd.h2008-05-15 14:12:10.000000000
-0700include/linux/jbd.h +3
-0@@ -667,6 +667,9 @@ struct journal_s*/ wait_queue_head_t j_wait_transaction_locked; +/* Wait queu for waiting for data buffers to flushed to disk*/ +wait_queue_head_t j_wait_data_flushed; +/* Wait queue for waiting for checkpointing to complete */ wait_queue_head_t j_wait_logspace;Index:
linux-2.6.26-rc1/fs/jbd/commit.c
===================================================================
---linux-2.6.26-rc1.orig/fs/jbd/commit.c2008-05-03 11:59:44.000000000
-0700
+++linux-2.6.26-rc1/fs/jbd/commit.c2008-05-15 14:12:46.000000000 -0700fs/jbd/commit.c +1
-0@@ -462,6 +462,7 @@ void journal_commit_transaction(journal_* clean by now, so check that it is in fact empty. */ J_ASSERT (commit_transaction->t_sync_datalist ==NULL ); +wake_up (&journal->j_wait_data_flushed) jbd_debug (3, "JBD: commit phase 3\n");Index:
linux-2.6.26-rc1/fs/jbd/journal.c
===================================================================
---linux-2.6.26-rc1.orig/fs/jbd/journal.c2008-05-14 16:36:41.000000000
-0700
+++linux-2.6.26-rc1/fs/jbd/journal.c2008-05-15 14:13:02.000000000
-0700fs/jbd/journal.c +1
-0@@ -660,6 +660,7 @@ static journal_t * journal_init_common ( goto fail;init_waitqueue_head (&journal->j_wait_transaction_locked); +init_waitqueue_head (&journal->j_wait_data_flushed);init_waitqueue_head (&journal->j_wait_logspace);init_waitqueue_head (&journal->j_wait_done_commit);init_waitqueue_head (&journal->j_wait_checkpoint);Index:
linux-2.6.26-rc1/fs/jbd/transaction.c
===================================================================
---linux-2.6.26-rc1.orig/fs/jbd/transaction.c2008-05-03
11:59:44.000000000 -0700
+++linux-2.6.26-rc1/fs/jbd/transaction.c2008-05-16 09:27:21.000000000
-0700fs/jbd/transaction.c +3
-0@@ -1648,12 +1648,49 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting
Quoted 8 lines
+ * them on
t_syncdata_listort_locked_list.
+ *
+ *Journal_try_to_free_buffers()will call this function to
+ * wait for the current transaction finishing syncing data buffers, before
+ * try to free that buffer.
+ *
+ * Called withjournal->j_state_lockhold.
Fix wrapping lines ?
Quoted 68 lines
+ */
fs/jbd/transaction.c +39
-2+static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + +/* +* If the current transaction is flushing and waiting for data buffers +* (t_state is T_FLUSH), wait for the j_wait_data_flushed event +*/ +if (transaction->t_state ==T_FLUSH ) { +DEFINE_WAIT (wait); + +prepare_to_wait (&journal->j_wait_data_flushed, + &wait,TASK_UNINTERRUPTIBLE ); +spin_unlock (&journal->j_state_lock); +schedule (); +finish_wait (&journal->j_wait_data_flushed, &wait); +spin_lock (&journal->j_state_lock); + } +return ; +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: unused for allocation purpose. Here is used +* as a flag to tell if direct IO is attemping to free buffers. * * * For all the buffers on this page, @@ -1682,13 +1719,16 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;int ret =0 ; +int dio = gfp_mask & __GFP_REPEAT;J_ASSERT (PageLocked (page));@@ -1713,7 +1753,31 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* In the case of concurrent direct IO and buffered IO, +* There are a number of places where we +* could race with journal_commit_transaction(), the later still +* helds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers,
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1774
Quoted 10 lines
fs/jbd/transaction.c +10
-0+* resulting in an unexpected EIO error +* returns back to the generic_file_direct_IO() +* +* So let's wait for the current transaction finished flush +* dirty data buffers before we try to free those buffers +* again. This wait is needed by direct IO code path only, +* gfp_mask __GFP_REPEAT is passed from the direct IO code +* path to flag if we need to wait and retry free buffers. +*/ +if (ret ==0 && dio) {
drop "dio" variable and compare here, like
if (ret == 0 && (gfp_mask & __GFP_REPEAT)
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1777
Quoted 5 lines
fs/jbd/transaction.c +5
-0+spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + }
Thanks,
Badari
Re: [PATCH] Fix DIO EIO error caused by race between jbd_commit_transaction() and journal_try_to_drop_buffers()
Quoted 25 lines · Badari Pulavarty
Quoted 13 lines
fs/jbd/transaction.c +8
-0@@ -1713,7 +1753,31 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* In the case of concurrent direct IO and buffered IO, +* There are a number of places where we +* could race with journal_commit_transaction(), the later still +* helds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers, fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1774
Quoted 10 lines
fs/jbd/transaction.c +10
-0+* resulting in an unexpected EIO error +* returns back to the generic_file_direct_IO() +* +* So let's wait for the current transaction finished flush +* dirty data buffers before we try to free those buffers +* again. This wait is needed by direct IO code path only, +* gfp_mask __GFP_REPEAT is passed from the direct IO code +* path to flag if we need to wait and retry free buffers. +*/ +if (ret ==0 && dio) {drop "dio" variable and compare here, like
Quoted 2 lines
if (ret == 0 && (gfp_mask & __GFP_REPEAT)
Okay, will do. Also will update the patch with other format changes you suggested.
Quoted 6 lines
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1777
Quoted 5 lines
fs/jbd/transaction.c +5
-0+spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + }
Also, This patch changed the struct journal_s to add a new wait queue, so that journal_try_to_free_buffers() could only need to wait for data to be commited, rather than using j_wait_done_commit queue and wait for the whole transaction being committed. It might break other fs that uses journal_s, thus not worth it. Will update the patch.
Re: [PATCH] Fix DIO EIO error caused by race between jbd_commit_transaction() and journal_try_to_drop_buffers()
In reply to Mingming CaoSmall nits..
Quoted 33 lines · Mingming Cao
This patch fixed a few races between direct IO and kjournld commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers
before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, and cause EIO error returns back to direct IO.With this patch, in case of direct IO and when
try_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/commit.c | 1 + fs/jbd/journal.c | 1 + fs/jbd/transaction.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/jbd.h | 3 +++ 4 files changed, 51 insertions(+)Index:
linux-2.6.26-rc1/include/linux/jbd.h
===================================================================
include/linux/jbd.h struct journal_s · line 670
Quoted 4 lines
include/linux/jbd.h +1
-0@@ -667,6 +667,9 @@ struct journal_s*/ wait_queue_head_t j_wait_transaction_locked; +/* Wait queu for waiting for data buffers to flushed to disk*/
Fix typo - "queue" and also this line is wrapping. Could you spilt it into 2 lines ?
Quoted 10 lines
include/linux/jbd.h +2
-0+wait_queue_head_t j_wait_data_flushed; +/* Wait queue for waiting for checkpointing to complete */ wait_queue_head_t j_wait_logspace;Index:
linux-2.6.26-rc1/fs/jbd/commit.c
===================================================================
fs/jbd/commit.c void journal_commit_transaction(journal_ · line 465
Quoted 4 lines
fs/jbd/commit.c +1
-0@@ -462,6 +462,7 @@ void journal_commit_transaction(journal_* clean by now, so check that it is in fact empty. */ J_ASSERT (commit_transaction->t_sync_datalist ==NULL ); +wake_up (&journal->j_wait_data_flushed)
missing ";" ? Did it compile ? :)
Quoted 19 lines
fs/jbd/commit.c +0
-0jbd_debug (3 ,"JBD: commit phase 3\n" );Index:
linux-2.6.26-rc1/fs/jbd/journal.c
===================================================================fs/jbd/journal.c +1
-0@@ -660,6 +660,7 @@ static journal_t * journal_init_common ( goto fail;init_waitqueue_head (&journal->j_wait_transaction_locked); +init_waitqueue_head (&journal->j_wait_data_flushed);init_waitqueue_head (&journal->j_wait_logspace);init_waitqueue_head (&journal->j_wait_done_commit);init_waitqueue_head (&journal->j_wait_checkpoint);Index:
linux-2.6.26-rc1/fs/jbd/transaction.c
===================================================================
fs/jbd/transaction.c out: · line 1652
Quoted 5 lines
fs/jbd/transaction.c +2
-0@@ -1648,12 +1648,47 @@ out:return ; } +/* +* Journal_try_to_free_buffers() will call this function to w
drop "w"
fs/jbd/transaction.c out: · line 1653
Quoted 1 line
fs/jbd/transaction.c +1
-0+* waiting for the current transaction finishing syncing data buffers, before
fix grammar - "wait for the current transaction to finish syncing data buffers, .."
Quoted 9 lines
fs/jbd/transaction.c +9
-0+* try to free that buffer. +* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ;
fs/jbd/transaction.c out: · line 1672
Quoted 10 lines
fs/jbd/transaction.c +10
-0+ + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + +/* +* If the current transaction is flushing and waiting for data buffers +* (t_state is T_FLUSH), wait for the j_wait_data_flushed event +*/
use "tabs" instead of "whitespaces"
fs/jbd/transaction.c out: · line 1681
Quoted 9 lines
fs/jbd/transaction.c +9
-0+if (transaction->t_state ==T_FLUSH ) { +DEFINE_WAIT (wait); + +prepare_to_wait (&journal->j_wait_data_flushed, + &wait,TASK_UNINTERRUPTIBLE ); +spin_unlock (&journal->j_state_lock); +schedule (); +finish_wait (&journal->j_wait_data_flushed, &wait); +spin_lock (&journal->j_state_lock);
again "whitespace" issues for the entire function..
Quoted 34 lines
fs/jbd/transaction.c +9
-2+ } +return ; +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: unused for allocation purpose. Here is used +* as a flag to tell if direct IO is attemping to free buffers. * * * For all the buffers on this page, @@ -1682,13 +1717,16 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;int ret =0 ; +int dio = gfp_mask & __GFP_REPEAT;J_ASSERT (PageLocked (page));@@ -1713,7 +1751,31 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh))
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1761
Quoted 10 lines
fs/jbd/transaction.c +7
-0goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* In the case of concurrent direct IO and buffered IO, +* There are a number of places where we +* could race with journal_commit_transaction(), the later still +* helds the reference to the buffers to free while processing them.
"holds the reference to the buffers while processing them".
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1766
Quoted 5 lines
fs/jbd/transaction.c +5
-0+* try_to_free_buffers() failed to free those buffers, +* resulting in an unexpected EIO error +* returns back to the generic_file_direct_IO() +* +* So let's wait for the current transaction finished flush
"wait for the commited transaction to finish the flush of dirty data"
Quoted 31 lines
fs/jbd/transaction.c +12
-0+* dirty data buffers before we try to free those buffers +* again. This wait is needed by direct IO code path only, +* gfp_mask __GFP_REPEAT is passed from the direct IO code +* path to flag if we need to wait and retry free buffers. +*/ +if (ret ==0 && dio) { +spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }Index:
linux-2.6.26-rc1/mm/truncate.c
===================================================================mm/truncate.c +2
-1@@ -346,7 +346,8 @@ invalidate_complete_page2(struct addressif (page->mapping != mapping)return 0 ; -if (PagePrivate (page) && !try_to_release_page (page,GFP_KERNEL )) +if (PagePrivate (page) && + !try_to_release_page (page,GFP_KERNEL |__GFP_REPEAT))return 0 ;write_lock_irq (&mapping->tree_lock);
PATCH 1/1 JBD: Fix DIO EIO error caused by race between free buffer and commit trasanction
This patch fixed a few races between direct IO and kjournald commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause journal_try_to_free_buffers() fail to free the data buffers, when jbd is committing the transaction that has those data buffers on its t_syncdata_list or t_locked_list. journal_commit_transaction() still holds the reference to those buffers
before data reach to disk and buffers are removed from thet_syncdata_list of t_locked_list. This prevent the concurrent journal_try_to_free_buffers() to free those buffers at the same time, but cause EIO error returns back to direct IO.
With this patch, in case of direct IO and when try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.
--- fs/jbd/transaction.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/truncate.c | 3 +- 2 files changed, 57 insertions(+), 3 deletions(-)
fs/jbd/transaction.c +55 -2
@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: unused for allocation purpose. Here is used +* as a flag to tell if direct IO is attemping to free buffers. * * * For all the buffers on this page,
@@ -1682,9 +1709,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1713,7 +1742,31 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* In the case of concurrent direct IO and buffered IO, +* There are a number of places where we +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers, +* resulting in an unexpected EIO error +* returns back to the generic_file_direct_IO() +* +* So let's wait for the current transaction to finish flush of +* dirty data buffers before we try to free those buffers +* again. This wait is needed by direct IO code path only, +* gfp_mask __GFP_REPEAT is passed from the direct IO code +* path to flag if we need to wait and retry free buffers. +*/ +if (ret ==0 && gfp_mask & __GFP_REPEAT) { +spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }
mm/truncate.c +2 -1
@@ -346,7 +346,8 @@ invalidate_complete_page2(struct addressif (page->mapping != mapping)return 0 ; -if (PagePrivate (page) && !try_to_release_page (page,GFP_KERNEL )) +if (PagePrivate (page) && + !try_to_release_page (page,GFP_KERNEL |__GFP_REPEAT))return 0 ;write_lock_irq (&mapping->tree_lock);
Re: [PATCH] JBD: Fix DIO EIO error caused by race between free buffer and commit trasanction
Hi,
Quoted 15 lines
This patch fixed a few races between direct IO and kjournald commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, but cause EIO error returns back to direct IO.
Quoted 4 lines
With this patch, in case of direct IO and when
try_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.
If Andrew or Christoph wouldn't beat you for "inventive use" ofgfp_mask, I'm fine with the patch as well ;). You can add
Quoted 115 lines
Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/transaction.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/truncate.c | 3 +- 2 files changed, 57 insertions(+), 3 deletions(-)Index:
linux-2.6.26-rc2/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +55
-2@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: unused for allocation purpose. Here is used +* as a flag to tell if direct IO is attemping to free buffers. * * * For all the buffers on this page, @@ -1682,9 +1709,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;@@ -1713,7 +1742,31 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* In the case of concurrent direct IO and buffered IO, +* There are a number of places where we +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers, +* resulting in an unexpected EIO error +* returns back to the generic_file_direct_IO() +* +* So let's wait for the current transaction to finish flush of +* dirty data buffers before we try to free those buffers +* again. This wait is needed by direct IO code path only, +* gfp_mask __GFP_REPEAT is passed from the direct IO code +* path to flag if we need to wait and retry free buffers. +*/ +if (ret ==0 && gfp_mask & __GFP_REPEAT) { +spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }Index:
linux-2.6.26-rc2/mm/truncate.c
===================================================================mm/truncate.c +2
-2@@ -346,7 +346,8 @@ invalidate_complete_page2(struct addressif (page->mapping != mapping)return 0 ; -if (PagePrivate (page) && !try_to_release_page (page,GFP_KERNEL )) +if (PagePrivate (page) && + !try_to_release_page (page,GFP_KERNEL |__GFP_REPEAT))return 0 ;write_lock_irq (&mapping->tree_lock); --
Quoted 4 lines
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Honza
Signature · 2 lines
Jan Kara <[email protected]> SuSE CR Labs
Re: [PATCH] JBD: Fix DIO EIO error caused by race between free buffer and commit trasanction
Quoted 17 lines · Jan Kara
Hi,
Quoted 15 lines
This patch fixed a few races between direct IO and kjournald commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, but cause EIO error returns back to direct IO.
Quoted 8 lines
Quoted 4 lines
With this patch, in case of direct IO and when
try_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.If Andrew or Christoph wouldn't beat you for "inventive use" of
gfp_mask, I'm fine with the patch as well ;). You can addAcked-by Jan Kara <[email protected]>
This is less intrusive way to fix this problem. The gfp_mask was marked as unused in try_to_free_page(). I looked at filesystems in the kernel, there is only a few defined releasepage() callback, and only xfs checks the flag(but not used). btrfs is actually using it though. I thought about the way you have suggested, i.e.clean up this gfp_mask and and replace with a flag. I am not entirely sure if it we need to change the address_space_operations and fix all the filesystems for this matter.
Andrew, what do you think? Is this approach acceptable?
Thanks and regards,
Mingming
Quoted 121 lines
Quoted 115 lines
Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/transaction.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/truncate.c | 3 +- 2 files changed, 57 insertions(+), 3 deletions(-)Index:
linux-2.6.26-rc2/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +55
-2@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: unused for allocation purpose. Here is used +* as a flag to tell if direct IO is attemping to free buffers. * * * For all the buffers on this page, @@ -1682,9 +1709,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;@@ -1713,7 +1742,31 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* In the case of concurrent direct IO and buffered IO, +* There are a number of places where we +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers, +* resulting in an unexpected EIO error +* returns back to the generic_file_direct_IO() +* +* So let's wait for the current transaction to finish flush of +* dirty data buffers before we try to free those buffers +* again. This wait is needed by direct IO code path only, +* gfp_mask __GFP_REPEAT is passed from the direct IO code +* path to flag if we need to wait and retry free buffers. +*/ +if (ret ==0 && gfp_mask & __GFP_REPEAT) { +spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }Index:
linux-2.6.26-rc2/mm/truncate.c
===================================================================mm/truncate.c +2
-2@@ -346,7 +346,8 @@ invalidate_complete_page2(struct addressif (page->mapping != mapping)return 0 ; -if (PagePrivate (page) && !try_to_release_page (page,GFP_KERNEL )) +if (PagePrivate (page) && + !try_to_release_page (page,GFP_KERNEL |__GFP_REPEAT))return 0 ;write_lock_irq (&mapping->tree_lock); --Quoted 4 lines
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/Honza
Andrew Morton
maintainerRe: [PATCH] JBD: Fix DIO EIO error caused by race between free buffer and commit trasanction
Quoted 35 lines · On Mon, 19 May 2008 12:59:18 -0700 Mingming Cao <[email protected]> wrote:
Quoted 17 lines · Jan Kara
Hi,
Quoted 15 lines
This patch fixed a few races between direct IO and kjournald commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, but cause EIO error returns back to direct IO.Quoted 8 lines
Quoted 4 lines
With this patch, in case of direct IO and when
try_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.If Andrew or Christoph wouldn't beat you for "inventive use" of
gfp_mask, I'm fine with the patch as well ;). You can addAcked-by Jan Kara <[email protected]>This is less intrusive way to fix this problem. The
gfp_maskwas marked as unused intry_to_free_page(). I looked at filesystems in the kernel, there is only a few definedreleasepage()callback, and only xfs checks the flag(but not used). btrfs is actually using it though. I thought about the way you have suggested, i.e.clean up thisgfp_maskand and replace with a flag. I am not entirely sure if it we need to change theaddress_space_operationsand fix all the filesystems for this matter.
Quoted 2 lines
Andrew, what do you think? Is this approach acceptable?
<wakes up>
Please ensure that the final patch is sufficiently well changelogged to permit me to remain asleep ;)
The ->releasepage semantics are fairly ad-hoc and have grown over time. It'd be nice to prevent them from becoming vaguer than they are.
It has been (approximately?) the case that code paths which really care about having the page released will set __GFP_WAIT (via GFP_KERNEL) whereas code paths which are happy with best-effort will clear __GFP_WAIT (with a "0'). And that's reasonsable - __GFP_WAIT here means "be synchronous" whereas !__GFP_WAIT means "be non-blocking".
Is that old convention not sufficient here as well? Two problem areas I see are mm/vmscan.c and fs/splice.c (there may be others).
In mm/vmscan.c we probably don't want your new synchronous behaviour and it might well be deadlockable anyway. No probs, that's what __GFP_FS is for.
In fs/splice.c, reading the comment there I have a feeling that you've found another bug, and that splice _does_ want your new synchronous behaviour?
Re: [PATCH] JBD: Fix DIO EIO error caused by race between free buffer and commit trasanction
Quoted 38 lines · Andrew Morton
Quoted 35 lines · On Mon, 19 May 2008 12:59:18 -0700 Mingming Cao <[email protected]> wrote:
Quoted 17 lines · Jan Kara
Hi,
Quoted 15 lines
This patch fixed a few races between direct IO and kjournald commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, but cause EIO error returns back to direct IO.Quoted 8 lines
Quoted 4 lines
With this patch, in case of direct IO and when
try_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.If Andrew or Christoph wouldn't beat you for "inventive use" of
gfp_mask, I'm fine with the patch as well ;). You can addAcked-by Jan Kara <[email protected]>This is less intrusive way to fix this problem. The
gfp_maskwas marked as unused intry_to_free_page(). I looked at filesystems in the kernel, there is only a few definedreleasepage()callback, and only xfs checks the flag(but not used). btrfs is actually using it though. I thought about the way you have suggested, i.e.clean up thisgfp_maskand and replace with a flag. I am not entirely sure if it we need to change theaddress_space_operationsand fix all the filesystems for this matter.
Quoted 7 lines
Quoted 2 lines
Andrew, what do you think? Is this approach acceptable?
<wakes up>
Please ensure that the final patch is sufficiently well changelogged to permit me to remain asleep ;)
:-)
Quoted 9 lines
The ->releasepage semantics are fairly ad-hoc and have grown over time. It'd be nice to prevent them from becoming vaguer than they are.
It has been (approximately?) the case that code paths which really care about having the page released will set
__GFP_WAIT(viaGFP_KERNEL) whereas code paths which are happy with best-effort will clear__GFP_WAIT(with a "0'). And that's reasonsable -__GFP_WAIThere means "be synchronous" whereas !__GFP_WAIT means "be non-blocking".
This make sense to me.
Quoted 8 lines
Is that old convention not sufficient here as well? Two problem areas I see are
mm/vmscan.candfs/splice.c(there may be others).In
mm/vmscan.cwe probably don't want your new synchronous behaviour and it might well be deadlockable anyway. No probs, that's what__GFP_FSis for.
Sure. We could check __GFP_FS and __GFP_WAIT, and that make sense.
Quoted 3 lines
In
fs/splice.c, reading the comment there I have a feeling that you've found another bug, and that splice _does_ want your new synchronous behaviour?
Yes, it looks like page_cache_pipe_buf_steal() expects page is free before removeing it by passing the GFP_KERNEL flag, but currently ext3 could fails to releasepage when it called. In fact try_to_release_page() return value is ignored in page_cache_pipe_buf_steal(), should probably checked the failure case.
The other caller of try_to_release_page() in mm/splice.c is fallback_migrate_page(), which does want the synchronous behaviour to make sure buffers are dropped.
I will reuse the GFP_WAIT and GFP_FS flag in the updated patch.
Thanks for your feedback.
Mingming
Quoted 4 lines
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH] JBD: Fix DIO EIO error caused by race between free buffer and commit trasanction
Quoted 80 lines · Mingming Cao
Quoted 38 lines · Andrew Morton
Quoted 35 lines · On Mon, 19 May 2008 12:59:18 -0700 Mingming Cao <[email protected]> wrote:
Quoted 17 lines · Jan Kara
Hi,
Quoted 15 lines
This patch fixed a few races between direct IO and kjournald commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, but cause EIO error returns back to direct IO.Quoted 8 lines
Quoted 4 lines
With this patch, in case of direct IO and when
try_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.If Andrew or Christoph wouldn't beat you for "inventive use" of
gfp_mask, I'm fine with the patch as well ;). You can addAcked-by Jan Kara <[email protected]>This is less intrusive way to fix this problem. The
gfp_maskwas marked as unused intry_to_free_page(). I looked at filesystems in the kernel, there is only a few definedreleasepage()callback, and only xfs checks the flag(but not used). btrfs is actually using it though. I thought about the way you have suggested, i.e.clean up thisgfp_maskand and replace with a flag. I am not entirely sure if it we need to change theaddress_space_operationsand fix all the filesystems for this matter.Quoted 7 lines
Quoted 2 lines
Andrew, what do you think? Is this approach acceptable?
<wakes up>
Please ensure that the final patch is sufficiently well changelogged to permit me to remain asleep ;)
:-)
Quoted 9 lines
The ->releasepage semantics are fairly ad-hoc and have grown over time. It'd be nice to prevent them from becoming vaguer than they are.
It has been (approximately?) the case that code paths which really care about having the page released will set
__GFP_WAIT(viaGFP_KERNEL) whereas code paths which are happy with best-effort will clear__GFP_WAIT(with a "0'). And that's reasonsable -__GFP_WAIThere means "be synchronous" whereas !__GFP_WAIT means "be non-blocking".This make sense to me.
Quoted 8 lines
Is that old convention not sufficient here as well? Two problem areas I see are
mm/vmscan.candfs/splice.c(there may be others).In
mm/vmscan.cwe probably don't want your new synchronous behaviour and it might well be deadlockable anyway. No probs, that's what__GFP_FSis for.Sure. We could check
__GFP_FSand__GFP_WAIT, and that make sense.Quoted 3 lines
In
fs/splice.c, reading the comment there I have a feeling that you've found another bug, and that splice _does_ want your new synchronous behaviour?Yes, it looks like
page_cache_pipe_buf_steal()expects page is free before removeing it by passing theGFP_KERNELflag, but currently ext3 could fails to releasepage when it called. In facttry_to_release_page()return value is ignored inpage_cache_pipe_buf_steal(), should probably checked the failure case.
Quoted 3 lines
The other caller of
try_to_release_page()inmm/splice.cisfallback_migrate_page(), which does want the synchronous behaviour to make sure buffers are dropped.
So something like this, then?
fs/splice.c +3 -2
@@ -58,8 +58,8 @@ static int page_cache_pipe_buf_steal(struct pipe_inode_info *pipe,*/ wait_on_page_writeback (page); -if (PagePrivate (page)) -try_to_release_page (page,GFP_KERNEL ); +if (PagePrivate (page) && !try_to_release_page (page,GFP_KERNEL )) + goto out_unlock;/* * If we succeeded in removing the mapping, set LRU flag
@@ -75,6 +75,7 @@ static int page_cache_pipe_buf_steal(struct pipe_inode_info *pipe,* Raced with truncate or failed to remove page from current * address space, unlock and return failure. */ +out_unlock:unlock_page (page);return 1 ; }
Signature · 1 line
Jens Axboe
Mingming Cao
Acked-byRe: [PATCH] JBD: Fix DIO EIO error caused by race between free buffer and commit trasanction
Quoted 81 lines · Jens Axboe
Quoted 80 lines · Mingming Cao
Quoted 38 lines · Andrew Morton
Quoted 35 lines · On Mon, 19 May 2008 12:59:18 -0700 Mingming Cao <[email protected]> wrote:
Quoted 17 lines · Jan Kara
Hi,
Quoted 15 lines
This patch fixed a few races between direct IO and kjournald commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause
journal_try_to_free_buffers()fail to free the data buffers, when jbd is committing the transaction that has those data buffers on itst_syncdata_listort_locked_list.journal_commit_transaction()still holds the reference to those buffers before data reach to disk and buffers are removed from thet_syncdata_listoft_locked_list. This prevent the concurrentjournal_try_to_free_buffers()to free those buffers at the same time, but cause EIO error returns back to direct IO.Quoted 8 lines
Quoted 4 lines
With this patch, in case of direct IO and when
try_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.If Andrew or Christoph wouldn't beat you for "inventive use" of
gfp_mask, I'm fine with the patch as well ;). You can addAcked-by Jan Kara <[email protected]>This is less intrusive way to fix this problem. The
gfp_maskwas marked as unused intry_to_free_page(). I looked at filesystems in the kernel, there is only a few definedreleasepage()callback, and only xfs checks the flag(but not used). btrfs is actually using it though. I thought about the way you have suggested, i.e.clean up thisgfp_maskand and replace with a flag. I am not entirely sure if it we need to change theaddress_space_operationsand fix all the filesystems for this matter.Quoted 7 lines
Quoted 2 lines
Andrew, what do you think? Is this approach acceptable?
<wakes up>
Please ensure that the final patch is sufficiently well changelogged to permit me to remain asleep ;)
:-)
Quoted 9 lines
The ->releasepage semantics are fairly ad-hoc and have grown over time. It'd be nice to prevent them from becoming vaguer than they are.
It has been (approximately?) the case that code paths which really care about having the page released will set
__GFP_WAIT(viaGFP_KERNEL) whereas code paths which are happy with best-effort will clear__GFP_WAIT(with a "0'). And that's reasonsable -__GFP_WAIThere means "be synchronous" whereas !__GFP_WAIT means "be non-blocking".This make sense to me.
Quoted 8 lines
Is that old convention not sufficient here as well? Two problem areas I see are
mm/vmscan.candfs/splice.c(there may be others).In
mm/vmscan.cwe probably don't want your new synchronous behaviour and it might well be deadlockable anyway. No probs, that's what__GFP_FSis for.Sure. We could check
__GFP_FSand__GFP_WAIT, and that make sense.Quoted 3 lines
In
fs/splice.c, reading the comment there I have a feeling that you've found another bug, and that splice _does_ want your new synchronous behaviour?Yes, it looks like
page_cache_pipe_buf_steal()expects page is free before removeing it by passing theGFP_KERNELflag, but currently ext3 could fails to releasepage when it called. In facttry_to_release_page()return value is ignored inpage_cache_pipe_buf_steal(), should probably checked the failure case.
Quoted 6 lines
Quoted 3 lines
The other caller of
try_to_release_page()inmm/splice.cisfallback_migrate_page(), which does want the synchronous behaviour to make sure buffers are dropped.So something like this, then?
Quoted 24 lines
fs/splice.c +3
-2@@ -58,8 +58,8 @@ static int page_cache_pipe_buf_steal(struct pipe_inode_info *pipe,*/ wait_on_page_writeback (page); -if (PagePrivate (page)) -try_to_release_page (page,GFP_KERNEL ); +if (PagePrivate (page) && !try_to_release_page (page,GFP_KERNEL )) + goto out_unlock;/* * If we succeeded in removing the mapping, set LRU flag @@ -75,6 +75,7 @@ static int page_cache_pipe_buf_steal(struct pipe_inode_info *pipe,* Raced with truncate or failed to remove page from current * address space, unlock and return failure. */ +out_unlock:unlock_page (page);return 1 ; }
PATCH v2 1/1 JBD: Fix race between free buffer and commit trasanction
In reply to Mingming CaoUpdated patch to use existing GFP_KERNEL mask to indicating synchornous wait in journal_try_to_free_buffers() to resolve the race with journal_commit_transaction().
JBD: fix race between journal_try_to_free_buffers() and jbd commit transaction
From: Mingming Cao <[email protected]>
journal_try_to_free_buffers() could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller of journal_try_to_free_buffers() request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller of releasepage() also expecting the buffer to be dropped when passed with GFP_KERNEL mask to the releasepage()->journal_try_to_free_buffers().
With this patch, if the caller is passing the GFP_KERNEL to indicating this call could wait, in case of try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish commit the current committing transaction , then try to free those buffers again with journal locked.
--- fs/jbd/transaction.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 54 insertions(+), 4 deletions(-)
fs/jbd/transaction.c +54 -2
@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: specifies whether the call may block +* (__GFP_WAIT & __GFP_FS via GFP_KERNEL) * * * For all the buffers on this page,
@@ -1682,9 +1709,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1713,7 +1742,30 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && gfp_mask &GFP_KERNEL ) { +spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }
mm/filemap.c +1 -2
@@ -2581,9 +2581,8 @@ out:* Otherwise return zero. * * The @gfp_mask argument specifies whether I/O may be performed to release -* this page (__GFP_IO), and whether the call may block (__GFP_WAIT). +* this page (__GFP_IO), and whether the call may block (__GFP_WAIT & __GFP_FS). * -* NOTE: @gfp_mask may go away, and this function may become non-blocking. */ int try_to_release_page (struct page *page,gfp_t gfp_mask) {
Jan Kara
maintainerRe: [PATCH-v2] JBD: Fix race between free buffer and commit trasanction
Quoted 48 lines
JBD: fix race between
journal_try_to_free_buffers()and jbd commit transactionFrom: Mingming Cao <[email protected]>
journal_try_to_free_buffers()could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller ofjournal_try_to_free_buffers()request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller ofreleasepage()also expecting the buffer to be dropped when passed withGFP_KERNELmask to the releasepage()->journal_try_to_free_buffers().With this patch, if the caller is passing the
GFP_KERNELto indicating this call could wait, in case oftry_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish commit the current committing transaction , then try to free those buffers again with journal locked.Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/transaction.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 54 insertions(+), 4 deletions(-)Index:
linux-2.6.26-rc2/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +16
-0@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; +
fs/jbd/transaction.c out: · line 1676
Quoted 10 lines
fs/jbd/transaction.c +10
-0+ transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}
What is actually the point of entering the function with j_state_lock
held and also keeping it after return? It should be enough to take it and release it just inside this function, shouldn't it?
fs/jbd/transaction.c out: · line 1683
Quoted 7 lines
fs/jbd/transaction.c +2
-1/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: specifies whether the call may block +* (__GFP_WAIT & __GFP_FS via GFP_KERNEL)
This comment seems a bit misleading to me - I'd rather write there:
@gfp_mask: we use the mask to detect how hard should we try to release buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to release the buffers.
Quoted 28 lines
fs/jbd/transaction.c +10
-1* * * For all the buffers on this page, @@ -1682,9 +1709,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;@@ -1713,7 +1742,30 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1762
Quoted 10 lines
fs/jbd/transaction.c +10
-0+* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && gfp_mask &GFP_KERNEL ) {
I think this test is wrong - it should rather be something like
(ret == 0 && (gfp_mask & GFP_KERNEL == GFP_KERNEL)) - or even expand the test to gfp_mask & __GFP_WAIT && gfp_mask & __GFP_FS && gfp_mask & __GFP_IO.
Quoted 14 lines
fs/jbd/transaction.c +6
-0+spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }Index:
linux-2.6.26-rc2/mm/filemap.c
===================================================================
mm/filemap.c out: · line 2584
Quoted 5 lines
mm/filemap.c +1
-1@@ -2581,9 +2581,8 @@ out:* Otherwise return zero. * * The @gfp_mask argument specifies whether I/O may be performed to release -* this page (__GFP_IO), and whether the call may block (__GFP_WAIT). +* this page (__GFP_IO), and whether the call may block (__GFP_WAIT & __GFP_FS).
Probably __GFP_WAIT | __GFP_IO here... But I'm not sure why do we
really change this...
mm/filemap.c out: · line 2588
Quoted 6 lines
mm/filemap.c +0
-1* -* NOTE: @gfp_mask may go away, and this function may become non-blocking. */ int try_to_release_page (struct page *page,gfp_t gfp_mask) {
Honza
Signature · 2 lines
Jan Kara <[email protected]> SuSE CR Labs
Re: [PATCH-v2] JBD: Fix race between free buffer and commit trasanction
Quoted 53 lines · Jan Kara
Quoted 53 lines
JBD: fix race between
journal_try_to_free_buffers()and jbd commit transactionFrom: Mingming Cao <[email protected]>
journal_try_to_free_buffers()could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller ofjournal_try_to_free_buffers()request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller ofreleasepage()also expecting the buffer to be dropped when passed withGFP_KERNELmask to the releasepage()->journal_try_to_free_buffers().With this patch, if the caller is passing the
GFP_KERNELto indicating this call could wait, in case oftry_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish commit the current committing transaction , then try to free those buffers again with journal locked.Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/transaction.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 54 insertions(+), 4 deletions(-)Index:
linux-2.6.26-rc2/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +21
-0@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; +
Quoted 9 lines
fs/jbd/transaction.c out: · line 1676
Quoted 5 lines
fs/jbd/transaction.c +5
-0+ tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}What is actually the point of entering the function with
j_state_lock
held and also keeping it after return? It should be enough to take it and release it just inside this function, shouldn't it?
I was worried about the case when we call try_to_free_buffers() again, it races with the current transaction commit again. Is it possible? I guess the question is whether it is possible to have buffers on the same page attached to different transaction. If so, I think we need to keep the journal state lock while retry try_to_free_buffers(), so that the retry won't race with the commit transaction again...
Quoted 7 lines
fs/jbd/transaction.c out: · line 1683
Quoted 7 lines
fs/jbd/transaction.c +2
-1/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: specifies whether the call may block +* (__GFP_WAIT & __GFP_FS via GFP_KERNEL)
Quoted 6 lines
This comment seems a bit misleading to me - I'd rather write there:
@gfp_mask: we use the mask to detect how hard should we try to release buffers. If
__GFP_WAITand__GFP_FSis set, we wait for commit code to release the buffers.
Sure.
Quoted 34 lines
Quoted 34 lines
fs/jbd/transaction.c +16
-1* * * For all the buffers on this page, @@ -1682,9 +1709,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;@@ -1713,7 +1742,30 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of
Quoted 9 lines
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1762
Quoted 4 lines
fs/jbd/transaction.c +4
-0+* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && gfp_mask &GFP_KERNEL ) {I think this test is wrong - it should rather be something like
(ret == 0 && (gfp_mask&GFP_KERNEL==GFP_KERNEL)) - or even expand the test togfp_mask&__GFP_WAIT&&gfp_mask&__GFP_FS&&gfp_mask&__GFP_IO.
Thanks for pointing this out.
Quoted 13 lines
Quoted 13 lines
fs/jbd/transaction.c +6
-0+spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }Index:
linux-2.6.26-rc2/mm/filemap.c
===================================================================mm/filemap.c +0
-0
Quoted 9 lines
mm/filemap.c out: · line 2584
Quoted 6 lines
mm/filemap.c +1
-1@@ -2581,9 +2581,8 @@ out:* Otherwise return zero. * * The @gfp_mask argument specifies whether I/O may be performed to release -* this page (__GFP_IO), and whether the call may block (__GFP_WAIT). +* this page (__GFP_IO), and whether the call may block (__GFP_WAIT & __GFP_FS). Probably
__GFP_WAIT|__GFP_IOhere... But I'm not sure why do we
really change this...
For try_to_release_page(),we should wait only when (__GFP_WAIT & __GFP_FS), isn't it?
Quoted 8 lines
mm/filemap.c out: · line 2588
Quoted 6 lines
mm/filemap.c +0
-1* -* NOTE: @gfp_mask may go away, and this function may become non-blocking. */ int try_to_release_page (struct page *page,gfp_t gfp_mask) {Honza
Thanks, patch v3 to follow.
Mingming
Jan Kara
maintainerRe: [PATCH-v2] JBD: Fix race between free buffer and commit trasanction
Quoted 44 lines · Jan Kara
Quoted 33 lines
Quoted 33 lines
fs/jbd/transaction.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 54 insertions(+), 4 deletions(-)Index:
linux-2.6.26-rc2/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +21
-0@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; +Quoted 9 lines
fs/jbd/transaction.c out: · line 1676
Quoted 5 lines
fs/jbd/transaction.c +5
-0+ tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}What is actually the point of entering the function with
j_state_lock
held and also keeping it after return? It should be enough to take it and release it just inside this function, shouldn't it?
Quoted 6 lines
I was worried about the case when we call
try_to_free_buffers()again, it races with the current transaction commit again. Is it possible? I guess the question is whether it is possible to have buffers on the same page attached to different transaction. If so, I think we need to keep the journal state lock while retrytry_to_free_buffers(), so that the retry won't race with the commit transaction again...
Well, but by the time log_wait_commit() finishes, it may well
happen that a new transaction is already started so your lock doesn't help you much. And the page you are called on is actually locked, so noone can really mess with it until you unlock it... So I think you can just use the lock for obtaining tid and then drop it.
Honza
PS: For JBD2 you'd need to be a bit more careful because you cannot call log_wait_commit() while holding page lock (we have reversed locking order for ext4) - but ordered-mode rewrite patch actually fixes this problem and I'm going to submit the splitted patches on Monday or Tuesday (I only need to test them that I didn't do something stupid while porting them to ext4)...
Signature · 2 lines
Jan Kara <[email protected]> SuSE CR Labs
Mingming Cao
Reviewed-by Badari PulavartyRe: [PATCH-v2] JBD: Fix race between free buffer and commit trasanction
Quoted 50 lines · Jan Kara
Quoted 44 lines · Jan Kara
Quoted 33 lines
Quoted 33 lines
fs/jbd/transaction.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 54 insertions(+), 4 deletions(-)Index:
linux-2.6.26-rc2/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +21
-0@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; +Quoted 9 lines
fs/jbd/transaction.c out: · line 1676
Quoted 5 lines
fs/jbd/transaction.c +5
-0+ tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}What is actually the point of entering the function with
j_state_lock
held and also keeping it after return? It should be enough to take it and release it just inside this function, shouldn't it?Quoted 6 lines
I was worried about the case when we call
try_to_free_buffers()again, it races with the current transaction commit again. Is it possible? I guess the question is whether it is possible to have buffers on the same page attached to different transaction. If so, I think we need to keep the journal state lock while retrytry_to_free_buffers(), so that the retry won't race with the commit transaction again...
Quoted 6 lines
Well, but by the time
log_wait_commit()finishes, it may well
happen that a new transaction is already started so your lock doesn't help you much. And the page you are called on is actually locked, so noone can really mess with it until you unlock it... So I think you can just use the lock for obtaining tid and then drop it.
You are right that the page was locked during the process we are trying to free the buffer. so I agree it's safe to drop the lock.
Quoted 9 lines
Honza
PS: For JBD2 you'd need to be a bit more careful because you cannot call
log_wait_commit()while holding page lock (we have reversed locking order for ext4) - but ordered-mode rewrite patch actually fixes this problem and I'm going to submit the splitted patches on Monday or Tuesday (I only need to test them that I didn't do something stupid while porting them to ext4)...
Thanks for pointing this out. I think when we put back the reversed locking order and new ordered mode the jbd2 patch could go away...
Updated patch for JBD (take 4) below.
Mingming
JBD: fix race between journal_try_to_free_buffers() and jbd commit transaction
From: Mingming Cao <[email protected]>
journal_try_to_free_buffers() could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller of journal_try_to_free_buffers() request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller of releasepage() also expecting the buffer to be dropped when passed with GFP_KERNEL mask to the releasepage()->journal_try_to_free_buffers().
With this patch, if the caller is passing the GFP_KERNEL to indicating this call could wait, in case of try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish commit the current committing transaction , then try to free those buffers again with journal locked.
--- fs/jbd/transaction.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 56 insertions(+), 4 deletions(-)
fs/jbd/transaction.c +55 -2
@@ -1648,12 +1648,42 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + +spin_lock (&journal->j_state_lock); + transaction = journal->j_committing_transaction; + +if (!transaction) { +spin_unlock (&journal->j_state_lock); +return ; + } + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: we use the mask to detect how hard should we try to release +* buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to +* release the buffers. * * * For all the buffers on this page,
@@ -1682,9 +1712,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1713,7 +1745,28 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask &GFP_KERNEL ==GFP_KERNEL )) { +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); + } + busy:return ret; }
mm/filemap.c +1 -2
@@ -2581,9 +2581,8 @@ out:* Otherwise return zero. * * The @gfp_mask argument specifies whether I/O may be performed to release -* this page (__GFP_IO), and whether the call may block (__GFP_WAIT). +* this page (__GFP_IO), and whether the call may block (__GFP_WAIT & __GFP_FS). * -* NOTE: @gfp_mask may go away, and this function may become non-blocking. */ int try_to_release_page (struct page *page,gfp_t gfp_mask) {
Re: [PATCH-v2] JBD: Fix race between free buffer and commit trasanction
Quoted 173 lines · Mingming Cao
Quoted 50 lines · Jan Kara
Quoted 44 lines · Jan Kara
Quoted 33 lines
Quoted 33 lines
fs/jbd/transaction.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 54 insertions(+), 4 deletions(-)Index:
linux-2.6.26-rc2/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +21
-0@@ -1648,12 +1648,39 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; +Quoted 9 lines
fs/jbd/transaction.c out: · line 1678
Quoted 5 lines
fs/jbd/transaction.c +5
-0+ tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}What is actually the point of entering the function with
j_state_lock
held and also keeping it after return? It should be enough to take it and release it just inside this function, shouldn't it?Quoted 6 lines
I was worried about the case when we call
try_to_free_buffers()again, it races with the current transaction commit again. Is it possible? I guess the question is whether it is possible to have buffers on the same page attached to different transaction. If so, I think we need to keep the journal state lock while retrytry_to_free_buffers(), so that the retry won't race with the commit transaction again...Quoted 6 lines
Well, but by the time
log_wait_commit()finishes, it may well
happen that a new transaction is already started so your lock doesn't help you much. And the page you are called on is actually locked, so noone can really mess with it until you unlock it... So I think you can just use the lock for obtaining tid and then drop it.You are right that the page was locked during the process we are trying to free the buffer. so I agree it's safe to drop the lock.
Quoted 9 lines
Honza
PS: For JBD2 you'd need to be a bit more careful because you cannot call
log_wait_commit()while holding page lock (we have reversed locking order for ext4) - but ordered-mode rewrite patch actually fixes this problem and I'm going to submit the splitted patches on Monday or Tuesday (I only need to test them that I didn't do something stupid while porting them to ext4)...Thanks for pointing this out. I think when we put back the reversed locking order and new ordered mode the jbd2 patch could go away...
Updated patch for JBD (take 4) below.
MingmingJBD: fix race between
journal_try_to_free_buffers()and jbd commit transactionFrom: Mingming Cao <[email protected]>
journal_try_to_free_buffers()could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller ofjournal_try_to_free_buffers()request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller ofreleasepage()also expecting the buffer to be dropped when passed withGFP_KERNELmask to the releasepage()->journal_try_to_free_buffers().With this patch, if the caller is passing the
GFP_KERNELto indicating this call could wait, in case oftry_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish commit the current committing transaction , then try to free those buffers again with journal locked.Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/transaction.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 56 insertions(+), 4 deletions(-)Index:
linux-2.6.26-rc3/fs/jbd/transaction.c
===================================================================fs/jbd/transaction.c +41
-2@@ -1648,12 +1648,42 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + +spin_lock (&journal->j_state_lock); + transaction = journal->j_committing_transaction; + +if (!transaction) { +spin_unlock (&journal->j_state_lock); +return ; + } + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: we use the mask to detect how hard should we try to release +* buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to +* release the buffers. * * * For all the buffers on this page, @@ -1682,9 +1712,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;@@ -1713,7 +1745,28 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1765
Quoted 10 lines
fs/jbd/transaction.c +10
-0+* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask &GFP_KERNEL ==GFP_KERNEL )) {
I think Andrew prefered this test to be expanded but otherwise the patch
is fine now. You can add:
Thanks for solving this :)
Quoted 13 lines
fs/jbd/transaction.c +4
-0+journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); + } + busy:return ret; }Index:
linux-2.6.26-rc3/mm/filemap.c
===================================================================
mm/filemap.c out: · line 2588
Quoted 10 lines
mm/filemap.c +1
-2@@ -2581,9 +2581,8 @@ out:* Otherwise return zero. * * The @gfp_mask argument specifies whether I/O may be performed to release -* this page (__GFP_IO), and whether the call may block (__GFP_WAIT). +* this page (__GFP_IO), and whether the call may block (__GFP_WAIT & __GFP_FS). * -* NOTE: @gfp_mask may go away, and this function may become non-blocking. */ int try_to_release_page (struct page *page,gfp_t gfp_mask) {
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: [PATCH-v2] JBD: Fix race between free buffer and commit trasanction
Quoted 39 lines · Jan Kara
Quoted 25 lines · Mingming Cao
fs/jbd/transaction.c +10
-1@@ -1682,9 +1712,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;@@ -1713,7 +1745,28 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1765
Quoted 10 lines
fs/jbd/transaction.c +10
-0+* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask &GFP_KERNEL ==GFP_KERNEL )) {I think Andrew prefered this test to be expanded but otherwise the patch
is fine now. You can add:
Quoted 2 lines
Acked-by Jan Kara <[email protected]>
Okay, I will update the patch and cleanup the history, sent it to Andrew. Thanks.
Mingming
PATCH 1/1 JBD: Fix race between free buffer and commit trasanction
In reply to Jan KaraUpdated patch after Jan acked.
journal_try_to_free_buffers() could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller of journal_try_to_free_buffers() request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller of releasepage() also expecting the buffer to be dropped when passed with GFP_KERNEL mask to the releasepage()->journal_try_to_free_buffers().
With this patch, if the caller is passing the __GFP_WAIT and __GFP_FS to indicating this call could wait, in case of try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish commit the current committing transaction, then try to free those buffers again.
--- fs/jbd/transaction.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 56 insertions(+), 4 deletions(-)
fs/jbd/transaction.c +55 -2
@@ -1648,12 +1648,42 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + +spin_lock (&journal->j_state_lock); + transaction = journal->j_committing_transaction; + +if (!transaction) { +spin_unlock (&journal->j_state_lock); +return ; + } + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: we use the mask to detect how hard should we try to release +* buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to +* release the buffers. * * * For all the buffers on this page,
@@ -1682,9 +1712,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1713,7 +1745,28 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask & __GFP_WAIT) && (gfp_mask & __GFP_FS)) { +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); + } + busy:return ret; }
mm/filemap.c +1 -2
@@ -2581,9 +2581,8 @@ out:* Otherwise return zero. * * The @gfp_mask argument specifies whether I/O may be performed to release -* this page (__GFP_IO), and whether the call may block (__GFP_WAIT). +* this page (__GFP_IO), and whether the call may block (__GFP_WAIT & __GFP_FS). * -* NOTE: @gfp_mask may go away, and this function may become non-blocking. */ int try_to_release_page (struct page *page,gfp_t gfp_mask) {
PATCH 1/1 JBD2: Fix race between free buffer and commit trasanction
journal_try_to_free_buffers() could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller of journal_try_to_free_buffers() request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller of releasepage() also expecting the buffer to be dropped when passed with GFP_KERNEL mask to the releasepage()->journal_try_to_free_buffers().
With this patch, if the caller is passing the __GFP_WAIT and __GFP_FS to indicating this call could wait, in case of try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish commit the current committing transaction, then try to free those buffers again.
--- fs/jbd2/transaction.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-)
fs/jbd2/transaction.c +57 -2
@@ -1656,12 +1656,42 @@ out:return ; } +/* +* jbd2_journal_try_to_free_buffers() could race with jbd2_journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* jbd2_journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void jbd2_journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + +spin_lock (&journal->j_state_lock); + transaction = journal->j_committing_transaction; + +if (!transaction) { +spin_unlock (&journal->j_state_lock); +return ; + } + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +jbd2_log_wait_commit (journal, tid); +}/** * int jbd2_journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: we use the mask to detect how hard should we try to release +* buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to +* release the buffers. * * * For all the buffers on this page,
@@ -1690,9 +1720,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int jbd2_journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1721,7 +1753,30 @@ int jbd2_journal_try_to_free_buffers(jouif (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where jbd2_journal_try_to_free_buffers() +* could race with jbd2_journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask & __GFP_WAIT) && (gfp_mask & __GFP_FS)) { +spin_lock (&journal->j_state_lock); +jbd2_journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }
Re: [PATCH][take 5] JBD2: Fix race between free buffer and commit trasanction
Quoted 9 lines · Mingming Cao
Index:
linux-2.6.26-rc3/fs/jbd2/transaction.c
===================================================================fs/jbd2/transaction.c +1
-0@@ -1656,12 +1656,42 @@ out:return ; } +/*
fs/jbd2/transaction.c out: · line 1669
Quoted 10 lines
fs/jbd2/transaction.c +10
-0+* jbd2_journal_try_to_free_buffers() could race with jbd2_journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* jbd2_journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/
We are taking the spin_lock again in the function ??
Quoted 17 lines
fs/jbd2/transaction.c +17
-0+static void jbd2_journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + +spin_lock (&journal->j_state_lock); + transaction = journal->j_committing_transaction; + +if (!transaction) { +spin_unlock (&journal->j_state_lock); +return ; + } + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +jbd2_log_wait_commit (journal, tid); +}
[.... snip.... ]
fs/jbd2/transaction.c int jbd2_journal_try_to_free_buffers(jou · line 1782
Quoted 12 lines
fs/jbd2/transaction.c +7
-0+if (ret ==0 && (gfp_mask & __GFP_WAIT) && (gfp_mask & __GFP_FS)) { +spin_lock (&journal->j_state_lock); +jbd2_journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }
-aneesh
Re: [PATCH][take 5] JBD2: Fix race between free buffer and commit trasanction
Quoted 9 lines · Aneesh Kumar K.V
Quoted 8 lines · Mingming Cao
Index:
linux-2.6.26-rc3/fs/jbd2/transaction.c
===================================================================fs/jbd2/transaction.c +0
-0@@ -1656,12 +1656,42 @@ out:return ; }
Quoted 14 lines
fs/jbd2/transaction.c out: · line 1669
Quoted 11 lines
fs/jbd2/transaction.c +11
-0+/* +* jbd2_journal_try_to_free_buffers() could race with jbd2_journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* jbd2_journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ We are taking the
spin_lockagain in the function ??
Thanks. I noticed this yesterday and have sent Andrew updated patch to
replace the one he just added to mm tree but forget to copy to the list. Here is the updated patch,
Mingming
--- fs/jbd2/transaction.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-)
fs/jbd2/transaction.c +57 -3
@@ -1656,12 +1656,43 @@ out:return ; } +/* +* jbd2_journal_try_to_free_buffers() could race with +* jbd2_journal_commit_transaction(). The later might still hold the +* reference count to the buffers when inspecting them on +* t_syncdata_list or t_locked_list. +* +* jbd2_journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +*/ +static void jbd2_journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + +spin_lock (&journal->j_state_lock); + transaction = journal->j_committing_transaction; + +if (!transaction) { +spin_unlock (&journal->j_state_lock); +return ; + } + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +jbd2_log_wait_commit (journal, tid); +}/** * int jbd2_journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: we use the mask to detect how hard should we try to release +* buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to +* release the buffers. * * * For all the buffers on this page,
@@ -1690,9 +1721,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int jbd2_journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1708,7 +1741,8 @@ int jbd2_journal_try_to_free_buffers(jou/* * We take our own ref against the journal_head here to avoid * having to add tons of locking around each instance of -* jbd2_journal_remove_journal_head() and jbd2_journal_put_journal_head(). +* jbd2_journal_remove_journal_head() and +* jbd2_journal_put_journal_head(). */ jh =jbd2_journal_grab_journal_head (bh);if (!jh)
@@ -1721,7 +1755,28 @@ int jbd2_journal_try_to_free_buffers(jouif (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where jbd2_journal_try_to_free_buffers() +* could race with jbd2_journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask & __GFP_WAIT) && (gfp_mask & __GFP_FS)) { +jbd2_journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); + } + busy:return ret; }
PATCH 1/2 JBD: Fix race between free buffer and commit trasanction
In reply to Mingming CaoChanges since take 2:
- fix a bug pointed by Jan, and updated the comments
journal_try_to_free_buffers() could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller of journal_try_to_free_buffers() request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller of releasepage() also expecting the buffer to be dropped when passed with GFP_KERNEL mask to the releasepage()->journal_try_to_free_buffers().
With this patch, if the caller is passing the GFP_KERNEL to indicating this call could wait, in case of try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish commit the current committing transaction , then try to free those buffers again with journal locked.
--- fs/jbd/transaction.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 56 insertions(+), 4 deletions(-)
fs/jbd/transaction.c +55 -2
@@ -1648,12 +1648,40 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: we use the mask to detect how hard should we try to release +* buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to +* release the buffers. * * * For all the buffers on this page,
@@ -1682,9 +1710,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1713,7 +1743,30 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask &GFP_KERNEL ==GFP_KERNEL )) { +spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }
mm/filemap.c +1 -2
@@ -2581,9 +2581,8 @@ out:* Otherwise return zero. * * The @gfp_mask argument specifies whether I/O may be performed to release -* this page (__GFP_IO), and whether the call may block (__GFP_WAIT). +* this page (__GFP_IO), and whether the call may block (__GFP_WAIT & __GFP_FS). * -* NOTE: @gfp_mask may go away, and this function may become non-blocking. */ int try_to_release_page (struct page *page,gfp_t gfp_mask) {
Andrew Morton
maintainerRe: [PATCH 1/2][TAKE3] JBD: Fix race between free buffer and commit trasanction
Quoted 2 lines · On Wed, 21 May 2008 16:38:07 -0700 Mingming <[email protected]> wrote:
Subject: [PATCH 1/2][TAKE3] JBD: Fix race between free buffer and commit trasanction
"fix race between buffer freeing and transaction commit", perhaps.
Quoted 29 lines
Changes since take 2:
- fix a bug pointed by Jan, and updated the comments
journal_try_to_free_buffers()could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller ofjournal_try_to_free_buffers()request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller ofreleasepage()also expecting the buffer to be dropped when passed withGFP_KERNELmask to the releasepage()->journal_try_to_free_buffers().With this patch, if the caller is passing the
GFP_KERNELto indicating this call could wait, in case oftry_to_free_buffers()failed, let's waiting forjournal_commit_transaction()to finish commit the current committing transaction , then try to free those buffers again with journal locked.Signed-off-by Mingming Cao <[email protected]>Reviewed-by Badari Pulavarty <[email protected]>--- fs/jbd/transaction.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++-- mm/filemap.c | 3 -- 2 files changed, 56 insertions(+), 4 deletions(-)Index:
linux-2.6.26-rc3/fs/jbd/transaction.c
===================================================================
fs/jbd/transaction.c out: · line 1653
Quoted 6 lines
fs/jbd/transaction.c +3
-0@@ -1648,12 +1648,40 @@ out:return ; } +/* +* journal_try_to_free_buffers() could race with journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting
"latter"
"hold a reference on"
fs/jbd/transaction.c out: · line 1656
Quoted 3 lines
fs/jbd/transaction.c +3
-0+* them on t_syncdata_list or t_locked_list. +* +* Journal_try_to_free_buffers() will call this function to
"journal_try_to_free_buffers"
fs/jbd/transaction.c out: · line 1658
Quoted 2 lines
fs/jbd/transaction.c +2
-0+* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer.
"trying"
fs/jbd/transaction.c out: · line 1660
Quoted 2 lines
fs/jbd/transaction.c +2
-0+* +* Called with journal->j_state_lock hold.
"held"
fs/jbd/transaction.c out: · line 1664
Quoted 4 lines
fs/jbd/transaction.c +4
-0+*/ +static void journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ;
Unneeded initialisation. Could just do
transaction_t *transaction = journal->j_committing_transaction;
Quoted 38 lines
fs/jbd/transaction.c +18
-2+tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: we use the mask to detect how hard should we try to release +* buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to +* release the buffers. * * * For all the buffers on this page, @@ -1682,9 +1710,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1752
Quoted 10 lines
fs/jbd/transaction.c +6
-0@@ -1713,7 +1743,30 @@ int journal_try_to_free_buffers(journal_if (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where journal_try_to_free_buffers() +* could race with journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them.
"the latter still holds a reference on the buffers"
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1754
Quoted 2 lines
fs/jbd/transaction.c +2
-0+* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise
"callers"
"request the"
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1759
Quoted 5 lines
fs/jbd/transaction.c +4
-0* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of
"the flush"
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1763
Quoted 4 lines
fs/jbd/transaction.c +4
-0+* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask &GFP_KERNEL ==GFP_KERNEL )) {
Sorry about all the spelling flames ;) I'd normally just fix them myself rather than typing them all into an email and having you type them in again, etc. But I think the patch needs to be respun anyway.
The mask-and-compare with GFP_KERNEL does appear to be correct, but it is quite unusual. Generally in a situation like this we will test for the specific __GFP_foo flags which we're interested in. For documentation reasons if nothing else.
So the preferred form here would be
if (ret == 0 &&
(gfp_mask & (__GFP_WAIT|__GFP_FS)) == (__GFP_WAIT|__GFP_FS)) {which really tells the reader what we're trying to do here. And I don't think this code cares about __GFP_IO, even though it would be mighty peculirr (probably buggy) for someone to do alloc_pages(__GFP_FS|__GFP_WAIT).
fs/jbd/transaction.c int journal_try_to_free_buffers(journal_ · line 1768
Quoted 5 lines
fs/jbd/transaction.c +5
-0+spin_lock (&journal->j_state_lock); +journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + }
Did we actually need to hold j_state_lock across the try_to_free_buffers() call here? Because it'll increase hold times and will introduce a lock-ranking dependency which we might not otherwise have had (I didn't check).
Quoted 8 lines
fs/jbd/transaction.c +0
-0busy:return ret; }Index:
linux-2.6.26-rc3/mm/filemap.c
===================================================================
mm/filemap.c out: · line 2586
Quoted 7 lines
mm/filemap.c +1
-2@@ -2581,9 +2581,8 @@ out:* Otherwise return zero. * * The @gfp_mask argument specifies whether I/O may be performed to release -* this page (__GFP_IO), and whether the call may block (__GFP_WAIT). +* this page (__GFP_IO), and whether the call may block (__GFP_WAIT & __GFP_FS). * -* NOTE: @gfp_mask may go away, and this function may become non-blocking.
Yup, that note is dead.
Quoted 4 lines
mm/filemap.c +0
-0*/ int try_to_release_page (struct page *page,gfp_t gfp_mask) {
PATCH 2/2 JBD2: Fix race between free buffer and commit trasanction
In reply to Mingming Caojournal_try_to_free_buffers() could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller of journal_try_to_free_buffers() request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller of releasepage() also expecting the buffer to be dropped when passed with GFP_KERNEL mask to the releasepage()->journal_try_to_free_buffers().
With this patch, if the caller is passing the GFP_KERNEL to indicating this call could wait, in case of try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish commit the current committing transaction , then try to free those buffers again with journal locked.
--- fs/jbd2/transaction.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-)
fs/jbd2/transaction.c +55 -2
@@ -1656,12 +1656,40 @@ out:return ; } +/* +* jbd2_journal_try_to_free_buffers() could race with jbd2_journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* jbd2_journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void jbd2_journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +jbd2_log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int jbd2_journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: we use the mask to detect how hard should we try to release +* buffers. If __GFP_WAIT and __GFP_FS is set, we wait for commit code to +* release the buffers. * * * For all the buffers on this page,
@@ -1690,9 +1718,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int jbd2_journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1721,7 +1751,30 @@ int jbd2_journal_try_to_free_buffers(jouif (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where jbd2_journal_try_to_free_buffers() +* could race with jbd2_journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && (gfp_mask &GFP_KERNEL ==GFP_KERNEL )) { +spin_lock (&journal->j_state_lock); +jbd2_journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }
PATCH 1/1 JBD2: Fix race between journal free buffer and commit trasanction
In reply to Mingming CaoJBD2: fix race between jbd2_journal_try_to_free_buffers() and jbd2 commit transaction
From: Mingming Cao <[email protected]>
journal_try_to_free_buffers() could race with jbd commit transaction when the later is holding the buffer reference while waiting for the data buffer to flush to disk. If the caller of journal_try_to_free_buffers() request tries hard to release the buffers, it will treat the failure as error and return back to the caller. We have seen the directo IO failed due to this race. Some of the caller of releasepage() also expecting the buffer to be dropped when passed with GFP_KERNEL mask to the releasepage()->journal_try_to_free_buffers().
With this patch, if the caller is passing the GFP_KERNEL to indicating this call could wait, in case of try_to_free_buffers() failed, let's waiting for journal_commit_transaction() to finish commit the current committing transaction , then try to free those buffers again with journal locked.
--- fs/jbd2/transaction.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-)
fs/jbd2/transaction.c +54 -2
@@ -1656,12 +1656,39 @@ out:return ; } +/* +* jbd2_journal_try_to_free_buffers() could race with jbd2_journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* jbd2_journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void jbd2_journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +jbd2_log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int jbd2_journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: specifies whether the call may block +* (__GFP_WAIT & __GFP_FS via GFP_KERNEL) * * * For all the buffers on this page,
@@ -1690,9 +1717,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int jbd2_journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1721,7 +1750,30 @@ int jbd2_journal_try_to_free_buffers(jouif (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* There are a number of places where jbd2_journal_try_to_free_buffers() +* could race with jbd2_journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers. Some of the +* caller of releasepage() request page buffers to be dropped, otherwise +* treat the fail-to-free as errors (such as generic_file_direct_IO()) +* +* So, if the caller of try_to_release_page() wants the synchronous +* behaviour(i.e make sure buffers are dropped upon return), +* let's wait for the current transaction to finish flush of +* dirty data buffers, then try to free those buffers again, +* with the journal locked. +*/ +if (ret ==0 && gfp_mask &GFP_KERNEL ) { +spin_lock (&journal->j_state_lock); +jbd2_journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }
PATCH 1/1 JBD2: Fix DIO EIO error caused by race between free buffer and commit trasanction
In reply to Badari PulavartyJBD2: fix DIO error caused by race with DIO free_buffers and jbd2 commit transaction
From: Mingming Cao <[email protected]>
This patch fixed a few races between direct IO and kjournlad commit transaction. An unexpected EIO error gets returned to direct IO caller when it failed to free those data buffers. This could be reproduced easily with parallel direct write and buffered write to the same file
More specific, those races could cause jbd2_journal_try_to_free_buffers() fail to free the data buffers, when jbd is committing the transaction that has those data buffers on its t_syncdata_list or t_locked_list. jbd2_journal_commit_transaction() still holds the reference to those buffers
before data reach to disk and buffers are removed from thet_syncdata_list of t_locked_list. This prevent the concurrent jbd2_journal_try_to_free_buffers() to free those buffers at the same time, but cause EIO error returns back to direct IO.
With this patch, in case of direct IO and when try_to_free_buffers() failed, let's waiting for jbd2_journal_commit_transaction() to finish flushing the current committing transaction's data buffers to disk, then try to free those buffers again.
--- fs/jbd2/transaction.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-)
fs/jbd2/transaction.c +55 -2
@@ -1656,12 +1656,39 @@ out:return ; } +/* +* jbd2_journal_try_to_free_buffers() could race with jbd2_journal_commit_transaction() +* The later might still hold the reference count to the buffers when inspecting +* them on t_syncdata_list or t_locked_list. +* +* jbd2_journal_try_to_free_buffers() will call this function to +* wait for the current transaction to finish syncing data buffers, before +* try to free that buffer. +* +* Called with journal->j_state_lock hold. +*/ +static void jbd2_journal_wait_for_transaction_sync_data (journal_t *journal) +{ +transaction_t *transaction =NULL ; +tid_t tid; + + transaction = journal->j_committing_transaction; + +if (!transaction) +return ; + + tid = transaction->t_tid; +spin_unlock (&journal->j_state_lock); +jbd2_log_wait_commit (journal, tid); +spin_lock (&journal->j_state_lock); +}/** * int jbd2_journal_try_to_free_buffers() - try to free page buffers. * @journal: journal for operation * @page: to try and free -* @unused_gfp_mask: unused +* @gfp_mask: unused for allocation purpose. Here is used +* as a flag to tell if direct IO is attemping to free buffers. * * * For all the buffers on this page,
@@ -1690,9 +1717,11 @@ out:* journal_try_to_free_buffer() is changing its state. But that * cannot happen because we never reallocate freed data as metadata * while the data is part of a transaction. Yes? +* +* Return 0 on failure, 1 on success */ int jbd2_journal_try_to_free_buffers (journal_t *journal, -struct page *page,gfp_t unused_gfp_mask) +struct page *page,gfp_t gfp_mask) {struct buffer_head *head;struct buffer_head *bh;
@@ -1721,7 +1750,31 @@ int jbd2_journal_try_to_free_buffers(jouif (buffer_jbd (bh)) goto busy; }while ((bh = bh->b_this_page) != head); + ret =try_to_free_buffers (page); + +/* +* In the case of concurrent direct IO and buffered IO, +* There are a number of places where we +* could race with jbd2_journal_commit_transaction(), the later still +* holds the reference to the buffers to free while processing them. +* try_to_free_buffers() failed to free those buffers, +* resulting in an unexpected EIO error +* returns back to the generic_file_direct_IO() +* +* So let's wait for the current transaction to finish flush of +* dirty data buffers before we try to free those buffers +* again. This wait is needed by direct IO code path only, +* gfp_mask __GFP_REPEAT is passed from the direct IO code +* path to flag if we need to wait and retry free buffers. +*/ +if (ret ==0 && gfp_mask & __GFP_REPEAT) { +spin_lock (&journal->j_state_lock); +jbd2_journal_wait_for_transaction_sync_data (journal); + ret =try_to_free_buffers (page); +spin_unlock (&journal->j_state_lock); + } + busy:return ret; }
PATCH 1/1 JBD2:need hold j_state_lock to updates to transaction t_state to T_COMMIT
In reply to Jan KaraUpdating the current transaction's t_state is being protected by j_state_lock. We need to the same when updating the t_state to T_COMMIT.
--- fs/jbd2/commit.c | 2 ++ 1 file changed, 2 insertions(+)
fs/jbd2/commit.c +2 -0
@@ -560,7 +560,9 @@ void jbd2_journal_commit_transaction(jou* transaction! Now comes the tricky part: we need to write out * metadata. Loop over the transaction's entire buffer list: */ +spin_lock (&journal->j_state_lock); commit_transaction->t_state =T_COMMIT ; +spin_unlock (&journal->j_state_lock); stats.u.run.rs_logging = jiffies; stats.u.run.rs_flushing = jbd2_time_diff(stats.u.run.rs_flushing,