Skip to content

Commit 96f1e09

Browse files
jankaratytso
authored andcommitted
jbd2: avoid long hold times of j_state_lock while committing a transaction
We can hold j_state_lock for writing at the beginning of jbd2_journal_commit_transaction() for a rather long time (reportedly for 30 ms) due cleaning revoke bits of all revoked buffers under it. The handling of revoke tables as well as cleaning of t_reserved_list, and checkpoint lists does not need j_state_lock for anything. It is only needed to prevent new handles from joining the transaction. Generally T_LOCKED transaction state prevents new handles from joining the transaction - except for reserved handles which have to allowed to join while we wait for other handles to complete. To prevent reserved handles from joining the transaction while cleaning up lists, add new transaction state T_SWITCH and watch for it when starting reserved handles. With this we can just drop the lock for operations that don't need it. Reported-and-tested-by: Adrian Hunter <adrian.hunter@intel.com> Suggested-by: "Theodore Y. Ts'o" <tytso@mit.edu> Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
1 parent fb265c9 commit 96f1e09

File tree

3 files changed

+42
-5
lines changed

3 files changed

+42
-5
lines changed

fs/jbd2/commit.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,8 @@ void jbd2_journal_commit_transaction(journal_t *journal)
439439
finish_wait(&journal->j_wait_updates, &wait);
440440
}
441441
spin_unlock(&commit_transaction->t_handle_lock);
442+
commit_transaction->t_state = T_SWITCH;
443+
write_unlock(&journal->j_state_lock);
442444

443445
J_ASSERT (atomic_read(&commit_transaction->t_outstanding_credits) <=
444446
journal->j_max_transaction_buffers);
@@ -505,6 +507,7 @@ void jbd2_journal_commit_transaction(journal_t *journal)
505507
atomic_sub(atomic_read(&journal->j_reserved_credits),
506508
&commit_transaction->t_outstanding_credits);
507509

510+
write_lock(&journal->j_state_lock);
508511
trace_jbd2_commit_flushing(journal, commit_transaction);
509512
stats.run.rs_flushing = jiffies;
510513
stats.run.rs_locked = jbd2_time_diff(stats.run.rs_locked,

fs/jbd2/transaction.c

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ static inline void update_t_max_wait(transaction_t *transaction,
138138
}
139139

140140
/*
141-
* Wait until running transaction passes T_LOCKED state. Also starts the commit
142-
* if needed. The function expects running transaction to exist and releases
143-
* j_state_lock.
141+
* Wait until running transaction passes to T_FLUSH state and new transaction
142+
* can thus be started. Also starts the commit if needed. The function expects
143+
* running transaction to exist and releases j_state_lock.
144144
*/
145145
static void wait_transaction_locked(journal_t *journal)
146146
__releases(journal->j_state_lock)
@@ -160,6 +160,32 @@ static void wait_transaction_locked(journal_t *journal)
160160
finish_wait(&journal->j_wait_transaction_locked, &wait);
161161
}
162162

163+
/*
164+
* Wait until running transaction transitions from T_SWITCH to T_FLUSH
165+
* state and new transaction can thus be started. The function releases
166+
* j_state_lock.
167+
*/
168+
static void wait_transaction_switching(journal_t *journal)
169+
__releases(journal->j_state_lock)
170+
{
171+
DEFINE_WAIT(wait);
172+
173+
if (WARN_ON(!journal->j_running_transaction ||
174+
journal->j_running_transaction->t_state != T_SWITCH))
175+
return;
176+
prepare_to_wait(&journal->j_wait_transaction_locked, &wait,
177+
TASK_UNINTERRUPTIBLE);
178+
read_unlock(&journal->j_state_lock);
179+
/*
180+
* We don't call jbd2_might_wait_for_commit() here as there's no
181+
* waiting for outstanding handles happening anymore in T_SWITCH state
182+
* and handling of reserved handles actually relies on that for
183+
* correctness.
184+
*/
185+
schedule();
186+
finish_wait(&journal->j_wait_transaction_locked, &wait);
187+
}
188+
163189
static void sub_reserved_credits(journal_t *journal, int blocks)
164190
{
165191
atomic_sub(blocks, &journal->j_reserved_credits);
@@ -183,7 +209,8 @@ static int add_transaction_credits(journal_t *journal, int blocks,
183209
* If the current transaction is locked down for commit, wait
184210
* for the lock to be released.
185211
*/
186-
if (t->t_state == T_LOCKED) {
212+
if (t->t_state != T_RUNNING) {
213+
WARN_ON_ONCE(t->t_state >= T_FLUSH);
187214
wait_transaction_locked(journal);
188215
return 1;
189216
}
@@ -360,8 +387,14 @@ static int start_this_handle(journal_t *journal, handle_t *handle,
360387
/*
361388
* We have handle reserved so we are allowed to join T_LOCKED
362389
* transaction and we don't have to check for transaction size
363-
* and journal space.
390+
* and journal space. But we still have to wait while running
391+
* transaction is being switched to a committing one as it
392+
* won't wait for any handles anymore.
364393
*/
394+
if (transaction->t_state == T_SWITCH) {
395+
wait_transaction_switching(journal);
396+
goto repeat;
397+
}
365398
sub_reserved_credits(journal, blocks);
366399
handle->h_reserved = 0;
367400
}

include/linux/jbd2.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ struct transaction_s
575575
enum {
576576
T_RUNNING,
577577
T_LOCKED,
578+
T_SWITCH,
578579
T_FLUSH,
579580
T_COMMIT,
580581
T_COMMIT_DFLUSH,

0 commit comments

Comments
 (0)