List:Commits« Previous MessageNext Message »
From:Kristofer Pettersson Date:April 2 2009 9:16am
Subject:bzr commit into mysql-5.1-bugteam branch (kristofer.pettersson:2822)
Bug#43758
View as plain text  
#At file:///home/thek/Development/cpp/mysqlbzr/51-bug43748/ based on revid:joro@stripped

 2822 Kristofer Pettersson	2009-04-02
      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?
            
      A solution to this situation is to use a broadcast on the thread
      group which contain result set writers.
        
      Note about the test case:
      * If the thread policy in Query_cache::wait_while_flush_is_in_progress
        is set to RELEASE_ONE_ON_EACH_SIGNAL then the test case will hang.
        This should show the existence of a problem before the patch.
      * Two thread groups used to avoid using broadcast where it isn't needed.
        If it worth having two groups? Is substituting signal for broadcast enough?

    added:
      mysql-test/r/query_cache_debug2.result
      mysql-test/t/query_cache_debug2.test
    modified:
      sql/sql_cache.cc
      sql/sql_cache.h
=== added file 'mysql-test/r/query_cache_debug2.result'
--- a/mysql-test/r/query_cache_debug2.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/r/query_cache_debug2.result	2009-04-02 09:16:11 +0000
@@ -0,0 +1,96 @@
+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)) engine=InnoDB;
+CREATE TABLE t2 (a VARCHAR(100)) engine=InnoDB;
+CREATE TABLE t3 (a VARCHAR(100)) engine=InnoDB;
+CREATE TABLE t4 (a VARCHAR(100)) engine=InnoDB;
+CREATE TABLE t5 (a VARCHAR(100)) engine=InnoDB;
+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');
+**
+** 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%';;
+** Assert that the expect process status is obtained.
+**
+** 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;
+** 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;;
+** 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 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 id FROM information_schema.processlist WHERE state='wait_in_query_cache_insert' LIMIT 1 INTO @thread_id;
+KILL QUERY @thread_id;
+SELECT 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 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
+a
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+DELETE FROM t1;
+DELETE FROM t2;
+DELETE FROM t3;
+a
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+DELETE FROM t4;
+DELETE FROM t5;
+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;

=== added file 'mysql-test/t/query_cache_debug2.test'
--- a/mysql-test/t/query_cache_debug2.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/query_cache_debug2.test	2009-04-02 09:16:11 +0000
@@ -0,0 +1,133 @@
+--source include/have_innodb.inc
+#
+# BUG#43748
+#
+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)) engine=InnoDB;
+CREATE TABLE t2 (a VARCHAR(100)) engine=InnoDB;
+CREATE TABLE t3 (a VARCHAR(100)) engine=InnoDB;
+CREATE TABLE t4 (a VARCHAR(100)) engine=InnoDB;
+CREATE TABLE t5 (a VARCHAR(100)) engine=InnoDB;
+
+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 (con1, localhost, root, ,test);
+connect (con2, localhost, root, ,test);
+connect (con3, localhost, root, ,test);
+
+connection con3;
+--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 ** Assert that the expect process status is obtained.
+LET $wait_condition= SELECT COUNT(*)= 1 FROM information_schema.processlist WHERE state= 'wait_in_query_cache_invalidate1';
+--source include/wait_condition.inc
+-- echo **
+
+connection con1;
+--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 con2;
+--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 ** Assert that the two SELECT-stmt threads to reach the hook.
+LET $wait_condition= SELECT 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 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 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 id FROM information_schema.processlist WHERE state='wait_in_query_cache_insert' LIMIT 1 INTO @thread_id;
+KILL QUERY @thread_id;
+SELECT 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 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 con1;
+reap;
+DELETE FROM t1;
+DELETE FROM t2;
+DELETE FROM t3;
+
+connection con2;
+reap;
+DELETE FROM t4;
+DELETE FROM t5;
+
+connection con3;
+reap;
+
+connection default;
+disconnect con1;
+disconnect con2;
+disconnect con3;
+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;

