diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 62f6fd58a1b6b..9dd92387b8d41 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -4529,6 +4529,58 @@ lock_rec_unlock( g.cell(), first_lock, heap_no); } +/** Spin budget for lock_release_try(), lock_release_on_prepare_try() and +lock_rec_unlock_unmodified() trylock attempts on per-cell and per-table +latches. + +Those paths hold trx->mutex while attempting the trylock (the reverse of +the standard order used by lock_rec_convert_impl_to_expl()), so blocking +acquisition would risk deadlock. A single CAS attempt fails too readily +under contention: any one failure across the trx's record/table locks +marks the whole try-pass as unsuccessful, and after 5 such passes +lock_release() escalates to lock_sys.wr_lock for the entire trx, which +then blocks every concurrent lock_sys.rd_lock acquirer in lock_rec_lock() +and lock_table(). + +A bounded spin (CAS, PAUSE between attempts, no syscall, no blocking) lets a transient +holder release without changing the deadlock-avoidance guarantee. The +upper bound on extra trx->mutex hold time is LOCK_RELEASE_TRY_SPIN_BUDGET +* pause-cost (tens to ~100ns per iteration, microarchitecture-dependent). */ +static constexpr unsigned LOCK_RELEASE_TRY_SPIN_BUDGET= 16; + +/** Try to acquire a lock_sys hash cell latch with a bounded spin. +The Latch template parameter avoids naming the type lock_sys_t::hash_latch, +which is private to lock_sys_t. Template deduction handles it. +@return whether the latch was acquired */ +template +static inline bool cell_latch_try_acquire_spin(Latch *latch) +{ + for (unsigned spin= LOCK_RELEASE_TRY_SPIN_BUDGET;;) + { + if (latch->try_acquire()) + return true; + if (spin <= 1) + return false; + --spin; + MY_RELAX_CPU(); + } +} + +/** Try to acquire a dict_table_t::lock_latch with a bounded spin. +@return whether the latch was acquired */ +static inline bool table_lock_mutex_try_acquire_spin(dict_table_t *table) +{ + for (unsigned spin= LOCK_RELEASE_TRY_SPIN_BUDGET;;) + { + if (table->lock_mutex_trylock()) + return true; + if (spin <= 1) + return false; + --spin; + MY_RELAX_CPU(); + } +} + /** Release the explicit locks of a committing transaction, and release possible other transactions waiting because of these locks. @return whether the operation succeeded */ @@ -4573,7 +4625,7 @@ static bool lock_release_try(trx_t *trx) auto &lock_hash= lock_sys.hash_get(lock->type_mode); auto cell= lock_hash.cell_get(lock->un_member.rec_lock.page_id.fold()); auto latch= lock_sys_t::hash_table::latch(cell); - if (!latch->try_acquire()) + if (!cell_latch_try_acquire_spin(latch)) all_released= false; else { @@ -4588,7 +4640,7 @@ static bool lock_release_try(trx_t *trx) ut_ad(table->id >= DICT_HDR_FIRST_ID || (lock->mode() != LOCK_IX && lock->mode() != LOCK_X) || trx->dict_operation || trx->was_dict_operation); - if (!table->lock_mutex_trylock()) + if (!table_lock_mutex_try_acquire_spin(table)) all_released= false; else { @@ -4831,7 +4883,8 @@ bool lock_rec_unlock_unmodified(buf_block_t *block, hash_cell_t *cell, computed cell address invalid */ cell= lock_sys.rec_hash.cell_get( lock->un_member.rec_lock.page_id.fold()); - if (!lock_sys_t::hash_table::latch(cell)->try_acquire()) + auto latch= lock_sys_t::hash_table::latch(cell); + if (!cell_latch_try_acquire_spin(latch)) return false; } if (lock->trx != impl_trx) @@ -4889,7 +4942,7 @@ static bool lock_release_on_prepare_try(trx_t *trx, bool unlock_unmodified) const auto fold = lock->un_member.rec_lock.page_id.fold(); auto cell= lock_sys.rec_hash.cell_get(fold); auto latch= lock_sys_t::hash_table::latch(cell); - if (latch->try_acquire()) + if (cell_latch_try_acquire_spin(latch)) { if (!rec_granted_exclusive_not_gap) { @@ -4926,7 +4979,7 @@ static bool lock_release_on_prepare_try(trx_t *trx, bool unlock_unmodified) computed cell address invalid */ cell= lock_sys.rec_hash.cell_get(fold); latch= lock_sys_t::hash_table::latch(cell); - if (latch->try_acquire()) + if (cell_latch_try_acquire_spin(latch)) { if (!lock_rec_unlock_unmodified(block, cell, lock, offsets)) @@ -4962,7 +5015,7 @@ static bool lock_release_on_prepare_try(trx_t *trx, bool unlock_unmodified) switch (lock->mode()) { case LOCK_IS: case LOCK_S: - if (table->lock_mutex_trylock()) + if (table_lock_mutex_try_acquire_spin(table)) { lock_table_dequeue(lock, false); table->lock_mutex_unlock();