Since transactions are perpetually created, add a static
dedicated create transaction function, and call it where needed.
(inside journal create and in the commit function).
Move the journal_force_checkpoint_locked into the commit function and out of
diskfs_journal_start_transaction().
This makes commit somewhat heavier than before (it is now responsible for
freeing up space when needed) but it happens only in the context
of a dedicated background thread or when sync is requested by the
caller.
In turn, diskfs_journal_start_transaction(), which is called many times
per every RPC call, becomes just a ref-count bump under a lock.
---
ext2fs/journal.c | 119 ++++++++++++++++++++++++++---------------------
1 file changed, 66 insertions(+), 53 deletions(-)
diff --git a/ext2fs/journal.c b/ext2fs/journal.c
index d58d7e914..9eccabe20 100644
--- a/ext2fs/journal.c
+++ b/ext2fs/journal.c
@@ -1297,6 +1297,36 @@ journal_forget_freed_blocks (journal_t *journal,
journal_freed_extent_t *ext)
flush_to_disk ();
}
+/* Install a running transaction with t_updates == 0.
+ The journal lock is held on entry and on return, including failure.
+ j_running_transaction must be NULL. This does not drop the lock, so
+ commit can publish the successor with no gap for start to observe. */
+static diskfs_transaction_t *
+journal_create_running_transaction_locked (journal_t *journal)
+{
+ diskfs_transaction_t *txn;
+
+ assert_backtrace (!journal->j_running_transaction);
+
+ txn = calloc (1, sizeof (diskfs_transaction_t));
+ if (!txn)
+ return NULL;
+
+ if (journal_map_init (&txn->t_buffer_map, 0) != 0)
+ {
+ free (txn);
+ return NULL;
+ }
+
+ txn->t_tid = journal->j_transaction_sequence++;
+ txn->t_state = T_RUNNING;
+ txn->t_updates = 0;
+
+ journal->j_running_transaction = txn;
+ JRNL_LOG_DEBUG ("[TRX] Created NEW TID %u", txn->t_tid);
+ return txn;
+}
+
journal_t *
journal_create (struct node *journal_inode)
{
@@ -1334,6 +1364,15 @@ journal_create (struct node *journal_inode)
pthread_cond_init (&j->j_flush_wait, NULL);
j->j_must_exit = 0;
+
+ /* The running transaction lives for the whole life of the journal.
+ Commit installs the next one before it drops the lock, and start only
+ joins. Publish this one before kjournald runs. */
+ JOURNAL_LOCK (j);
+ if (!journal_create_running_transaction_locked (j))
+ ext2_panic ("Cannot create initial journal transaction.");
+ JOURNAL_UNLOCK (j);
+
if (pthread_create (&kjournald_tid, NULL, kjournald_thread, j) != 0)
JRNL_LOG_WARN ("Failed to create a flusher thread.");
else
@@ -1693,53 +1732,21 @@ out:
return err;
}
+/* Join the running transaction. A successor is installed by commit
+ before the previous one leaves T_RUNNING, so this never allocates.
+ Shutdown is the exception: j_must_exit means commit did not install
+ a successor, and the caller must tolerate NULL. */
static diskfs_transaction_t *
-diskfs_journal_start_transaction_locked (journal_t *journal)
+journal_join_transaction_locked (journal_t *journal)
{
- if (journal->j_must_exit)
- return NULL;
-
diskfs_transaction_t *txn;
- if (ext2_journal->j_free < ext2_journal->j_min_free)
- {
- JRNL_LOG_DEBUG
- ("[TRX] Journal full (Free: %u). Forcing checkpoint.",
- ext2_journal->j_free);
-
- journal_force_checkpoint_locked (journal);
- }
-
- txn = ext2_journal->j_running_transaction;
-
- if (txn)
- {
- assert_backtrace (txn->t_state == T_RUNNING);
- txn->t_updates++;
- }
- else
- {
- txn = calloc (1, sizeof (diskfs_transaction_t));
- if (!txn)
- {
- JOURNAL_UNLOCK (journal);
- return NULL;
- }
-
- if (journal_map_init (&txn->t_buffer_map, 0) != 0)
- {
- free (txn);
- JOURNAL_UNLOCK (journal);
- return NULL;
- }
- txn->t_tid = journal->j_transaction_sequence++;
- txn->t_state = T_RUNNING;
- txn->t_updates = 1;
-
- journal->j_running_transaction = txn;
- JRNL_LOG_DEBUG ("[TRX] Created NEW TID %u", txn->t_tid);
- }
+ if (journal->j_must_exit)
+ return NULL;
+ txn = journal->j_running_transaction;
+ assert_backtrace (txn && txn->t_state == T_RUNNING);
+ txn->t_updates++;
return txn;
}
@@ -1747,11 +1754,11 @@ diskfs_transaction_t *
diskfs_journal_start_transaction (void)
{
diskfs_transaction_t *txn;
- if (!ext2_journal)
+ if (!ext2_journal || ext2_journal->j_must_exit)
return NULL;
JOURNAL_LOCK (ext2_journal);
- txn = diskfs_journal_start_transaction_locked (ext2_journal);
+ txn = journal_join_transaction_locked (ext2_journal);
JOURNAL_UNLOCK (ext2_journal);
return txn;
}
@@ -2063,14 +2070,20 @@ journal_commit_running_transaction_locked (journal_t
*journal)
}
journal->j_committing_transaction = txn;
+
+ /* Checkpoint drops the lock. Do it while txn is still running, so a
+ start that sneaks in joins txn and is waited for below. Other
+ committers are already blocked on j_committing_transaction. */
+ if (journal->j_free < journal->j_min_free)
+ journal_force_checkpoint_locked (journal);
+
journal->j_running_transaction = NULL;
if (!journal->j_must_exit)
{
- /* Instantly spawn the next txn so that there is no gap. */
- diskfs_transaction_t *run =
- diskfs_journal_start_transaction_locked (journal);
- if (run)
- run->t_updates--; /* We are just seeding it, not joining it! */
+ /* Publish the successor before any unlock. t_updates is 0: this
+ thread is not a participant. */
+ if (!journal_create_running_transaction_locked (journal))
+ JRNL_LOG_WARN ("Failed to create the successor transaction.");
}
txn->t_state = T_LOCKED;
@@ -2083,9 +2096,9 @@ journal_commit_running_transaction_locked (journal_t
*journal)
journal_ensure_commit_space_locked (journal, txn);
txn->t_log_start = journal_next_block_would_be (journal);
- /* We unlock for IO! j_running_transaction is NULL and t_state of this one
- * is T_LOCKED, nothing else is modifing it, we are safe to iterate it's maps
- * etc.*/
+ /* Unlock for I/O. The successor is already j_running_transaction, and
+ this transaction is T_LOCKED, so start joins the successor. Participants
+ of this one have drained. Its map is stable. */
JOURNAL_UNLOCK (journal);
/* Write Data (I/O) */
@@ -2738,7 +2751,7 @@ journal_notify_block_changed (block_t block)
JOURNAL_LOCK (ext2_journal);
diskfs_transaction_t *txn =
- diskfs_journal_start_transaction_locked (ext2_journal);
+ journal_join_transaction_locked (ext2_journal);
if (journal_dirty_block_locked (txn, block))
JRNL_LOG_WARN ("Didn't manage to add a dirty block %u to the journal.",
block);
--
2.55.0