Repository navigation
[2026.10.7] lock: steal a free locker from another stripe before failing (recd015) - #256
Merged
Merged
Conversation
P12 (2d8397d) gave each of the 64 locker stripes its own free list. An empty stripe refills with a batch of st_lockers >> 2 lockers capped by st_maxlockers - st_lockers and spread round-robin across all stripes. Once st_lockers reaches st_maxlockers (= lk_max_lockers) the cap is zero, so a create landing on a stripe whose free list is empty fails with "Lock table is out of available locker entries" even though other stripes still hold idle free lockers and the number of lockers actually in use is well below the configured limit. Recovery's backward pass creates one locker per prepared transaction; the id hash concentrates those creates on a few stripes, so a long prepare run hits the dry-stripe case and panics recovery. The TCL recd015 btree test ("prepare 10000 txns", lk_max_lockers 10005) fails on the backward pass with BDB2055 then DB_RUNRECOVERY. It passed on Berkeley DB 5.3.28 and on the commit just before P12 (e2d2790); it has failed since 2d8397d, shipped in v2026.10.5. Fix: in __lock_getlocker_int, when the stripe's free list is empty and the region cannot grow st_lockers any further, steal a free locker from another stripe before failing. This path already holds LOCK_LOCKERS (every stripe, exclusive), so every stripe's free list is safe to touch. The stolen locker's stripe is recomputed from its id when it is inserted below, which is the stealing stripe, so it joins this stripe's bucket chain and ulinks list and, when freed, returns to this stripe's free list -- a one-way migration, never a cross-stripe leak. With the P12 sharding switch off (locker_shard == 0) every locker lives on stripe 0 and the steal loop finds nothing, so behaviour is unchanged. No region-format change. Adds test/db/lock_stripe_exhaustion.c + run_lock_stripe_exhaustion.sh, a deterministic black-box regression test (public lock_id / lock_id_free only): fill the pool, free every locker off a target stripe so that stripe is dry while the region is full, then create on the target stripe. FAILs before the fix (ENOMEM), PASSes with it. (cherry picked from commit 6ff1561)
Contributor
ABI diff vs
|
Contributor
Coccinelle convention checksNo new violations. ✅ Resolved since baseline (3) -- update dist/cocci/baseline.txt to lock these in. |
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[2026.10.7] lock: steal a free locker from another stripe before failing (recd015)
LTS backport of the master fix (
cherry-pick -x). The P12 per-stripe lockerregression shipped in v2026.10.5, so the exhaustion is present on
lts/2026.10.7.See the master PR for the full root-cause writeup. In short: P12 gave each of
the 64 locker stripes its own free list; once
st_lockersreachesst_maxlockers(=lk_max_lockers) a create landing on a stripe whose freelist is empty fails with "Lock table is out of available locker entries" even
though other stripes still hold idle free lockers. Recovery's backward pass
concentrates locker creates on a few stripes (one per prepared txn), so
recd015 -btree("prepare 10000 txn",lk_max_lockers 10005) panicked on thebackward pass. The fix steals a free locker from another stripe (held under
LOCK_LOCKERS) before failing; the stolen locker migrates one-way to thestealing stripe. No region-format, soname or ABI change.
LTS gates (dev box,
--enable-debug --enable-diagnostic --enable-test --enable-tcl)0xe67a9de8byte-identical to thelts/2026.10.7base.libdb-2026.0.so; exportedsymbols 1974 == 1974, identical to a clean LTS base built with the same
configure; handle sizes identical (DB_ENV 2088). Helpers are static.
run_lock_stripe_exhaustionFAILs on the LTS base (BDB2055, ENOMEM attry 63) and PASSes on the backport. TCL
recd015 -btreeon the backport:0 fail-lines,Recd015.b.1: Running recovery ... complete.LTS diagnostic
test/dbtier: exit 0, 0 FAILED (33 runners),run_lock_stripe_exhaustionPASS.-xtrailer present(
cherry picked from commit 6ff1561b1…).