Applied both, thanks!

Samuel

Milos Nikic, le mer. 30 sept. 2026 15:14:22 -0700, a ecrit:
> This fixes a split-RPC bug introduced in 296bacec9 and the resulting shadow 
> buffer corruptions. Previously, publishing the successor transaction too 
> early allowed a single RPC to be split across multiple transactions. 
> Furthermore, overlapping epochs invalidated the t_updates == 0 hydration 
> invariant, as threads in the new transaction could edit the live cache while 
> the old one was still copying.
> 
> The fix reworks transaction handling to make the journal epoch a thread-local 
> property for the duration of the RPC (similar to JBD2's 
> current->journal_info).
> 
> - Thread-Local Nesting: Outermost starts record the transaction in TLS. 
> Nested starts return that same exact transaction and increment a depth 
> counter without taking locks.
> 
> - Active Threads: t_updates is renamed to t_active_threads to reflect that it 
> now counts unique participant threads, not open handles.
> 
> - Safe Draining: Successor publishing is pushed until after the previous 
> transaction finishes draining. Outermost starts will now safely wait for the 
> successor since they hold no node locks yet.
> 
> - Pager Carveout: Because a pager thread waiting on the journal would cause a 
> deadlock, it receives an explicit exemption. Pager callbacks mark their 
> thread, allowing them to bypass the wait and join the draining transaction to 
> safely complete their bounded operation.
> 
> - Thread-Local Sync: Sync requests are now thread-local. This prevents one 
> thread's synchronous commit from forcing all other RPCs sharing the 
> transaction to block (which caused severe rm -rf slowdowns).
> 
> - Shadow Copy Safety: The needs_copy flag is now cleared under the lock 
> before the unlocked copy, preventing the sweep from clobbering a concurrent 
> re-arm.
> ---
>  ext2fs/journal.c | 252 +++++++++++++++++++++++++++++++++++++----------
>  ext2fs/journal.h |   9 ++
>  ext2fs/pager.c   |  10 ++
>  3 files changed, 220 insertions(+), 51 deletions(-)
> 
> diff --git a/ext2fs/journal.c b/ext2fs/journal.c
> index 44f93514d..cc3aeb35a 100644
> --- a/ext2fs/journal.c
> +++ b/ext2fs/journal.c
> @@ -118,6 +118,9 @@
>  #define JOURNAL_WAIT(cond, j) \
>      pthread_cond_wait((cond), &(j)->j_state_lock)
>  
> +#define IS_RUNNING_TID(j, tid) \
> +    ((j)->j_running_transaction && (j)->j_running_transaction->t_tid == 
> (tid))
> +
>  #define JRNL_LIFEBOAT_CAPACITY 512
>  
>  #define JRNL_LIFEBOAT_ALLOC_MASK_LEN 8
> @@ -142,6 +145,30 @@ struct journal_lifeboat
>  static struct journal_lifeboat ext2_lifeboat;
>  static pthread_t kjournald_tid;
>  
> +/* The handle this thread currently holds, and how many starts are open
> +   on it.  One RPC is one thread, and its metadata updates must all land
> +   in one transaction.  The outermost start on a thread is the only one
> +   that looks at the journal; every nested start on the same thread
> +   returns this transaction.  Only the outermost handle is counted in
> +   t_active_threads, so t_active_threadscounts threads, and 
> t_active_threads== 0 means no
> +   thread holds a handle. */
> +static __thread diskfs_transaction_t *journal_thread_txn;
> +static __thread unsigned int journal_thread_depth;
> +
> +/* Set while this thread runs a pager callback.  A pager thread must never
> +   wait on the journal: an RPC thread inside a transaction may be waiting
> +   on this very thread for a page, and the commit waits on that RPC.  An
> +   outermost start on a pager thread therefore joins the committing
> +   transaction while it drains rather than waiting for the successor. */
> +static __thread int journal_thread_is_pager;
> +
> +/* Set when a nested call on this thread asked for a synchronous commit.
> +   The RPC tail reads it through diskfs_journal_needs_sync and decides
> +   between commit and stop.  It is per thread so an RPC waits only for
> +   its own request; another thread's request on the shared transaction
> +   is that thread's business.  Cleared by the outermost start. */
> +static __thread int journal_thread_sync;
> +
>  /**
>   * Holds one modified block (4KB) that needs to be written to the journal.
>   */
> @@ -205,7 +232,7 @@ struct diskfs_transaction
>    /* The Log Position */
>    uint32_t t_log_start;              /* Where this transaction starts in the 
> ring */
>  
> -  uint32_t t_updates;                /* Refcount: How many threads are in 
> this transaction? */
> +  uint32_t t_active_threads;         /* Refcount: How many threads are in 
> this transaction? */
>  
>    /* The Map: block_t -> journal_buffer_t (for O(1) lookups).
>     * We use a per-transaction map rather than a global, journal-wide map.
> @@ -259,6 +286,8 @@ typedef struct journal
>    pthread_mutex_t j_state_lock;      /* Protects the pointers below */
>    pthread_cond_t j_commit_wait;      /* Cond. var. while waiting for the tx 
> to be ready. */
>    pthread_cond_t j_flush_wait;       /* Cond. var for safely waiting on 
> physical flushes */
> +  pthread_cond_t j_running_wait;     /* Cond. var for starts waiting while 
> the
> +                                        running transaction drains for 
> commit */
>    /* The Transactions */
>    diskfs_transaction_t *j_running_transaction;       /* Currently filling */
>    diskfs_transaction_t *j_committing_transaction;    /* Transaction that is
> @@ -897,8 +926,12 @@ journal_record_freed_blocks (block_t start, unsigned 
> long count)
>    ext->fe_count = count;
>  
>    JOURNAL_LOCK (ext2_journal);
> -  diskfs_transaction_t *txn = ext2_journal->j_running_transaction;
> -  if (!txn || txn->t_state != T_RUNNING)
> +  /* Record against this thread's transaction when it holds one: the free
> +     belongs to the same RPC, and that transaction may be T_LOCKED while
> +     j_running_transaction is NULL.  Otherwise use the running one. */
> +  diskfs_transaction_t *txn = journal_thread_depth > 0
> +    ? journal_thread_txn : ext2_journal->j_running_transaction;
> +  if (!txn || (txn->t_state != T_RUNNING && txn->t_state != T_LOCKED))
>      {
>        JRNL_LOG_DEBUG ("Cannot record freed blocks, no running transaction.");
>        /* The transaction was committed by another thread before we locked!
> @@ -1008,15 +1041,15 @@ static void
>  journal_stop_transaction_locked (journal_t *journal,
>                                diskfs_transaction_t *txn)
>  {
> -  if (txn->t_updates == 0)
> +  if (txn->t_active_threads == 0)
>      {
>        /* This implies a double-stop or corruption */
>        JRNL_LOG_WARN ("Logic Error: Transaction stopped too many times!");
>        return;
>      }
> -  txn->t_updates--;
> +  txn->t_active_threads --;
>    /* Continue until the map is proven clean while locked */
> -  while (txn->t_updates == 0)
> +  while (txn->t_active_threads == 0)
>      {
>        size_t iter = 0;
>        journal_buffer_t *jb_exp;
> @@ -1031,6 +1064,11 @@ journal_stop_transaction_locked (journal_t *journal,
>           {
>             /* ALWAYS hydrate from the live VM cache. The lifeboat is for
>                delayed physical I/O, not for sourcing WAL shadow data! */
> +           /* Clear the flag here, under the lock, before the copy.  A
> +              dirty that lands while we copy unlocked sets it again and
> +              the next sweep recopies.  Clearing after the copy could
> +              overwrite that re-arm and lose the recopy. */
> +           jb_exp->needs_copy = 0;
>             jb_exp->jb_next = NULL;
>             if (!copy_list_head)
>               copy_list_head = jb_exp;
> @@ -1047,8 +1085,8 @@ journal_stop_transaction_locked (journal_t *journal,
>         break;
>       }
>  
> -      /* Now lockless hydration, we will update t_updates to > 0 so none can 
> steal the txn. */
> -      txn->t_updates++;
> +      /* Now lockless hydration, we will update t_active_threads to > 0 so 
> none can steal the txn. */
> +      txn->t_active_threads ++;
>        JOURNAL_UNLOCK (journal);
>  
>        journal_buffer_t *curr = copy_list_head;
> @@ -1056,10 +1094,10 @@ journal_stop_transaction_locked (journal_t *journal,
>       {
>         /**
>            * Calculate the pointer to the live Mach VM cache for this block.
> -          * Because t_updates is 0 AND we hold a lock, we are mathematically
> +          * Because t_active_threads is 0 AND we hold a lock, we are 
> mathematically
>            * guaranteed that no VFS threads are currently mutating this block
>            * because if they were mutating it they would have to first obtain
> -          * the journal lock AND also increase the t_updates.
> +          * the journal lock AND also increase the t_active_threads .
>            */
>         void *live_cache_ptr = bptr (curr->jb_blocknr);
>         /**
> @@ -1070,11 +1108,9 @@ journal_stop_transaction_locked (journal_t *journal,
>            * WAL so it can overwrite the pager's rushed data during recovery!
>            */
>         memcpy (curr->jb_shadow_data, live_cache_ptr, block_size);
> -       /* We are done with this block even if t_updates reach 0 again before
> -        * this transaction is committed. If we get notified that this block
> -        * has been modified again journal_dirty_blocks must set needs_copy
> -        * back to 1. */
> -       curr->needs_copy = 0;
> +       /* needs_copy was cleared under the lock when this block was
> +        * listed.  If the block is modified again, journal_dirty_block
> +        * sets it back to 1 and a later sweep recopies. */
>  
>         journal_buffer_t *next = curr->jb_next;
>         curr->jb_next = NULL;
> @@ -1083,10 +1119,10 @@ journal_stop_transaction_locked (journal_t *journal,
>  
>        JOURNAL_LOCK (journal);
>        /* We need to decrement what we incremented. */
> -      txn->t_updates--;
> +      txn->t_active_threads--;
>  
> -      /* If t_updates is still 0, the loop repeats to verify no blocks were
> -         added. If t_updates > 0, another thread joined while we were 
> unlocked
> +      /* If t_active_threads is still 0, the loop repeats to verify no 
> blocks were
> +         added. If t_active_threads > 0, another thread joined while we were 
> unlocked
>           and they will handle the final sweep. We safely fall out. */
>      }
>  }
> @@ -1236,7 +1272,7 @@ journal_forget_freed_blocks (journal_t *journal, 
> journal_freed_extent_t *ext)
>      flush_to_disk ();
>  }
>  
> -/* Install a running transaction with t_updates == 0.
> +/* Install a running transaction with t_active_threads == 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. */
> @@ -1259,7 +1295,7 @@ journal_create_running_transaction_locked (journal_t 
> *journal)
>  
>    txn->t_tid = journal->j_transaction_sequence++;
>    txn->t_state = T_RUNNING;
> -  txn->t_updates = 0;
> +  txn->t_active_threads = 0;
>  
>    journal->j_running_transaction = txn;
>    JRNL_LOG_DEBUG ("[TRX] Created NEW TID %u", txn->t_tid);
> @@ -1599,37 +1635,113 @@ 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. */
> +/* Join the running transaction.  Only the outermost start on a thread
> +   comes here; nested starts are served from journal_thread_txn.
> +
> +   While commit drains a transaction (T_LOCKED), j_running_transaction is
> +   NULL and the successor does not exist yet.  An RPC thread waits here
> +   until the successor is published.  The wait is safe because an RPC
> +   takes its outermost start before any node lock, so a waiting starter
> +   holds nothing a draining participant needs.  A pager thread joins the
> +   draining transaction instead: it must never wait on the journal, and
> +   its work is one bounded operation that only extends the drain.
> +
> +   Nothing else is ever observed here: commit publishes the successor
> +   before it drops the lock, so a NULL j_running_transaction with no
> +   commit in flight means the successor could not be allocated.  Shutdown
> +   is the exception: j_must_exit means commit did not install a
> +   successor, and the caller must tolerate NULL. */
>  static diskfs_transaction_t *
>  journal_join_transaction_locked (journal_t *journal)
>  {
>    diskfs_transaction_t *txn;
>  
> +  while (!(txn = journal->j_running_transaction))
> +    {
> +      diskfs_transaction_t *commit = journal->j_committing_transaction;
> +
> +      if (journal->j_must_exit)
> +     return NULL;
> +
> +      assert_backtrace (commit);
> +
> +      if (journal_thread_is_pager && commit->t_state == T_LOCKED)
> +     {
> +       txn = commit;
> +       break;
> +     }
> +
> +      JOURNAL_WAIT (&journal->j_running_wait, journal);
> +    }
> +
>    if (journal->j_must_exit)
>      return NULL;
>  
> -  txn = journal->j_running_transaction;
> -  assert_backtrace (txn && txn->t_state == T_RUNNING);
> -  txn->t_updates++;
> +  assert_backtrace (txn->t_state == T_RUNNING
> +                 || (journal_thread_is_pager && txn->t_state == T_LOCKED));
> +  txn->t_active_threads ++;
>    return txn;
>  }
>  
> +/* Called by the ext2fs pager callbacks on entry (ON = 1) and exit
> +   (ON = 0).  See journal_thread_is_pager. */
> +void
> +journal_thread_set_pager (int on)
> +{
> +  journal_thread_is_pager = on;
> +}
> +
> +/* Open a handle on the running transaction for this thread.  The outermost
> +   start of an RPC may wait while a commit drains the previous transaction;
> +   it must therefore be taken before any node lock.  A pager thread never
> +   waits here.  Nested starts return the thread's transaction at once. */
>  diskfs_transaction_t *
>  diskfs_journal_start_transaction (void)
>  {
>    diskfs_transaction_t *txn;
> +
> +  if (journal_thread_depth > 0)
> +    {
> +      /* Nested start.  The outer handle already holds a t_active_threads 
> count,
> +         so this transaction cannot drain until that handle is released.
> +         It may already be T_LOCKED: commit is waiting for us.  Joining it
> +         here keeps the whole RPC in one transaction.  No journal lock and
> +         no t_active_threads change are needed. */
> +      journal_thread_depth++;
> +      return journal_thread_txn;
> +    }
> +
>    if (!ext2_journal || ext2_journal->j_must_exit)
>      return NULL;
>  
>    JOURNAL_LOCK (ext2_journal);
>    txn = journal_join_transaction_locked (ext2_journal);
>    JOURNAL_UNLOCK (ext2_journal);
> +
> +  if (txn)
> +    {
> +      journal_thread_txn = txn;
> +      journal_thread_depth = 1;
> +      journal_thread_sync = 0;
> +    }
>    return txn;
>  }
>  
> +/* Close one start on this thread.  Returns 1 when TXN was the outermost
> +   handle and the caller must release the t_active_threads count it holds.  
> */
> +static int
> +journal_thread_release (diskfs_transaction_t *txn)
> +{
> +  assert_backtrace (journal_thread_depth > 0);
> +  assert_backtrace (txn == journal_thread_txn);
> +
> +  if (--journal_thread_depth > 0)
> +    return 0;
> +
> +  journal_thread_txn = NULL;
> +  return 1;
> +}
> +
>  /**
>   * Adds a modified filesystem block to the SPECIFIC transaction handle.
>   * Defers the actual memory copy until the transaction stops.
> @@ -1646,17 +1758,29 @@ journal_dirty_block (diskfs_transaction_t *txn, 
> block_t fs_blocknr)
>    return err;
>  }
>  
> +/* Two flags.  journal_thread_sync tells this thread's RPC tail to commit
> +   rather than stop.  txn->sync_needed tells the outermost stop to wake
> +   kjournald when t_active_threads reaches 0, for callers that never commit
> +   (the pager reaches diskfs_drop_node with diskfs_synchronous set). */
>  void
>  diskfs_journal_set_sync (diskfs_transaction_t *txn)
>  {
> -  if (txn)
> -    txn->sync_needed = 1;
> +  if (!txn)
> +    return;
> +
> +  assert_backtrace (txn == journal_thread_txn);
> +  journal_thread_sync = 1;
> +  txn->sync_needed = 1;
>  }
>  
>  int
>  diskfs_journal_needs_sync (diskfs_transaction_t *txn)
>  {
> -  return txn ? txn->sync_needed : 0;
> +  if (!txn)
> +    return 0;
> +
> +  assert_backtrace (txn == journal_thread_txn);
> +  return journal_thread_sync;
>  }
>  
>  /* Helper to reset the header for a new block */
> @@ -1957,28 +2081,37 @@ journal_commit_running_transaction_locked (journal_t 
> *journal)
>    if (journal->j_free < journal->j_min_free)
>      journal_force_checkpoint_locked (journal);
>  
> +  /* Drain.  No successor exists while txn is T_LOCKED: an outermost start
> +     on an RPC thread waits in journal_join_transaction_locked, a pager
> +     thread joins txn.  So every thread that can edit a block in this map
> +     is counted int_active_threads , and t_active_threads == 0 means the 
> last shadow
> +     copy taken by diskfs_journal_stop_transaction_locked is final.  The
> +     successor is published only after that, so no thread in the next
> +     transaction ever edits a block this one has yet to copy.  This
> +     thread holds no handle: kjournald never starts one, and
> +     diskfs_journal_commit_transaction releases its handle first. */
>    journal->j_running_transaction = NULL;
> -  if (!journal->j_must_exit)
> -    {
> -      /* 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;
>  
> -  while (txn->t_updates > 0)
> +  while (txn->t_active_threads > 0)
>      JOURNAL_WAIT (&journal->j_commit_wait, journal);
>  
>    txn->t_state = T_FLUSHING;
>  
> +  /* Publish the successor before any unlock, then release the starters
> +     waiting for it.  On shutdown there is no successor; the broadcast
> +     lets them see j_must_exit and return NULL. */
> +  if (!journal->j_must_exit
> +      && !journal_create_running_transaction_locked (journal))
> +    JRNL_LOG_WARN ("Failed to create the successor transaction.");
> +  pthread_cond_broadcast (&journal->j_running_wait);
> +
>    journal_ensure_commit_space_locked (journal, txn);
>  
>    txn->t_log_start = journal_next_block_would_be (journal);
> -  /* 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. */
> +  /* Unlock for I/O.  The successor is j_running_transaction, and this
> +     transaction is T_FLUSHING with its participants drained.  Its map and
> +     shadow data are stable. */
>    JOURNAL_UNLOCK (journal);
>  
>    /* Write Data (I/O) */
> @@ -2144,6 +2277,7 @@ journal_create (struct node *journal_inode)
>    pthread_cond_init (&j->j_commit_wait, NULL);
>    pthread_cond_init (&j->j_flusher_wakeup, NULL);
>    pthread_cond_init (&j->j_flush_wait, NULL);
> +  pthread_cond_init (&j->j_running_wait, NULL);
>  
>    j->j_must_exit = 0;
>  
> @@ -2206,7 +2340,7 @@ journal_quiesce_checkpoints (void)
>                                 &ext2_journal->j_state_lock, CLOCK_MONOTONIC, 
> &ts);
>    if (err)
>      {
> -      /* If we hit ETIMEDOUT, a VFS thread likely leaked a t_updates refcount
> +      /* If we hit ETIMEDOUT, a VFS thread likely leaked a t_active_threads 
> refcount
>           due to a signal interruption or crash. We MUST bail out without
>           clearing the checkpoint list so the WAL replays on next boot! */
>        JRNL_LOG_WARN
> @@ -2240,23 +2374,30 @@ diskfs_journal_stop_transaction_locked (journal_t 
> *journal,
>    journal_stop_transaction_locked (journal, txn);
>  
>    /* Semi auto-commit? */
> -  if (txn->t_updates == 0 && (txn->sync_needed ||
> +  if (txn->t_active_threads == 0 && (txn->sync_needed ||
>        (txn->t_buffer_map.size >= journal->j_max_transaction_buffers)))
>      pthread_cond_signal (&journal->j_flusher_wakeup);
>  }
>  
>  /* Ends the caller's participation in the given transaction TXN.
>     This informs the journal that the logical operation is complete.
> -   If the caller is the final participant (t_updates reaches 0) AND any
> -   participant flagged the transaction for a synchronous commit, this 
> function
> -   will automatically perform the physical disk flush. Otherwise, this 
> function
> -   returns immediately without waiting for the transaction to commit.
> +   A nested stop only closes this thread's inner start.  When the outermost
> +   handle is released and the caller is the final participant 
> (t_active_threads
> +   reaches 0), a transaction flagged for sync, or one that has reached its
> +   size limit, wakes kjournald.  This function never waits for the commit:
> +   the pager reaches it, and a pager thread that waits deadlocks.
>  
>     This function consumes TXN. The caller must not use TXN after this call. 
> */
>  void
>  diskfs_journal_stop_transaction (diskfs_transaction_t *txn)
>  {
> -  if (!ext2_journal || !txn)
> +  if (!txn)
> +    return;
> +
> +  if (!journal_thread_release (txn))
> +    return;
> +
> +  if (!ext2_journal)
>      return;
>  
>    JOURNAL_LOCK (ext2_journal);
> @@ -2283,6 +2424,12 @@ diskfs_journal_commit_transaction 
> (diskfs_transaction_t *opaque_txn)
>    diskfs_transaction_t *txn = (diskfs_transaction_t *) opaque_txn;
>    uint32_t tid = txn->t_tid;
>  
> +  /* Commit belongs at the end of the RPC, on the outermost handle.  A
> +     nested commit would wait for t_active_threads to drain while this 
> thread's
> +     outer handle holds a count: a self-deadlock.  Catch it here. */
> +  int outermost = journal_thread_release (txn);
> +  assert_backtrace (outermost);
> +
>    JRNL_LOG_DEBUG ("Committing tx id: %u.", txn->t_tid);
>  
>    JOURNAL_LOCK (ext2_journal);
> @@ -2295,7 +2442,7 @@ diskfs_journal_commit_transaction (diskfs_transaction_t 
> *opaque_txn)
>                   tid);
>    /* Check if the transaction is currently RUNNING.
>       If it is, We "steal" it and become the committer. */
> -  if (ext2_journal->j_running_transaction == txn)
> +  if (IS_RUNNING_TID (ext2_journal, tid))
>      {
>        error_t err = journal_commit_running_transaction_locked (ext2_journal);
>        if (err)
> @@ -2709,7 +2856,10 @@ diskfs_journal_shutdown (void)
>    if (!ext2_journal)
>      return;
>  
> -  ext2_journal->j_must_exit = 1;     /* Signal kjournald so it doesn't wake 
> up */
> +  JOURNAL_LOCK (ext2_journal);
> +  ext2_journal->j_must_exit = 1;    /* Signal kjournald so it doesn't wake 
> up */
> +  pthread_cond_broadcast (&ext2_journal->j_running_wait); /* Wake waiting 
> starters */
> +  JOURNAL_UNLOCK (ext2_journal);
>  
>    /* 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 5860b50a1..ab7ab1e6b 100644
> --- a/ext2fs/journal.h
> +++ b/ext2fs/journal.h
> @@ -86,4 +86,13 @@ journal_store_read (block_t start_block, size_t length, 
> void **buf,
>   */
>  void journal_record_freed_blocks (block_t start, unsigned long count);
>  
> +/**
> + * Marks the calling thread as running a pager callback (ON = 1) or done
> + * with it (ON = 0).  A pager thread must never wait on the journal: an
> + * RPC thread inside a transaction may be waiting on it for a page.  While
> + * marked, diskfs_journal_start_transaction joins the transaction that a
> + * commit is draining instead of waiting for its successor.
> + */
> +void journal_thread_set_pager (int on);
> +
>  #endif //_JOURNAL_H
> diff --git a/ext2fs/pager.c b/ext2fs/pager.c
> index cba908427..42941150e 100644
> --- a/ext2fs/pager.c
> +++ b/ext2fs/pager.c
> @@ -743,6 +743,10 @@ pager_unlock_page (struct user_pager_info *pager, 
> vm_offset_t page)
>        struct node *node = pager->node;
>        struct disknode *dn = diskfs_node_disknode (node);
>  
> +      /* Block allocation below starts a journal transaction.  Mark this
> +      thread so that start never waits on a draining commit.  */
> +      journal_thread_set_pager (1);
> +
>        pthread_rwlock_wrlock (&dn->alloc_lock);
>  
>        partial_page = (page + vm_page_size > node->allocsize);
> @@ -787,6 +791,8 @@ pager_unlock_page (struct user_pager_info *pager, 
> vm_offset_t page)
>  
>        pthread_rwlock_unlock (&dn->alloc_lock);
>  
> +      journal_thread_set_pager (0);
> +
>        if (err == ENOSPC)
>       ext2_warning ("This filesystem is out of space.");
>        else if (err)
> @@ -974,7 +980,11 @@ pager_clear_user_data (struct user_pager_info *upi)
>        assert_backtrace (!pager || pager_get_upi (pager) != upi);
>        pthread_spin_unlock (&node_to_page_lock);
>  
> +      /* Dropping the last reference can write the node back, which starts
> +      a journal transaction on this pager thread.  */
> +      journal_thread_set_pager (1);
>        diskfs_nrele_light (upi->node);
> +      journal_thread_set_pager (0);
>      }
>  }
>  
> -- 
> 2.55.0
> 

-- 
Samuel
<c> hiri, le cri ici, c des marrants
<c> j'ai un rep ".uglyhackdirectorywithoutacls" ds mon home
 -+- #ens-mim en stage -+-

Reply via email to