diff options
author | Kristofer Pettersson <kristofer.pettersson@sun.com> | 2009-06-16 10:34:47 +0200 |
---|---|---|
committer | Kristofer Pettersson <kristofer.pettersson@sun.com> | 2009-06-16 10:34:47 +0200 |
commit | 02e5ad98817136d97374b4cc8d05083cd2ea6f8a (patch) | |
tree | 666211f28806c8b03460cba6db5e54ae2cafc3b3 /mysql-test/r/query_cache_debug.result | |
parent | 62a32540fc8ff953ff0bbf360ffa273d0d2e28c4 (diff) | |
download | mariadb-git-02e5ad98817136d97374b4cc8d05083cd2ea6f8a.tar.gz |
Bug#43758 Query cache can lock up threads in 'freeing items' state
Early patch submitted for discussion.
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.
Diffstat (limited to 'mysql-test/r/query_cache_debug.result')
-rw-r--r-- | mysql-test/r/query_cache_debug.result | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/mysql-test/r/query_cache_debug.result b/mysql-test/r/query_cache_debug.result index b03a71d3fec..b83bccef39c 100644 --- a/mysql-test/r/query_cache_debug.result +++ b/mysql-test/r/query_cache_debug.result @@ -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' 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' 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; |