-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Major - P3
-
Affects Version/s: None
-
Component/s: Cache and Eviction
-
None
-
Storage Engines - Transactions
-
295.583
-
SE Transactions - 2026-07-31, SE Transactions - 2026-08-14
-
3
Problem
An application thread that enters the eviction assist at the end of __wt_txn_commit or
__wt_txn_rollback can be captured indefinitely. The assist loop only exits when the cache drops
back below its eviction triggers, and every other exit is inert for a caller in this position:
- __wt_txn_is_blocking declines the thread, because txn->mod_count is 0 once
__txn_release has run and no operation timer is set for an ordinary write. - cache_max_wait_ms defaults to 0 at both connection and session level, which means wait
forever. - The max_progress cap inside the loop is unreachable. It is gated on pct_full < 100.0, but
the pct_full that __wt_evict_needed returns is at or above 100 whenever any trigger is
exceeded, and an exceeded trigger is the only condition under which the loop runs. Every exit from
the loop is therefore via the "eviction no longer needed" clause.
When the dirty content cannot be reclaimed the loop never terminates. Under precise checkpoints
this is reachable and stable: the eviction walk skips pages whose newest commit timestamp is beyond
the pinned stable timestamp, and pages with mixed updates reconcile but restore the unstable
updates, so dirty bytes never fall.
Impact
The captured thread holds no transaction state, so nothing can be rolled back to relieve the
pressure, and it can no longer advance the timestamps that would make the cache reclaimable. In
MongoDB the oplog visibility trigger and the registered commit handlers both run after
WT_SESSION::commit_transaction returns, so a captured writer never publishes its write to
replication. With enough writers captured the majority commit point and the stable timestamp
freeze, which keeps the dirty content unreclaimable, and the node deadlocks permanently.
Fix
Add a bounded argument to the assist, set only by the two post-resolution call sites, that caps
the assist at 10ms. The bound is timed independently of session->cache_wait_us, which is shared
with the rest of the enclosing API call and is not tracked for internal sessions. A new statistic,
eviction_app_bounded_wait_exceeded, counts the releases. All other call sites keep their current
behaviour.
The pre-operation call sites are deliberately left unbounded. A thread blocked there holds no
snapshot, no oplog slot and no unpublished commit, so blocking it is the intended backpressure and
cannot stall the timestamps.
Out of scope
Eviction still applies backpressure against bytes that no amount of eviction can reclaim, so the
scrub churn and the recurring stalls remain. Excluding the dirty content beyond the stable
timestamp from the trigger accounting is the complementary fix and should be tracked separately.
updates_needed is not gated on busy, so an unreclaimable update-byte total can still capture
a thread at __cursor_enter. That is a pre-operation site where a bound is not obviously safe, and
it is not what SERVER-132391 observed.