Commit b9e1ee63 authored by Kristofer Pettersson's avatar Kristofer Pettersson

Automerge

parents 3cc3aa7d 02e5ad98
......@@ -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;
......@@ -112,3 +112,147 @@ 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' 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;
......@@ -352,11 +352,6 @@ TODO list:
#define RW_UNLOCK(M) {DBUG_PRINT("lock", ("rwlock unlock 0x%lx",(ulong)(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 0x%lx",\
__LINE__,(ulong)(B))); \
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_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()
......@@ -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
directive.
......@@ -713,14 +840,8 @@ void query_cache_insert(NET *net, const char *packet, ulong length)
DBUG_EXECUTE_IF("wait_in_query_cache_insert",
debug_wait_for_kill("wait_in_query_cache_insert"); );
STRUCT_LOCK(&query_cache.structure_guard_mutex);
bool interrupt;
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
if (interrupt)
{
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
if (query_cache.try_lock())
DBUG_VOID_RETURN;
}
Query_cache_block *query_block= (Query_cache_block*)net->query_cache_query;
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
invalidated; there is nothing left to do.
*/
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
query_cache.unlock();
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.refused++;
// append_result_data no success => we need unlock
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
query_cache.unlock();
DBUG_VOID_RETURN;
}
......@@ -777,14 +898,8 @@ void query_cache_abort(NET *net)
if (net->query_cache_query == 0)
DBUG_VOID_RETURN;
STRUCT_LOCK(&query_cache.structure_guard_mutex);
bool interrupt;
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
if (interrupt)
{
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
if (query_cache.try_lock())
DBUG_VOID_RETURN;
}
/*
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););
}
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
query_cache.unlock();
DBUG_VOID_RETURN;
}
......@@ -832,15 +946,8 @@ void query_cache_end_of_result(THD *thd)
emb_count_querycache_size(thd));
#endif
STRUCT_LOCK(&query_cache.structure_guard_mutex);
bool interrupt;
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
if (interrupt)
{
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
if (query_cache.try_lock())
DBUG_VOID_RETURN;
}
query_block= ((Query_cache_block*) thd->net.query_cache_query);
if (query_block)
......@@ -869,10 +976,9 @@ void query_cache_end_of_result(THD *thd)
*/
DBUG_ASSERT(0);
query_cache.free_query(query_block);
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
query_cache.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);
......@@ -885,13 +991,11 @@ void query_cache_end_of_result(THD *thd)
/* Drop the writer. */
header->writer(0);
thd->net.query_cache_query= 0;
BLOCK_UNLOCK_WR(query_block);
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
}
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
query_cache.unlock();
DBUG_VOID_RETURN;
}
......@@ -950,11 +1054,7 @@ ulong Query_cache::resize(ulong query_cache_size_arg)
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
......@@ -986,13 +1086,10 @@ ulong Query_cache::resize(ulong query_cache_size_arg)
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);
}
......@@ -1089,15 +1186,16 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
*/
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.
*/
STRUCT_UNLOCK(&structure_guard_mutex);
if (try_lock())
DBUG_VOID_RETURN;
if (query_cache_size == 0)
{
unlock();
DBUG_VOID_RETURN;
}
DUMP(this);
......@@ -1105,7 +1203,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
if (ask_handler_allowance(thd, tables_used))
{
refused++;
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
DBUG_VOID_RETURN;
}
......@@ -1153,7 +1251,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
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))
......@@ -1163,7 +1261,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
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);
......@@ -1173,7 +1271,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
header->writer(net);
header->tables_type(tables_type);
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
// init_n_lock make query block locked
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
refused++;
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
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
refused++;
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
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)
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;
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",
("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
......@@ -1448,7 +1545,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
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
......@@ -1491,7 +1588,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
}
move_to_query_list_end(query_block);
hits++;
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
/*
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
err_unlock:
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
err:
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.
*/
......@@ -1700,14 +1756,11 @@ void Query_cache::invalidate(char *db)
bool restart= FALSE;
DBUG_ENTER("Query_cache::invalidate (db)");
STRUCT_LOCK(&structure_guard_mutex);
bool interrupt;
wait_while_table_flush_is_in_progress(&interrupt);
if (interrupt)
{
STRUCT_UNLOCK(&structure_guard_mutex);
return;
}
/*
Lock the query cache and queue all invalidation attempts to avoid
the risk of a race between invalidation, cache inserts and flushes.
*/
lock();
THD *thd= current_thd;
......@@ -1763,7 +1816,7 @@ void Query_cache::invalidate(char *db)
} while (restart);
} // end if( tables_blocks )
}
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
DBUG_VOID_RETURN;
}
......@@ -1787,7 +1840,10 @@ void Query_cache::invalidate_by_MyISAM_filename(const char *filename)
void 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)
{
DUMP(this);
......@@ -1796,7 +1852,7 @@ void Query_cache::flush()
}
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
DBUG_VOID_RETURN;
}
......@@ -1815,18 +1871,16 @@ void Query_cache::pack(ulong join_limit, uint iteration_limit)
{
DBUG_ENTER("Query_cache::pack");
bool interrupt;
STRUCT_LOCK(&structure_guard_mutex);
wait_while_table_flush_is_in_progress(&interrupt);
if (interrupt)
{
STRUCT_UNLOCK(&structure_guard_mutex);
/*
If the entire qc is being invalidated we can bail out early
instead of waiting for the lock.
*/
if (try_lock())
DBUG_VOID_RETURN;
}
if (query_cache_size == 0)
{
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
DBUG_VOID_RETURN;
}
......@@ -1836,7 +1890,7 @@ void Query_cache::pack(ulong join_limit, uint iteration_limit)
pack_cache();
} while ((++i < iteration_limit) && join_results(join_limit));
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
DBUG_VOID_RETURN;
}
......@@ -1851,9 +1905,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);
......@@ -1872,7 +1926,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;
DBUG_VOID_RETURN;
}
......@@ -2112,23 +2166,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)
......@@ -2136,10 +2176,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);
}
/*
......@@ -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
Query_cache::append_result_data(Query_cache_block **current_block,
ulong data_len, uchar* data,
......@@ -2342,10 +2374,6 @@ Query_cache::append_result_data(Query_cache_block **current_block,
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));
}
......@@ -2376,10 +2404,6 @@ Query_cache::append_result_data(Query_cache_block **current_block,
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,
......@@ -2394,7 +2418,7 @@ Query_cache::append_result_data(Query_cache_block **current_block,
else
{
// It is success (nobody can prevent us write data)
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
}
// 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)
{
// 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
......@@ -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)
{
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();
}
......@@ -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 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
......@@ -2646,7 +2657,7 @@ Query_cache::invalidate_table_internal(THD *thd, uchar *key, uint32 key_length)
/**
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.
......@@ -4170,10 +4181,7 @@ my_bool Query_cache::check_integrity(bool locked)
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 (hash_check(&queries))
{
......@@ -4422,7 +4430,7 @@ my_bool Query_cache::check_integrity(bool locked)
}
DBUG_ASSERT(result == 0);
if (!locked)
STRUCT_UNLOCK(&structure_guard_mutex);
unlock();
DBUG_RETURN(result);
}
......
......@@ -272,12 +272,12 @@ 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;
void free_query_internal(Query_cache_block *point);
void invalidate_table_internal(THD *thd, uchar *key, uint32 key_length);
......@@ -380,8 +380,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
......@@ -469,11 +467,6 @@ protected:
friend void query_cache_end_of_result(THD *thd);
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
We don't protect these with ifndef DBUG_OFF to not have to recompile
......@@ -491,6 +484,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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment