diff options
author | Jon Olav Hauglid <jon.hauglid@sun.com> | 2010-02-11 11:23:39 +0100 |
---|---|---|
committer | Jon Olav Hauglid <jon.hauglid@sun.com> | 2010-02-11 11:23:39 +0100 |
commit | 3d6a89e7929292a8ca4c369d03e7d723045626b6 (patch) | |
tree | 61c2376a790a37e251fcfa30ba118b660e25e60d | |
parent | d7f203c79fdb25bef8756016efc63628187520cd (diff) | |
download | mariadb-git-3d6a89e7929292a8ca4c369d03e7d723045626b6.tar.gz |
Bug #45225 Locking: hang if drop table with no timeout
This patch introduces timeouts for metadata locks.
The timeout is specified in seconds using the new dynamic system
variable "lock_wait_timeout" which has both GLOBAL and SESSION
scopes. Allowed values range from 1 to 31536000 seconds (= 1 year).
The default value is 1 year.
The new server parameter "lock-wait-timeout" can be used to set
the default value parameter upon server startup.
"lock_wait_timeout" applies to all statements that use metadata locks.
These include DML and DDL operations on tables, views, stored procedures
and stored functions. They also include LOCK TABLES, FLUSH TABLES WITH
READ LOCK and HANDLER statements.
The patch also changes thr_lock.c code (table data locks used by MyISAM
and other simplistic engines) to use the same system variable.
InnoDB row locks are unaffected.
One exception to the handling of the "lock_wait_timeout" variable
is delayed inserts. All delayed inserts are executed with a timeout
of 1 year regardless of the setting for the global variable. As the
connection issuing the delayed insert gets no notification of
delayed insert timeouts, we want to avoid unnecessary timeouts.
It's important to note that the timeout value is used for each lock
acquired and that one statement can take more than one lock.
A statement can therefore block for longer than the lock_wait_timeout
value before reporting a timeout error. When lock timeout occurs,
ER_LOCK_WAIT_TIMEOUT is reported.
Test case added to lock_multi.test.
include/my_pthread.h:
Added macros for comparing two timespec structs.
include/thr_lock.h:
Introduced timeouts for thr_lock.c locks.
mysql-test/r/mysqld--help-notwin.result:
Updated result file with the new server variable.
mysql-test/r/mysqld--help-win.result:
Updated result file with the new server variable.
mysql-test/suite/sys_vars/r/lock_wait_timeout_basic.result:
Added basic test for the new server variable.
mysql-test/suite/sys_vars/t/lock_wait_timeout_basic.test:
Added basic test for the new server variable.
mysys/thr_lock.c:
Introduced timeouts for thr_lock.c locks.
sql/mdl.cc:
Introduced timeouts for metadata locks.
sql/mdl.h:
Introduced timeouts for metadata locks.
sql/sql_base.cc:
Introduced timeouts in tdc_wait_for_old_versions().
sql/sql_class.h:
Added new server variable lock_wait_timeout.
sql/sys_vars.cc:
Added new server variable lock_wait_timeout.
-rw-r--r-- | include/my_pthread.h | 40 | ||||
-rw-r--r-- | include/thr_lock.h | 12 | ||||
-rw-r--r-- | mysql-test/r/lock_multi.result | 88 | ||||
-rw-r--r-- | mysql-test/r/mysqld--help-notwin.result | 4 | ||||
-rw-r--r-- | mysql-test/r/mysqld--help-win.result | 4 | ||||
-rw-r--r-- | mysql-test/suite/sys_vars/r/lock_wait_timeout_basic.result | 177 | ||||
-rw-r--r-- | mysql-test/suite/sys_vars/t/lock_wait_timeout_basic.test | 213 | ||||
-rw-r--r-- | mysql-test/t/lock_multi.test | 161 | ||||
-rw-r--r-- | mysys/thr_lock.c | 32 | ||||
-rw-r--r-- | sql/lock.cc | 12 | ||||
-rw-r--r-- | sql/mdl.cc | 114 | ||||
-rw-r--r-- | sql/mdl.h | 14 | ||||
-rw-r--r-- | sql/sql_base.cc | 43 | ||||
-rw-r--r-- | sql/sql_class.h | 1 | ||||
-rw-r--r-- | sql/sql_delete.cc | 3 | ||||
-rw-r--r-- | sql/sql_insert.cc | 12 | ||||
-rw-r--r-- | sql/sql_show.cc | 4 | ||||
-rw-r--r-- | sql/sql_table.cc | 3 | ||||
-rw-r--r-- | sql/sys_vars.cc | 6 |
19 files changed, 855 insertions, 88 deletions
diff --git a/include/my_pthread.h b/include/my_pthread.h index 343bff6ba72..5402d3929de 100644 --- a/include/my_pthread.h +++ b/include/my_pthread.h @@ -102,6 +102,19 @@ struct timespec { (ABSTIME).max_timeout_msec= (long)((NSEC)/1000000); \ } +/** + Compare two timespec structs. + + @retval 1 If TS1 ends after TS2. + + @retval 0 If TS1 is equal to TS2. + + @retval -1 If TS1 ends before TS2. +*/ +#define cmp_timespec(TS1, TS2) \ + ((TS1.tv.i64 > TS2.tv.i64) ? 1 : \ + ((TS1.tv.i64 < TS2.tv.i64) ? -1 : 0)) + int win_pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_create(pthread_t *, const pthread_attr_t *, pthread_handler, void *); @@ -414,6 +427,33 @@ int my_pthread_mutex_trylock(pthread_mutex_t *mutex); #endif /* !set_timespec_nsec */ #endif /* HAVE_TIMESPEC_TS_SEC */ +/** + Compare two timespec structs. + + @retval 1 If TS1 ends after TS2. + + @retval 0 If TS1 is equal to TS2. + + @retval -1 If TS1 ends before TS2. +*/ +#ifdef HAVE_TIMESPEC_TS_SEC +#ifndef cmp_timespec +#define cmp_timespec(TS1, TS2) \ + ((TS1.ts_sec > TS2.ts_sec || \ + (TS1.ts_sec == TS2.ts_sec && TS1.ts_nsec > TS2.ts_nsec)) ? 1 : \ + ((TS1.ts_sec < TS2.ts_sec || \ + (TS1.ts_sec == TS2.ts_sec && TS1.ts_nsec < TS2.ts_nsec)) ? -1 : 0)) +#endif /* !cmp_timespec */ +#else +#ifndef cmp_timespec +#define cmp_timespec(TS1, TS2) \ + ((TS1.tv_sec > TS2.tv_sec || \ + (TS1.tv_sec == TS2.tv_sec && TS1.tv_nsec > TS2.tv_nsec)) ? 1 : \ + ((TS1.tv_sec < TS2.tv_sec || \ + (TS1.tv_sec == TS2.tv_sec && TS1.tv_nsec < TS2.tv_nsec)) ? -1 : 0)) +#endif /* !cmp_timespec */ +#endif /* HAVE_TIMESPEC_TS_SEC */ + /* safe_mutex adds checking to mutex for easier debugging */ #if defined(__NETWARE__) && !defined(SAFE_MUTEX_DETECT_DESTROY) diff --git a/include/thr_lock.h b/include/thr_lock.h index 3dc8857dcd0..527d1288fe4 100644 --- a/include/thr_lock.h +++ b/include/thr_lock.h @@ -156,10 +156,12 @@ void thr_lock_data_init(THR_LOCK *lock,THR_LOCK_DATA *data, void *status_param); enum enum_thr_lock_result thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, - enum thr_lock_type lock_type); + enum thr_lock_type lock_type, + ulong lock_wait_timeout); void thr_unlock(THR_LOCK_DATA *data); enum enum_thr_lock_result thr_multi_lock(THR_LOCK_DATA **data, - uint count, THR_LOCK_OWNER *owner); + uint count, THR_LOCK_OWNER *owner, + ulong lock_wait_timeout); void thr_multi_unlock(THR_LOCK_DATA **data,uint count); void thr_lock_merge_status(THR_LOCK_DATA **data, uint count); @@ -167,10 +169,12 @@ void thr_abort_locks(THR_LOCK *lock, my_bool upgrade_lock); my_bool thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread); void thr_print_locks(void); /* For debugging */ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, - enum thr_lock_type new_lock_type); + enum thr_lock_type new_lock_type, + ulong lock_wait_timeout); void thr_downgrade_write_lock(THR_LOCK_DATA *data, enum thr_lock_type new_lock_type); -my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data); +my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data, + ulong lock_wait_timeout); #ifdef __cplusplus } #endif diff --git a/mysql-test/r/lock_multi.result b/mysql-test/r/lock_multi.result index 7a5fb445edf..d67868e927a 100644 --- a/mysql-test/r/lock_multi.result +++ b/mysql-test/r/lock_multi.result @@ -348,3 +348,91 @@ commit; # Switching to connection 'default'. drop view v1; drop table t1; +# +# Bug#45225 Locking: hang if drop table with no timeout +# +# These tests also provide function coverage for the +# lock_wait_timeout server variable. +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (id int); +SET SESSION lock_wait_timeout= 1; +# +# Test 1: acquire exclusive lock +# +# Connection default +START TRANSACTION; +INSERT INTO t1 VALUES (1); +# Connection 2 +DROP TABLE t1; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +# Connection default +COMMIT; +# +# Test 2: upgrade shared lock +# +# Connection default +START TRANSACTION; +SELECT * FROM t1; +id +1 +# Connection 2 +ALTER TABLE t1 RENAME TO t2; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +# Connection default +COMMIT; +# +# Test 3: acquire shared lock +# +# Connection default +LOCK TABLE t1 WRITE; +# Connection 2 +INSERT INTO t1(id) VALUES (2); +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +# Connection default +UNLOCK TABLES; +# +# Test 4: table level locks +# +# Connection default +LOCK TABLE t1 READ; +# Connection 2 +INSERT INTO t1(id) VALUES(4); +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +# Connection default +UNLOCK TABLES; +# +# Test 5: Waiting on Table Definition Cache (TDC) +# +# Connection default +LOCK TABLE t1 READ; +# Connection con3 +# Sending: +FLUSH TABLES; +# Connection con2 +SELECT * FROM t1; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +# Connection default +UNLOCK TABLES; +# Connection con3 +# Reaping: FLUSH TABLES +# +# Test 6: Timeouts in I_S queries +# +# Connection default +CREATE TABLE t2 (id INT); +LOCK TABLE t2 WRITE; +# Connection con3 +# Sending: +DROP TABLE t1, t2; +# Connection con2 +SELECT table_name, table_comment FROM information_schema.tables +WHERE table_schema= 'test' AND table_name= 't1'; +table_name table_comment +t1 Lock wait timeout exceeded; try restarting transaction +# Connection default +UNLOCK TABLES; +# Connection con3 +# Reaping: DROP TABLE t1, t2 +# Connection default +# Cleanup diff --git a/mysql-test/r/mysqld--help-notwin.result b/mysql-test/r/mysqld--help-notwin.result index a66b722ba4b..1bb98581341 100644 --- a/mysql-test/r/mysqld--help-notwin.result +++ b/mysql-test/r/mysqld--help-notwin.result @@ -225,6 +225,9 @@ The following options may be given as the first argument: the week. --local-infile Enable LOAD DATA LOCAL INFILE (Defaults to on; use --skip-local-infile to disable.) + --lock-wait-timeout=# + Timeout in seconds to wait for a lock before returning an + error. -l, --log[=name] Log connections and queries to file (deprecated option, use --general-log/--general-log-file instead). --log-bin[=name] Log update queries in binary format. Optional (but @@ -839,6 +842,7 @@ lc-messages en_US lc-messages-dir MYSQL_SHAREDIR/ lc-time-names en_US local-infile TRUE +lock-wait-timeout 31536000 log-bin (No default value) log-bin-index (No default value) log-bin-trust-function-creators FALSE diff --git a/mysql-test/r/mysqld--help-win.result b/mysql-test/r/mysqld--help-win.result index e7048c71a48..e48d2a9faa7 100644 --- a/mysql-test/r/mysqld--help-win.result +++ b/mysql-test/r/mysqld--help-win.result @@ -224,6 +224,9 @@ The following options may be given as the first argument: the week. --local-infile Enable LOAD DATA LOCAL INFILE (Defaults to on; use --skip-local-infile to disable.) + --lock-wait-timeout=# + Timeout in seconds to wait for a lock before returning an + error. -l, --log[=name] Log connections and queries to file (deprecated option, use --general-log/--general-log-file instead). --log-bin[=name] Log update queries in binary format. Optional (but @@ -842,6 +845,7 @@ lc-messages en_US lc-messages-dir MYSQL_SHAREDIR/ lc-time-names en_US local-infile TRUE +lock-wait-timeout 31536000 log-bin (No default value) log-bin-index (No default value) log-bin-trust-function-creators FALSE diff --git a/mysql-test/suite/sys_vars/r/lock_wait_timeout_basic.result b/mysql-test/suite/sys_vars/r/lock_wait_timeout_basic.result new file mode 100644 index 00000000000..6d1e8d3bb59 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/lock_wait_timeout_basic.result @@ -0,0 +1,177 @@ +SET @start_global_value = @@global.lock_wait_timeout; +SELECT @start_global_value; +@start_global_value +31536000 +SET @start_session_value = @@session.lock_wait_timeout; +SELECT @start_session_value; +@start_session_value +31536000 +'#--------------------FN_DYNVARS_002_01-------------------------#' +SET @@global.lock_wait_timeout = 100; +SET @@global.lock_wait_timeout = DEFAULT; +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +31536000 +SET @@session.lock_wait_timeout = 200; +SET @@session.lock_wait_timeout = DEFAULT; +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +31536000 +'#--------------------FN_DYNVARS_002_02-------------------------#' +SET @@global.lock_wait_timeout = @start_global_value; +SELECT @@global.lock_wait_timeout = 31536000; +@@global.lock_wait_timeout = 31536000 +1 +SET @@session.lock_wait_timeout = @start_session_value; +SELECT @@session.lock_wait_timeout = 31536000; +@@session.lock_wait_timeout = 31536000 +1 +'#--------------------FN_DYNVARS_002_03-------------------------#' +SET @@global.lock_wait_timeout = 1; +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +1 +SET @@global.lock_wait_timeout = 60020; +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +60020 +SET @@global.lock_wait_timeout = 65535; +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +65535 +'#--------------------FN_DYNVARS_002_04-------------------------#' +SET @@session.lock_wait_timeout = 1; +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +1 +SET @@session.lock_wait_timeout = 50050; +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +50050 +SET @@session.lock_wait_timeout = 65535; +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +65535 +'#------------------FN_DYNVARS_002_05-----------------------#' +SET @@global.lock_wait_timeout = 0; +Warnings: +Warning 1292 Truncated incorrect lock_wait_timeout value: '0' +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +1 +SET @@global.lock_wait_timeout = -1024; +Warnings: +Warning 1292 Truncated incorrect lock_wait_timeout value: '-1024' +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +1 +SET @@global.lock_wait_timeout = 31536001; +Warnings: +Warning 1292 Truncated incorrect lock_wait_timeout value: '31536001' +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +31536000 +SET @@global.lock_wait_timeout = ON; +ERROR 42000: Incorrect argument type to variable 'lock_wait_timeout' +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +31536000 +SET @@global.lock_wait_timeout = OFF; +ERROR 42000: Incorrect argument type to variable 'lock_wait_timeout' +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +31536000 +SET @@global.lock_wait_timeout = test; +ERROR 42000: Incorrect argument type to variable 'lock_wait_timeout' +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +31536000 +SET @@session.lock_wait_timeout = 0; +Warnings: +Warning 1292 Truncated incorrect lock_wait_timeout value: '0' +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +1 +SET @@session.lock_wait_timeout = -2; +Warnings: +Warning 1292 Truncated incorrect lock_wait_timeout value: '-2' +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +1 +SET @@session.lock_wait_timeout = 31537000; +Warnings: +Warning 1292 Truncated incorrect lock_wait_timeout value: '31537000' +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +31536000 +SET @@session.lock_wait_timeout = ON; +ERROR 42000: Incorrect argument type to variable 'lock_wait_timeout' +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +31536000 +SET @@session.lock_wait_timeout = OFF; +ERROR 42000: Incorrect argument type to variable 'lock_wait_timeout' +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +31536000 +SET @@session.lock_wait_timeout = test; +ERROR 42000: Incorrect argument type to variable 'lock_wait_timeout' +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +31536000 +'#------------------FN_DYNVARS_002_06-----------------------#' +SELECT @@global.lock_wait_timeout = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES +WHERE VARIABLE_NAME='lock_wait_timeout'; +@@global.lock_wait_timeout = VARIABLE_VALUE +1 +'#------------------FN_DYNVARS_002_07-----------------------#' +SELECT @@session.lock_wait_timeout = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.SESSION_VARIABLES +WHERE VARIABLE_NAME='lock_wait_timeout'; +@@session.lock_wait_timeout = VARIABLE_VALUE +1 +'#------------------FN_DYNVARS_002_08-----------------------#' +SET @@global.lock_wait_timeout = TRUE; +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +1 +SET @@global.lock_wait_timeout = FALSE; +Warnings: +Warning 1292 Truncated incorrect lock_wait_timeout value: '0' +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +1 +'#---------------------FN_DYNVARS_001_09----------------------#' +SET @@global.lock_wait_timeout = 10; +SET @@session.lock_wait_timeout = 11; +SELECT @@lock_wait_timeout = @@global.lock_wait_timeout; +@@lock_wait_timeout = @@global.lock_wait_timeout +0 +'#---------------------FN_DYNVARS_001_10----------------------#' +SET @@lock_wait_timeout = 100; +SELECT @@lock_wait_timeout = @@local.lock_wait_timeout; +@@lock_wait_timeout = @@local.lock_wait_timeout +1 +SELECT @@local.lock_wait_timeout = @@session.lock_wait_timeout; +@@local.lock_wait_timeout = @@session.lock_wait_timeout +1 +'#---------------------FN_DYNVARS_001_11----------------------#' +SET lock_wait_timeout = 1; +SELECT @@lock_wait_timeout; +@@lock_wait_timeout +1 +SELECT local.lock_wait_timeout; +ERROR 42S02: Unknown table 'local' in field list +SELECT session.lock_wait_timeout; +ERROR 42S02: Unknown table 'session' in field list +SELECT lock_wait_timeout = @@session.lock_wait_timeout; +ERROR 42S22: Unknown column 'lock_wait_timeout' in 'field list' +SET @@global.lock_wait_timeout = @start_global_value; +SELECT @@global.lock_wait_timeout; +@@global.lock_wait_timeout +31536000 +SET @@session.lock_wait_timeout = @start_session_value; +SELECT @@session.lock_wait_timeout; +@@session.lock_wait_timeout +31536000 diff --git a/mysql-test/suite/sys_vars/t/lock_wait_timeout_basic.test b/mysql-test/suite/sys_vars/t/lock_wait_timeout_basic.test new file mode 100644 index 00000000000..770bda0e053 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/lock_wait_timeout_basic.test @@ -0,0 +1,213 @@ +############## mysql-test\t\lock_wait_timeout_basic.test ####################### +# # +# Variable Name: lock_wait_timeout # +# Scope: GLOBAL & SESSION # +# Access Type: Dynamic # +# Data Type: Numeric # +# Default Value: 1 # +# Range: 1 - 31536000 # +# # +# # +# Creation Date: 2010-02-08 # +# Author: Jon Olav Hauglid # +# # +# Description: Test Cases of Dynamic System Variable "lock_wait_timeout" # +# that checks behavior of this variable in the following ways # +# * Default Value # +# * Valid & Invalid values # +# * Scope & Access method # +# * Data Integrity # +# # +# Reference: http://dev.mysql.com/doc/refman/5.5/en/ # +# server-system-variables.html#option_mysqld_lock-wait-timeout # +# # +################################################################################ + +--source include/load_sysvars.inc + +##################################################################### +# START OF lock_wait_timeout TESTS # +##################################################################### + +############################################################# +# Save initial value # +############################################################# + +SET @start_global_value = @@global.lock_wait_timeout; +SELECT @start_global_value; +SET @start_session_value = @@session.lock_wait_timeout; +SELECT @start_session_value; + + +--echo '#--------------------FN_DYNVARS_002_01-------------------------#' +##################################################################### +# Display the DEFAULT value of lock_wait_timeout # +##################################################################### + +SET @@global.lock_wait_timeout = 100; +SET @@global.lock_wait_timeout = DEFAULT; +SELECT @@global.lock_wait_timeout; + +SET @@session.lock_wait_timeout = 200; +SET @@session.lock_wait_timeout = DEFAULT; +SELECT @@session.lock_wait_timeout; + + +--echo '#--------------------FN_DYNVARS_002_02-------------------------#' +##################################################################### +# Check the DEFAULT value of lock_wait_timeout # +##################################################################### + +SET @@global.lock_wait_timeout = @start_global_value; +SELECT @@global.lock_wait_timeout = 31536000; +SET @@session.lock_wait_timeout = @start_session_value; +SELECT @@session.lock_wait_timeout = 31536000; + + +--echo '#--------------------FN_DYNVARS_002_03-------------------------#' +############################################################################### +# Change the value of lock_wait_timeout to a valid value for GLOBAL Scope # +############################################################################### + +SET @@global.lock_wait_timeout = 1; +SELECT @@global.lock_wait_timeout; +SET @@global.lock_wait_timeout = 60020; +SELECT @@global.lock_wait_timeout; +SET @@global.lock_wait_timeout = 65535; +SELECT @@global.lock_wait_timeout; + + +--echo '#--------------------FN_DYNVARS_002_04-------------------------#' +############################################################################### +# Change the value of lock_wait_timeout to a valid value for SESSION Scope # +############################################################################### + +SET @@session.lock_wait_timeout = 1; +SELECT @@session.lock_wait_timeout; +SET @@session.lock_wait_timeout = 50050; +SELECT @@session.lock_wait_timeout; +SET @@session.lock_wait_timeout = 65535; +SELECT @@session.lock_wait_timeout; + + +--echo '#------------------FN_DYNVARS_002_05-----------------------#' +################################################################# +# Change the value of lock_wait_timeout to an invalid value # +################################################################# +# for global scope +SET @@global.lock_wait_timeout = 0; +SELECT @@global.lock_wait_timeout; +SET @@global.lock_wait_timeout = -1024; +SELECT @@global.lock_wait_timeout; +SET @@global.lock_wait_timeout = 31536001; +SELECT @@global.lock_wait_timeout; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.lock_wait_timeout = ON; +SELECT @@global.lock_wait_timeout; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.lock_wait_timeout = OFF; +SELECT @@global.lock_wait_timeout; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.lock_wait_timeout = test; +SELECT @@global.lock_wait_timeout; +# for session scope +SET @@session.lock_wait_timeout = 0; +SELECT @@session.lock_wait_timeout; +SET @@session.lock_wait_timeout = -2; +SELECT @@session.lock_wait_timeout; +SET @@session.lock_wait_timeout = 31537000; +SELECT @@session.lock_wait_timeout; + +--Error ER_WRONG_TYPE_FOR_VAR +SET @@session.lock_wait_timeout = ON; +SELECT @@session.lock_wait_timeout; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@session.lock_wait_timeout = OFF; +SELECT @@session.lock_wait_timeout; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@session.lock_wait_timeout = test; +SELECT @@session.lock_wait_timeout; + + + +--echo '#------------------FN_DYNVARS_002_06-----------------------#' +#################################################################### +# Check if the value in GLOBAL Table matches value in variable # +#################################################################### + +SELECT @@global.lock_wait_timeout = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES +WHERE VARIABLE_NAME='lock_wait_timeout'; + + +--echo '#------------------FN_DYNVARS_002_07-----------------------#' +#################################################################### +# Check if the value in SESSION Table matches value in variable # +#################################################################### + +SELECT @@session.lock_wait_timeout = VARIABLE_VALUE +FROM INFORMATION_SCHEMA.SESSION_VARIABLES +WHERE VARIABLE_NAME='lock_wait_timeout'; + + +--echo '#------------------FN_DYNVARS_002_08-----------------------#' +#################################################################### +# Check if TRUE and FALSE values can be used on variable # +#################################################################### + +SET @@global.lock_wait_timeout = TRUE; +SELECT @@global.lock_wait_timeout; +SET @@global.lock_wait_timeout = FALSE; +SELECT @@global.lock_wait_timeout; + + +--echo '#---------------------FN_DYNVARS_001_09----------------------#' +############################################################################### +# Check if global and session variables are independant of each other # +############################################################################### + +SET @@global.lock_wait_timeout = 10; +SET @@session.lock_wait_timeout = 11; +SELECT @@lock_wait_timeout = @@global.lock_wait_timeout; + + +--echo '#---------------------FN_DYNVARS_001_10----------------------#' +############################################################################## +# Check if accessing variable with SESSION,LOCAL and without SCOPE points # +# to same session variable # +############################################################################## + +SET @@lock_wait_timeout = 100; +SELECT @@lock_wait_timeout = @@local.lock_wait_timeout; +SELECT @@local.lock_wait_timeout = @@session.lock_wait_timeout; + + +--echo '#---------------------FN_DYNVARS_001_11----------------------#' +############################################################################### +# Check if lock_wait_timeout can be accessed with and without @@ sign # +############################################################################### + +SET lock_wait_timeout = 1; +SELECT @@lock_wait_timeout; +--Error ER_UNKNOWN_TABLE +SELECT local.lock_wait_timeout; +--Error ER_UNKNOWN_TABLE +SELECT session.lock_wait_timeout; +--Error ER_BAD_FIELD_ERROR +SELECT lock_wait_timeout = @@session.lock_wait_timeout; + + +#################################### +# Restore initial value # +#################################### + +SET @@global.lock_wait_timeout = @start_global_value; +SELECT @@global.lock_wait_timeout; +SET @@session.lock_wait_timeout = @start_session_value; +SELECT @@session.lock_wait_timeout; + + +################################################### +# END OF lock_wait_timeout TESTS # +################################################### + diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index 5b64f60b6eb..1080b44c448 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -877,5 +877,166 @@ drop view v1; drop table t1; +--echo # +--echo # Bug#45225 Locking: hang if drop table with no timeout +--echo # +--echo # These tests also provide function coverage for the +--echo # lock_wait_timeout server variable. +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (id int); + +connect(con2, localhost, root,,); +SET SESSION lock_wait_timeout= 1; + +--echo # +--echo # Test 1: acquire exclusive lock +--echo # + +--echo # Connection default +connection default; +START TRANSACTION; +INSERT INTO t1 VALUES (1); + +--echo # Connection 2 +connection con2; +--error ER_LOCK_WAIT_TIMEOUT +DROP TABLE t1; + +--echo # Connection default +connection default; +COMMIT; + +--echo # +--echo # Test 2: upgrade shared lock +--echo # + +--echo # Connection default +connection default; +START TRANSACTION; +SELECT * FROM t1; + +--echo # Connection 2 +connection con2; +--error ER_LOCK_WAIT_TIMEOUT +ALTER TABLE t1 RENAME TO t2; + +--echo # Connection default +connection default; +COMMIT; + +--echo # +--echo # Test 3: acquire shared lock +--echo # + +--echo # Connection default +connection default; +LOCK TABLE t1 WRITE; + +--echo # Connection 2 +connection con2; +--error ER_LOCK_WAIT_TIMEOUT +INSERT INTO t1(id) VALUES (2); + +--echo # Connection default +connection default; +UNLOCK TABLES; + +--echo # +--echo # Test 4: table level locks +--echo # + +--echo # Connection default +connection default; +LOCK TABLE t1 READ; + +--echo # Connection 2 +connection con2; +--error ER_LOCK_WAIT_TIMEOUT +INSERT INTO t1(id) VALUES(4); + +--echo # Connection default +connection default; +UNLOCK TABLES; + +--echo # +--echo # Test 5: Waiting on Table Definition Cache (TDC) +--echo # + +connect(con3, localhost, root); + +--echo # Connection default +connection default; +LOCK TABLE t1 READ; + +--echo # Connection con3 +connection con3; +--echo # Sending: +--send FLUSH TABLES + +--echo # Connection con2 +connection con2; +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE state = "Flushing tables" AND info = "FLUSH TABLES"; +--source include/wait_condition.inc +--error ER_LOCK_WAIT_TIMEOUT +SELECT * FROM t1; + +--echo # Connection default +connection default; +UNLOCK TABLES; + +--echo # Connection con3 +connection con3; +--echo # Reaping: FLUSH TABLES +--reap + +--echo # +--echo # Test 6: Timeouts in I_S queries +--echo # + +--echo # Connection default +connection default; +CREATE TABLE t2 (id INT); +LOCK TABLE t2 WRITE; + +--echo # Connection con3 +connection con3; +--echo # Sending: +--send DROP TABLE t1, t2 + +--echo # Connection con2 +connection con2; +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE state = "Waiting for table" AND info = "DROP TABLE t1, t2"; +--source include/wait_condition.inc +# Note: This query causes two timeouts. +# 1: try_acquire_high_prio_shared_mdl_lock on t1 +# 2: recover_from_failed_open on t1 +SELECT table_name, table_comment FROM information_schema.tables + WHERE table_schema= 'test' AND table_name= 't1'; + +--echo # Connection default +connection default; +UNLOCK TABLES; + +--echo # Connection con3 +connection con3; +--echo # Reaping: DROP TABLE t1, t2 +--reap + +--echo # Connection default +connection default; +--echo # Cleanup +disconnect con2; +disconnect con3; + + # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index 901620d3a93..2590654c672 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -389,13 +389,12 @@ static void wake_up_waiters(THR_LOCK *lock); static enum enum_thr_lock_result wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, - my_bool in_wait_list) + my_bool in_wait_list, ulong lock_wait_timeout) { struct st_my_thread_var *thread_var= my_thread_var; mysql_cond_t *cond= &thread_var->suspend; struct timespec wait_timeout; enum enum_thr_lock_result result= THR_LOCK_ABORTED; - my_bool can_deadlock= test(data->owner->info->n_cursors); const char *old_proc_info; DBUG_ENTER("wait_for_lock"); @@ -438,14 +437,10 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, old_proc_info= proc_info_hook(NULL, "Table lock", __func__, __FILE__, __LINE__); - if (can_deadlock) - set_timespec(wait_timeout, table_lock_wait_timeout); + set_timespec(wait_timeout, lock_wait_timeout); while (!thread_var->abort || in_wait_list) { - int rc= (can_deadlock ? - mysql_cond_timedwait(cond, &data->lock->mutex, - &wait_timeout) : - mysql_cond_wait(cond, &data->lock->mutex)); + int rc= mysql_cond_timedwait(cond, &data->lock->mutex, &wait_timeout); /* We must break the wait if one of the following occurs: - the connection has been aborted (!thread_var->abort), but @@ -517,7 +512,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, enum enum_thr_lock_result thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, - enum thr_lock_type lock_type) + enum thr_lock_type lock_type, ulong lock_wait_timeout) { THR_LOCK *lock=data->lock; enum enum_thr_lock_result result= THR_LOCK_SUCCESS; @@ -782,7 +777,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, goto end; } /* Can't get lock yet; Wait for it */ - DBUG_RETURN(wait_for_lock(wait_queue, data, 0)); + DBUG_RETURN(wait_for_lock(wait_queue, data, 0, lock_wait_timeout)); end: mysql_mutex_unlock(&lock->mutex); DBUG_RETURN(result); @@ -1041,7 +1036,8 @@ static void sort_locks(THR_LOCK_DATA **data,uint count) enum enum_thr_lock_result -thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) +thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner, + ulong lock_wait_timeout) { THR_LOCK_DATA **pos,**end; DBUG_ENTER("thr_multi_lock"); @@ -1051,7 +1047,8 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) /* lock everything */ for (pos=data,end=data+count; pos < end ; pos++) { - enum enum_thr_lock_result result= thr_lock(*pos, owner, (*pos)->type); + enum enum_thr_lock_result result= thr_lock(*pos, owner, (*pos)->type, + lock_wait_timeout); if (result != THR_LOCK_SUCCESS) { /* Aborted */ thr_multi_unlock(data,(uint) (pos-data)); @@ -1472,7 +1469,8 @@ void thr_downgrade_write_lock(THR_LOCK_DATA *in_data, /* Upgrade a WRITE_DELAY lock to a WRITE_LOCK */ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, - enum thr_lock_type new_lock_type) + enum thr_lock_type new_lock_type, + ulong lock_wait_timeout) { THR_LOCK *lock=data->lock; DBUG_ENTER("thr_upgrade_write_delay_lock"); @@ -1515,13 +1513,14 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, { check_locks(lock,"waiting for lock",0); } - DBUG_RETURN(wait_for_lock(&lock->write_wait,data,1)); + DBUG_RETURN(wait_for_lock(&lock->write_wait,data,1, lock_wait_timeout)); } /* downgrade a WRITE lock to a WRITE_DELAY lock if there is pending locks */ -my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data) +my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data, + ulong lock_wait_timeout) { THR_LOCK *lock=data->lock; enum thr_lock_type write_lock_type; @@ -1553,7 +1552,8 @@ my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data) free_all_read_locks(lock,0); mysql_mutex_unlock(&lock->mutex); - DBUG_RETURN(thr_upgrade_write_delay_lock(data, write_lock_type)); + DBUG_RETURN(thr_upgrade_write_delay_lock(data, write_lock_type, + lock_wait_timeout)); } diff --git a/sql/lock.cc b/sql/lock.cc index 97756893e2b..fa58e43f6bb 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -336,7 +336,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, rc= thr_lock_errno_to_mysql[(int) thr_multi_lock(sql_lock->locks + sql_lock->lock_count, sql_lock->lock_count, - thd->lock_id)]; + thd->lock_id, + thd->variables.lock_wait_timeout)]; if (rc > 1) /* a timeout or a deadlock */ { if (sql_lock->table_count) @@ -983,7 +984,8 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) mdl_requests.push_front(&global_request); - if (thd->mdl_context.acquire_locks(&mdl_requests)) + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) return 1; return 0; @@ -1055,7 +1057,8 @@ bool lock_routine_name(THD *thd, bool is_function, mdl_requests.push_front(&mdl_request); mdl_requests.push_front(&global_request); - if (thd->mdl_context.acquire_locks(&mdl_requests)) + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) return TRUE; DEBUG_SYNC(thd, "after_wait_locked_pname"); @@ -1258,7 +1261,8 @@ bool Global_read_lock::lock_global_read_lock(THD *thd) MDL_SHARED)); mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED); - if (thd->mdl_context.acquire_lock(&mdl_request)) + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) { /* Our thread was killed -- return back to initial state. */ mysql_mutex_lock(&LOCK_global_read_lock); diff --git a/sql/mdl.cc b/sql/mdl.cc index e48ae8a3f59..245d6d8a018 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -772,45 +772,25 @@ static inline void mdl_exit_cond(THD *thd, } -MDL_context::mdl_signal_type MDL_context::wait() +MDL_context::mdl_signal_type MDL_context::timed_wait(struct timespec + *abs_timeout) { const char *old_msg; - st_my_thread_var *mysys_var= my_thread_var; - mdl_signal_type result; - - mysql_mutex_lock(&m_signal_lock); - - old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_signal_cond, &m_signal_lock); - - while (! m_signal && !mysys_var->abort) - mysql_cond_wait(&m_signal_cond, &m_signal_lock); - - result= m_signal; - - MDL_EXIT_COND(m_thd, mysys_var, &m_signal_lock, old_msg); - - return result; -} - - -MDL_context::mdl_signal_type MDL_context::timed_wait(ulong timeout) -{ - struct timespec abstime; - const char *old_msg; mdl_signal_type result; st_my_thread_var *mysys_var= my_thread_var; + int wait_result= 0; mysql_mutex_lock(&m_signal_lock); old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_signal_cond, &m_signal_lock); - if (! m_signal) - { - set_timespec(abstime, timeout); - mysql_cond_timedwait(&m_signal_cond, &m_signal_lock, &abstime); - } + while (!m_signal && !mysys_var->abort && + wait_result != ETIMEDOUT && wait_result != ETIME) + wait_result= mysql_cond_timedwait(&m_signal_cond, &m_signal_lock, + abs_timeout); - result= (m_signal != NO_WAKE_UP) ? m_signal : TIMEOUT_WAKE_UP; + result= (m_signal != NO_WAKE_UP || mysys_var->abort) ? + m_signal : TIMEOUT_WAKE_UP; MDL_EXIT_COND(m_thd, mysys_var, &m_signal_lock, old_msg); @@ -1197,15 +1177,17 @@ MDL_context::find_ticket(MDL_request *mdl_request, @param mdl_request [in/out] Lock request object for lock to be acquired + @param lock_wait_timeout [in] Seconds to wait before timeout. + @retval FALSE Success. MDL_request::ticket points to the ticket for the lock. @retval TRUE Failure (Out of resources or waiting is aborted), */ bool -MDL_context::acquire_lock(MDL_request *mdl_request) +MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) { - return acquire_lock_impl(mdl_request); + return acquire_lock_impl(mdl_request, lock_wait_timeout); } @@ -1397,6 +1379,8 @@ void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) @param mdl_request Request for the lock to be acqured. + @param lock_wait_timeout Seconds to wait before timeout. + @note Should not be used outside of MDL subsystem. Instead one should call acquire_lock() or acquire_locks() methods which ensure that conditions for deadlock-free @@ -1406,13 +1390,17 @@ void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) @retval TRUE Failure */ -bool MDL_context::acquire_lock_impl(MDL_request *mdl_request) +bool MDL_context::acquire_lock_impl(MDL_request *mdl_request, + ulong lock_wait_timeout) { MDL_lock *lock; MDL_ticket *ticket; bool not_used; st_my_thread_var *mysys_var= my_thread_var; MDL_key *key= &mdl_request->key; + struct timespec abs_timeout; + struct timespec abs_shortwait; + set_timespec(abs_timeout, lock_wait_timeout); mysql_mutex_assert_not_owner(&LOCK_open); @@ -1464,16 +1452,31 @@ bool MDL_context::acquire_lock_impl(MDL_request *mdl_request) /* There is a shared or exclusive lock on the object. */ DEBUG_SYNC(m_thd, "mdl_acquire_lock_wait"); - bool is_deadlock= (find_deadlock() || timed_wait(1) == VICTIM_WAKE_UP); + bool is_deadlock= find_deadlock(); + bool is_timeout= FALSE; + if (!is_deadlock) + { + set_timespec(abs_shortwait, 1); + bool timeout_is_near= cmp_timespec(abs_shortwait, abs_timeout) > 0; + mdl_signal_type wait_result= + timed_wait(timeout_is_near ? &abs_timeout : &abs_shortwait); + + if (timeout_is_near && wait_result == TIMEOUT_WAKE_UP) + is_timeout= TRUE; + else if (wait_result == VICTIM_WAKE_UP) + is_deadlock= TRUE; + } stop_waiting(); - if (is_deadlock || mysys_var->abort) + if (mysys_var->abort || is_deadlock || is_timeout) { lock->remove_ticket(&MDL_lock::m_waiting, ticket); MDL_ticket::destroy(ticket); if (is_deadlock) my_error(ER_LOCK_DEADLOCK, MYF(0)); + else if (is_timeout) + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); return TRUE; } rw_wrlock(&lock->m_rwlock); @@ -1513,6 +1516,8 @@ extern "C" int mdl_request_ptr_cmp(const void* ptr1, const void* ptr2) @param mdl_requests List of requests for locks to be acquired. + @param lock_wait_timeout Seconds to wait before timeout. + @note The list of requests should not contain non-exclusive lock requests. There should not be any acquired locks in the context. @@ -1522,7 +1527,8 @@ extern "C" int mdl_request_ptr_cmp(const void* ptr1, const void* ptr2) @retval TRUE Failure */ -bool MDL_context::acquire_locks(MDL_request_list *mdl_requests) +bool MDL_context::acquire_locks(MDL_request_list *mdl_requests, + ulong lock_wait_timeout) { MDL_request_list::Iterator it(*mdl_requests); MDL_request **sort_buf, **p_req; @@ -1552,7 +1558,7 @@ bool MDL_context::acquire_locks(MDL_request_list *mdl_requests) for (p_req= sort_buf; p_req < sort_buf + req_count; p_req++) { - if (acquire_lock_impl(*p_req)) + if (acquire_lock_impl(*p_req, lock_wait_timeout)) goto err; } my_free(sort_buf, MYF(0)); @@ -1578,6 +1584,8 @@ err: Used in ALTER TABLE, when a copy of the table with the new definition has been constructed. + @param lock_wait_timeout Seconds to wait before timeout. + @note In case of failure to upgrade lock (e.g. because upgrader was killed) leaves lock in its original state (locked in shared mode). @@ -1592,7 +1600,8 @@ err: */ bool -MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket) +MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, + ulong lock_wait_timeout) { MDL_request mdl_xlock_request; MDL_ticket *mdl_svp= mdl_savepoint(); @@ -1614,7 +1623,7 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket) mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE); - if (acquire_lock_impl(&mdl_xlock_request)) + if (acquire_lock_impl(&mdl_xlock_request, lock_wait_timeout)) DBUG_RETURN(TRUE); is_new_ticket= ! has_lock(mdl_svp, mdl_xlock_request.ticket); @@ -1803,21 +1812,25 @@ bool MDL_context::find_deadlock() Does not acquire the locks! + @param lock_wait_timeout Seconds to wait before timeout. + @retval FALSE Success. One can try to obtain metadata locks. @retval TRUE Failure (thread was killed or deadlock is possible). */ bool -MDL_context::wait_for_lock(MDL_request *mdl_request) +MDL_context::wait_for_lock(MDL_request *mdl_request, ulong lock_wait_timeout) { MDL_lock *lock; st_my_thread_var *mysys_var= my_thread_var; + struct timespec abs_timeout; + set_timespec(abs_timeout, lock_wait_timeout); mysql_mutex_assert_not_owner(&LOCK_open); DBUG_ASSERT(mdl_request->ticket == NULL); - while (!mysys_var->abort) + while (TRUE) { /* We have to check if there are some HANDLERs open by this thread @@ -1860,19 +1873,32 @@ MDL_context::wait_for_lock(MDL_request *mdl_request) set_deadlock_weight(MDL_DEADLOCK_WEIGHT_DML); will_wait_for(pending_ticket); - bool is_deadlock= (find_deadlock() || wait() == VICTIM_WAKE_UP); + bool is_deadlock= find_deadlock(); + bool is_timeout= FALSE; + if (!is_deadlock) + { + mdl_signal_type wait_result= timed_wait(&abs_timeout); + if (wait_result == TIMEOUT_WAKE_UP) + is_timeout= TRUE; + else if (wait_result == VICTIM_WAKE_UP) + is_deadlock= TRUE; + } stop_waiting(); lock->remove_ticket(&MDL_lock::m_waiting, pending_ticket); MDL_ticket::destroy(pending_ticket); - if (is_deadlock) + + if (mysys_var->abort || is_deadlock || is_timeout) { - my_error(ER_LOCK_DEADLOCK, MYF(0)); + if (is_deadlock) + my_error(ER_LOCK_DEADLOCK, MYF(0)); + else if (is_timeout) + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); return TRUE; } } - return mysys_var->abort; + return TRUE; } diff --git a/sql/mdl.h b/sql/mdl.h index 05a0b44dc9b..d43548fb65f 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -466,13 +466,14 @@ public: void destroy(); bool try_acquire_lock(MDL_request *mdl_request); - bool acquire_lock(MDL_request *mdl_request); - bool acquire_locks(MDL_request_list *requests); - bool upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket); + bool acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout); + bool acquire_locks(MDL_request_list *requests, ulong lock_wait_timeout); + bool upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, + ulong lock_wait_timeout); bool clone_ticket(MDL_request *mdl_request); - bool wait_for_lock(MDL_request *mdl_request); + bool wait_for_lock(MDL_request *mdl_request, ulong lock_wait_timeout); void release_all_locks_for_name(MDL_ticket *ticket); void release_lock(MDL_ticket *ticket); @@ -644,7 +645,7 @@ private: MDL_ticket *find_ticket(MDL_request *mdl_req, bool *is_transactional); void release_locks_stored_before(MDL_ticket *sentinel); - bool acquire_lock_impl(MDL_request *mdl_request); + bool acquire_lock_impl(MDL_request *mdl_request, ulong lock_wait_timeout); bool find_deadlock(); @@ -680,8 +681,7 @@ private: mysql_mutex_unlock(&m_signal_lock); } - mdl_signal_type wait(); - mdl_signal_type timed_wait(ulong timeout); + mdl_signal_type timed_wait(struct timespec *abs_timeout); mdl_signal_type peek_signal() { diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 1564838548f..ae5c5803545 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2148,7 +2148,8 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, table->s->table_name.str, (ulong) table->s, table->db_stat, table->s->version)); - if (thd->mdl_context.upgrade_shared_lock_to_exclusive(table->mdl_ticket)) + if (thd->mdl_context.upgrade_shared_lock_to_exclusive( + table->mdl_ticket, thd->variables.lock_wait_timeout)) DBUG_RETURN(TRUE); mysql_mutex_lock(&LOCK_open); @@ -2362,7 +2363,8 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, mdl_requests.push_front(mdl_request); mdl_requests.push_front(global_request); - if (thd->mdl_context.acquire_locks(&mdl_requests)) + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) return 1; } else @@ -3843,7 +3845,8 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, switch (m_action) { case OT_WAIT_MDL_LOCK: - result= thd->mdl_context.wait_for_lock(mdl_request); + result= thd->mdl_context.wait_for_lock(mdl_request, + thd->variables.lock_wait_timeout); break; case OT_WAIT_TDC: result= tdc_wait_for_old_versions(thd, &m_mdl_requests); @@ -3862,7 +3865,9 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, mdl_requests.push_front(&mdl_xlock_request); mdl_requests.push_front(&mdl_global_request); - if ((result= thd->mdl_context.acquire_locks(&mdl_requests))) + if ((result= + thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout))) break; DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); @@ -3893,7 +3898,9 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, mdl_requests.push_front(&mdl_xlock_request); mdl_requests.push_front(&mdl_global_request); - if ((result= thd->mdl_context.acquire_locks(&mdl_requests))) + if ((result= + thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout))) break; DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); @@ -4381,7 +4388,8 @@ open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, mdl_requests.push_front(global_request); } - if (thd->mdl_context.acquire_locks(&mdl_requests)) + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) return TRUE; for (table= tables_start; table && table != tables_end; @@ -8530,6 +8538,9 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests) TABLE_SHARE *share; const char *old_msg; MDL_request *mdl_request; + struct timespec abstime; + set_timespec(abstime, thd->variables.lock_wait_timeout); + int wait_result= 0; while (!thd->killed) { @@ -8557,15 +8568,31 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests) } if (!mdl_request) { + /* + Reset wait_result here in case this was the final check + after getting a timeout from mysql_cond_timedwait(). + */ + wait_result= 0; + mysql_mutex_unlock(&LOCK_open); + break; + } + if (wait_result == ETIMEDOUT || wait_result == ETIME) + { + /* + Test for timeout here instead of right after mysql_cond_timedwait(). + This allows for a final iteration and a final check before reporting + ER_LOCK_WAIT_TIMEOUT. + */ mysql_mutex_unlock(&LOCK_open); + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); break; } old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table"); - mysql_cond_wait(&COND_refresh, &LOCK_open); + wait_result= mysql_cond_timedwait(&COND_refresh, &LOCK_open, &abstime); /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */ thd->exit_cond(old_msg); } - return thd->killed; + return thd->killed || wait_result == ETIMEDOUT || wait_result == ETIME; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 9e33bea25c0..7c935d376f9 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -342,6 +342,7 @@ typedef struct system_variables ulong auto_increment_increment, auto_increment_offset; ulong bulk_insert_buff_size; ulong join_buff_size; + ulong lock_wait_timeout; ulong max_allowed_packet; ulong max_error_count; ulong max_length_for_sort_data; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 0f5d51f924d..ea466da8ea1 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1210,7 +1210,8 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) mdl_requests.push_front(&mdl_request); mdl_requests.push_front(&mdl_global_request); - if (thd->mdl_context.acquire_locks(&mdl_requests)) + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) DBUG_RETURN(TRUE); has_mdl_lock= TRUE; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 7587a447445..45c9c0363dd 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1823,6 +1823,12 @@ public: */ thd.lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_DELAYED); thd.set_current_stmt_binlog_format_row_if_mixed(); + /* + Prevent changes to global.lock_wait_timeout from affecting + delayed insert threads as any timeouts in delayed inserts + are not communicated to the client. + */ + thd.variables.lock_wait_timeout= LONG_TIMEOUT; bzero((char*) &thd.net, sizeof(thd.net)); // Safety bzero((char*) &table_list, sizeof(table_list)); // Safety @@ -2708,7 +2714,8 @@ bool Delayed_insert::handle_inserts(void) table->use_all_columns(); thd_proc_info(&thd, "upgrading lock"); - if (thr_upgrade_write_delay_lock(*thd.lock->locks, delayed_lock)) + if (thr_upgrade_write_delay_lock(*thd.lock->locks, delayed_lock, + thd.variables.lock_wait_timeout)) { /* This can happen if thread is killed either by a shutdown @@ -2893,7 +2900,8 @@ bool Delayed_insert::handle_inserts(void) goto err; } query_cache_invalidate3(&thd, table, 1); - if (thr_reschedule_write_lock(*thd.lock->locks)) + if (thr_reschedule_write_lock(*thd.lock->locks, + thd.variables.lock_wait_timeout)) { /* This is not known to happen. */ my_error(ER_DELAYED_CANT_CHANGE_LOCK,MYF(ME_FATALERROR), diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 2a05cbc561d..2e1827f9a35 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3103,7 +3103,9 @@ try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, thd->mdl_context.try_acquire_lock(&table->mdl_request)) && !table->mdl_request.ticket && !can_deadlock) { - if ((error= thd->mdl_context.wait_for_lock(&table->mdl_request))) + if ((error= + thd->mdl_context.wait_for_lock(&table->mdl_request, + thd->variables.lock_wait_timeout))) break; } return error; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 30725d2035c..d6c656080b4 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4403,7 +4403,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, mdl_requests.push_front(&table_list->mdl_request); mdl_requests.push_front(&mdl_global_request); - if (thd->mdl_context.acquire_locks(&mdl_requests)) + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) DBUG_RETURN(0); has_mdl_lock= TRUE; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 73d01f5341d..085e8b8ee1e 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -853,6 +853,12 @@ static Sys_var_mybool Sys_local_infile( "local_infile", "Enable LOAD DATA LOCAL INFILE", GLOBAL_VAR(opt_local_infile), CMD_LINE(OPT_ARG), DEFAULT(TRUE)); +static Sys_var_ulong Sys_lock_wait_timeout( + "lock_wait_timeout", + "Timeout in seconds to wait for a lock before returning an error.", + SESSION_VAR(lock_wait_timeout), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(LONG_TIMEOUT), BLOCK_SIZE(1)); + #ifdef HAVE_MLOCKALL static Sys_var_mybool Sys_locked_in_memory( "locked_in_memory", |