=== modified file 'sql/sql_cache.cc'
--- a/sql/sql_cache.cc	2009-03-17 20:29:24 +0000
+++ b/sql/sql_cache.cc	2009-04-02 09:16:11 +0000
@@ -705,7 +705,7 @@ void query_cache_init_query(NET *net)
 void query_cache_insert(NET *net, const char *packet, ulong length)
 {
   DBUG_ENTER("query_cache_insert");
-
+  THD *thd= current_thd;
   /* See the comment on double-check locking usage above. */
   if (net->query_cache_query == 0)
     DBUG_VOID_RETURN;
@@ -715,13 +715,16 @@ void query_cache_insert(NET *net, const 
 
   STRUCT_LOCK(&query_cache.structure_guard_mutex);
   bool interrupt;
-  query_cache.wait_while_table_flush_is_in_progress(&interrupt);
+  query_cache.wait_while_table_flush_is_in_progress(&interrupt,
+    Query_cache::RELEASE_ALL_CONCURRENTLY);
+  
   if (interrupt)
   {
+    thd_proc_info(thd, thd->query);
     STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
     DBUG_VOID_RETURN;
   }
-
+  thd_proc_info(thd, thd->query);
   Query_cache_block *query_block= (Query_cache_block*)net->query_cache_query;
   if (!query_block)
   {
@@ -776,10 +779,11 @@ void query_cache_abort(NET *net)
   /* See the comment on double-check locking usage above. */
   if (net->query_cache_query == 0)
     DBUG_VOID_RETURN;
-
+  thd_proc_info(thd, "query cache aborting");
   STRUCT_LOCK(&query_cache.structure_guard_mutex);
   bool interrupt;
-  query_cache.wait_while_table_flush_is_in_progress(&interrupt);
+  query_cache.wait_while_table_flush_is_in_progress(&interrupt,
+    Query_cache::RELEASE_ALL_CONCURRENTLY);
   if (interrupt)
   {
     STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
@@ -814,6 +818,7 @@ void query_cache_end_of_result(THD *thd)
   Query_cache_block *query_block;
   DBUG_ENTER("query_cache_end_of_result");
 
+  thd_proc_info(thd, "query cache end of result");
   /* See the comment on double-check locking usage above. */
   if (thd->net.query_cache_query == 0)
     DBUG_VOID_RETURN;
@@ -835,7 +840,8 @@ void query_cache_end_of_result(THD *thd)
   STRUCT_LOCK(&query_cache.structure_guard_mutex);
 
   bool interrupt;
-  query_cache.wait_while_table_flush_is_in_progress(&interrupt);
+  query_cache.wait_while_table_flush_is_in_progress(&interrupt,
+    Query_cache::RELEASE_ALL_CONCURRENTLY);
   if (interrupt)
   {
     STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
@@ -988,7 +994,7 @@ ulong Query_cache::resize(ulong query_ca
 
   STRUCT_LOCK(&structure_guard_mutex);
   m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
-  pthread_cond_signal(&COND_cache_status_changed);
+  signal_all_queues_waiting_for_status_change();
   if (new_query_cache_size)
     DBUG_EXECUTE("check_querycache",check_integrity(1););
   STRUCT_UNLOCK(&structure_guard_mutex);
@@ -1668,8 +1674,10 @@ void Query_cache::invalidate(THD *thd, c
     The structure_guard_mutex will in any case be locked.
 */
 
-void Query_cache::wait_while_table_flush_is_in_progress(bool *interrupt)
+void Query_cache::wait_while_table_flush_is_in_progress(bool *interrupt,
+  Query_cache::Wait_policy policy)
 {
+  THD *thd= current_thd;
   while (is_flushing())
   {
     /*
@@ -1678,16 +1686,35 @@ void Query_cache::wait_while_table_flush
     */
     if (m_cache_status == Query_cache::FLUSH_IN_PROGRESS)
     {
+      thd_proc_info(thd, "skip query cache; flush in progress");
       *interrupt= TRUE;
       return;
     }
+    pthread_cond_t *queue;
+    switch(policy)
+    {
+      case Query_cache::RELEASE_ALL_CONCURRENTLY:
+        DBUG_PRINT("Query_cache",("Wait for pthread broadcast"));
+        queue= &COND_cache_status_changed_broadcast;
+        break;
+      case Query_cache::RELEASE_ONE_ON_EACH_SIGNAL:
+        DBUG_PRINT("Query_cache",("Wait for pthread signal"));
+        queue= &COND_cache_status_changed;
+        break;
+      default:
+        DBUG_ASSERT(0);
+    }
+
     /*
       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);
+    thd_proc_info(thd, "waiting on query cache table flush");
+    if (m_cache_status == Query_cache::TABLE_FLUSH_IN_PROGRESS) {
+      pthread_cond_wait(queue, &structure_guard_mutex);
+    }
   }
   *interrupt= FALSE;
+  thd_proc_info(thd, "query cache");
 }
 
 
@@ -1702,7 +1729,8 @@ void Query_cache::invalidate(char *db)
 
   STRUCT_LOCK(&structure_guard_mutex);
   bool interrupt;
-  wait_while_table_flush_is_in_progress(&interrupt);
+  wait_while_table_flush_is_in_progress(&interrupt,
+    Query_cache::RELEASE_ONE_ON_EACH_SIGNAL);
   if (interrupt)
   {
     STRUCT_UNLOCK(&structure_guard_mutex);
@@ -1787,6 +1815,8 @@ void Query_cache::invalidate_by_MyISAM_f
 void Query_cache::flush()
 {
   DBUG_ENTER("Query_cache::flush");
+  DBUG_EXECUTE_IF("wait_in_query_cache_flush1",
+                  debug_wait_for_kill("wait_in_query_cache_flush1"););
   STRUCT_LOCK(&structure_guard_mutex);
   if (query_cache_size > 0)
   {
@@ -1817,7 +1847,8 @@ void Query_cache::pack(ulong join_limit,
 
   bool interrupt;
   STRUCT_LOCK(&structure_guard_mutex);
-  wait_while_table_flush_is_in_progress(&interrupt);
+  wait_while_table_flush_is_in_progress(&interrupt,
+    Query_cache::RELEASE_ONE_ON_EACH_SIGNAL);
   if (interrupt)
   {
     STRUCT_UNLOCK(&structure_guard_mutex);
@@ -2129,7 +2160,8 @@ void Query_cache::flush_cache()
   */
   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)
   {
@@ -2139,7 +2171,7 @@ void Query_cache::flush_cache()
 
   STRUCT_LOCK(&structure_guard_mutex);
   m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
-  pthread_cond_signal(&COND_cache_status_changed);
+  signal_all_queues_waiting_for_status_change();
 }
 
 /*
@@ -2591,8 +2623,11 @@ void Query_cache::invalidate_table(THD *
 void Query_cache::invalidate_table(THD *thd, uchar * key, uint32  key_length)
 {
   bool interrupt;
+  DBUG_EXECUTE_IF("wait_in_query_cache_invalidate1",
+                  debug_wait_for_kill("wait_in_query_cache_invalidate1"); );
   STRUCT_LOCK(&structure_guard_mutex);
-  wait_while_table_flush_is_in_progress(&interrupt);
+  wait_while_table_flush_is_in_progress(&interrupt,
+    Query_cache::RELEASE_ONE_ON_EACH_SIGNAL);
   if (interrupt)
   {
     STRUCT_UNLOCK(&structure_guard_mutex);
@@ -2607,7 +2642,8 @@ void Query_cache::invalidate_table(THD *
   */
   m_cache_status= Query_cache::TABLE_FLUSH_IN_PROGRESS;
   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)
     invalidate_table_internal(thd, key, key_length);
 
@@ -2618,7 +2654,7 @@ void Query_cache::invalidate_table(THD *
     net_real_write might be waiting on a change on the m_cache_status
     variable.
   */
-  pthread_cond_signal(&COND_cache_status_changed);
+  signal_all_queues_waiting_for_status_change();
   STRUCT_UNLOCK(&structure_guard_mutex);
 }
 
@@ -2634,6 +2670,7 @@ void Query_cache::invalidate_table(THD *
 void
 Query_cache::invalidate_table_internal(THD *thd, uchar *key, uint32 key_length)
 {
+  DBUG_ENTER("Query_cache::invalidate_table_internal");
   Query_cache_block *table_block=
     (Query_cache_block*)hash_search(&tables, key, key_length);
   if (table_block)
@@ -2641,6 +2678,7 @@ Query_cache::invalidate_table_internal(T
     Query_cache_block_table *list_root= table_block->table(0);
     invalidate_query_block_list(thd, list_root);
   }
+  DBUG_VOID_RETURN;
 }
 
 /**

=== modified file 'sql/sql_cache.h'
--- a/sql/sql_cache.h	2008-07-24 13:41:55 +0000
+++ b/sql/sql_cache.h	2009-04-02 09:16:11 +0000
@@ -272,11 +272,25 @@ public:
 
 
 private:
+  /**
+    This condition queue is signaled when ever query cache status changes.
+    Threads waiting in this queue will reset, flush or invalidate
+    either a part of the cache or all of the cache. The threads will be
+    executed in a serialized order.
+  */
   pthread_cond_t COND_cache_status_changed;
+  /**
+    This condition queue is broadcasted when ever query cache status changes.
+    Threads waiting in this queue are writing a result set to the cache and
+    should be executed concurrently when ever the lock is dropped.
+  */
+  pthread_cond_t COND_cache_status_changed_broadcast;
 
   enum Cache_status { NO_FLUSH_IN_PROGRESS, FLUSH_IN_PROGRESS,
                       TABLE_FLUSH_IN_PROGRESS };
 
+  enum Wait_policy { RELEASE_ONE_ON_EACH_SIGNAL, RELEASE_ALL_CONCURRENTLY };
+  
   Cache_status m_cache_status;
 
   void free_query_internal(Query_cache_block *point);
@@ -380,7 +394,22 @@ protected:
 	      Query_cache_block *pprev);
   my_bool join_results(ulong join_limit);
 
-  void wait_while_table_flush_is_in_progress(bool *interrupt);
+  void wait_while_table_flush_is_in_progress(bool *interrupt,
+        Query_cache::Wait_policy policy);
+
+  void signal_all_queues_waiting_for_status_change(void)
+  {
+    /*
+      Release on thread waiting on a status change
+    */
+    pthread_cond_signal(&COND_cache_status_changed);
+
+    /*
+      Release all threads waiting on a status change
+    */
+    pthread_cond_broadcast(&COND_cache_status_changed_broadcast);
+  }
+
 
   /*
     Following function control structure_guard_mutex


Attachment: [text/bzr-bundle] bzr/kristofer.pettersson@sun.com-20090402091611-w0fn9p0u51artzz3.bundle
Thread
bzr commit into mysql-5.1-bugteam branch (kristofer.pettersson:2822)Bug#43758Kristofer Pettersson2 Apr