diff options
Diffstat (limited to 'storage/innobase/lock/lock0wait.cc')
-rw-r--r-- | storage/innobase/lock/lock0wait.cc | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/storage/innobase/lock/lock0wait.cc b/storage/innobase/lock/lock0wait.cc new file mode 100644 index 00000000000..99059f19813 --- /dev/null +++ b/storage/innobase/lock/lock0wait.cc @@ -0,0 +1,517 @@ +/***************************************************************************** + +Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved. + +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, Suite 500, Boston, MA 02110-1335 USA + +*****************************************************************************/ + +/**************************************************//** +@file lock/lock0wait.cc +The transaction lock system + +Created 25/5/2010 Sunny Bains +*******************************************************/ + +#define LOCK_MODULE_IMPLEMENTATION + +#include "srv0mon.h" +#include "que0que.h" +#include "lock0lock.h" +#include "row0mysql.h" +#include "srv0start.h" +#include "ha_prototypes.h" +#include "lock0priv.h" + +UNIV_INTERN ibool srv_lock_timeout_active = FALSE; +UNIV_INTERN ulint srv_n_lock_wait_count = 0; +UNIV_INTERN ulint srv_n_lock_wait_current_count = 0; +UNIV_INTERN ib_int64_t srv_n_lock_wait_time = 0; +UNIV_INTERN ulint srv_n_lock_max_wait_time = 0; + +UNIV_INTERN os_event_t srv_timeout_event; + +/*********************************************************************//** +Print the contents of the lock_sys_t::waiting_threads array. */ +static +void +lock_wait_table_print(void) +/*=======================*/ +{ + ulint i; + const srv_slot_t* slot; + + ut_ad(lock_wait_mutex_own()); + + slot = lock_sys->waiting_threads; + + for (i = 0; i < OS_THREAD_MAX_N; i++, ++slot) { + + fprintf(stderr, + "Slot %lu: thread type %lu," + " in use %lu, susp %lu, timeout %lu, time %lu\n", + (ulong) i, + (ulong) slot->type, + (ulong) slot->in_use, + (ulong) slot->suspended, + slot->wait_timeout, + (ulong) difftime(ut_time(), 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 + OS_THREAD_MAX_N; +#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 = OS_THREAD_MAX_N; 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(NULL); + ut_a(slot->event); + } + + os_event_reset(slot->event); + slot->suspended = TRUE; + slot->suspend_time = ut_time(); + 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 + OS_THREAD_MAX_N); + + return(slot); + } + } + + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: There appear to be %lu user" + " threads currently waiting\n" + "InnoDB: inside InnoDB, which is the" + " upper limit. Cannot continue operation.\n" + "InnoDB: As a last thing, we print" + " a list of waiting threads.\n", (ulong) OS_THREAD_MAX_N); + + lock_wait_table_print(); + + ut_error; + return(NULL); +} + +/***************************************************************//** +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. */ +UNIV_INTERN +void +lock_wait_suspend_thread( +/*=====================*/ + que_thr_t* thr) /*!< in: query thread associated with the + user OS thread */ +{ + srv_slot_t* slot; + double wait_time; + trx_t* trx; + ulint had_dict_lock; + ibool was_declared_inside_innodb; + ib_int64_t start_time = 0; + ib_int64_t finish_time; + ulint sec; + ulint ms; + ulong lock_wait_timeout; + + 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); + + if (thr->lock_state == QUE_THR_LOCK_ROW) { + srv_n_lock_wait_count++; + srv_n_lock_wait_current_count++; + + if (ut_usectime(&sec, &ms) == -1) { + start_time = -1; + } else { + start_time = (ib_int64_t) sec * 1000000 + ms; + } + } + + /* Wake the lock timeout monitor thread, if it is suspended */ + + os_event_set(srv_timeout_event); + + lock_wait_mutex_exit(); + trx_mutex_exit(trx); + + 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); + 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. */ + + was_declared_inside_innodb = trx->declared_to_be_inside_innodb; + + if (was_declared_inside_innodb) { + /* We must declare this OS thread to exit InnoDB, since a + possible other thread holding a lock which this thread waits + for must be allowed to enter, sooner or later */ + + srv_conc_force_exit_innodb(trx); + } + + os_event_wait(slot->event); + + /* After resuming, reacquire the data dictionary latch if + necessary. */ + + if (was_declared_inside_innodb) { + + /* Return back inside InnoDB */ + + srv_conc_force_enter_innodb(trx); + } + + if (had_dict_lock) { + + row_mysql_freeze_data_dictionary(trx); + } + + wait_time = ut_difftime(ut_time(), slot->suspend_time); + + /* Release the slot for others to use */ + + lock_wait_table_release_slot(slot); + + if (thr->lock_state == QUE_THR_LOCK_ROW) { + ulint diff_time; + + if (ut_usectime(&sec, &ms) == -1) { + finish_time = -1; + } else { + finish_time = (ib_int64_t) sec * 1000000 + ms; + } + + diff_time = (ulint) (finish_time - start_time); + + srv_n_lock_wait_current_count--; + srv_n_lock_wait_time = srv_n_lock_wait_time + diff_time; + + if (diff_time > srv_n_lock_max_wait_time && + /* only update the variable if we successfully + retrieved the start and finish times. See Bug#36819. */ + start_time != -1 && finish_time != -1) { + srv_n_lock_max_wait_time = diff_time; + } + } + + if (lock_wait_timeout < 100000000 + && wait_time > (double) lock_wait_timeout) { + + 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. */ +UNIV_INTERN +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 */ +{ + trx_t* trx; + double wait_time; + ib_time_t suspend_time = slot->suspend_time; + + ut_ad(lock_wait_mutex_own()); + + ut_ad(slot->in_use); + + ut_ad(slot->suspended); + + wait_time = ut_difftime(ut_time(), suspend_time); + + 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) { + + ut_a(trx->lock.que_state == TRX_QUE_LOCK_WAIT); + + lock_cancel_waiting_and_release(trx->lock.wait_lock); + } + + lock_mutex_exit(); + + trx_mutex_exit(trx); + } + +} + +/*********************************************************************//** +A thread which wakes up threads whose lock wait may have lasted too long. +@return a dummy parameter */ +extern "C" UNIV_INTERN +os_thread_ret_t +DECLARE_THREAD(lock_wait_timeout_thread)( +/*=====================================*/ + void* arg __attribute__((unused))) + /* in: a dummy parameter required by + os_thread_create */ +{ + ib_int64_t sig_count = 0; + +#ifdef UNIV_PFS_THREAD + pfs_register_thread(srv_lock_timeout_thread_key); +#endif + srv_lock_timeout_active = TRUE; + + do { + srv_slot_t* slot; + + /* When someone is waiting for a lock, we wake up every second + and check if a timeout has passed for a lock wait */ + + os_event_wait_time_low(srv_timeout_event, 1000000, sig_count); + + if (srv_shutdown_state >= SRV_SHUTDOWN_CLEANUP) { + break; + } + + lock_wait_mutex_enter(); + + /* Check all slots for user threads that are waiting + on locks, and if they have exceeded the time limit. */ + + for (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) { + lock_wait_check_and_cancel(slot); + } + } + + sig_count = os_event_reset(srv_timeout_event); + + lock_wait_mutex_exit(); + + } while (srv_shutdown_state < SRV_SHUTDOWN_CLEANUP); + + srv_lock_timeout_active = FALSE; + + /* We count the number of threads in os_thread_exit(). A created + thread should always use that to exit and not use return() to exit. */ + + os_thread_exit(NULL); + + OS_THREAD_DUMMY_RETURN; +} |