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) *

Reply via email to