Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 27 additions & 30 deletions src/lock/lock.c
Original file line number Diff line number Diff line change
Expand Up @@ -1255,38 +1255,35 @@ again: if (obj == NULL) {
if (lock_mode == DB_LOCK_WRITE &&
sh_off == sireadlp->holder) {
/*
* Upgrading our own SIREAD to WRITE: drop the
* SIREAD marker to avoid self-conflicts.
* Our own SIREAD marker on this object. A
* transaction never forms an rw-antidependency
* with itself, so skip it for conflict purposes --
* but DO NOT remove it. Conflict detection is at
* PAGE granularity: write-locking this page does
* NOT mean we are writing the same record we read.
* T may have READ key k1 on this page (leaving the
* marker) and now be WRITE-locking the page only to
* update a DIFFERENT key k2 that happens to share
* the leaf -- or merely because the B-tree write
* descent couples a write lock across this leaf.
* Dropping the marker here erased "T read this
* page", so a later writer of the page by ANOTHER
* transaction found no marker and the rw-edge
* T --rw--> W was never recorded -- a write skew
* across two leaves of a multi-level B-tree then
* committed silently (the SSI cross-page miss).
*
* Account for the detail reference the marker
* held, exactly as the other two removal sites
* (__lock_sicommit, __lock_siclean_obj) do and
* with the same td_off guard the grant used.
* Without it si_ref stays permanently above the
* true marker count, so the owning detail (and
* the locker deferring on it) can never be
* reclaimed: a snapshot txn that reads then
* writes the same key leaks a detail, a locker
* and their mutex slots on every iteration until
* txn_begin returns ENOMEM.
*
* This is the reader's OWN marker (sh_off ==
* holder), so the detail is its live, running
* transaction: si_ref cannot reach zero here and
* no reclaim can trigger underneath us.
* Keeping the marker is correct and does not leak:
* it persists on the object exactly like any other
* committed reader's marker and is reclaimed by the
* normal GC (__lock_sicommit detaches it at commit;
* __lock_siclean_obj frees it once its snapshot is
* no longer visible to any active reader), which is
* also what balances si_ref -- so the #137 accounting
* the old eager drop maintained is preserved by the
* ordinary path, not lost.
*/
SH_TAILQ_REMOVE(&sh_obj->sireaders,
sireadlp, links, __db_lock);
if (atomic_read_relaxed(&region->nsireaders) > 0)
(void)atomic_dec(env,
&region->nsireaders);
if (sh_locker->td_off != INVALID_ROFF)
(void)atomic_dec(env,
&LOCKER_TD(env, sh_locker)->si_ref);
if ((ret = __lock_freelock(lt, sireadlp,
LOCK_HOLDER(env, sireadlp),
DB_LOCK_UNLINK | DB_LOCK_FREE)) != 0)
goto err;
continue;
} else if (lock_mode == DB_LOCK_WRITE &&
sh_off != sireadlp->holder &&
(LOCK_OWNER(env, sireadlp)->status == TXN_RUNNING ||
Expand Down
5 changes: 5 additions & 0 deletions test/MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ isolation ssi_gc_pressure@serializable-default
isolation ssi_gc_pressure@serializable-1
isolation ssi_crash_pivot-default optional
isolation ssi_crash_pivot-1 optional
# ssi_multiread_skew is the regression gate for the cross-leaf write-skew fix
# (SSI keeps its own SIREAD marker instead of dropping it on a same-page write
# lock). It FAILed on every pre-fix build and must now PASS.
isolation ssi_multiread_skew-default
isolation ssi_multiread_skew-1
# The two SSI mechanism gates. ssi_gc_pressure runs at both levels (the
# snapshot pass is the anti-vacuity control). ssi_crash_pivot needs a
# DIAGNOSTIC build for its in-commit crash hook, so it is optional -- but the
Expand Down
21 changes: 20 additions & 1 deletion test/isolation/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ ISO_SSI_GATES=${ISO_SSI_GATES:-1}
ISO_PARTS=${ISO_PARTS:-"default 1"}

mkdir -p "$OUT"
for t in test_iso_anomaly test_ssi_gc_pressure test_ssi_crash_pivot; do
for t in test_iso_anomaly test_ssi_gc_pressure test_ssi_crash_pivot \
ssi_multiread_skew; do
# shellcheck disable=SC2086
$CC $CFLAGS "$HERE/$t.c" "$LIBDBA" $LDLIBS -o "$OUT/$t"
echo "built $OUT/$t"
Expand Down Expand Up @@ -152,6 +153,24 @@ run_ssi_gates() {
# "nobody invoked it" must not look the same to the gate.
hi_emit ssi_crash_pivot skip
fi

# Regression gate for the cross-leaf write-skew fix (SSI keeps its
# own SIREAD marker instead of dropping it on a same-page write
# lock). ssi_multiread_skew FAILed on every pre-fix build and must
# now PASS at every tree depth and timing.
echo "--- SSI gate: multi-level cross-leaf write skew prevented"\
"(parts=$PARTS_LABEL)"
mkdir -p "mrskew-$PARTS_LABEL"
if ( cd "mrskew-$PARTS_LABEL" && SMR_ATTEMPTS=40 SMR_EXTRA=0 \
SMR_FILLER=400 SMR_SEQUENTIAL=1 \
timeout "$ISO_TIMEOUT" ../ssi_multiread_skew ); then
hi_emit "ssi_multiread_skew-$PARTS_LABEL" pass
else
echo " FAIL: SSI admitted the multi-level write skew --"\
"the cross-leaf detection fix has regressed."
hi_emit "ssi_multiread_skew-$PARTS_LABEL" fail
_rc=1
fi
fi
return $_rc
}
Expand Down
Loading
Loading