Commit ba869023 authored by dingdinghua's avatar dingdinghua Committed by Theodore Ts'o

jbd2: delay discarding buffers in journal_unmap_buffer

Delay discarding buffers in journal_unmap_buffer until
we know that "add to orphan" operation has definitely been
committed, otherwise the log space of committing transation
may be freed and reused before truncate get committed, updates
may get lost if crash happens.
Signed-off-by: default avatardingdinghua <dingdinghua@nrchpc.ac.cn>
Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent aca92ff6
...@@ -930,12 +930,12 @@ restart_loop: ...@@ -930,12 +930,12 @@ restart_loop:
/* A buffer which has been freed while still being /* A buffer which has been freed while still being
* journaled by a previous transaction may end up still * journaled by a previous transaction may end up still
* being dirty here, but we want to avoid writing back * being dirty here, but we want to avoid writing back
* that buffer in the future now that the last use has * that buffer in the future after the "add to orphan"
* been committed. That's not only a performance gain, * operation been committed, That's not only a performance
* it also stops aliasing problems if the buffer is left * gain, it also stops aliasing problems if the buffer is
* behind for writeback and gets reallocated for another * left behind for writeback and gets reallocated for another
* use in a different page. */ * use in a different page. */
if (buffer_freed(bh)) { if (buffer_freed(bh) && !jh->b_next_transaction) {
clear_buffer_freed(bh); clear_buffer_freed(bh);
clear_buffer_jbddirty(bh); clear_buffer_jbddirty(bh);
} }
......
...@@ -1727,6 +1727,21 @@ static int journal_unmap_buffer(journal_t *journal, struct buffer_head *bh) ...@@ -1727,6 +1727,21 @@ static int journal_unmap_buffer(journal_t *journal, struct buffer_head *bh)
if (!jh) if (!jh)
goto zap_buffer_no_jh; goto zap_buffer_no_jh;
/*
* We cannot remove the buffer from checkpoint lists until the
* transaction adding inode to orphan list (let's call it T)
* is committed. Otherwise if the transaction changing the
* buffer would be cleaned from the journal before T is
* committed, a crash will cause that the correct contents of
* the buffer will be lost. On the other hand we have to
* clear the buffer dirty bit at latest at the moment when the
* transaction marking the buffer as freed in the filesystem
* structures is committed because from that moment on the
* buffer can be reallocated and used by a different page.
* Since the block hasn't been freed yet but the inode has
* already been added to orphan list, it is safe for us to add
* the buffer to BJ_Forget list of the newest transaction.
*/
transaction = jh->b_transaction; transaction = jh->b_transaction;
if (transaction == NULL) { if (transaction == NULL) {
/* First case: not on any transaction. If it /* First case: not on any transaction. If it
...@@ -1783,16 +1798,15 @@ static int journal_unmap_buffer(journal_t *journal, struct buffer_head *bh) ...@@ -1783,16 +1798,15 @@ static int journal_unmap_buffer(journal_t *journal, struct buffer_head *bh)
} else if (transaction == journal->j_committing_transaction) { } else if (transaction == journal->j_committing_transaction) {
JBUFFER_TRACE(jh, "on committing transaction"); JBUFFER_TRACE(jh, "on committing transaction");
/* /*
* If it is committing, we simply cannot touch it. We * The buffer is committing, we simply cannot touch
* can remove it's next_transaction pointer from the * it. So we just set j_next_transaction to the
* running transaction if that is set, but nothing * running transaction (if there is one) and mark
* else. */ * buffer as freed so that commit code knows it should
* clear dirty bits when it is done with the buffer.
*/
set_buffer_freed(bh); set_buffer_freed(bh);
if (jh->b_next_transaction) { if (journal->j_running_transaction && buffer_jbddirty(bh))
J_ASSERT(jh->b_next_transaction == jh->b_next_transaction = journal->j_running_transaction;
journal->j_running_transaction);
jh->b_next_transaction = NULL;
}
jbd2_journal_put_journal_head(jh); jbd2_journal_put_journal_head(jh);
spin_unlock(&journal->j_list_lock); spin_unlock(&journal->j_list_lock);
jbd_unlock_bh_state(bh); jbd_unlock_bh_state(bh);
...@@ -1969,7 +1983,7 @@ void jbd2_journal_file_buffer(struct journal_head *jh, ...@@ -1969,7 +1983,7 @@ void jbd2_journal_file_buffer(struct journal_head *jh,
*/ */
void __jbd2_journal_refile_buffer(struct journal_head *jh) void __jbd2_journal_refile_buffer(struct journal_head *jh)
{ {
int was_dirty; int was_dirty, jlist;
struct buffer_head *bh = jh2bh(jh); struct buffer_head *bh = jh2bh(jh);
J_ASSERT_JH(jh, jbd_is_locked_bh_state(bh)); J_ASSERT_JH(jh, jbd_is_locked_bh_state(bh));
...@@ -1991,8 +2005,13 @@ void __jbd2_journal_refile_buffer(struct journal_head *jh) ...@@ -1991,8 +2005,13 @@ void __jbd2_journal_refile_buffer(struct journal_head *jh)
__jbd2_journal_temp_unlink_buffer(jh); __jbd2_journal_temp_unlink_buffer(jh);
jh->b_transaction = jh->b_next_transaction; jh->b_transaction = jh->b_next_transaction;
jh->b_next_transaction = NULL; jh->b_next_transaction = NULL;
__jbd2_journal_file_buffer(jh, jh->b_transaction, if (buffer_freed(bh))
jh->b_modified ? BJ_Metadata : BJ_Reserved); jlist = BJ_Forget;
else if (jh->b_modified)
jlist = BJ_Metadata;
else
jlist = BJ_Reserved;
__jbd2_journal_file_buffer(jh, jh->b_transaction, jlist);
J_ASSERT_JH(jh, jh->b_transaction->t_state == T_RUNNING); J_ASSERT_JH(jh, jh->b_transaction->t_state == T_RUNNING);
if (was_dirty) if (was_dirty)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment