Applied, thanks!
Milos Nikic, le dim. 27 sept. 2026 14:46:08 -0700, a ecrit:
> 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
>
>
--
Samuel
> Allez, soyez sympa ... traduisez-lui "linux"
Linux, c'est comme le miel : c'est vachement bon mais ça attire les
mouches. En plus, ça colle aux doigts et on a du mal à s'en défaire.
-+- TP in: Guide du linuxien pervers - "Barrez vous les mouches !"