#At file:///Users/thek/Development/mysql-pe/ based on revid:alexey.kopytov@stripped
3369 Kristofer Pettersson 2009-06-17
Bug#43758 Query cache can lock up threads in 'freeing items' state
It is possible for more than one thread to enter the condition
in query_cache_insert(), but the condition predicate is to
signal one thread each time the cache status changes between
the following states: {NO_FLUSH_IN_PROGRESS,FLUSH_IN_PROGRESS,
TABLE_FLUSH_IN_PROGRESS}
Consider three threads THD1, THD2, THD3
THD2: select ... => Got a writer in ::store_query
THD3: select ... => Got a writer in ::store_query
THD1: flush tables => qc status= FLUSH_IN_PROGRESS;
new writers are blocked.
THD2: select ... => Still got a writer and enters cond in
query_cache_insert
THD3: select ... => Still got a writer and enters cond in
query_cache_insert
THD1: flush tables => finished and signal status change.
THD2: select ... => Wakes up and completes the insert.
THD3: select ... => Happily waiting for better times. Why hurry?
This patch is a refactoring of this lock system. It introduces four new methods:
Query_cache::try_lock()
Query_cache::lock()
Query_cache::lock_and_suspend()
Query_cache::unlock()
This change also deprecates wait_while_table_flush_is_in_progress(). All threads are
queued and put on a conditional wait. On each unlock the queue is signalled. This resolve
the issues with left over threads. To assure that no threads are spending unnecessary
time waiting a signal broadcast is issued every time a lock is taken before a full
cache flush.
@ mysql-test/r/query_cache_debug.result
* Added test case for bug43758
@ mysql-test/t/query_cache_debug.test
* Added test case for bug43758
@ sql/sql_cache.cc
* Replaced calls to wait_while_table_flush_is_in_progress() with
calls to try_lock(), lock_and_suspend() and unlock().
* Renamed enumeration Cache_status to Cache_lock_status.
* Renamed enumeration items to UNLOCKED, LOCKED_NO_WAIT and LOCKED.
If the LOCKED_NO_WAIT lock type is used to lock the query cache, other
threads using try_lock() will fail to acquire the lock.
This is useful if the query cache is temporary disabled due to
a full table flush.
@ sql/sql_cache.h
* Replaced calls to wait_while_table_flush_is_in_progress() with
calls to try_lock(), lock_and_suspend() and unlock().
* Renamed enumeration Cache_status to Cache_lock_status.
* Renamed enumeration items to UNLOCKED, LOCKED_NO_WAIT and LOCKED.
If the LOCKED_NO_WAIT lock type is used to lock the query cache, other
threads using try_lock() will fail to acquire the lock.
This is useful if the query cache is temporary disabled due to
a full table flush.
modified:
mysql-test/r/query_cache_debug.result
mysql-test/t/query_cache_debug.test
sql/sql_cache.cc
sql/sql_cache.h
=== modified file 'mysql-test/r/query_cache_debug.result'
--- a/mysql-test/r/query_cache_debug.result 2009-06-16 10:23:14 +0000
+++ b/mysql-test/r/query_cache_debug.result 2009-06-17 18:00:28 +0000
@@ -71,3 +71,111 @@ DROP TABLE t1,t2;
SET GLOBAL concurrent_insert= DEFAULT;
SET GLOBAL query_cache_size= DEFAULT;
SET GLOBAL query_cache_type= DEFAULT;
+#
+# Bug43758 Query cache can lock up threads in 'freeing items' state
+#
+FLUSH STATUS;
+SET GLOBAL query_cache_type=DEMAND;
+SET GLOBAL query_cache_size= 1024*768;
+DROP TABLE IF EXISTS t1,t2,t3,t4,t5;
+CREATE TABLE t1 (a VARCHAR(100));
+CREATE TABLE t2 (a VARCHAR(100));
+CREATE TABLE t3 (a VARCHAR(100));
+CREATE TABLE t4 (a VARCHAR(100));
+CREATE TABLE t5 (a VARCHAR(100));
+INSERT INTO t1 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t2 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t3 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t4 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t5 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+=================================== Connection thd1
+**
+** Load Query Cache with a result set and one table.
+**
+SELECT SQL_CACHE * FROM t1;
+a
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+*************************************************************************
+** We want to accomplish the following state:
+** - Query cache status: TABLE_FLUSH_IN_PROGRESS
+** - THD1: invalidate_table_internal (iterating query blocks)
+** - THD2: query_cache_insert (cond_wait)
+** - THD3: query_cache_insert (cond_wait)
+** - No thread should be holding the structure_guard_mutex.
+**
+** First step is to place a DELETE-statement on the debug hook just
+** before the mutex lock in invalidate_table_internal.
+** This will allow new result sets to be written into the QC.
+**
+SET SESSION debug='+d,wait_in_query_cache_invalidate1';
+SET SESSION debug='+d,wait_in_query_cache_invalidate2';
+DELETE FROM t1 WHERE a like '%a%';;
+=================================== Connection default
+** Assert that the expect process status is obtained.
+**
+=================================== Connection thd2
+** 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
+** the first part of the result set is written.
+SET SESSION debug='+d,wait_in_query_cache_insert';
+SELECT SQL_CACHE * FROM t2 UNION SELECT * FROM t3;
+=================================== Connection thd3
+** On THD3: Insert another result into the cache and block on the same
+** debug hook.
+SET SESSION debug='+d,wait_in_query_cache_insert';
+SELECT SQL_CACHE * FROM t4 UNION SELECT * FROM t5;;
+=================================== Connection default
+** Assert that the two SELECT-stmt threads to reach the hook.
+**
+**
+** 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
+** 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;
+KILL QUERY @flush_thread_id;
+** Assert that we reach the next debug hook.
+**
+** Signal the remaining debug hooks blocking THD2 and THD3.
+** The threads will grab the guard mutex enter the wait condition and
+** and finally release the mutex. The threads will continue to wait
+** until a broadcast signal reaches them causing both threads to
+** 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;
+KILL QUERY @thread_id;
+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.
+** The stmt will complete the query cache invalidation and return
+** cache status to NO_FLUSH_IN_PROGRESS. On the status change
+** One signal will be sent to the thread group waiting for executing
+** invalidations and a broadcast signal will be sent to the thread
+** 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;
+KILL QUERY @flush_thread_id;
+**
+*************************************************************************
+** No tables should be locked
+=================================== Connection thd2
+a
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+DELETE FROM t1;
+DELETE FROM t2;
+DELETE FROM t3;
+=================================== Connection thd3
+a
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+DELETE FROM t4;
+DELETE FROM t5;
+=================================== Connection thd1
+** Done.
+SET GLOBAL query_cache_size= 0;
+# Restore defaults
+RESET QUERY CACHE;
+FLUSH STATUS;
+DROP TABLE t1,t2,t3,t4,t5;
+SET GLOBAL query_cache_size= DEFAULT;
+SET GLOBAL query_cache_type= DEFAULT;
=== modified file 'mysql-test/t/query_cache_debug.test'
--- a/mysql-test/t/query_cache_debug.test 2009-06-16 10:23:14 +0000
+++ b/mysql-test/t/query_cache_debug.test 2009-06-17 18:00:28 +0000
@@ -113,3 +113,146 @@ DROP TABLE t1,t2;
SET GLOBAL concurrent_insert= DEFAULT;
SET GLOBAL query_cache_size= DEFAULT;
SET GLOBAL query_cache_type= DEFAULT;
+
+
+--echo #
+--echo # Bug43758 Query cache can lock up threads in 'freeing items' state
+--echo #
+FLUSH STATUS;
+SET GLOBAL query_cache_type=DEMAND;
+SET GLOBAL query_cache_size= 1024*768;
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2,t3,t4,t5;
+--enable_warnings
+CREATE TABLE t1 (a VARCHAR(100));
+CREATE TABLE t2 (a VARCHAR(100));
+CREATE TABLE t3 (a VARCHAR(100));
+CREATE TABLE t4 (a VARCHAR(100));
+CREATE TABLE t5 (a VARCHAR(100));
+
+INSERT INTO t1 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t2 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t3 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t4 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t5 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+
+connect (thd2, localhost, root, ,test);
+connect (thd3, localhost, root, ,test);
+connect (thd1, localhost, root, ,test);
+
+connection thd1;
+--echo =================================== Connection thd1
+--echo **
+--echo ** Load Query Cache with a result set and one table.
+--echo **
+SELECT SQL_CACHE * FROM t1;
+--echo *************************************************************************
+--echo ** We want to accomplish the following state:
+--echo ** - Query cache status: TABLE_FLUSH_IN_PROGRESS
+--echo ** - THD1: invalidate_table_internal (iterating query blocks)
+--echo ** - THD2: query_cache_insert (cond_wait)
+--echo ** - THD3: query_cache_insert (cond_wait)
+--echo ** - No thread should be holding the structure_guard_mutex.
+--echo **
+--echo ** First step is to place a DELETE-statement on the debug hook just
+--echo ** before the mutex lock in invalidate_table_internal.
+--echo ** This will allow new result sets to be written into the QC.
+--echo **
+SET SESSION debug='+d,wait_in_query_cache_invalidate1';
+SET SESSION debug='+d,wait_in_query_cache_invalidate2';
+--send DELETE FROM t1 WHERE a like '%a%';
+
+connection default;
+--echo =================================== Connection default
+--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';
+--source include/wait_condition.inc
+-- echo **
+
+connection thd2;
+--echo =================================== Connection thd2
+--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 ** the first part of the result set is written.
+SET SESSION debug='+d,wait_in_query_cache_insert';
+--send SELECT SQL_CACHE * FROM t2 UNION SELECT * FROM t3
+
+connection thd3;
+--echo =================================== Connection thd3
+--echo ** On THD3: Insert another result into the cache and block on the same
+--echo ** debug hook.
+SET SESSION debug='+d,wait_in_query_cache_insert';
+--send SELECT SQL_CACHE * FROM t4 UNION SELECT * FROM t5;
+
+connection default;
+--echo =================================== Connection default
+--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';
+--source include/wait_condition.inc
+--echo **
+--echo **
+
+--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 ** 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;
+KILL QUERY @flush_thread_id;
+--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';
+--source include/wait_condition.inc
+
+--echo **
+--echo ** Signal the remaining debug hooks blocking THD2 and THD3.
+--echo ** The threads will grab the guard mutex enter the wait condition and
+--echo ** and finally release the mutex. The threads will continue to wait
+--echo ** until a broadcast signal reaches them causing both threads to
+--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;
+KILL QUERY @thread_id;
+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 ** Finally signal the DELETE statement on THD1 one last time.
+--echo ** The stmt will complete the query cache invalidation and return
+--echo ** cache status to NO_FLUSH_IN_PROGRESS. On the status change
+--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 ** 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;
+KILL QUERY @flush_thread_id;
+
+--echo **
+--echo *************************************************************************
+--echo ** No tables should be locked
+connection thd2;
+--echo =================================== Connection thd2
+reap;
+DELETE FROM t1;
+DELETE FROM t2;
+DELETE FROM t3;
+
+connection thd3;
+--echo =================================== Connection thd3
+reap;
+DELETE FROM t4;
+DELETE FROM t5;
+
+connection thd1;
+--echo =================================== Connection thd1
+reap;
+
+--echo ** Done.
+
+connection default;
+disconnect thd1;
+disconnect thd2;
+disconnect thd3;
+SET GLOBAL query_cache_size= 0;
+
+--echo # Restore defaults
+RESET QUERY CACHE;
+FLUSH STATUS;
+DROP TABLE t1,t2,t3,t4,t5;
+SET GLOBAL query_cache_size= DEFAULT;
+SET GLOBAL query_cache_type= DEFAULT;
+exit;
=== modified file 'sql/sql_cache.cc'
--- a/sql/sql_cache.cc 2009-06-16 10:23:14 +0000
+++ b/sql/sql_cache.cc 2009-06-17 18:00:28 +0000
@@ -353,11 +353,6 @@ TODO list:
#define RW_UNLOCK(M) {DBUG_PRINT("lock", ("rwlock unlock %p",(M))); \
if (!rw_unlock(M)) DBUG_PRINT("lock", ("rwlock unlock ok")); \
else DBUG_PRINT("lock", ("rwlock unlock FAILED %d", errno)); }
-#define STRUCT_LOCK(M) {DBUG_PRINT("lock", ("%d struct lock...",__LINE__)); \
- pthread_mutex_lock(M);DBUG_PRINT("lock", ("struct lock OK"));}
-#define STRUCT_UNLOCK(M) { \
- DBUG_PRINT("lock", ("%d struct unlock...",__LINE__)); \
- pthread_mutex_unlock(M);DBUG_PRINT("lock", ("struct unlock OK"));}
#define BLOCK_LOCK_WR(B) {DBUG_PRINT("lock", ("%d LOCK_WR %p",\
__LINE__,(B))); \
B->query()->lock_writing();}
@@ -404,8 +399,6 @@ static void debug_wait_for_kill(const ch
#define RW_WLOCK(M) rw_wrlock(M)
#define RW_RLOCK(M) rw_rdlock(M)
#define RW_UNLOCK(M) rw_unlock(M)
-#define STRUCT_LOCK(M) pthread_mutex_lock(M)
-#define STRUCT_UNLOCK(M) pthread_mutex_unlock(M)
#define BLOCK_LOCK_WR(B) B->query()->lock_writing()
#define BLOCK_LOCK_RD(B) B->query()->lock_reading()
#define BLOCK_UNLOCK_WR(B) B->query()->unlock_writing()
@@ -421,6 +414,140 @@ TYPELIB query_cache_type_typelib=
/**
+ Serialize access to the query cache.
+ If the lock cannot be granted the thread hangs in a conditional wait which
+ is signalled on each unlock.
+
+ The lock attempt will also fail without wait if lock_and_suspend() is in
+ effect by another thread. This enables a quick path in execution to skip waits
+ when the outcome is known.
+
+ @return
+ @retval FALSE An exclusive lock was taken
+ @retval TRUE The locking attempt failed
+*/
+
+bool Query_cache::try_lock(void)
+{
+ bool interrupt= FALSE;
+ DBUG_ENTER("Query_cache::try_lock");
+
+ pthread_mutex_lock(&structure_guard_mutex);
+ while (1)
+ {
+ if (m_cache_lock_status == Query_cache::UNLOCKED)
+ {
+ m_cache_lock_status= Query_cache::LOCKED;
+#ifndef DBUG_OFF
+ THD *thd= current_thd;
+ if (thd)
+ m_cache_lock_thread_id= thd->thread_id;
+#endif
+ break;
+ }
+ else if (m_cache_lock_status == Query_cache::LOCKED_NO_WAIT)
+ {
+ /*
+ If query cache is protected by a LOCKED_NO_WAIT lock this thread
+ should avoid using the query cache as it is being evicted.
+ */
+ interrupt= TRUE;
+ break;
+ }
+ else
+ {
+ DBUG_ASSERT(m_cache_lock_status == Query_cache::LOCKED);
+ pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
+ }
+ }
+ pthread_mutex_unlock(&structure_guard_mutex);
+
+ DBUG_RETURN(interrupt);
+}
+
+
+/**
+ Serialize access to the query cache.
+ If the lock cannot be granted the thread hangs in a conditional wait which
+ is signalled on each unlock.
+
+ This method also suspends the query cache so that other threads attempting to
+ lock the cache with try_lock() will fail directly without waiting.
+
+ It is used by all methods which flushes or destroys the whole cache.
+ */
+
+void Query_cache::lock_and_suspend(void)
+{
+ DBUG_ENTER("Query_cache::lock_and_suspend");
+
+ pthread_mutex_lock(&structure_guard_mutex);
+ while (m_cache_lock_status != Query_cache::UNLOCKED)
+ pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
+ m_cache_lock_status= Query_cache::LOCKED_NO_WAIT;
+#ifndef DBUG_OFF
+ THD *thd= current_thd;
+ if (thd)
+ m_cache_lock_thread_id= thd->thread_id;
+#endif
+ /* Wake up everybody, a whole cache flush is starting! */
+ pthread_cond_broadcast(&COND_cache_status_changed);
+ pthread_mutex_unlock(&structure_guard_mutex);
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Serialize access to the query cache.
+ If the lock cannot be granted the thread hangs in a conditional wait which
+ is signalled on each unlock.
+
+ It is used by all methods which invalidates one or more tables.
+ */
+
+void Query_cache::lock(void)
+{
+ DBUG_ENTER("Query_cache::lock");
+
+ pthread_mutex_lock(&structure_guard_mutex);
+ while (m_cache_lock_status != Query_cache::UNLOCKED)
+ pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
+ m_cache_lock_status= Query_cache::LOCKED;
+#ifndef DBUG_OFF
+ THD *thd= current_thd;
+ if (thd)
+ m_cache_lock_thread_id= thd->thread_id;
+#endif
+ pthread_mutex_unlock(&structure_guard_mutex);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Set the query cache to UNLOCKED and signal waiting threads.
+*/
+
+void Query_cache::unlock(void)
+{
+ DBUG_ENTER("Query_cache::unlock");
+ pthread_mutex_lock(&structure_guard_mutex);
+#ifndef DBUG_OFF
+ THD *thd= current_thd;
+ if (thd)
+ DBUG_ASSERT(m_cache_lock_thread_id == thd->thread_id);
+#endif
+ DBUG_ASSERT(m_cache_lock_status == Query_cache::LOCKED ||
+ m_cache_lock_status == Query_cache::LOCKED_NO_WAIT);
+ m_cache_lock_status= Query_cache::UNLOCKED;
+ DBUG_PRINT("Query_cache",("Sending signal"));
+ pthread_cond_signal(&COND_cache_status_changed);
+ pthread_mutex_unlock(&structure_guard_mutex);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
Helper function for determine if a SELECT statement has a SQL_NO_CACHE
directive.
@@ -731,14 +858,8 @@ Query_cache::insert(Query_cache_tls *que
debug_wait_for_kill("wait_in_query_cache_insert"); );
- STRUCT_LOCK(&structure_guard_mutex);
- bool interrupt;
- wait_while_table_flush_is_in_progress(&interrupt);
- if (interrupt)
- {
- STRUCT_UNLOCK(&structure_guard_mutex);
+ if (try_lock())
DBUG_VOID_RETURN;
- }
Query_cache_block *query_block = query_cache_tls->first_query_block;
if (query_block == NULL)
@@ -746,7 +867,7 @@ Query_cache::insert(Query_cache_tls *que
/*
The writer has been dropped; nothing left to do.
*/
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
BLOCK_LOCK_WR(query_block);
@@ -756,11 +877,6 @@ Query_cache::insert(Query_cache_tls *que
DUMP(this);
DBUG_PRINT("qcache", ("insert packet %lu bytes long",length));
- /*
- On success, STRUCT_UNLOCK is done by append_result_data. Otherwise, we
- still need structure_guard_mutex to free the query, and therefore unlock
- it later in this function.
- */
if (!append_result_data(&result, length, (uchar*) packet,
query_block))
{
@@ -771,7 +887,7 @@ Query_cache::insert(Query_cache_tls *que
query_cache.free_query(query_block);
query_cache.refused++;
// append_result_data no success => we need unlock
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
@@ -794,14 +910,8 @@ Query_cache::abort(Query_cache_tls *quer
if (is_disabled() || query_cache_tls->first_query_block == NULL)
DBUG_VOID_RETURN;
- STRUCT_LOCK(&structure_guard_mutex);
- bool interrupt;
- wait_while_table_flush_is_in_progress(&interrupt);
- if (interrupt)
- {
- STRUCT_UNLOCK(&structure_guard_mutex);
+ if (try_lock())
DBUG_VOID_RETURN;
- }
/*
While we were waiting another thread might have changed the status
@@ -819,7 +929,7 @@ Query_cache::abort(Query_cache_tls *quer
DBUG_EXECUTE("check_querycache", check_integrity(1););
}
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
@@ -850,15 +960,8 @@ void Query_cache::end_of_result(THD *thd
emb_count_querycache_size(thd), 0);
#endif
- STRUCT_LOCK(&structure_guard_mutex);
-
- bool interrupt;
- wait_while_table_flush_is_in_progress(&interrupt);
- if (interrupt)
- {
- STRUCT_UNLOCK(&structure_guard_mutex);
+ if (try_lock())
DBUG_VOID_RETURN;
- }
query_block= query_cache_tls->first_query_block;
if (query_block)
@@ -886,11 +989,10 @@ void Query_cache::end_of_result(THD *thd
and removed from QC.
*/
DBUG_ASSERT(0);
- query_cache.free_query(query_block);
- STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
+ free_query(query_block);
+ unlock();
DBUG_VOID_RETURN;
}
-
last_result_block= header->result()->prev;
allign_size= ALIGN_SIZE(last_result_block->used);
len= max(query_cache.min_allocation_unit, allign_size);
@@ -907,7 +1009,7 @@ void Query_cache::end_of_result(THD *thd
DBUG_EXECUTE("check_querycache", check_integrity(1););
}
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
@@ -966,11 +1068,7 @@ ulong Query_cache::resize(ulong query_ca
query_cache_size_arg));
DBUG_ASSERT(initialized);
- STRUCT_LOCK(&structure_guard_mutex);
- while (is_flushing())
- pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
- m_cache_status= Query_cache::FLUSH_IN_PROGRESS;
- STRUCT_UNLOCK(&structure_guard_mutex);
+ lock_and_suspend();
/*
Wait for all readers and writers to exit. When the list of all queries
@@ -1002,13 +1100,10 @@ ulong Query_cache::resize(ulong query_ca
query_cache_size= query_cache_size_arg;
new_query_cache_size= init_cache();
- STRUCT_LOCK(&structure_guard_mutex);
- m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
- pthread_cond_signal(&COND_cache_status_changed);
if (new_query_cache_size)
DBUG_EXECUTE("check_querycache",check_integrity(1););
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_RETURN(new_query_cache_size);
}
@@ -1107,15 +1202,16 @@ def_week_frmt: %lu, in_trans: %d, autoco
*/
ha_release_temporary_latches(thd);
- STRUCT_LOCK(&structure_guard_mutex);
- if (query_cache_size == 0 || is_flushing())
+ /*
+ A table- or a full flush operation can potentially take a long time to
+ finish. We choose not to wait for them and skip caching statements
+ instead.
+ */
+ if (try_lock())
+ DBUG_VOID_RETURN;
+ if (query_cache_size == 0)
{
- /*
- A table- or a full flush operation can potentially take a long time to
- finish. We choose not to wait for them and skip caching statements
- instead.
- */
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
DUMP(this);
@@ -1123,7 +1219,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
if (ask_handler_allowance(thd, tables_used))
{
refused++;
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
@@ -1171,7 +1267,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
DBUG_PRINT("qcache", ("insertion in query hash"));
header->unlock_n_destroy();
free_memory_block(query_block);
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
goto end;
}
if (!register_all_tables(query_block, tables_used, local_tables))
@@ -1181,7 +1277,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
my_hash_delete(&queries, (uchar *) query_block);
header->unlock_n_destroy();
free_memory_block(query_block);
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
goto end;
}
double_linked_list_simple_include(query_block, &queries_blocks);
@@ -1191,7 +1287,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
header->writer(&thd->query_cache_tls);
header->tables_type(tables_type);
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
// init_n_lock make query block locked
BLOCK_UNLOCK_WR(query_block);
@@ -1200,7 +1296,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
{
// We have not enough memory to store query => do nothing
refused++;
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_PRINT("warning", ("Can't allocate query"));
}
}
@@ -1208,7 +1304,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
{
// Another thread is processing the same query => do nothing
refused++;
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_PRINT("qcache", ("Another thread process same query"));
}
}
@@ -1303,18 +1399,12 @@ Query_cache::send_result_to_client(THD *
}
}
- STRUCT_LOCK(&structure_guard_mutex);
+ if (try_lock())
+ goto err;
if (query_cache_size == 0)
goto err_unlock;
- if (is_flushing())
- {
- /* Return; Query cache is temporarily disabled while we flush. */
- DBUG_PRINT("qcache",("query cache disabled"));
- goto err_unlock;
- }
-
Query_cache_block *query_block;
tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE;
@@ -1445,7 +1535,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
DBUG_PRINT("qcache",
("Temporary table detected: '%s.%s'",
table_list.db, table_list.alias));
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
/*
We should not store result of this query because it contain
temporary tables => assign following variable to make check
@@ -1466,7 +1556,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
DBUG_PRINT("qcache",
("probably no SELECT access to %s.%s => return to normal processing",
table_list.db, table_list.alias));
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
thd->lex->safe_to_cache_query=0; // Don't try to cache this
BLOCK_UNLOCK_RD(query_block);
DBUG_RETURN(-1); // Privilege error
@@ -1509,7 +1599,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
}
move_to_query_list_end(query_block);
hits++;
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
/*
Send cached result to client
@@ -1549,7 +1639,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
DBUG_RETURN(1); // Result sent to client
err_unlock:
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
err:
MYSQL_QUERY_CACHE_MISS(thd->query);
DBUG_RETURN(0); // Query was not cached
@@ -1681,47 +1771,6 @@ void Query_cache::invalidate(THD *thd, c
/**
- Synchronize the thread with any flushing operations.
-
- This helper function is called whenever a thread needs to operate on the
- query cache structure (example: during invalidation). If a table flush is in
- progress this function will wait for it to stop. If a full flush is in
- progress, the function will set the interrupt parameter to indicate that the
- current operation is redundant and should be interrupted.
-
- @param[out] interrupt This out-parameter will be set to TRUE if the calling
- function is redundant and should be interrupted.
-
- @return If the interrupt-parameter is TRUE then m_cache_status is set to
- NO_FLUSH_IN_PROGRESS. If the interrupt-parameter is FALSE then
- m_cache_status is set to FLUSH_IN_PROGRESS.
- The structure_guard_mutex will in any case be locked.
-*/
-
-void Query_cache::wait_while_table_flush_is_in_progress(bool *interrupt)
-{
- while (is_flushing())
- {
- /*
- If there already is a full flush in progress query cache isn't enabled
- and additional flushes are redundant; just return instead.
- */
- if (m_cache_status == Query_cache::FLUSH_IN_PROGRESS)
- {
- *interrupt= TRUE;
- return;
- }
- /*
- If a table flush is in progress; wait on cache status to change.
- */
- if (m_cache_status == Query_cache::TABLE_FLUSH_IN_PROGRESS)
- pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
- }
- *interrupt= FALSE;
-}
-
-
-/**
Remove all cached queries that uses the given database.
*/
@@ -1731,14 +1780,7 @@ void Query_cache::invalidate(char *db)
DBUG_ENTER("Query_cache::invalidate (db)");
if (is_disabled())
DBUG_VOID_RETURN;
- STRUCT_LOCK(&structure_guard_mutex);
- bool interrupt;
- wait_while_table_flush_is_in_progress(&interrupt);
- if (interrupt)
- {
- STRUCT_UNLOCK(&structure_guard_mutex);
- DBUG_VOID_RETURN;
- }
+ lock();
THD *thd= current_thd;
@@ -1794,7 +1836,7 @@ void Query_cache::invalidate(char *db)
} while (restart);
} // end if( tables_blocks )
}
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
@@ -1821,7 +1863,8 @@ void Query_cache::flush()
if (is_disabled())
DBUG_VOID_RETURN;
- STRUCT_LOCK(&structure_guard_mutex);
+ lock_and_suspend();
+
if (query_cache_size > 0)
{
DUMP(this);
@@ -1830,7 +1873,7 @@ void Query_cache::flush()
}
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
@@ -1850,18 +1893,13 @@ void Query_cache::pack(ulong join_limit,
DBUG_ENTER("Query_cache::pack");
if (is_disabled())
DBUG_VOID_RETURN;
- bool interrupt;
- STRUCT_LOCK(&structure_guard_mutex);
- wait_while_table_flush_is_in_progress(&interrupt);
- if (interrupt)
- {
- STRUCT_UNLOCK(&structure_guard_mutex);
+
+ if (try_lock())
DBUG_VOID_RETURN;
- }
if (query_cache_size == 0)
{
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
@@ -1871,7 +1909,7 @@ void Query_cache::pack(ulong join_limit,
pack_cache();
} while ((++i < iteration_limit) && join_results(join_limit));
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_VOID_RETURN;
}
@@ -1886,9 +1924,9 @@ void Query_cache::destroy()
else
{
/* Underlying code expects the lock. */
- STRUCT_LOCK(&structure_guard_mutex);
+ lock_and_suspend();
free_cache();
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
pthread_cond_destroy(&COND_cache_status_changed);
pthread_mutex_destroy(&structure_guard_mutex);
@@ -1907,7 +1945,7 @@ void Query_cache::init()
DBUG_ENTER("Query_cache::init");
pthread_mutex_init(&structure_guard_mutex,MY_MUTEX_INIT_FAST);
pthread_cond_init(&COND_cache_status_changed, NULL);
- m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
+ m_cache_lock_status= Query_cache::UNLOCKED;
initialized = 1;
/*
If we explicitly turn off query cache from the command line query cache will
@@ -2157,23 +2195,9 @@ void Query_cache::free_cache()
void Query_cache::flush_cache()
{
- /*
- If there is flush in progress, wait for it to finish, and then do
- our flush. This is necessary because something could be added to
- the cache before we acquire the lock again, and some code (like
- Query_cache::free_cache()) depends on the fact that after the
- flush the cache is empty.
- */
- while (is_flushing())
- pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
-
- /*
- Setting 'FLUSH_IN_PROGRESS' will prevent other threads from using
- the cache while we are in the middle of the flush, and we release
- the lock so that other threads won't block.
- */
- m_cache_status= Query_cache::FLUSH_IN_PROGRESS;
- STRUCT_UNLOCK(&structure_guard_mutex);
+
+ DBUG_EXECUTE_IF("wait_in_query_cache_flush2",
+ debug_wait_for_kill("wait_in_query_cache_flush2"););
my_hash_reset(&queries);
while (queries_blocks != 0)
@@ -2181,10 +2205,6 @@ void Query_cache::flush_cache()
BLOCK_LOCK_WR(queries_blocks);
free_query_internal(queries_blocks);
}
-
- STRUCT_LOCK(&structure_guard_mutex);
- m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
- pthread_cond_signal(&COND_cache_status_changed);
}
/*
@@ -2364,10 +2384,6 @@ Query_cache::write_block_data(ulong data
}
-/*
- On success STRUCT_UNLOCK(&query_cache.structure_guard_mutex) will be done.
-*/
-
my_bool
Query_cache::append_result_data(Query_cache_block **current_block,
ulong data_len, uchar* data,
@@ -2387,10 +2403,6 @@ Query_cache::append_result_data(Query_ca
if (*current_block == 0)
{
DBUG_PRINT("qcache", ("allocated first result data block %lu", data_len));
- /*
- STRUCT_UNLOCK(&structure_guard_mutex) Will be done by
- write_result_data if success;
- */
DBUG_RETURN(write_result_data(current_block, data_len, data, query_block,
Query_cache_block::RES_BEG));
}
@@ -2421,10 +2433,6 @@ Query_cache::append_result_data(Query_ca
DBUG_PRINT("qcache", ("allocate new block for %lu bytes",
data_len-last_block_free_space));
Query_cache_block *new_block = 0;
- /*
- On success STRUCT_UNLOCK(&structure_guard_mutex) will be done
- by the next call
- */
success = write_result_data(&new_block, data_len-last_block_free_space,
(uchar*)(((uchar*)data)+last_block_free_space),
query_block,
@@ -2439,7 +2447,7 @@ Query_cache::append_result_data(Query_ca
else
{
// It is success (nobody can prevent us write data)
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
}
// Now finally write data to the last block
@@ -2477,7 +2485,7 @@ my_bool Query_cache::write_result_data(Q
if (success)
{
// It is success (nobody can prevent us write data)
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
uint headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
ALIGN_SIZE(sizeof(Query_cache_result)));
#ifndef EMBEDDED_LIBRARY
@@ -2635,36 +2643,23 @@ void Query_cache::invalidate_table(THD *
void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length)
{
- bool interrupt;
- STRUCT_LOCK(&structure_guard_mutex);
- wait_while_table_flush_is_in_progress(&interrupt);
- if (interrupt)
- {
- STRUCT_UNLOCK(&structure_guard_mutex);
- return;
- }
+ DBUG_EXECUTE_IF("wait_in_query_cache_invalidate1",
+ debug_wait_for_kill("wait_in_query_cache_invalidate1"); );
/*
- Setting 'TABLE_FLUSH_IN_PROGRESS' will temporarily disable the cache
- so that structural changes to cache won't block the entire server.
- However, threads requesting to change the query cache will still have
- to wait for the flush to finish.
+ Lock the query cache and queue all invalidation attempts to avoid
+ the risk of a race between invalidation, cache inserts and flushes.
*/
- m_cache_status= Query_cache::TABLE_FLUSH_IN_PROGRESS;
- STRUCT_UNLOCK(&structure_guard_mutex);
+ lock();
+
+ DBUG_EXECUTE_IF("wait_in_query_cache_invalidate2",
+ debug_wait_for_kill("wait_in_query_cache_invalidate2"); );
+
if (query_cache_size > 0)
invalidate_table_internal(thd, key, key_length);
- STRUCT_LOCK(&structure_guard_mutex);
- m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
-
- /*
- net_real_write might be waiting on a change on the m_cache_status
- variable.
- */
- pthread_cond_signal(&COND_cache_status_changed);
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
}
@@ -2673,7 +2668,7 @@ void Query_cache::invalidate_table(THD *
The caller must ensure that no other thread is trying to work with
the query cache when this function is executed.
- @pre structure_guard_mutex is acquired or TABLE_FLUSH_IN_PROGRESS is set.
+ @pre structure_guard_mutex is acquired or LOCKED is set.
*/
void
@@ -2691,7 +2686,7 @@ Query_cache::invalidate_table_internal(T
/**
Invalidate a linked list of query cache blocks.
- Each block tries to aquire a block level lock before
+ Each block tries to acquire a block level lock before
free_query is a called. This function will in turn affect
related table- and result-blocks.
@@ -4217,10 +4212,7 @@ my_bool Query_cache::check_integrity(boo
DBUG_ENTER("check_integrity");
if (!locked)
- STRUCT_LOCK(&structure_guard_mutex);
-
- while (is_flushing())
- pthread_cond_wait(&COND_cache_status_changed,&structure_guard_mutex);
+ lock_and_suspend();
if (my_hash_check(&queries))
{
@@ -4469,7 +4461,7 @@ my_bool Query_cache::check_integrity(boo
}
DBUG_ASSERT(result == 0);
if (!locked)
- STRUCT_UNLOCK(&structure_guard_mutex);
+ unlock();
DBUG_RETURN(result);
}
=== modified file 'sql/sql_cache.h'
--- a/sql/sql_cache.h 2009-06-16 10:23:14 +0000
+++ b/sql/sql_cache.h 2009-06-17 18:00:28 +0000
@@ -274,12 +274,13 @@ public:
private:
+#ifndef DBUG_OFF
+ my_thread_id m_cache_lock_thread_id;
+#endif
pthread_cond_t COND_cache_status_changed;
-
- enum Cache_status { NO_FLUSH_IN_PROGRESS, FLUSH_IN_PROGRESS,
- TABLE_FLUSH_IN_PROGRESS };
-
- Cache_status m_cache_status;
+ enum Cache_lock_status { UNLOCKED, LOCKED_NO_WAIT, LOCKED };
+ Cache_lock_status m_cache_lock_status;
+
bool m_query_cache_is_disabled;
void free_query_internal(Query_cache_block *point);
void invalidate_table_internal(THD *thd, uchar *key, uint32 key_length);
@@ -383,8 +384,6 @@ protected:
Query_cache_block *pprev);
my_bool join_results(ulong join_limit);
- void wait_while_table_flush_is_in_progress(bool *interrupt);
-
/*
Following function control structure_guard_mutex
by themself or don't need structure_guard_mutex
@@ -470,12 +469,6 @@ protected:
void destroy();
-
- bool is_flushing(void)
- {
- return (m_cache_status != Query_cache::NO_FLUSH_IN_PROGRESS);
- }
-
void insert(Query_cache_tls *query_cache_tls,
const char *packet,
ulong length,
@@ -501,6 +494,11 @@ protected:
Query_cache_block_table * point,
const char *name);
my_bool in_blocks(Query_cache_block * point);
+
+ bool try_lock(void);
+ void lock(void);
+ void lock_and_suspend(void);
+ void unlock(void);
};
extern Query_cache query_cache;
Attachment: [text/bzr-bundle] bzr/kristofer.pettersson@sun.com-20090617180028-b428d2rfno9kkt4d.bundle
| Thread |
|---|
| • bzr commit into mysql-pe branch (kristofer.pettersson:3369) Bug#43758 | Kristofer Pettersson | 24 Jun |