Skip to content

[2026.10.7] lock: steal a free locker from another stripe before failing (recd015) - #256

Merged
gburd merged 2 commits into
lts/2026.10.7from
backport/2026.10.7/recd015-lockers
Nov 3, 2026
Merged

gburd merged 2 commits into
lts/2026.10.7from
backport/2026.10.7/recd015-lockers

Conversation

@gburd

@gburd gburd commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

[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 locker
regression 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_lockers reaches
st_maxlockers (= lk_max_lockers) 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. 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 the
backward pass. The fix steals a free locker from another stripe (held under
LOCK_LOCKERS) before failing; the stolen locker migrates one-way to the
stealing stripe. No region-format, soname or ABI change.

LTS gates (dev box, --enable-debug --enable-diagnostic --enable-test --enable-tcl)

  • L1 region signature — backport 0xe67a9de8 byte-identical to the
    lts/2026.10.7 base.
  • L2 soname / symbols / handle sizes — soname libdb-2026.0.so; exported
    symbols 1974 == 1974, identical to a clean LTS base built with the same
    configure; handle sizes identical (DB_ENV 2088). Helpers are static.
  • L3 FAIL-before / PASS-after —
    run_lock_stripe_exhaustion FAILs on the LTS base (BDB2055, ENOMEM at
    try 63) and PASSes on the backport. TCL recd015 -btree on the backport:
    0 fail-lines, Recd015.b.1: Running recovery ... complete.
    LTS diagnostic test/db tier: exit 0, 0 FAILED (33 runners),
    run_lock_stripe_exhaustion PASS.
  • L4 cherry-pick — -x trailer present
    (cherry picked from commit 6ff1561b1…).

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)
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

ABI diff vs v2026.10.7.1 (libabigail — authoritative)

abidiff exit bitmask: 0

Removed exported symbols (nm -D, _NNNN version suffix normalized)

None.


Advisory: libabigail/nm is the authoritative binary-ABI check; Coccinelle is complementary source-level early warning. See dist/cocci/README.md.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Coccinelle convention checks

No new violations. ✅

Resolved since baseline (3) -- update dist/cocci/baseline.txt to lock these in.
rule_mutex_unbalanced|MUTEX_UNBALANCED|src/btree/bt_curadj.c|return (ret);
rule_mutex_unbalanced|MUTEX_UNBALANCED|src/crypto/mersenne/mt19937db.c|return (ret);
rule_mutex_unbalanced|MUTEX_UNBALANCED|src/mp/mp_register.c|return (ret);

@gburd

gburd commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

Lead: the src hunk is byte-identical to #255, cherry-picked with -x, and the region signature is unchanged. The 4 red checks are the ubuntu-26.04 image problem (run_qam_extent_vrfy rc=124, timeout), already fixed for LTS by #248. Merge #248 first, then re-run. Merge-ready.

@gburd
gburd marked this pull request as ready for review November 3, 2026 22:57
@gburd
gburd merged commit e458b5e into lts/2026.10.7 Nov 3, 2026
67 checks passed
@gburd
gburd deleted the backport/2026.10.7/recd015-lockers branch November 3, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant