From a65241a59f4032f9ea1ca886afdeb169786876df Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Tue, 30 Jun 2026 13:18:49 +0200 Subject: [PATCH] MDEV-40218 lock_rec_unlock_unmodified() can release a stale per-cell latch On a secondary index, lock_rec_unlock_unmodified() drops the cell latch and lock_sys.latch before calling lock_sec_rec_some_has_impl(), then re-acquires lock_sys.latch in shared mode, recomputes the cell address and latches the newly computed cell. A concurrent lock_sys_t::hash_table::resize() (rec_hash grows with the buffer pool) during that window reallocates the cell array, so the cell and its latch move. On success the function returned true while holding the latch of the new cell, but lock_release_on_prepare_try() then released the latch variable it had computed from the old cell address, which could be stale. Fix: make the cell parameter of lock_rec_unlock_unmodified() an in/out reference so the function reports the cell it currently holds, and have the CELL caller release that cell's latch. This reuses the cell address the function already recomputed, avoiding a second rec_hash lookup. --- storage/innobase/lock/lock0lock.cc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 40a09b3a79090..1cf4296f474a6 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -4534,7 +4534,9 @@ static void lock_rec_unlock(hash_cell_t &cell, lock_t *lock, ulint heap_no) /** Release locks to unmodified records on a clustered index page. @param block the block containing locked records -@param cell lock_sys.rec_hash cell of lock +@param cell lock_sys.rec_hash cell of lock; updated in place to the + currently held cell, which a concurrent + lock_sys_t::hash_table::resize() may have moved @param lock record lock @param offsets storage for rec_get_offsets() @tparam latch_type how the caller of the function latched lock_sys, @@ -4542,7 +4544,7 @@ static void lock_rec_unlock(hash_cell_t &cell, lock_t *lock, ulint heap_no) @return true if the cell was latched successfully or if latch_type is GLOBAL, false otherwise */ template -bool lock_rec_unlock_unmodified(buf_block_t *block, hash_cell_t *cell, +bool lock_rec_unlock_unmodified(buf_block_t *block, hash_cell_t *&cell, lock_t *lock, rec_offs *offsets) { DEBUG_SYNC_C("lock_rec_unlock_unmodified_start"); @@ -4716,7 +4718,12 @@ static bool lock_release_on_prepare_try(trx_t *trx, bool unlock_unmodified) offsets)) all_released= false; else - latch->release(); + /* lock_rec_unlock_unmodified() may have released and + re-acquired lock_sys, during which a concurrent + lock_sys_t::hash_table::resize() could move the cell. It + reports the currently held cell in cell, so release that + cell's latch rather than the now possibly stale latch. */ + lock_sys_t::hash_table::latch(cell)->release(); } else all_released= false;