Applied, thanks!
Milos Nikic, le dim. 27 sept. 2026 20:57:33 -0700, a ecrit:
> Standardizes the transaction handling in ext2, and reduces the journal
> surface.
>
> Removed invocation of reduntant commit call in diskfs_journal_shutdown
> (). diskfs_journal_shutdown calls journal_quiesce_checkpoints which
> commits immidiatelly and its enough.
>
> Some functions in the journal had to be physically moved around to accomodate
> the change.
> ---
> ext2fs/journal.c | 331 +++++++++++++++++++++--------------------------
> ext2fs/journal.h | 12 --
> ext2fs/pager.c | 12 +-
> 3 files changed, 152 insertions(+), 203 deletions(-)
>
> diff --git a/ext2fs/journal.c b/ext2fs/journal.c
> index fcd90f05e..44f93514d 100644
> --- a/ext2fs/journal.c
> +++ b/ext2fs/journal.c
> @@ -635,67 +635,6 @@ init_map (journal_t *journal, struct node *jnode)
> journal->map.inode = jnode;
> }
>
> -/**
> - * The background journal thread (kjournald).
> - * * Wakes up every 5 seconds to commit the currently running transaction
> - * to the journal ring buffer. Crucially, this ONLY writes to the log,
> - * not the main filesystem.
> - *
> - * Purpose:
> - * - Data Loss Bound: Caps the maximum window of lost work to 5 seconds
> - * (matching standard Linux ext3/ext4 behavior), rather than relying on
> - * the 30-second diskfs_sync_everything() interval.
> - * - Transaction Sizing: Prevents the in-memory shadow buffer map from
> - * growing infinitely large during heavy metadata storms (e.g., compiling).
> - * - Pager Optimization: By eagerly committing transactions in the
> background,
> - * we proactively satisfy the Write-Ahead Log (WAL) barrier. This ensures
> - * that when the Mach VM Pager eventually needs to flush dirty pages to the
> - * main disk, it doesn't stall the system waiting for synchronous journal
> I/O.
> - *
> - * Future work: The interval could be made dynamic based on VFS load, but
> - * a static 5-second interval provides a solid baseline.
> - */
> -static void *
> -kjournald_thread (void *arg)
> -{
> - journal_t *journal = (journal_t *) arg;
> - struct timespec ts;
> -
> - JOURNAL_LOCK (journal);
> - while (!journal->j_must_exit)
> - {
> - clock_gettime (CLOCK_MONOTONIC, &ts);
> - ts.tv_sec += 5;
> -
> - pthread_cond_clockwait (&journal->j_flusher_wakeup,
> - &journal->j_state_lock, CLOCK_MONOTONIC, &ts);
> -
> - if (journal->j_must_exit)
> - break;
> - if (diskfs_readonly)
> - continue;
> -
> - if (journal->j_running_transaction)
> - {
> - JRNL_LOG_DEBUG ("Woke the journal up:\n"
> - " - Sequence: %u\n"
> - " - Start (Head): %u\n"
> - " - First Data Block: %u\n"
> - " - Total Blocks: %u",
> - journal->j_transaction_sequence, journal->j_head,
> - journal->j_first, journal->j_last);
> -
> - JOURNAL_UNLOCK (journal);
> - error_t err = journal_commit_running_transaction ();
> - if (err)
> - JRNL_LOG_WARN ("Background commit failed: %s", strerror (err));
> - JOURNAL_LOCK (journal);
> - }
> - }
> - JOURNAL_UNLOCK (journal);
> - return NULL;
> -}
> -
> static block_t
> get_journal_phys_block (journal_t *journal, uint32_t idx)
> {
> @@ -1327,78 +1266,6 @@ journal_create_running_transaction_locked (journal_t
> *journal)
> return txn;
> }
>
> -journal_t *
> -journal_create (struct node *journal_inode)
> -{
> - journal_t *j = calloc (1, sizeof (journal_t));
> - if (!j)
> - ext2_panic ("Cannot create journal struct.");
> -
> - init_map (j, journal_inode);
> -
> - /* Take ownership of the inode ref */
> - diskfs_nref (journal_inode);
> -
> - /* Set generic defaults (Will be overwritten by Superblock read later) */
> - j->j_first = 1; /* Skip SB block by default */
> - j->j_last = j->map.total_blocks - 1;
> - uint32_t total_len = j->j_last - j->j_first;
> - j->j_free = total_len;
> -
> - j->j_max_transaction_buffers = total_len / JRNL_MAX_TRANS_RATIO;
> - if (j->j_max_transaction_buffers < JRNL_MIN_BATCH_BLOCKS)
> - j->j_max_transaction_buffers = JRNL_MIN_BATCH_BLOCKS;
> - j->j_min_free = j->j_max_transaction_buffers + JRNL_METADATA_OVERHEAD;
> - j->j_descriptor_buf = malloc (block_size);
> - j->j_commit_buf = malloc (block_size);
> - if (!j->j_descriptor_buf || !j->j_commit_buf)
> - ext2_panic ("No RAM for commit buffers!");
> -
> - if (journal_load_superblock (j) != 0)
> - ext2_panic ("[JOURNAL] Failed to load superblock!");
> - j->j_last_committed_tid = j->j_transaction_sequence - 1;
> - pthread_cond_init (&j->j_commit_done, NULL);
> - pthread_mutex_init (&j->j_state_lock, NULL);
> - pthread_cond_init (&j->j_commit_wait, NULL);
> - pthread_cond_init (&j->j_flusher_wakeup, NULL);
> - 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
> - JRNL_LOG_DEBUG ("Created flusher thread.");
> -
> - j->j_pool_memory =
> - calloc (JRNL_MAX_FREE_BUFFERS, sizeof (journal_buffer_t));
> - if (!j->j_pool_memory)
> - ext2_panic ("[JOURNAL] No RAM for buffer pool!");
> -
> - /* Chain the contiguous blocks together into a free list */
> - for (int i = 0; i < JRNL_MAX_FREE_BUFFERS - 1; i++)
> - j->j_pool_memory[i].jb_next = &j->j_pool_memory[i + 1];
> -
> - /* The last block points to NULL, and the head points to block 0 */
> - j->j_pool_memory[JRNL_MAX_FREE_BUFFERS - 1].jb_next = NULL;
> - j->j_free_buffers = &j->j_pool_memory[0];
> -
> - ext2_lifeboat.payloads =
> - mmap (NULL, JRNL_LIFEBOAT_CAPACITY * block_size, PROT_READ | PROT_WRITE,
> - MAP_ANON | MAP_PRIVATE, -1, 0);
> - if (ext2_lifeboat.payloads == MAP_FAILED)
> - ext2_panic ("[JOURNAL] No RAM for lifeboat cache!");
> - return j;
> -}
> -
> /**
> * Forcefully clears the checkpoint list.
> * SAFE ONLY after a full filesystem sync.
> @@ -2057,6 +1924,19 @@ journal_commit_running_transaction_locked (journal_t
> *journal)
> uint32_t commit_loc;
> diskfs_transaction_t *txn;
>
> + txn = ext2_journal->j_running_transaction;
> +
> + if (!txn || txn->t_state != T_RUNNING)
> + {
> + err = EINVAL;
> + goto out;
> + }
> + if (txn->t_buffer_map.size == 0)
> + {
> + JRNL_LOG_DEBUG ("Txn %u is empty. Keeping it open.", txn->t_tid);
> + goto out;
> + }
> +
> while (journal->j_committing_transaction != NULL)
> JOURNAL_WAIT (&journal->j_commit_done, journal);
>
> @@ -2170,6 +2050,137 @@ out:
> return err;
> }
>
> +/**
> + * The background journal thread (kjournald).
> + * * Wakes up every 5 seconds to commit the currently running transaction
> + * to the journal ring buffer. Crucially, this ONLY writes to the log,
> + * not the main filesystem.
> + *
> + * Purpose:
> + * - Data Loss Bound: Caps the maximum window of lost work to 5 seconds
> + * (matching standard Linux ext3/ext4 behavior), rather than relying on
> + * the 30-second diskfs_sync_everything() interval.
> + * - Transaction Sizing: Prevents the in-memory shadow buffer map from
> + * growing infinitely large during heavy metadata storms (e.g., compiling).
> + * - Pager Optimization: By eagerly committing transactions in the
> background,
> + * we proactively satisfy the Write-Ahead Log (WAL) barrier. This ensures
> + * that when the Mach VM Pager eventually needs to flush dirty pages to the
> + * main disk, it doesn't stall the system waiting for synchronous journal
> I/O.
> + *
> + * Future work: The interval could be made dynamic based on VFS load, but
> + * a static 5-second interval provides a solid baseline.
> + */
> +static void *
> +kjournald_thread (void *arg)
> +{
> + journal_t *journal = (journal_t *) arg;
> + struct timespec ts;
> +
> + JOURNAL_LOCK (journal);
> + while (!journal->j_must_exit)
> + {
> + clock_gettime (CLOCK_MONOTONIC, &ts);
> + ts.tv_sec += 5;
> +
> + pthread_cond_clockwait (&journal->j_flusher_wakeup,
> + &journal->j_state_lock, CLOCK_MONOTONIC, &ts);
> +
> + if (journal->j_must_exit)
> + break;
> + if (diskfs_readonly)
> + continue;
> +
> + if (journal->j_running_transaction)
> + {
> + JRNL_LOG_DEBUG ("Woke the journal up:\n"
> + " - Sequence: %u\n"
> + " - Start (Head): %u\n"
> + " - First Data Block: %u\n"
> + " - Total Blocks: %u",
> + journal->j_transaction_sequence, journal->j_head,
> + journal->j_first, journal->j_last);
> +
> + error_t err = journal_commit_running_transaction_locked
> (ext2_journal);
> + if (err)
> + JRNL_LOG_WARN ("Background commit failed: %s", strerror (err));
> + }
> + }
> + JOURNAL_UNLOCK (journal);
> + return NULL;
> +}
> +
> +journal_t *
> +journal_create (struct node *journal_inode)
> +{
> + journal_t *j = calloc (1, sizeof (journal_t));
> + if (!j)
> + ext2_panic ("Cannot create journal struct.");
> +
> + init_map (j, journal_inode);
> +
> + /* Take ownership of the inode ref */
> + diskfs_nref (journal_inode);
> +
> + /* Set generic defaults (Will be overwritten by Superblock read later) */
> + j->j_first = 1; /* Skip SB block by default */
> + j->j_last = j->map.total_blocks - 1;
> + uint32_t total_len = j->j_last - j->j_first;
> + j->j_free = total_len;
> +
> + j->j_max_transaction_buffers = total_len / JRNL_MAX_TRANS_RATIO;
> + if (j->j_max_transaction_buffers < JRNL_MIN_BATCH_BLOCKS)
> + j->j_max_transaction_buffers = JRNL_MIN_BATCH_BLOCKS;
> + j->j_min_free = j->j_max_transaction_buffers + JRNL_METADATA_OVERHEAD;
> + j->j_descriptor_buf = malloc (block_size);
> + j->j_commit_buf = malloc (block_size);
> + if (!j->j_descriptor_buf || !j->j_commit_buf)
> + ext2_panic ("No RAM for commit buffers!");
> +
> + if (journal_load_superblock (j) != 0)
> + ext2_panic ("[JOURNAL] Failed to load superblock!");
> + j->j_last_committed_tid = j->j_transaction_sequence - 1;
> + pthread_cond_init (&j->j_commit_done, NULL);
> + pthread_mutex_init (&j->j_state_lock, NULL);
> + pthread_cond_init (&j->j_commit_wait, NULL);
> + pthread_cond_init (&j->j_flusher_wakeup, NULL);
> + 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
> + JRNL_LOG_DEBUG ("Created flusher thread.");
> +
> + j->j_pool_memory =
> + calloc (JRNL_MAX_FREE_BUFFERS, sizeof (journal_buffer_t));
> + if (!j->j_pool_memory)
> + ext2_panic ("[JOURNAL] No RAM for buffer pool!");
> +
> + /* Chain the contiguous blocks together into a free list */
> + for (int i = 0; i < JRNL_MAX_FREE_BUFFERS - 1; i++)
> + j->j_pool_memory[i].jb_next = &j->j_pool_memory[i + 1];
> +
> + /* The last block points to NULL, and the head points to block 0 */
> + j->j_pool_memory[JRNL_MAX_FREE_BUFFERS - 1].jb_next = NULL;
> + j->j_free_buffers = &j->j_pool_memory[0];
> +
> + ext2_lifeboat.payloads =
> + mmap (NULL, JRNL_LIFEBOAT_CAPACITY * block_size, PROT_READ | PROT_WRITE,
> + MAP_ANON | MAP_PRIVATE, -1, 0);
> + if (ext2_lifeboat.payloads == MAP_FAILED)
> + ext2_panic ("[JOURNAL] No RAM for lifeboat cache!");
> + return j;
> +}
> +
> /**
> * Safely marks the journal as clean on disk.
> * MUST only be called after sync_global(1) ensures no pager I/O is in
> flight,
> @@ -2253,57 +2264,6 @@ diskfs_journal_stop_transaction (diskfs_transaction_t
> *txn)
> JOURNAL_UNLOCK (ext2_journal);
> }
>
> -/* Forces the currently running transaction (if any) to safely commit to the
> - physical journal log.
> -
> - This function unconditionally blocks the calling thread until all VFS
> - participants currently in the running transaction finish their updates
> - (t_updates reaches 0) and the Write-Ahead Log barrier is physically
> crossed.
> -
> - Unlike diskfs_journal_commit_transaction, this function does not take a
> - transaction handle as an argument. It is a global barrier used by
> background
> - flushers (kjournald), pager sync operations, and unmount routines to
> ensure
> - strict durability of all recently dirtied metadata. */
> -error_t
> -journal_commit_running_transaction (void)
> -{
> - diskfs_transaction_t *txn;
> - error_t err = 0;
> - if (!ext2_journal)
> - return 0;
> -
> - JOURNAL_LOCK (ext2_journal);
> - txn = ext2_journal->j_running_transaction;
> -
> - if (!txn || txn->t_state != T_RUNNING)
> - {
> - err = EINVAL;
> - goto out;
> - }
> - if (txn->t_buffer_map.size == 0)
> - {
> - JRNL_LOG_DEBUG ("Txn %u is empty. Keeping it open.", txn->t_tid);
> - goto out;
> - }
> - /**
> - * Note on buffer hydration (memory copying):
> - * We do not explicitly copy Mach VM memory into the journal shadow buffers
> - * here. That responsibility strictly belongs to
> - * journal_stop_transaction_locked.
> - *
> - * 1. If t_updates == 0: The last VFS thread to exit the transaction has
> - * already populated the shadow buffers. We will just flush them.
> - * 2. If t_updates > 0: The internal locked commit function will flip the
> - * state to T_LOCKED (preventing new threads from joining) and put this
> - * thread to sleep. The last active VFS thread will eventually call stop(),
> - * hit t_updates == 0, safely copy the memory, and wake us up.
> - */
> - err = journal_commit_running_transaction_locked (ext2_journal);
> -out:
> - JOURNAL_UNLOCK (ext2_journal);
> - return err;
> -}
> -
> /* Ends the caller's participation in the transaction TXN and strictly
> guarantees a synchronous commit to disk.
>
> @@ -2750,9 +2710,6 @@ diskfs_journal_shutdown (void)
> return;
>
> ext2_journal->j_must_exit = 1; /* Signal kjournald so it doesn't wake
> up */
> - /* Commit any pending transaction (e.g. the superblock clean-state
> - flags written by diskfs_set_hypermetadata). */
> - journal_commit_running_transaction ();
>
> /* Sync the disk pager to ensure all shadow data is on disk. */
> sync_global (1);
> diff --git a/ext2fs/journal.h b/ext2fs/journal.h
> index a96739f9f..5860b50a1 100644
> --- a/ext2fs/journal.h
> +++ b/ext2fs/journal.h
> @@ -86,16 +86,4 @@ journal_store_read (block_t start_block, size_t length,
> void **buf,
> */
> void journal_record_freed_blocks (block_t start, unsigned long count);
>
> -/* Forces the currently running transaction (if any) to safely commit to the
> - * physical journal log.
> - *
> - * This function unconditionally blocks the calling thread until all VFS
> - * participants currently in the running transaction finish their updates
> - * (t_updates reaches 0) and the Write-Ahead Log barrier is physically
> crossed.
> - * Unlike diskfs_journal_commit_transaction, this function does not take a
> - * transaction handle as an argument. It is a global barrier used by
> background
> - * flushers (kjournald), pager sync operations, and unmount routines to
> ensure
> - * strict durability of all recently dirtied metadata. */
> -error_t journal_commit_running_transaction (void);
> -
> #endif //_JOURNAL_H
> diff --git a/ext2fs/pager.c b/ext2fs/pager.c
> index efc104022..cba908427 100644
> --- a/ext2fs/pager.c
> +++ b/ext2fs/pager.c
> @@ -29,6 +29,7 @@
>
> /* XXX */
> #include "../libpager/priv.h"
> +#include "libdiskfs/diskfs.h"
>
> /* A ports bucket to hold disk pager ports. */
> struct port_bucket *disk_pager_bucket;
> @@ -1568,11 +1569,14 @@ diskfs_shutdown_pager (void)
> return 0;
> }
>
> - write_all_disknodes ();
> - journal_commit_running_transaction ();
>
> + diskfs_transaction_t *txn = diskfs_journal_start_transaction ();
> +
> + write_all_disknodes ();
> ports_bucket_iterate (file_pager_bucket, shutdown_one);
>
> + diskfs_journal_commit_transaction (txn);
> +
> if (!ext2_journal)
> {
> error_t err = store_sync (store);
> @@ -1595,10 +1599,10 @@ diskfs_sync_everything (int wait)
> return 0;
> }
>
> + diskfs_transaction_t *txn = diskfs_journal_start_transaction ();
> write_all_disknodes ();
> - /* We only commit if there is a journal and we have a running transaction
> */
> - journal_commit_running_transaction ();
> ports_bucket_iterate (file_pager_bucket, sync_one);
> + diskfs_journal_commit_transaction (txn);
>
> /* Do things on the the disk pager. */
> sync_global (wait);
> --
> 2.55.0
>
--
Samuel
Créer une hiérarchie supplementaire pour remedier à un problème (?) de
dispersion est d'une logique digne des Shadocks.
* BT in: Guide du Cabaliste Usenet - La Cabale vote oui (les Shadocks aussi) *