Commit 76643a46 authored by Davi Arnaut's avatar Davi Arnaut

Bug#56822: Add a thread state for sessions waiting on the query cache lock

The problem was that threads waiting on the query cache lock
are not easily seen due to the lack of a state indicating that
the thread is waiting on the said lock. This made it difficult
for users to quickly spot (for example, via SHOW PROCESSLIST)
a query cache contention problem.

The solution is to update the thread state when the query cache
lock needs to be acquired. Whenever the lock is to be acquired,
the thread state is updated to "Waiting for query cache lock"
and is reset once the lock is granted or the wait is interrupted.
The intention is to make query cache related hangs more evident.

To further investigate query cache related locking problems, one
may use PERFORMANCE_SCHEMA to track the overhead associated with
the locking bits and determine which particular lock is being a
contention point.
parent 8fceaa38
...@@ -5,20 +5,24 @@ drop table if exists t1; ...@@ -5,20 +5,24 @@ drop table if exists t1;
create table t1 (a varchar(100)); create table t1 (a varchar(100));
insert into t1 values ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'); insert into t1 values ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
Activate debug hook and attempt to retrieve the statement from the cache. Activate debug hook and attempt to retrieve the statement from the cache.
set session debug='+d,wait_in_query_cache_insert'; set debug_sync="wait_in_query_cache_insert SIGNAL parked WAIT_FOR go";
select SQL_CACHE * from t1;; select SQL_CACHE * from t1;;
set debug_sync="now WAIT_FOR parked";
On a second connection; clear the query cache. On a second connection; clear the query cache.
show status like 'Qcache_queries_in_cache'; show status like 'Qcache_queries_in_cache';
Variable_name Value Variable_name Value
Qcache_queries_in_cache 1 Qcache_queries_in_cache 1
set global query_cache_size= 0; set global query_cache_size= 0;
Signal the debug hook to release the lock. Signal the debug hook to release the lock.
select id from information_schema.processlist where state='wait_in_query_cache_insert' into @thread_id; set debug_sync="now SIGNAL go";
kill query @thread_id;
Show query cache status. Show query cache status.
show status like 'Qcache_queries_in_cache'; show status like 'Qcache_queries_in_cache';
Variable_name Value Variable_name Value
Qcache_queries_in_cache 0 Qcache_queries_in_cache 0
a
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
set debug_sync= 'RESET';
set global query_cache_size= 0; set global query_cache_size= 0;
use test; use test;
drop table t1; drop table t1;
...@@ -32,11 +36,12 @@ SET GLOBAL concurrent_insert= 1; ...@@ -32,11 +36,12 @@ SET GLOBAL concurrent_insert= 1;
SET GLOBAL query_cache_size= 1024*512; SET GLOBAL query_cache_size= 1024*512;
SET GLOBAL query_cache_type= ON; SET GLOBAL query_cache_type= ON;
# Switch to connection con1 # Switch to connection con1
SET SESSION debug='+d,wait_after_query_cache_invalidate'; SET DEBUG_SYNC = "wait_after_query_cache_invalidate SIGNAL parked WAIT_FOR go";
# Send concurrent insert, will wait in the query cache table invalidate # Send concurrent insert, will wait in the query cache table invalidate
INSERT INTO t1 VALUES (4); INSERT INTO t1 VALUES (4);
# Switch to connection default # Switch to connection default
# Wait for concurrent insert to reach the debug point # Wait for concurrent insert to reach the debug point
SET DEBUG_SYNC = "now WAIT_FOR parked";
# Switch to connection con2 # Switch to connection con2
# Send SELECT that shouldn't be cached # Send SELECT that shouldn't be cached
SELECT * FROM t1; SELECT * FROM t1;
...@@ -46,9 +51,7 @@ a ...@@ -46,9 +51,7 @@ a
3 3
# Switch to connection default # Switch to connection default
# Notify the concurrent insert to proceed # Notify the concurrent insert to proceed
SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST SET DEBUG_SYNC = "now SIGNAL go";
WHERE STATE = 'wait_after_query_cache_invalidate' INTO @thread_id;
KILL QUERY @thread_id;
# Switch to connection con1 # Switch to connection con1
# Gather insert result # Gather insert result
SHOW STATUS LIKE "Qcache_queries_in_cache"; SHOW STATUS LIKE "Qcache_queries_in_cache";
...@@ -66,6 +69,7 @@ Variable_name Value ...@@ -66,6 +69,7 @@ Variable_name Value
Qcache_queries_in_cache 1 Qcache_queries_in_cache 1
# Disconnect # Disconnect
# Restore defaults # Restore defaults
SET DEBUG_SYNC= 'RESET';
RESET QUERY CACHE; RESET QUERY CACHE;
DROP TABLE t1,t2; DROP TABLE t1,t2;
SET GLOBAL concurrent_insert= DEFAULT; SET GLOBAL concurrent_insert= DEFAULT;
...@@ -108,43 +112,44 @@ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ...@@ -108,43 +112,44 @@ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
** before the mutex lock in invalidate_table_internal. ** before the mutex lock in invalidate_table_internal.
** This will allow new result sets to be written into the QC. ** This will allow new result sets to be written into the QC.
** **
SET SESSION debug='+d,wait_in_query_cache_invalidate1'; SET DEBUG_SYNC="wait_in_query_cache_invalidate1 SIGNAL parked1_1 WAIT_FOR go1_1";
SET SESSION debug='+d,wait_in_query_cache_invalidate2'; SET DEBUG_SYNC="wait_in_query_cache_invalidate2 SIGNAL parked1_2 WAIT_FOR go1_2";
DELETE FROM t1 WHERE a like '%a%';; DELETE FROM t1 WHERE a like '%a%';;
=================================== Connection default =================================== Connection default
** Assert that the expect process status is obtained. ** Assert that the expect process status is obtained.
SET DEBUG_SYNC="now WAIT_FOR parked1_1";
** **
=================================== Connection thd2 =================================== Connection thd2
** On THD2: Insert a result into the cache. This attempt will be blocked ** On THD2: Insert a result into the cache. This attempt will be blocked
** because of a debug hook placed just before the mutex lock after which ** because of a debug hook placed just before the mutex lock after which
** the first part of the result set is written. ** the first part of the result set is written.
SET SESSION debug='+d,wait_in_query_cache_insert'; SET DEBUG_SYNC="wait_in_query_cache_insert SIGNAL parked2 WAIT_FOR go2";
SELECT SQL_CACHE * FROM t2 UNION SELECT * FROM t3; SELECT SQL_CACHE * FROM t2 UNION SELECT * FROM t3;
=================================== Connection thd3 =================================== Connection thd3
** On THD3: Insert another result into the cache and block on the same ** On THD3: Insert another result into the cache and block on the same
** debug hook. ** debug hook.
SET SESSION debug='+d,wait_in_query_cache_insert'; SET DEBUG_SYNC="wait_in_query_cache_insert SIGNAL parked3 WAIT_FOR go3";
SELECT SQL_CACHE * FROM t4 UNION SELECT * FROM t5;; SELECT SQL_CACHE * FROM t4 UNION SELECT * FROM t5;;
=================================== Connection default =================================== Connection default
** Assert that the two SELECT-stmt threads to reach the hook. ** Assert that the two SELECT-stmt threads to reach the hook.
SET DEBUG_SYNC="now WAIT_FOR parked2";
SET DEBUG_SYNC="now WAIT_FOR parked3";
** **
** **
** Signal the DELETE thread, THD1, to continue. It will enter the mutex ** Signal the DELETE thread, THD1, to continue. It will enter the mutex
** lock and set query cache status to TABLE_FLUSH_IN_PROGRESS and then ** lock and set query cache status to TABLE_FLUSH_IN_PROGRESS and then
** unlock the mutex before stopping on the next debug hook. ** unlock the mutex before stopping on the next debug hook.
SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_invalidate1' LIMIT 1 INTO @flush_thread_id; SET DEBUG_SYNC="now SIGNAL go1_1";
KILL QUERY @flush_thread_id;
** Assert that we reach the next debug hook. ** Assert that we reach the next debug hook.
SET DEBUG_SYNC="now WAIT_FOR parked1_2";
** **
** Signal the remaining debug hooks blocking THD2 and THD3. ** Signal the remaining debug hooks blocking THD2 and THD3.
** The threads will grab the guard mutex enter the wait condition and ** The threads will grab the guard mutex enter the wait condition and
** and finally release the mutex. The threads will continue to wait ** and finally release the mutex. The threads will continue to wait
** until a broadcast signal reaches them causing both threads to ** until a broadcast signal reaches them causing both threads to
** come alive and check the condition. ** come alive and check the condition.
SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_insert' ORDER BY id ASC LIMIT 1 INTO @thread_id; SET DEBUG_SYNC="now SIGNAL go2";
KILL QUERY @thread_id; SET DEBUG_SYNC="now SIGNAL go3";
SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_insert' ORDER BY id DESC LIMIT 1 INTO @thread_id;
KILL QUERY @thread_id;
** **
** Finally signal the DELETE statement on THD1 one last time. ** Finally signal the DELETE statement on THD1 one last time.
** The stmt will complete the query cache invalidation and return ** The stmt will complete the query cache invalidation and return
...@@ -152,8 +157,7 @@ KILL QUERY @thread_id; ...@@ -152,8 +157,7 @@ KILL QUERY @thread_id;
** One signal will be sent to the thread group waiting for executing ** One signal will be sent to the thread group waiting for executing
** invalidations and a broadcast signal will be sent to the thread ** invalidations and a broadcast signal will be sent to the thread
** group holding result set writers. ** group holding result set writers.
SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_invalidate2' LIMIT 1 INTO @flush_thread_id; SET DEBUG_SYNC="now SIGNAL go1_2";
KILL QUERY @flush_thread_id;
** **
************************************************************************* *************************************************************************
** No tables should be locked ** No tables should be locked
...@@ -172,6 +176,7 @@ DELETE FROM t4; ...@@ -172,6 +176,7 @@ DELETE FROM t4;
DELETE FROM t5; DELETE FROM t5;
=================================== Connection thd1 =================================== Connection thd1
** Done. ** Done.
SET DEBUG_SYNC= 'RESET';
SET GLOBAL query_cache_size= 0; SET GLOBAL query_cache_size= 0;
# Restore defaults # Restore defaults
RESET QUERY CACHE; RESET QUERY CACHE;
...@@ -179,3 +184,35 @@ FLUSH STATUS; ...@@ -179,3 +184,35 @@ FLUSH STATUS;
DROP TABLE t1,t2,t3,t4,t5; DROP TABLE t1,t2,t3,t4,t5;
SET GLOBAL query_cache_size= DEFAULT; SET GLOBAL query_cache_size= DEFAULT;
SET GLOBAL query_cache_type= DEFAULT; SET GLOBAL query_cache_type= DEFAULT;
#
# Bug#56822: Add a thread state for sessions waiting on the query cache lock
#
SET @old_query_cache_size= @@GLOBAL.query_cache_size;
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1),(2),(3);
SET GLOBAL concurrent_insert= 1;
SET GLOBAL query_cache_size= 1024*512;
SET GLOBAL query_cache_type= ON;
# Switch to connection con1
SET DEBUG_SYNC = "wait_in_query_cache_invalidate2 SIGNAL parked WAIT_FOR go";
# Send INSERT, will wait in the query cache table invalidation
INSERT INTO t1 VALUES (4);;
# Switch to connection default
# Wait for insert to reach the debug point
SET DEBUG_SYNC = "now WAIT_FOR parked";
# Switch to connection con2
# Send a query that should wait on the query cache lock
RESET QUERY CACHE;
# Switch to connection default
# Wait for the state to be reflected in the processlist
# Signal that the query cache can be unlocked
SET DEBUG_SYNC="now SIGNAL go";
# Reap con1 and disconnect
# Reap con2 and disconnect
# Restore defaults
SET DEBUG_SYNC= 'RESET';
RESET QUERY CACHE;
DROP TABLE t1;
SET GLOBAL query_cache_size= DEFAULT;
SET GLOBAL query_cache_type= DEFAULT;
--source include/not_embedded.inc --source include/not_embedded.inc
--source include/have_query_cache.inc --source include/have_query_cache.inc
--source include/have_debug.inc --source include/have_debug_sync.inc
# #
# Bug #30887 Server crashes on SET GLOBAL query_cache_size=0 # Bug #30887 Server crashes on SET GLOBAL query_cache_size=0
...@@ -18,12 +18,11 @@ connect (bug30887con2, localhost, root, ,test); ...@@ -18,12 +18,11 @@ connect (bug30887con2, localhost, root, ,test);
connection bug30887con1; connection bug30887con1;
--echo Activate debug hook and attempt to retrieve the statement from the cache. --echo Activate debug hook and attempt to retrieve the statement from the cache.
set session debug='+d,wait_in_query_cache_insert'; set debug_sync="wait_in_query_cache_insert SIGNAL parked WAIT_FOR go";
--send select SQL_CACHE * from t1; --send select SQL_CACHE * from t1;
connection default; connection default;
let $wait_condition= select count(*)= 1 from information_schema.processlist where state= 'wait_in_query_cache_insert'; set debug_sync="now WAIT_FOR parked";
--source include/wait_condition.inc
connection bug30887con2; connection bug30887con2;
--echo On a second connection; clear the query cache. --echo On a second connection; clear the query cache.
...@@ -32,14 +31,18 @@ set global query_cache_size= 0; ...@@ -32,14 +31,18 @@ set global query_cache_size= 0;
connection default; connection default;
--echo Signal the debug hook to release the lock. --echo Signal the debug hook to release the lock.
select id from information_schema.processlist where state='wait_in_query_cache_insert' into @thread_id; set debug_sync="now SIGNAL go";
kill query @thread_id;
--echo Show query cache status. --echo Show query cache status.
show status like 'Qcache_queries_in_cache'; show status like 'Qcache_queries_in_cache';
connection bug30887con1;
--reap
disconnect bug30887con1; disconnect bug30887con1;
disconnect bug30887con2; disconnect bug30887con2;
connection default;
set debug_sync= 'RESET';
set global query_cache_size= 0; set global query_cache_size= 0;
use test; use test;
drop table t1; drop table t1;
...@@ -67,18 +70,14 @@ connect(con2,localhost,root,,test,,); ...@@ -67,18 +70,14 @@ connect(con2,localhost,root,,test,,);
connection con1; connection con1;
--echo # Switch to connection con1 --echo # Switch to connection con1
SET SESSION debug='+d,wait_after_query_cache_invalidate'; SET DEBUG_SYNC = "wait_after_query_cache_invalidate SIGNAL parked WAIT_FOR go";
--echo # Send concurrent insert, will wait in the query cache table invalidate --echo # Send concurrent insert, will wait in the query cache table invalidate
--send INSERT INTO t1 VALUES (4) --send INSERT INTO t1 VALUES (4)
connection default; connection default;
--echo # Switch to connection default --echo # Switch to connection default
--echo # Wait for concurrent insert to reach the debug point --echo # Wait for concurrent insert to reach the debug point
let $wait_condition= SET DEBUG_SYNC = "now WAIT_FOR parked";
SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE STATE = "wait_after_query_cache_invalidate" AND
INFO = "INSERT INTO t1 VALUES (4)";
--source include/wait_condition.inc
connection con2; connection con2;
--echo # Switch to connection con2 --echo # Switch to connection con2
...@@ -88,9 +87,7 @@ SELECT * FROM t1; ...@@ -88,9 +87,7 @@ SELECT * FROM t1;
connection default; connection default;
--echo # Switch to connection default --echo # Switch to connection default
--echo # Notify the concurrent insert to proceed --echo # Notify the concurrent insert to proceed
SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST SET DEBUG_SYNC = "now SIGNAL go";
WHERE STATE = 'wait_after_query_cache_invalidate' INTO @thread_id;
KILL QUERY @thread_id;
connection con1; connection con1;
--echo # Switch to connection con1 --echo # Switch to connection con1
...@@ -107,6 +104,7 @@ disconnect con2; ...@@ -107,6 +104,7 @@ disconnect con2;
connection default; connection default;
--echo # Restore defaults --echo # Restore defaults
SET DEBUG_SYNC= 'RESET';
RESET QUERY CACHE; RESET QUERY CACHE;
DROP TABLE t1,t2; DROP TABLE t1,t2;
SET GLOBAL concurrent_insert= DEFAULT; SET GLOBAL concurrent_insert= DEFAULT;
...@@ -157,15 +155,14 @@ SELECT SQL_CACHE * FROM t1; ...@@ -157,15 +155,14 @@ SELECT SQL_CACHE * FROM t1;
--echo ** before the mutex lock in invalidate_table_internal. --echo ** before the mutex lock in invalidate_table_internal.
--echo ** This will allow new result sets to be written into the QC. --echo ** This will allow new result sets to be written into the QC.
--echo ** --echo **
SET SESSION debug='+d,wait_in_query_cache_invalidate1'; SET DEBUG_SYNC="wait_in_query_cache_invalidate1 SIGNAL parked1_1 WAIT_FOR go1_1";
SET SESSION debug='+d,wait_in_query_cache_invalidate2'; SET DEBUG_SYNC="wait_in_query_cache_invalidate2 SIGNAL parked1_2 WAIT_FOR go1_2";
--send DELETE FROM t1 WHERE a like '%a%'; --send DELETE FROM t1 WHERE a like '%a%';
connection default; connection default;
--echo =================================== Connection default --echo =================================== Connection default
--echo ** Assert that the expect process status is obtained. --echo ** Assert that the expect process status is obtained.
LET $wait_condition= SELECT SQL_NO_CACHE COUNT(*)= 1 FROM information_schema.processlist WHERE state= 'wait_in_query_cache_invalidate1'; SET DEBUG_SYNC="now WAIT_FOR parked1_1";
--source include/wait_condition.inc
-- echo ** -- echo **
connection thd2; connection thd2;
...@@ -173,32 +170,30 @@ connection thd2; ...@@ -173,32 +170,30 @@ connection thd2;
--echo ** On THD2: Insert a result into the cache. This attempt will be blocked --echo ** On THD2: Insert a result into the cache. This attempt will be blocked
--echo ** because of a debug hook placed just before the mutex lock after which --echo ** because of a debug hook placed just before the mutex lock after which
--echo ** the first part of the result set is written. --echo ** the first part of the result set is written.
SET SESSION debug='+d,wait_in_query_cache_insert'; SET DEBUG_SYNC="wait_in_query_cache_insert SIGNAL parked2 WAIT_FOR go2";
--send SELECT SQL_CACHE * FROM t2 UNION SELECT * FROM t3 --send SELECT SQL_CACHE * FROM t2 UNION SELECT * FROM t3
connection thd3; connection thd3;
--echo =================================== Connection thd3 --echo =================================== Connection thd3
--echo ** On THD3: Insert another result into the cache and block on the same --echo ** On THD3: Insert another result into the cache and block on the same
--echo ** debug hook. --echo ** debug hook.
SET SESSION debug='+d,wait_in_query_cache_insert'; SET DEBUG_SYNC="wait_in_query_cache_insert SIGNAL parked3 WAIT_FOR go3";
--send SELECT SQL_CACHE * FROM t4 UNION SELECT * FROM t5; --send SELECT SQL_CACHE * FROM t4 UNION SELECT * FROM t5;
connection default; connection default;
--echo =================================== Connection default --echo =================================== Connection default
--echo ** Assert that the two SELECT-stmt threads to reach the hook. --echo ** Assert that the two SELECT-stmt threads to reach the hook.
LET $wait_condition= SELECT SQL_NO_CACHE COUNT(*)= 2 FROM information_schema.processlist WHERE state='wait_in_query_cache_insert'; SET DEBUG_SYNC="now WAIT_FOR parked2";
--source include/wait_condition.inc SET DEBUG_SYNC="now WAIT_FOR parked3";
--echo ** --echo **
--echo ** --echo **
--echo ** Signal the DELETE thread, THD1, to continue. It will enter the mutex --echo ** Signal the DELETE thread, THD1, to continue. It will enter the mutex
--echo ** lock and set query cache status to TABLE_FLUSH_IN_PROGRESS and then --echo ** lock and set query cache status to TABLE_FLUSH_IN_PROGRESS and then
--echo ** unlock the mutex before stopping on the next debug hook. --echo ** unlock the mutex before stopping on the next debug hook.
SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_invalidate1' LIMIT 1 INTO @flush_thread_id; SET DEBUG_SYNC="now SIGNAL go1_1";
KILL QUERY @flush_thread_id;
--echo ** Assert that we reach the next debug hook. --echo ** Assert that we reach the next debug hook.
LET $wait_condition= SELECT SQL_NO_CACHE COUNT(*)= 1 FROM information_schema.processlist WHERE state='wait_in_query_cache_invalidate2'; SET DEBUG_SYNC="now WAIT_FOR parked1_2";
--source include/wait_condition.inc
--echo ** --echo **
--echo ** Signal the remaining debug hooks blocking THD2 and THD3. --echo ** Signal the remaining debug hooks blocking THD2 and THD3.
...@@ -206,10 +201,8 @@ LET $wait_condition= SELECT SQL_NO_CACHE COUNT(*)= 1 FROM information_schema.pro ...@@ -206,10 +201,8 @@ LET $wait_condition= SELECT SQL_NO_CACHE COUNT(*)= 1 FROM information_schema.pro
--echo ** and finally release the mutex. The threads will continue to wait --echo ** and finally release the mutex. The threads will continue to wait
--echo ** until a broadcast signal reaches them causing both threads to --echo ** until a broadcast signal reaches them causing both threads to
--echo ** come alive and check the condition. --echo ** come alive and check the condition.
SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_insert' ORDER BY id ASC LIMIT 1 INTO @thread_id; SET DEBUG_SYNC="now SIGNAL go2";
KILL QUERY @thread_id; SET DEBUG_SYNC="now SIGNAL go3";
SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_insert' ORDER BY id DESC LIMIT 1 INTO @thread_id;
KILL QUERY @thread_id;
--echo ** --echo **
--echo ** Finally signal the DELETE statement on THD1 one last time. --echo ** Finally signal the DELETE statement on THD1 one last time.
...@@ -218,11 +211,7 @@ KILL QUERY @thread_id; ...@@ -218,11 +211,7 @@ KILL QUERY @thread_id;
--echo ** One signal will be sent to the thread group waiting for executing --echo ** One signal will be sent to the thread group waiting for executing
--echo ** invalidations and a broadcast signal will be sent to the thread --echo ** invalidations and a broadcast signal will be sent to the thread
--echo ** group holding result set writers. --echo ** group holding result set writers.
SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_invalidate2' LIMIT 1 INTO @flush_thread_id; SET DEBUG_SYNC="now SIGNAL go1_2";
KILL QUERY @flush_thread_id;
LET $wait_condition= SELECT SQL_NO_CACHE COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE Id = @flush_thread_id AND Command = 'Sleep';
--source include/wait_condition.inc
--echo ** --echo **
--echo ************************************************************************* --echo *************************************************************************
...@@ -250,6 +239,7 @@ connection default; ...@@ -250,6 +239,7 @@ connection default;
disconnect thd1; disconnect thd1;
disconnect thd2; disconnect thd2;
disconnect thd3; disconnect thd3;
SET DEBUG_SYNC= 'RESET';
SET GLOBAL query_cache_size= 0; SET GLOBAL query_cache_size= 0;
connection default; connection default;
...@@ -259,4 +249,66 @@ FLUSH STATUS; ...@@ -259,4 +249,66 @@ FLUSH STATUS;
DROP TABLE t1,t2,t3,t4,t5; DROP TABLE t1,t2,t3,t4,t5;
SET GLOBAL query_cache_size= DEFAULT; SET GLOBAL query_cache_size= DEFAULT;
SET GLOBAL query_cache_type= DEFAULT; SET GLOBAL query_cache_type= DEFAULT;
exit;
--echo #
--echo # Bug#56822: Add a thread state for sessions waiting on the query cache lock
--echo #
SET @old_query_cache_size= @@GLOBAL.query_cache_size;
--disable_warnings
DROP TABLE IF EXISTS t1;
--enable_warnings
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1),(2),(3);
SET GLOBAL concurrent_insert= 1;
SET GLOBAL query_cache_size= 1024*512;
SET GLOBAL query_cache_type= ON;
connect(con1,localhost,root,,test,,);
connect(con2,localhost,root,,test,,);
connection con1;
--echo # Switch to connection con1
SET DEBUG_SYNC = "wait_in_query_cache_invalidate2 SIGNAL parked WAIT_FOR go";
--echo # Send INSERT, will wait in the query cache table invalidation
--send INSERT INTO t1 VALUES (4);
connection default;
--echo # Switch to connection default
--echo # Wait for insert to reach the debug point
SET DEBUG_SYNC = "now WAIT_FOR parked";
connection con2;
--echo # Switch to connection con2
--echo # Send a query that should wait on the query cache lock
--send RESET QUERY CACHE
connection default;
--echo # Switch to connection default
--echo # Wait for the state to be reflected in the processlist
let $wait_condition=
SELECT COUNT(*) = 1 FROM information_schema.processlist
WHERE state = "Waiting for query cache lock" AND info = "RESET QUERY CACHE";
--source include/wait_condition.inc
--echo # Signal that the query cache can be unlocked
SET DEBUG_SYNC="now SIGNAL go";
connection con1;
--echo # Reap con1 and disconnect
--reap
disconnect con1;
connection con2;
--echo # Reap con2 and disconnect
--reap
disconnect con2;
connection default;
--echo # Restore defaults
SET DEBUG_SYNC= 'RESET';
RESET QUERY CACHE;
DROP TABLE t1;
SET GLOBAL query_cache_size= DEFAULT;
SET GLOBAL query_cache_type= DEFAULT;
...@@ -334,6 +334,7 @@ TODO list: ...@@ -334,6 +334,7 @@ TODO list:
#include "tztime.h" // struct Time_zone #include "tztime.h" // struct Time_zone
#include "sql_acl.h" // SELECT_ACL #include "sql_acl.h" // SELECT_ACL
#include "sql_base.h" // TMP_TABLE_KEY_EXTRA #include "sql_base.h" // TMP_TABLE_KEY_EXTRA
#include "debug_sync.h" // DEBUG_SYNC
#ifdef HAVE_QUERY_CACHE #ifdef HAVE_QUERY_CACHE
#include <m_ctype.h> #include <m_ctype.h>
#include <my_dir.h> #include <my_dir.h>
...@@ -372,30 +373,32 @@ TODO list: ...@@ -372,30 +373,32 @@ TODO list:
#define DUMP(C) DBUG_EXECUTE("qcache", {\ #define DUMP(C) DBUG_EXECUTE("qcache", {\
(C)->cache_dump(); (C)->queries_dump();(C)->tables_dump();}) (C)->cache_dump(); (C)->queries_dump();(C)->tables_dump();})
/** /**
Causes the thread to wait in a spin lock for a query kill signal. Thread state to be used when the query cache lock needs to be acquired.
This function is used by the test frame work to identify race conditions. Sets the thread state name in the constructor, resets on destructor.
The signal is caught and ignored and the thread is not killed.
*/ */
static void debug_wait_for_kill(const char *info) struct Query_cache_wait_state
{ {
DBUG_ENTER("debug_wait_for_kill"); THD *m_thd;
const char *prev_info; const char *m_proc_info;
THD *thd;
thd= current_thd; Query_cache_wait_state(THD *thd, const char *func,
prev_info= thd->proc_info; const char *file, unsigned int line)
thd->proc_info= info; : m_thd(thd)
sql_print_information("%s", info); {
while(!thd->killed) if (m_thd)
my_sleep(1000); m_proc_info= set_thd_proc_info(m_thd,
thd->killed= THD::NOT_KILLED; "Waiting for query cache lock",
sql_print_information("Exit debug_wait_for_kill"); func, file, line);
thd->proc_info= prev_info; }
DBUG_VOID_RETURN;
} ~Query_cache_wait_state()
{
if (m_thd)
set_thd_proc_info(m_thd, m_proc_info, NULL, NULL, 0);
}
};
#else #else
#define RW_WLOCK(M) mysql_rwlock_wrlock(M) #define RW_WLOCK(M) mysql_rwlock_wrlock(M)
...@@ -429,6 +432,8 @@ static void debug_wait_for_kill(const char *info) ...@@ -429,6 +432,8 @@ static void debug_wait_for_kill(const char *info)
bool Query_cache::try_lock(bool use_timeout) bool Query_cache::try_lock(bool use_timeout)
{ {
bool interrupt= FALSE; bool interrupt= FALSE;
THD *thd= current_thd;
Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
DBUG_ENTER("Query_cache::try_lock"); DBUG_ENTER("Query_cache::try_lock");
mysql_mutex_lock(&structure_guard_mutex); mysql_mutex_lock(&structure_guard_mutex);
...@@ -438,7 +443,6 @@ bool Query_cache::try_lock(bool use_timeout) ...@@ -438,7 +443,6 @@ bool Query_cache::try_lock(bool use_timeout)
{ {
m_cache_lock_status= Query_cache::LOCKED; m_cache_lock_status= Query_cache::LOCKED;
#ifndef DBUG_OFF #ifndef DBUG_OFF
THD *thd= current_thd;
if (thd) if (thd)
m_cache_lock_thread_id= thd->thread_id; m_cache_lock_thread_id= thd->thread_id;
#endif #endif
...@@ -497,6 +501,8 @@ bool Query_cache::try_lock(bool use_timeout) ...@@ -497,6 +501,8 @@ bool Query_cache::try_lock(bool use_timeout)
void Query_cache::lock_and_suspend(void) void Query_cache::lock_and_suspend(void)
{ {
THD *thd= current_thd;
Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
DBUG_ENTER("Query_cache::lock_and_suspend"); DBUG_ENTER("Query_cache::lock_and_suspend");
mysql_mutex_lock(&structure_guard_mutex); mysql_mutex_lock(&structure_guard_mutex);
...@@ -504,7 +510,6 @@ void Query_cache::lock_and_suspend(void) ...@@ -504,7 +510,6 @@ void Query_cache::lock_and_suspend(void)
mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex); mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
m_cache_lock_status= Query_cache::LOCKED_NO_WAIT; m_cache_lock_status= Query_cache::LOCKED_NO_WAIT;
#ifndef DBUG_OFF #ifndef DBUG_OFF
THD *thd= current_thd;
if (thd) if (thd)
m_cache_lock_thread_id= thd->thread_id; m_cache_lock_thread_id= thd->thread_id;
#endif #endif
...@@ -525,6 +530,8 @@ void Query_cache::lock_and_suspend(void) ...@@ -525,6 +530,8 @@ void Query_cache::lock_and_suspend(void)
void Query_cache::lock(void) void Query_cache::lock(void)
{ {
THD *thd= current_thd;
Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
DBUG_ENTER("Query_cache::lock"); DBUG_ENTER("Query_cache::lock");
mysql_mutex_lock(&structure_guard_mutex); mysql_mutex_lock(&structure_guard_mutex);
...@@ -532,7 +539,6 @@ void Query_cache::lock(void) ...@@ -532,7 +539,6 @@ void Query_cache::lock(void)
mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex); mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
m_cache_lock_status= Query_cache::LOCKED; m_cache_lock_status= Query_cache::LOCKED;
#ifndef DBUG_OFF #ifndef DBUG_OFF
THD *thd= current_thd;
if (thd) if (thd)
m_cache_lock_thread_id= thd->thread_id; m_cache_lock_thread_id= thd->thread_id;
#endif #endif
...@@ -872,9 +878,7 @@ Query_cache::insert(Query_cache_tls *query_cache_tls, ...@@ -872,9 +878,7 @@ Query_cache::insert(Query_cache_tls *query_cache_tls,
if (is_disabled() || query_cache_tls->first_query_block == NULL) if (is_disabled() || query_cache_tls->first_query_block == NULL)
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
DBUG_EXECUTE_IF("wait_in_query_cache_insert", DEBUG_SYNC(current_thd, "wait_in_query_cache_insert");
debug_wait_for_kill("wait_in_query_cache_insert"); );
if (try_lock()) if (try_lock())
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
...@@ -1779,8 +1783,7 @@ void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used, ...@@ -1779,8 +1783,7 @@ void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used,
invalidate_table(thd, tables_used); invalidate_table(thd, tables_used);
} }
DBUG_EXECUTE_IF("wait_after_query_cache_invalidate", DEBUG_SYNC(thd, "wait_after_query_cache_invalidate");
debug_wait_for_kill("wait_after_query_cache_invalidate"););
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
...@@ -1971,8 +1974,7 @@ void Query_cache::flush() ...@@ -1971,8 +1974,7 @@ void Query_cache::flush()
if (is_disabled()) if (is_disabled())
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
DBUG_EXECUTE_IF("wait_in_query_cache_flush1", DEBUG_SYNC(current_thd, "wait_in_query_cache_flush1");
debug_wait_for_kill("wait_in_query_cache_flush1"););
lock_and_suspend(); lock_and_suspend();
if (query_cache_size > 0) if (query_cache_size > 0)
...@@ -2312,9 +2314,7 @@ void Query_cache::free_cache() ...@@ -2312,9 +2314,7 @@ void Query_cache::free_cache()
void Query_cache::flush_cache() void Query_cache::flush_cache()
{ {
DEBUG_SYNC(current_thd, "wait_in_query_cache_flush2");
DBUG_EXECUTE_IF("wait_in_query_cache_flush2",
debug_wait_for_kill("wait_in_query_cache_flush2"););
my_hash_reset(&queries); my_hash_reset(&queries);
while (queries_blocks != 0) while (queries_blocks != 0)
...@@ -2760,8 +2760,7 @@ void Query_cache::invalidate_table(THD *thd, TABLE *table) ...@@ -2760,8 +2760,7 @@ void Query_cache::invalidate_table(THD *thd, TABLE *table)
void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length) void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length)
{ {
DBUG_EXECUTE_IF("wait_in_query_cache_invalidate1", DEBUG_SYNC(thd, "wait_in_query_cache_invalidate1");
debug_wait_for_kill("wait_in_query_cache_invalidate1"); );
/* /*
Lock the query cache and queue all invalidation attempts to avoid Lock the query cache and queue all invalidation attempts to avoid
...@@ -2769,9 +2768,7 @@ void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length) ...@@ -2769,9 +2768,7 @@ void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length)
*/ */
lock(); lock();
DBUG_EXECUTE_IF("wait_in_query_cache_invalidate2", DEBUG_SYNC(thd, "wait_in_query_cache_invalidate2");
debug_wait_for_kill("wait_in_query_cache_invalidate2"); );
if (query_cache_size > 0) if (query_cache_size > 0)
invalidate_table_internal(thd, key, key_length); invalidate_table_internal(thd, key, key_length);
...@@ -2821,7 +2818,6 @@ Query_cache::invalidate_query_block_list(THD *thd, ...@@ -2821,7 +2818,6 @@ Query_cache::invalidate_query_block_list(THD *thd,
Query_cache_block *query_block= list_root->next->block(); Query_cache_block *query_block= list_root->next->block();
BLOCK_LOCK_WR(query_block); BLOCK_LOCK_WR(query_block);
free_query(query_block); free_query(query_block);
DBUG_EXECUTE_IF("debug_cache_locks", sleep(10););
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment