summaryrefslogtreecommitdiffstats
path: root/storage/innobase/lock/lock0wait.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/lock/lock0wait.cc')
-rw-r--r--storage/innobase/lock/lock0wait.cc515
1 files changed, 515 insertions, 0 deletions
diff --git a/storage/innobase/lock/lock0wait.cc b/storage/innobase/lock/lock0wait.cc
new file mode 100644
index 00000000..e5f71e0b
--- /dev/null
+++ b/storage/innobase/lock/lock0wait.cc
@@ -0,0 +1,515 @@
+/*****************************************************************************
+
+Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2014, 2020, MariaDB Corporation.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; version 2 of the License.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
+
+*****************************************************************************/
+
+/**************************************************//**
+@file lock/lock0wait.cc
+The transaction lock system
+
+Created 25/5/2010 Sunny Bains
+*******************************************************/
+
+#define LOCK_MODULE_IMPLEMENTATION
+
+#include "univ.i"
+#include <mysql/service_thd_wait.h>
+#include <mysql/service_wsrep.h>
+
+#include "srv0mon.h"
+#include "que0que.h"
+#include "lock0lock.h"
+#include "row0mysql.h"
+#include "srv0start.h"
+#include "lock0priv.h"
+#include "srv0srv.h"
+
+/*********************************************************************//**
+Print the contents of the lock_sys_t::waiting_threads array. */
+static
+void
+lock_wait_table_print(void)
+/*=======================*/
+{
+ ut_ad(lock_wait_mutex_own());
+
+ const srv_slot_t* slot = lock_sys.waiting_threads;
+
+ for (ulint i = 0; i < srv_max_n_threads; i++, ++slot) {
+
+ fprintf(stderr,
+ "Slot %lu:"
+ " in use %lu, timeout %lu, time %lu\n",
+ (ulong) i,
+ (ulong) slot->in_use,
+ slot->wait_timeout,
+ (ulong) difftime(time(NULL), slot->suspend_time));
+ }
+}
+
+/*********************************************************************//**
+Release a slot in the lock_sys_t::waiting_threads. Adjust the array last pointer
+if there are empty slots towards the end of the table. */
+static
+void
+lock_wait_table_release_slot(
+/*=========================*/
+ srv_slot_t* slot) /*!< in: slot to release */
+{
+#ifdef UNIV_DEBUG
+ srv_slot_t* upper = lock_sys.waiting_threads + srv_max_n_threads;
+#endif /* UNIV_DEBUG */
+
+ lock_wait_mutex_enter();
+
+ ut_ad(slot->in_use);
+ ut_ad(slot->thr != NULL);
+ ut_ad(slot->thr->slot != NULL);
+ ut_ad(slot->thr->slot == slot);
+
+ /* Must be within the array boundaries. */
+ ut_ad(slot >= lock_sys.waiting_threads);
+ ut_ad(slot < upper);
+
+ /* Note: When we reserve the slot we use the trx_t::mutex to update
+ the slot values to change the state to reserved. Here we are using the
+ lock mutex to change the state of the slot to free. This is by design,
+ because when we query the slot state we always hold both the lock and
+ trx_t::mutex. To reduce contention on the lock mutex when reserving the
+ slot we avoid acquiring the lock mutex. */
+
+ lock_mutex_enter();
+
+ slot->thr->slot = NULL;
+ slot->thr = NULL;
+ slot->in_use = FALSE;
+
+ lock_mutex_exit();
+
+ /* Scan backwards and adjust the last free slot pointer. */
+ for (slot = lock_sys.last_slot;
+ slot > lock_sys.waiting_threads && !slot->in_use;
+ --slot) {
+ /* No op */
+ }
+
+ /* Either the array is empty or the last scanned slot is in use. */
+ ut_ad(slot->in_use || slot == lock_sys.waiting_threads);
+
+ lock_sys.last_slot = slot + 1;
+
+ /* The last slot is either outside of the array boundary or it's
+ on an empty slot. */
+ ut_ad(lock_sys.last_slot == upper || !lock_sys.last_slot->in_use);
+
+ ut_ad(lock_sys.last_slot >= lock_sys.waiting_threads);
+ ut_ad(lock_sys.last_slot <= upper);
+
+ lock_wait_mutex_exit();
+}
+
+/*********************************************************************//**
+Reserves a slot in the thread table for the current user OS thread.
+@return reserved slot */
+static
+srv_slot_t*
+lock_wait_table_reserve_slot(
+/*=========================*/
+ que_thr_t* thr, /*!< in: query thread associated
+ with the user OS thread */
+ ulong wait_timeout) /*!< in: lock wait timeout value */
+{
+ ulint i;
+ srv_slot_t* slot;
+
+ ut_ad(lock_wait_mutex_own());
+ ut_ad(trx_mutex_own(thr_get_trx(thr)));
+
+ slot = lock_sys.waiting_threads;
+
+ for (i = srv_max_n_threads; i--; ++slot) {
+ if (!slot->in_use) {
+ slot->in_use = TRUE;
+ slot->thr = thr;
+ slot->thr->slot = slot;
+
+ if (slot->event == NULL) {
+ slot->event = os_event_create(0);
+ ut_a(slot->event);
+ }
+
+ os_event_reset(slot->event);
+ slot->suspend_time = time(NULL);
+ slot->wait_timeout = wait_timeout;
+
+ if (slot == lock_sys.last_slot) {
+ ++lock_sys.last_slot;
+ }
+
+ ut_ad(lock_sys.last_slot
+ <= lock_sys.waiting_threads + srv_max_n_threads);
+ if (!lock_sys.timeout_timer_active) {
+ lock_sys.timeout_timer_active = true;
+ lock_sys.timeout_timer->set_time(1000, 0);
+ }
+ return(slot);
+ }
+ }
+
+ ib::error() << "There appear to be " << srv_max_n_threads << " user"
+ " threads currently waiting inside InnoDB, which is the upper"
+ " limit. Cannot continue operation. Before aborting, we print"
+ " a list of waiting threads.";
+ lock_wait_table_print();
+
+ ut_error;
+ return(NULL);
+}
+
+#ifdef WITH_WSREP
+/*********************************************************************//**
+check if lock timeout was for priority thread,
+as a side effect trigger lock monitor
+@param[in] trx transaction owning the lock
+@param[in] locked true if trx and lock_sys.mutex is ownd
+@return false for regular lock timeout */
+static
+bool
+wsrep_is_BF_lock_timeout(
+ const trx_t* trx,
+ bool locked = true)
+{
+ bool long_wait= (trx->error_state != DB_DEADLOCK &&
+ srv_monitor_timer && trx->is_wsrep() &&
+ wsrep_thd_is_BF(trx->mysql_thd, false));
+ bool was_wait= true;
+
+ DBUG_EXECUTE_IF("wsrep_instrument_BF_lock_wait",
+ was_wait=false; long_wait=true;);
+
+ if (long_wait) {
+ ib::info() << "WSREP: BF lock wait long for trx:" << trx->id
+ << " query: " << wsrep_thd_query(trx->mysql_thd);
+
+ if (!locked)
+ lock_mutex_enter();
+
+ ut_ad(lock_mutex_own());
+
+ trx_print_latched(stderr, trx, 3000);
+ /* Note this will release lock_sys mutex */
+ lock_print_info_all_transactions(stderr);
+
+ if (locked)
+ lock_mutex_enter();
+
+ return was_wait;
+ } else
+ return false;
+}
+#endif /* WITH_WSREP */
+
+/***************************************************************//**
+Puts a user OS thread to wait for a lock to be released. If an error
+occurs during the wait trx->error_state associated with thr is
+!= DB_SUCCESS when we return. DB_LOCK_WAIT_TIMEOUT and DB_DEADLOCK
+are possible errors. DB_DEADLOCK is returned if selective deadlock
+resolution chose this transaction as a victim. */
+void
+lock_wait_suspend_thread(
+/*=====================*/
+ que_thr_t* thr) /*!< in: query thread associated with the
+ user OS thread */
+{
+ srv_slot_t* slot;
+ trx_t* trx;
+ ulong lock_wait_timeout;
+
+ ut_a(lock_sys.timeout_timer.get());
+ trx = thr_get_trx(thr);
+
+ if (trx->mysql_thd != 0) {
+ DEBUG_SYNC_C("lock_wait_suspend_thread_enter");
+ }
+
+ /* InnoDB system transactions (such as the purge, and
+ incomplete transactions that are being rolled back after crash
+ recovery) will use the global value of
+ innodb_lock_wait_timeout, because trx->mysql_thd == NULL. */
+ lock_wait_timeout = trx_lock_wait_timeout_get(trx);
+
+ lock_wait_mutex_enter();
+
+ trx_mutex_enter(trx);
+
+ trx->error_state = DB_SUCCESS;
+
+ if (thr->state == QUE_THR_RUNNING) {
+
+ ut_ad(thr->is_active);
+
+ /* The lock has already been released or this transaction
+ was chosen as a deadlock victim: no need to suspend */
+
+ if (trx->lock.was_chosen_as_deadlock_victim) {
+
+ trx->error_state = DB_DEADLOCK;
+ trx->lock.was_chosen_as_deadlock_victim = false;
+ }
+
+ lock_wait_mutex_exit();
+ trx_mutex_exit(trx);
+ return;
+ }
+
+ ut_ad(!thr->is_active);
+
+ slot = lock_wait_table_reserve_slot(thr, lock_wait_timeout);
+
+ lock_wait_mutex_exit();
+ trx_mutex_exit(trx);
+
+ ulonglong start_time = 0;
+
+ if (thr->lock_state == QUE_THR_LOCK_ROW) {
+ srv_stats.n_lock_wait_count.inc();
+ srv_stats.n_lock_wait_current_count++;
+ start_time = my_interval_timer();
+ }
+
+ ulint lock_type = ULINT_UNDEFINED;
+
+ /* The wait_lock can be cleared by another thread when the
+ lock is released. But the wait can only be initiated by the
+ current thread which owns the transaction. Only acquire the
+ mutex if the wait_lock is still active. */
+ if (const lock_t* wait_lock = trx->lock.wait_lock) {
+ lock_mutex_enter();
+ wait_lock = trx->lock.wait_lock;
+ if (wait_lock) {
+ lock_type = lock_get_type_low(wait_lock);
+ }
+ lock_mutex_exit();
+ }
+
+ ulint had_dict_lock = trx->dict_operation_lock_mode;
+
+ switch (had_dict_lock) {
+ case 0:
+ break;
+ case RW_S_LATCH:
+ /* Release foreign key check latch */
+ row_mysql_unfreeze_data_dictionary(trx);
+
+ DEBUG_SYNC_C("lock_wait_release_s_latch_before_sleep");
+ break;
+ default:
+ /* There should never be a lock wait when the
+ dictionary latch is reserved in X mode. Dictionary
+ transactions should only acquire locks on dictionary
+ tables, not other tables. All access to dictionary
+ tables should be covered by dictionary
+ transactions. */
+ ut_error;
+ }
+
+ ut_a(trx->dict_operation_lock_mode == 0);
+
+ /* Suspend this thread and wait for the event. */
+
+ /* Unknown is also treated like a record lock */
+ if (lock_type == ULINT_UNDEFINED || lock_type == LOCK_REC) {
+ thd_wait_begin(trx->mysql_thd, THD_WAIT_ROW_LOCK);
+ } else {
+ ut_ad(lock_type == LOCK_TABLE);
+ thd_wait_begin(trx->mysql_thd, THD_WAIT_TABLE_LOCK);
+ }
+
+ os_event_wait(slot->event);
+
+ thd_wait_end(trx->mysql_thd);
+
+ /* After resuming, reacquire the data dictionary latch if
+ necessary. */
+
+ if (had_dict_lock) {
+
+ row_mysql_freeze_data_dictionary(trx);
+ }
+
+ double wait_time = difftime(time(NULL), slot->suspend_time);
+
+ /* Release the slot for others to use */
+
+ lock_wait_table_release_slot(slot);
+
+ if (thr->lock_state == QUE_THR_LOCK_ROW) {
+ const ulonglong finish_time = my_interval_timer();
+
+ if (finish_time >= start_time) {
+ const ulint diff_time = static_cast<ulint>
+ ((finish_time - start_time) / 1000);
+ srv_stats.n_lock_wait_time.add(diff_time);
+ /* Only update the variable if we successfully
+ retrieved the start and finish times. See Bug#36819. */
+ if (diff_time > lock_sys.n_lock_max_wait_time) {
+ lock_sys.n_lock_max_wait_time = diff_time;
+ }
+ /* Record the lock wait time for this thread */
+ thd_storage_lock_wait(trx->mysql_thd, diff_time);
+ }
+
+ srv_stats.n_lock_wait_current_count--;
+
+ DBUG_EXECUTE_IF("lock_instrument_slow_query_log",
+ os_thread_sleep(1000););
+ }
+
+ /* The transaction is chosen as deadlock victim during sleep. */
+ if (trx->error_state == DB_DEADLOCK) {
+ return;
+ }
+
+ if (lock_wait_timeout < 100000000
+ && wait_time > (double) lock_wait_timeout
+#ifdef WITH_WSREP
+ && (!trx->is_wsrep()
+ || (!wsrep_is_BF_lock_timeout(trx, false)
+ && trx->error_state != DB_DEADLOCK))
+#endif /* WITH_WSREP */
+ ) {
+
+ trx->error_state = DB_LOCK_WAIT_TIMEOUT;
+
+ MONITOR_INC(MONITOR_TIMEOUT);
+ }
+
+ if (trx_is_interrupted(trx)) {
+
+ trx->error_state = DB_INTERRUPTED;
+ }
+}
+
+/********************************************************************//**
+Releases a user OS thread waiting for a lock to be released, if the
+thread is already suspended. */
+void
+lock_wait_release_thread_if_suspended(
+/*==================================*/
+ que_thr_t* thr) /*!< in: query thread associated with the
+ user OS thread */
+{
+ ut_ad(lock_mutex_own());
+ ut_ad(trx_mutex_own(thr_get_trx(thr)));
+
+ /* We own both the lock mutex and the trx_t::mutex but not the
+ lock wait mutex. This is OK because other threads will see the state
+ of this slot as being in use and no other thread can change the state
+ of the slot to free unless that thread also owns the lock mutex. */
+
+ if (thr->slot != NULL && thr->slot->in_use && thr->slot->thr == thr) {
+ trx_t* trx = thr_get_trx(thr);
+
+ if (trx->lock.was_chosen_as_deadlock_victim) {
+
+ trx->error_state = DB_DEADLOCK;
+ trx->lock.was_chosen_as_deadlock_victim = false;
+ }
+
+ os_event_set(thr->slot->event);
+ }
+}
+
+/*********************************************************************//**
+Check if the thread lock wait has timed out. Release its locks if the
+wait has actually timed out. */
+static
+void
+lock_wait_check_and_cancel(
+/*=======================*/
+ const srv_slot_t* slot) /*!< in: slot reserved by a user
+ thread when the wait started */
+{
+ ut_ad(lock_wait_mutex_own());
+ ut_ad(slot->in_use);
+
+ double wait_time = difftime(time(NULL), slot->suspend_time);
+ trx_t* trx = thr_get_trx(slot->thr);
+
+ if (trx_is_interrupted(trx)
+ || (slot->wait_timeout < 100000000
+ && (wait_time > (double) slot->wait_timeout
+ || wait_time < 0))) {
+
+ /* Timeout exceeded or a wrap-around in system
+ time counter: cancel the lock request queued
+ by the transaction and release possible
+ other transactions waiting behind; it is
+ possible that the lock has already been
+ granted: in that case do nothing */
+
+ lock_mutex_enter();
+
+ trx_mutex_enter(trx);
+
+ if (trx->lock.wait_lock != NULL) {
+
+ ut_a(trx->lock.que_state == TRX_QUE_LOCK_WAIT);
+
+#ifdef WITH_WSREP
+ if (!wsrep_is_BF_lock_timeout(trx)) {
+#endif /* WITH_WSREP */
+ lock_cancel_waiting_and_release(trx->lock.wait_lock);
+#ifdef WITH_WSREP
+ }
+#endif /* WITH_WSREP */
+ }
+
+ lock_mutex_exit();
+
+ trx_mutex_exit(trx);
+ }
+}
+
+/** A task which wakes up threads whose lock wait may have lasted too long */
+void lock_wait_timeout_task(void*)
+{
+ lock_wait_mutex_enter();
+
+ /* Check all slots for user threads that are waiting
+ on locks, and if they have exceeded the time limit. */
+ bool any_slot_in_use= false;
+ for (srv_slot_t *slot= lock_sys.waiting_threads;
+ slot < lock_sys.last_slot; ++slot)
+ {
+ /* We are doing a read without the lock mutex and/or the trx
+ mutex. This is OK because a slot can't be freed or reserved
+ without the lock wait mutex. */
+ if (slot->in_use)
+ {
+ any_slot_in_use= true;
+ lock_wait_check_and_cancel(slot);
+ }
+ }
+
+ if (any_slot_in_use)
+ lock_sys.timeout_timer->set_time(1000, 0);
+ else
+ lock_sys.timeout_timer_active= false;
+
+ lock_wait_mutex_exit();
+}