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.
This commit is contained in:
parent
8f2ff69434
commit
b22d02ad42
@ -71,3 +71,111 @@ DROP TABLE t1,t2;
|
|||||||
SET GLOBAL concurrent_insert= DEFAULT;
|
SET GLOBAL concurrent_insert= DEFAULT;
|
||||||
SET GLOBAL query_cache_size= DEFAULT;
|
SET GLOBAL query_cache_size= DEFAULT;
|
||||||
SET GLOBAL query_cache_type= 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;
|
||||||
|
@ -112,3 +112,147 @@ DROP TABLE t1,t2;
|
|||||||
SET GLOBAL concurrent_insert= DEFAULT;
|
SET GLOBAL concurrent_insert= DEFAULT;
|
||||||
SET GLOBAL query_cache_size= DEFAULT;
|
SET GLOBAL query_cache_size= DEFAULT;
|
||||||
SET GLOBAL query_cache_type= 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' 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;
|
||||||
|
--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;
|
||||||
|
|
||||||
|
connection default;
|
||||||
|
--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;
|
||||||
|
412
sql/sql_cache.cc
412
sql/sql_cache.cc
@ -352,11 +352,6 @@ TODO list:
|
|||||||
#define RW_UNLOCK(M) {DBUG_PRINT("lock", ("rwlock unlock 0x%lx",(ulong)(M))); \
|
#define RW_UNLOCK(M) {DBUG_PRINT("lock", ("rwlock unlock 0x%lx",(ulong)(M))); \
|
||||||
if (!rw_unlock(M)) DBUG_PRINT("lock", ("rwlock unlock ok")); \
|
if (!rw_unlock(M)) DBUG_PRINT("lock", ("rwlock unlock ok")); \
|
||||||
else DBUG_PRINT("lock", ("rwlock unlock FAILED %d", errno)); }
|
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 0x%lx",\
|
#define BLOCK_LOCK_WR(B) {DBUG_PRINT("lock", ("%d LOCK_WR 0x%lx",\
|
||||||
__LINE__,(ulong)(B))); \
|
__LINE__,(ulong)(B))); \
|
||||||
B->query()->lock_writing();}
|
B->query()->lock_writing();}
|
||||||
@ -403,8 +398,6 @@ static void debug_wait_for_kill(const char *info)
|
|||||||
#define RW_WLOCK(M) rw_wrlock(M)
|
#define RW_WLOCK(M) rw_wrlock(M)
|
||||||
#define RW_RLOCK(M) rw_rdlock(M)
|
#define RW_RLOCK(M) rw_rdlock(M)
|
||||||
#define RW_UNLOCK(M) rw_unlock(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_WR(B) B->query()->lock_writing()
|
||||||
#define BLOCK_LOCK_RD(B) B->query()->lock_reading()
|
#define BLOCK_LOCK_RD(B) B->query()->lock_reading()
|
||||||
#define BLOCK_UNLOCK_WR(B) B->query()->unlock_writing()
|
#define BLOCK_UNLOCK_WR(B) B->query()->unlock_writing()
|
||||||
@ -419,6 +412,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
|
Helper function for determine if a SELECT statement has a SQL_NO_CACHE
|
||||||
directive.
|
directive.
|
||||||
@ -713,14 +840,8 @@ void query_cache_insert(NET *net, const char *packet, ulong length)
|
|||||||
DBUG_EXECUTE_IF("wait_in_query_cache_insert",
|
DBUG_EXECUTE_IF("wait_in_query_cache_insert",
|
||||||
debug_wait_for_kill("wait_in_query_cache_insert"); );
|
debug_wait_for_kill("wait_in_query_cache_insert"); );
|
||||||
|
|
||||||
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
if (query_cache.try_lock())
|
||||||
bool interrupt;
|
|
||||||
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
|
|
||||||
if (interrupt)
|
|
||||||
{
|
|
||||||
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
|
||||||
|
|
||||||
Query_cache_block *query_block= (Query_cache_block*)net->query_cache_query;
|
Query_cache_block *query_block= (Query_cache_block*)net->query_cache_query;
|
||||||
if (!query_block)
|
if (!query_block)
|
||||||
@ -729,7 +850,7 @@ void query_cache_insert(NET *net, const char *packet, ulong length)
|
|||||||
We lost the writer and the currently processed query has been
|
We lost the writer and the currently processed query has been
|
||||||
invalidated; there is nothing left to do.
|
invalidated; there is nothing left to do.
|
||||||
*/
|
*/
|
||||||
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
query_cache.unlock();
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -755,7 +876,7 @@ void query_cache_insert(NET *net, const char *packet, ulong length)
|
|||||||
query_cache.free_query(query_block);
|
query_cache.free_query(query_block);
|
||||||
query_cache.refused++;
|
query_cache.refused++;
|
||||||
// append_result_data no success => we need unlock
|
// append_result_data no success => we need unlock
|
||||||
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
query_cache.unlock();
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -777,14 +898,8 @@ void query_cache_abort(NET *net)
|
|||||||
if (net->query_cache_query == 0)
|
if (net->query_cache_query == 0)
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
|
|
||||||
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
if (query_cache.try_lock())
|
||||||
bool interrupt;
|
|
||||||
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
|
|
||||||
if (interrupt)
|
|
||||||
{
|
|
||||||
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
While we were waiting another thread might have changed the status
|
While we were waiting another thread might have changed the status
|
||||||
@ -803,8 +918,7 @@ void query_cache_abort(NET *net)
|
|||||||
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
||||||
}
|
}
|
||||||
|
|
||||||
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
query_cache.unlock();
|
||||||
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,15 +946,8 @@ void query_cache_end_of_result(THD *thd)
|
|||||||
emb_count_querycache_size(thd));
|
emb_count_querycache_size(thd));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
if (query_cache.try_lock())
|
||||||
|
|
||||||
bool interrupt;
|
|
||||||
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
|
|
||||||
if (interrupt)
|
|
||||||
{
|
|
||||||
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
|
||||||
|
|
||||||
query_block= ((Query_cache_block*) thd->net.query_cache_query);
|
query_block= ((Query_cache_block*) thd->net.query_cache_query);
|
||||||
if (query_block)
|
if (query_block)
|
||||||
@ -869,10 +976,9 @@ void query_cache_end_of_result(THD *thd)
|
|||||||
*/
|
*/
|
||||||
DBUG_ASSERT(0);
|
DBUG_ASSERT(0);
|
||||||
query_cache.free_query(query_block);
|
query_cache.free_query(query_block);
|
||||||
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
query_cache.unlock();
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_result_block= header->result()->prev;
|
last_result_block= header->result()->prev;
|
||||||
allign_size= ALIGN_SIZE(last_result_block->used);
|
allign_size= ALIGN_SIZE(last_result_block->used);
|
||||||
len= max(query_cache.min_allocation_unit, allign_size);
|
len= max(query_cache.min_allocation_unit, allign_size);
|
||||||
@ -885,13 +991,11 @@ void query_cache_end_of_result(THD *thd)
|
|||||||
/* Drop the writer. */
|
/* Drop the writer. */
|
||||||
header->writer(0);
|
header->writer(0);
|
||||||
thd->net.query_cache_query= 0;
|
thd->net.query_cache_query= 0;
|
||||||
|
|
||||||
BLOCK_UNLOCK_WR(query_block);
|
BLOCK_UNLOCK_WR(query_block);
|
||||||
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
||||||
|
|
||||||
}
|
}
|
||||||
|
query_cache.unlock();
|
||||||
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -950,11 +1054,7 @@ ulong Query_cache::resize(ulong query_cache_size_arg)
|
|||||||
query_cache_size_arg));
|
query_cache_size_arg));
|
||||||
DBUG_ASSERT(initialized);
|
DBUG_ASSERT(initialized);
|
||||||
|
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
lock_and_suspend();
|
||||||
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);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Wait for all readers and writers to exit. When the list of all queries
|
Wait for all readers and writers to exit. When the list of all queries
|
||||||
@ -986,13 +1086,10 @@ ulong Query_cache::resize(ulong query_cache_size_arg)
|
|||||||
query_cache_size= query_cache_size_arg;
|
query_cache_size= query_cache_size_arg;
|
||||||
new_query_cache_size= init_cache();
|
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)
|
if (new_query_cache_size)
|
||||||
DBUG_EXECUTE("check_querycache",check_integrity(1););
|
DBUG_EXECUTE("check_querycache",check_integrity(1););
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
||||||
|
|
||||||
|
unlock();
|
||||||
DBUG_RETURN(new_query_cache_size);
|
DBUG_RETURN(new_query_cache_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1089,15 +1186,16 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
*/
|
*/
|
||||||
ha_release_temporary_latches(thd);
|
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)
|
||||||
{
|
{
|
||||||
/*
|
unlock();
|
||||||
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);
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
DUMP(this);
|
DUMP(this);
|
||||||
@ -1105,7 +1203,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
if (ask_handler_allowance(thd, tables_used))
|
if (ask_handler_allowance(thd, tables_used))
|
||||||
{
|
{
|
||||||
refused++;
|
refused++;
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1153,7 +1251,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
DBUG_PRINT("qcache", ("insertion in query hash"));
|
DBUG_PRINT("qcache", ("insertion in query hash"));
|
||||||
header->unlock_n_destroy();
|
header->unlock_n_destroy();
|
||||||
free_memory_block(query_block);
|
free_memory_block(query_block);
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
if (!register_all_tables(query_block, tables_used, local_tables))
|
if (!register_all_tables(query_block, tables_used, local_tables))
|
||||||
@ -1163,7 +1261,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
hash_delete(&queries, (uchar *) query_block);
|
hash_delete(&queries, (uchar *) query_block);
|
||||||
header->unlock_n_destroy();
|
header->unlock_n_destroy();
|
||||||
free_memory_block(query_block);
|
free_memory_block(query_block);
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
double_linked_list_simple_include(query_block, &queries_blocks);
|
double_linked_list_simple_include(query_block, &queries_blocks);
|
||||||
@ -1173,7 +1271,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
header->writer(net);
|
header->writer(net);
|
||||||
header->tables_type(tables_type);
|
header->tables_type(tables_type);
|
||||||
|
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
|
|
||||||
// init_n_lock make query block locked
|
// init_n_lock make query block locked
|
||||||
BLOCK_UNLOCK_WR(query_block);
|
BLOCK_UNLOCK_WR(query_block);
|
||||||
@ -1182,7 +1280,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
{
|
{
|
||||||
// We have not enough memory to store query => do nothing
|
// We have not enough memory to store query => do nothing
|
||||||
refused++;
|
refused++;
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
DBUG_PRINT("warning", ("Can't allocate query"));
|
DBUG_PRINT("warning", ("Can't allocate query"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1190,7 +1288,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
{
|
{
|
||||||
// Another thread is processing the same query => do nothing
|
// Another thread is processing the same query => do nothing
|
||||||
refused++;
|
refused++;
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
DBUG_PRINT("qcache", ("Another thread process same query"));
|
DBUG_PRINT("qcache", ("Another thread process same query"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1282,18 +1380,17 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
/*
|
||||||
|
Try to obtain an exclusive lock on the query cache. If the cache is
|
||||||
|
disabled or if a full cache flush is in progress, the attempt to
|
||||||
|
get the lock is aborted.
|
||||||
|
*/
|
||||||
|
if (try_lock())
|
||||||
|
goto err;
|
||||||
|
|
||||||
if (query_cache_size == 0)
|
if (query_cache_size == 0)
|
||||||
goto err_unlock;
|
goto err_unlock;
|
||||||
|
|
||||||
if (is_flushing())
|
|
||||||
{
|
|
||||||
/* Return; Query cache is temporarily disabled while we flush. */
|
|
||||||
DBUG_PRINT("qcache",("query cache disabled"));
|
|
||||||
goto err_unlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Check that we haven't forgot to reset the query cache variables;
|
Check that we haven't forgot to reset the query cache variables;
|
||||||
make sure there are no attached query cache writer to this thread.
|
make sure there are no attached query cache writer to this thread.
|
||||||
@ -1427,7 +1524,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
DBUG_PRINT("qcache",
|
DBUG_PRINT("qcache",
|
||||||
("Temporary table detected: '%s.%s'",
|
("Temporary table detected: '%s.%s'",
|
||||||
table_list.db, table_list.alias));
|
table_list.db, table_list.alias));
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
/*
|
/*
|
||||||
We should not store result of this query because it contain
|
We should not store result of this query because it contain
|
||||||
temporary tables => assign following variable to make check
|
temporary tables => assign following variable to make check
|
||||||
@ -1448,7 +1545,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
DBUG_PRINT("qcache",
|
DBUG_PRINT("qcache",
|
||||||
("probably no SELECT access to %s.%s => return to normal processing",
|
("probably no SELECT access to %s.%s => return to normal processing",
|
||||||
table_list.db, table_list.alias));
|
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
|
thd->lex->safe_to_cache_query=0; // Don't try to cache this
|
||||||
BLOCK_UNLOCK_RD(query_block);
|
BLOCK_UNLOCK_RD(query_block);
|
||||||
DBUG_RETURN(-1); // Privilege error
|
DBUG_RETURN(-1); // Privilege error
|
||||||
@ -1491,7 +1588,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
}
|
}
|
||||||
move_to_query_list_end(query_block);
|
move_to_query_list_end(query_block);
|
||||||
hits++;
|
hits++;
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Send cached result to client
|
Send cached result to client
|
||||||
@ -1530,7 +1627,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
|
|||||||
DBUG_RETURN(1); // Result sent to client
|
DBUG_RETURN(1); // Result sent to client
|
||||||
|
|
||||||
err_unlock:
|
err_unlock:
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
err:
|
err:
|
||||||
DBUG_RETURN(0); // Query was not cached
|
DBUG_RETURN(0); // Query was not cached
|
||||||
}
|
}
|
||||||
@ -1650,47 +1747,6 @@ void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
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.
|
Remove all cached queries that uses the given database.
|
||||||
*/
|
*/
|
||||||
@ -1700,14 +1756,11 @@ void Query_cache::invalidate(char *db)
|
|||||||
bool restart= FALSE;
|
bool restart= FALSE;
|
||||||
DBUG_ENTER("Query_cache::invalidate (db)");
|
DBUG_ENTER("Query_cache::invalidate (db)");
|
||||||
|
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
/*
|
||||||
bool interrupt;
|
Lock the query cache and queue all invalidation attempts to avoid
|
||||||
wait_while_table_flush_is_in_progress(&interrupt);
|
the risk of a race between invalidation, cache inserts and flushes.
|
||||||
if (interrupt)
|
*/
|
||||||
{
|
lock();
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
THD *thd= current_thd;
|
THD *thd= current_thd;
|
||||||
|
|
||||||
@ -1763,7 +1816,7 @@ void Query_cache::invalidate(char *db)
|
|||||||
} while (restart);
|
} while (restart);
|
||||||
} // end if( tables_blocks )
|
} // end if( tables_blocks )
|
||||||
}
|
}
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
@ -1787,7 +1840,10 @@ void Query_cache::invalidate_by_MyISAM_filename(const char *filename)
|
|||||||
void Query_cache::flush()
|
void Query_cache::flush()
|
||||||
{
|
{
|
||||||
DBUG_ENTER("Query_cache::flush");
|
DBUG_ENTER("Query_cache::flush");
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
DBUG_EXECUTE_IF("wait_in_query_cache_flush1",
|
||||||
|
debug_wait_for_kill("wait_in_query_cache_flush1"););
|
||||||
|
|
||||||
|
lock_and_suspend();
|
||||||
if (query_cache_size > 0)
|
if (query_cache_size > 0)
|
||||||
{
|
{
|
||||||
DUMP(this);
|
DUMP(this);
|
||||||
@ -1796,7 +1852,7 @@ void Query_cache::flush()
|
|||||||
}
|
}
|
||||||
|
|
||||||
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1815,18 +1871,16 @@ void Query_cache::pack(ulong join_limit, uint iteration_limit)
|
|||||||
{
|
{
|
||||||
DBUG_ENTER("Query_cache::pack");
|
DBUG_ENTER("Query_cache::pack");
|
||||||
|
|
||||||
bool interrupt;
|
/*
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
If the entire qc is being invalidated we can bail out early
|
||||||
wait_while_table_flush_is_in_progress(&interrupt);
|
instead of waiting for the lock.
|
||||||
if (interrupt)
|
*/
|
||||||
{
|
if (try_lock())
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
|
||||||
|
|
||||||
if (query_cache_size == 0)
|
if (query_cache_size == 0)
|
||||||
{
|
{
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1836,7 +1890,7 @@ void Query_cache::pack(ulong join_limit, uint iteration_limit)
|
|||||||
pack_cache();
|
pack_cache();
|
||||||
} while ((++i < iteration_limit) && join_results(join_limit));
|
} while ((++i < iteration_limit) && join_results(join_limit));
|
||||||
|
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1851,9 +1905,9 @@ void Query_cache::destroy()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Underlying code expects the lock. */
|
/* Underlying code expects the lock. */
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
lock_and_suspend();
|
||||||
free_cache();
|
free_cache();
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
|
|
||||||
pthread_cond_destroy(&COND_cache_status_changed);
|
pthread_cond_destroy(&COND_cache_status_changed);
|
||||||
pthread_mutex_destroy(&structure_guard_mutex);
|
pthread_mutex_destroy(&structure_guard_mutex);
|
||||||
@ -1872,7 +1926,7 @@ void Query_cache::init()
|
|||||||
DBUG_ENTER("Query_cache::init");
|
DBUG_ENTER("Query_cache::init");
|
||||||
pthread_mutex_init(&structure_guard_mutex,MY_MUTEX_INIT_FAST);
|
pthread_mutex_init(&structure_guard_mutex,MY_MUTEX_INIT_FAST);
|
||||||
pthread_cond_init(&COND_cache_status_changed, NULL);
|
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;
|
initialized = 1;
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
@ -2112,23 +2166,9 @@ void Query_cache::free_cache()
|
|||||||
|
|
||||||
void Query_cache::flush_cache()
|
void Query_cache::flush_cache()
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
If there is flush in progress, wait for it to finish, and then do
|
DBUG_EXECUTE_IF("wait_in_query_cache_flush2",
|
||||||
our flush. This is necessary because something could be added to
|
debug_wait_for_kill("wait_in_query_cache_flush2"););
|
||||||
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);
|
|
||||||
|
|
||||||
my_hash_reset(&queries);
|
my_hash_reset(&queries);
|
||||||
while (queries_blocks != 0)
|
while (queries_blocks != 0)
|
||||||
@ -2136,10 +2176,6 @@ void Query_cache::flush_cache()
|
|||||||
BLOCK_LOCK_WR(queries_blocks);
|
BLOCK_LOCK_WR(queries_blocks);
|
||||||
free_query_internal(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2319,10 +2355,6 @@ Query_cache::write_block_data(ulong data_len, uchar* data,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
On success STRUCT_UNLOCK(&query_cache.structure_guard_mutex) will be done.
|
|
||||||
*/
|
|
||||||
|
|
||||||
my_bool
|
my_bool
|
||||||
Query_cache::append_result_data(Query_cache_block **current_block,
|
Query_cache::append_result_data(Query_cache_block **current_block,
|
||||||
ulong data_len, uchar* data,
|
ulong data_len, uchar* data,
|
||||||
@ -2342,10 +2374,6 @@ Query_cache::append_result_data(Query_cache_block **current_block,
|
|||||||
if (*current_block == 0)
|
if (*current_block == 0)
|
||||||
{
|
{
|
||||||
DBUG_PRINT("qcache", ("allocated first result data block %lu", data_len));
|
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,
|
DBUG_RETURN(write_result_data(current_block, data_len, data, query_block,
|
||||||
Query_cache_block::RES_BEG));
|
Query_cache_block::RES_BEG));
|
||||||
}
|
}
|
||||||
@ -2376,10 +2404,6 @@ Query_cache::append_result_data(Query_cache_block **current_block,
|
|||||||
DBUG_PRINT("qcache", ("allocate new block for %lu bytes",
|
DBUG_PRINT("qcache", ("allocate new block for %lu bytes",
|
||||||
data_len-last_block_free_space));
|
data_len-last_block_free_space));
|
||||||
Query_cache_block *new_block = 0;
|
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,
|
success = write_result_data(&new_block, data_len-last_block_free_space,
|
||||||
(uchar*)(((uchar*)data)+last_block_free_space),
|
(uchar*)(((uchar*)data)+last_block_free_space),
|
||||||
query_block,
|
query_block,
|
||||||
@ -2394,7 +2418,7 @@ Query_cache::append_result_data(Query_cache_block **current_block,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// It is success (nobody can prevent us write data)
|
// It is success (nobody can prevent us write data)
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now finally write data to the last block
|
// Now finally write data to the last block
|
||||||
@ -2432,7 +2456,7 @@ my_bool Query_cache::write_result_data(Query_cache_block **result_block,
|
|||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
// It is success (nobody can prevent us write data)
|
// It is success (nobody can prevent us write data)
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
uint headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
|
uint headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
|
||||||
ALIGN_SIZE(sizeof(Query_cache_result)));
|
ALIGN_SIZE(sizeof(Query_cache_result)));
|
||||||
#ifndef EMBEDDED_LIBRARY
|
#ifndef EMBEDDED_LIBRARY
|
||||||
@ -2590,36 +2614,23 @@ 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)
|
||||||
{
|
{
|
||||||
bool interrupt;
|
DBUG_EXECUTE_IF("wait_in_query_cache_invalidate1",
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
debug_wait_for_kill("wait_in_query_cache_invalidate1"); );
|
||||||
wait_while_table_flush_is_in_progress(&interrupt);
|
|
||||||
if (interrupt)
|
|
||||||
{
|
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Setting 'TABLE_FLUSH_IN_PROGRESS' will temporarily disable the cache
|
Lock the query cache and queue all invalidation attempts to avoid
|
||||||
so that structural changes to cache won't block the entire server.
|
the risk of a race between invalidation, cache inserts and flushes.
|
||||||
However, threads requesting to change the query cache will still have
|
|
||||||
to wait for the flush to finish.
|
|
||||||
*/
|
*/
|
||||||
m_cache_status= Query_cache::TABLE_FLUSH_IN_PROGRESS;
|
lock();
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
||||||
|
DBUG_EXECUTE_IF("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);
|
||||||
|
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
unlock();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2628,7 +2639,7 @@ void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length)
|
|||||||
The caller must ensure that no other thread is trying to work with
|
The caller must ensure that no other thread is trying to work with
|
||||||
the query cache when this function is executed.
|
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
|
void
|
||||||
@ -2646,7 +2657,7 @@ Query_cache::invalidate_table_internal(THD *thd, uchar *key, uint32 key_length)
|
|||||||
/**
|
/**
|
||||||
Invalidate a linked list of query cache blocks.
|
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
|
free_query is a called. This function will in turn affect
|
||||||
related table- and result-blocks.
|
related table- and result-blocks.
|
||||||
|
|
||||||
@ -4170,10 +4181,7 @@ my_bool Query_cache::check_integrity(bool locked)
|
|||||||
DBUG_ENTER("check_integrity");
|
DBUG_ENTER("check_integrity");
|
||||||
|
|
||||||
if (!locked)
|
if (!locked)
|
||||||
STRUCT_LOCK(&structure_guard_mutex);
|
lock_and_suspend();
|
||||||
|
|
||||||
while (is_flushing())
|
|
||||||
pthread_cond_wait(&COND_cache_status_changed,&structure_guard_mutex);
|
|
||||||
|
|
||||||
if (hash_check(&queries))
|
if (hash_check(&queries))
|
||||||
{
|
{
|
||||||
@ -4422,7 +4430,7 @@ my_bool Query_cache::check_integrity(bool locked)
|
|||||||
}
|
}
|
||||||
DBUG_ASSERT(result == 0);
|
DBUG_ASSERT(result == 0);
|
||||||
if (!locked)
|
if (!locked)
|
||||||
STRUCT_UNLOCK(&structure_guard_mutex);
|
unlock();
|
||||||
DBUG_RETURN(result);
|
DBUG_RETURN(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,12 +272,12 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifndef DBUG_OFF
|
||||||
|
my_thread_id m_cache_lock_thread_id;
|
||||||
|
#endif
|
||||||
pthread_cond_t COND_cache_status_changed;
|
pthread_cond_t COND_cache_status_changed;
|
||||||
|
enum Cache_lock_status { UNLOCKED, LOCKED_NO_WAIT, LOCKED };
|
||||||
enum Cache_status { NO_FLUSH_IN_PROGRESS, FLUSH_IN_PROGRESS,
|
Cache_lock_status m_cache_lock_status;
|
||||||
TABLE_FLUSH_IN_PROGRESS };
|
|
||||||
|
|
||||||
Cache_status m_cache_status;
|
|
||||||
|
|
||||||
void free_query_internal(Query_cache_block *point);
|
void free_query_internal(Query_cache_block *point);
|
||||||
void invalidate_table_internal(THD *thd, uchar *key, uint32 key_length);
|
void invalidate_table_internal(THD *thd, uchar *key, uint32 key_length);
|
||||||
@ -380,8 +380,6 @@ protected:
|
|||||||
Query_cache_block *pprev);
|
Query_cache_block *pprev);
|
||||||
my_bool join_results(ulong join_limit);
|
my_bool join_results(ulong join_limit);
|
||||||
|
|
||||||
void wait_while_table_flush_is_in_progress(bool *interrupt);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Following function control structure_guard_mutex
|
Following function control structure_guard_mutex
|
||||||
by themself or don't need structure_guard_mutex
|
by themself or don't need structure_guard_mutex
|
||||||
@ -469,11 +467,6 @@ protected:
|
|||||||
friend void query_cache_end_of_result(THD *thd);
|
friend void query_cache_end_of_result(THD *thd);
|
||||||
friend void query_cache_abort(NET *net);
|
friend void query_cache_abort(NET *net);
|
||||||
|
|
||||||
bool is_flushing(void)
|
|
||||||
{
|
|
||||||
return (m_cache_status != Query_cache::NO_FLUSH_IN_PROGRESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The following functions are only used when debugging
|
The following functions are only used when debugging
|
||||||
We don't protect these with ifndef DBUG_OFF to not have to recompile
|
We don't protect these with ifndef DBUG_OFF to not have to recompile
|
||||||
@ -491,6 +484,11 @@ protected:
|
|||||||
Query_cache_block_table * point,
|
Query_cache_block_table * point,
|
||||||
const char *name);
|
const char *name);
|
||||||
my_bool in_blocks(Query_cache_block * point);
|
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;
|
extern Query_cache query_cache;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user