List:Commits« Previous MessageNext Message »
From:Kristofer Pettersson Date:June 10 2009 6:32pm
Subject:bzr commit into mysql-5.1-bugteam branch (kristofer.pettersson:2924)
Bug#43758
View as plain text  
#At file:///Users/thek/Development/51-bug43758/ based on revid:satya.bn@stripped

 2924 Kristofer Pettersson	2009-06-10
      Bug#43758 Query cache can lock up threads in 'freeing items' state
      
      It is possible for more than one thread to enter the condition
      in query_cache_insert(), but the condition predicate is to
      signal one thread each time the cache status changes between
      the following states: {NO_FLUSH_IN_PROGRESS,FLUSH_IN_PROGRESS,
      TABLE_FLUSH_IN_PROGRESS}
      
      Consider three threads THD1, THD2, THD3
      
       THD2: select ... => Got a writer in ::store_query
       THD3: select ... => Got a writer in ::store_query
       THD1: flush tables => qc status= FLUSH_IN_PROGRESS;
                           new writers are blocked.
       THD2: select ... => Still got a writer and enters cond in
                           query_cache_insert
       THD3: select ... => Still got a writer and enters cond in
                           query_cache_insert
       THD1: flush tables => finished and signal status change.
       THD2: select ... => Wakes up and completes the insert.
       THD3: select ... => Happily waiting for better times. Why hurry?
      
      This patch is a refactoring of this lock system. It introduces tree new methods:
       try_lock_query_cache()
       lock_quey_cache()
       unlock_query_cache()
      
      This change also makes wait_while_table_flush_is_in_progress().
      All threads are registered and put in a queue. On each unlock the first
      element in the queue is signalled. This resolve
      the issues with left over threads.
     @ mysql-test/r/query_cache_debug.result
        * Added test case for bug 43758
     @ mysql-test/t/query_cache_debug.test
        * Added test case for bug 43758
     @ sql/ilist.h
        * Introduced standard interface for a double linked intrusive list.
     @ sql/sql_cache.cc
        * Replaced calls to wait_while_table_flush_is_in_progress() with
          calls to try_lock_query_cache(), lock_query_cache() and unlock_query_cache().
        * Introduced new class Query_cache_queue_element.
        * Renamed enumeration Cache_status to Cache_lock_status.
        * Renamed enumeration items to UNLOCKED, LOCKED_NO_WAIT and LOCKED.
          If the SUSPENDING lock type is used to lock the query cache, other
          threads using try_lock_query_cache() will fail to acquire the lock.
          This is useful if the query cache is temporary disabled due to 
          a full table flush or defragmentation.
     @ sql/sql_cache.h
        * Replaced calls to wait_while_table_flush_is_in_progress() with
          calls to try_lock_query_cache(), lock_query_cache() and unlock_query_cache().
        * Introduced new class Query_cache_queue_element.
        * Renamed enumeration Cache_status to Cache_lock_status.
        * Renamed enumeration items to UNLOCKED, LOCKED_NO_WAIT and LOCKED.
          If the LOCKED_NO_WAIT lock type is used to lock the query cache, other
          threads using try_lock_query_cache() will fail to acquire the lock.
          This is useful if the query cache is temporary disabled due to 
          a full table flush or defragmentation.

    added:
      sql/ilist.h
    modified:
      mysql-test/r/query_cache_debug.result
      mysql-test/t/query_cache_debug.test
      sql/sql_cache.cc
      sql/sql_cache.h
=== modified file 'mysql-test/r/query_cache_debug.result'
--- a/mysql-test/r/query_cache_debug.result	2009-02-19 21:09:35 +0000
+++ b/mysql-test/r/query_cache_debug.result	2009-06-10 18:32:26 +0000
@@ -71,3 +71,111 @@ DROP TABLE t1,t2;
 SET GLOBAL concurrent_insert= DEFAULT;
 SET GLOBAL query_cache_size= DEFAULT;
 SET GLOBAL query_cache_type= DEFAULT;
+#
+# Bug43758 Query cache can lock up threads in 'freeing items' state
+#
+FLUSH STATUS;
+SET GLOBAL query_cache_type=DEMAND;
+SET GLOBAL query_cache_size= 1024*768;
+DROP TABLE IF EXISTS t1,t2,t3,t4,t5;
+CREATE TABLE t1 (a VARCHAR(100));
+CREATE TABLE t2 (a VARCHAR(100));
+CREATE TABLE t3 (a VARCHAR(100));
+CREATE TABLE t4 (a VARCHAR(100));
+CREATE TABLE t5 (a VARCHAR(100));
+INSERT INTO t1 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t2 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t3 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t4 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+INSERT INTO t5 VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
+=================================== Connection thd1
+**
+** Load Query Cache with a result set and one table.
+**
+SELECT SQL_CACHE * FROM t1;
+a
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+*************************************************************************
+** We want to accomplish the following state:
+**  - Query cache status: TABLE_FLUSH_IN_PROGRESS
+**  - THD1: invalidate_table_internal (iterating query blocks)
+**  - THD2: query_cache_insert (cond_wait)
+**  - THD3: query_cache_insert (cond_wait)
+**  - No thread should be holding the structure_guard_mutex.
+**
+** First step is to place a DELETE-statement on the debug hook just
+** before the mutex lock in invalidate_table_internal.
+** This will allow new result sets to be written into the QC.
+** 
+SET SESSION debug='+d,wait_in_query_cache_invalidate1';
+SET SESSION debug='+d,wait_in_query_cache_invalidate2';
+DELETE FROM t1 WHERE a like '%a%';;
+=================================== Connection default
+** Assert that the expect process status is obtained.
+**
+=================================== Connection thd2
+** On THD2: Insert a result into the cache. This attempt will be blocked
+** because of a debug hook placed just before the mutex lock after which
+** the first part of the result set is written.
+SET SESSION debug='+d,wait_in_query_cache_insert';
+SELECT SQL_CACHE * FROM t2 UNION SELECT * FROM t3;
+=================================== Connection thd3
+** On THD3: Insert another result into the cache and block on the same
+** debug hook.
+SET SESSION debug='+d,wait_in_query_cache_insert';
+SELECT SQL_CACHE * FROM t4 UNION SELECT * FROM t5;;
+=================================== Connection default
+** Assert that the two SELECT-stmt threads to reach the hook.
+**
+**
+** Signal the DELETE thread, THD1, to continue. It will enter the mutex
+** lock and set query cache status to TABLE_FLUSH_IN_PROGRESS and then
+** unlock the mutex before stopping on the next debug hook.
+SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_invalidate1' LIMIT 1 INTO @flush_thread_id;
+KILL QUERY @flush_thread_id;
+** Assert that we reach the next debug hook.
+**
+** Signal the remaining debug hooks blocking THD2 and THD3.
+** The threads will grab the guard mutex enter the wait condition and
+** and finally release the mutex. The threads will continue to wait
+** until a broadcast signal reaches them causing both threads to 
+** come alive and check the condition.
+SELECT SQL_NO_CACHE id FROM information_schema.processlist WHERE state='wait_in_query_cache_insert' 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;

=== modified file 'mysql-test/t/query_cache_debug.test'
--- a/mysql-test/t/query_cache_debug.test	2009-02-19 21:09:35 +0000
+++ b/mysql-test/t/query_cache_debug.test	2009-06-10 18:32:26 +0000
@@ -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;

=== added file 'sql/ilist.h'
--- a/sql/ilist.h	1970-01-01 00:00:00 +0000
+++ b/sql/ilist.h	2009-06-10 18:32:26 +0000
@@ -0,0 +1,121 @@
+/* Copyright (C) 2001-2006 MySQL AB
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; version 2 of the License.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+#ifndef _I_LIST_H
+#define _I_LIST_H
+
+class I_Linked_list_link
+{
+  void *obj;
+  I_Linked_list_link *next, *prev;
+public:
+  I_Linked_list_link() { init(); }
+  inline void init()
+  {
+    obj= NULL;
+    next= prev= this;
+  }
+  template <typename T, I_Linked_list_link T::*link> friend class I_Linked_list;
+};
+
+template <typename T, I_Linked_list_link T::*link>
+class I_Linked_list
+{
+  I_Linked_list_link head;
+
+private:
+  /** Insert a node between two consecutive nodes. */
+  void add(I_Linked_list_link *prev, I_Linked_list_link *next,
+           I_Linked_list_link *node)
+  {
+    next->prev = node;
+    node->next = next;
+    node->prev = prev;
+    prev->next = node;
+  }
+
+  /** Remove a node by linking its neighboring nodes. */
+  void rem(I_Linked_list_link *prev, I_Linked_list_link *next)
+  {
+    next->prev = prev;
+    prev->next = next;
+  }
+
+  I_Linked_list_link* node_ptr(T *obj)
+  {
+    return &(obj->*link);
+  }
+
+public:
+  I_Linked_list()
+  {
+    init();
+  }
+
+  void init()
+  {
+    head.obj= NULL;
+    head.next= head.prev= &head;
+  }
+
+  /** Removes all elements from the list. */
+  inline void empty()
+  {
+    init();
+  }
+
+  /** Returns true if list is empty. */
+  bool is_empty() const
+  {
+    return &head == head.next;
+  }
+
+  /** Add an element to the front of the list */
+  void push_front(T *obj)
+  {
+    I_Linked_list_link *node= node_ptr(obj);
+    node->obj= obj;
+    add(&head, head.next, node);
+  }
+
+  /** Add an element to the end of the list. */
+  void push_back(T *obj)
+  {
+    I_Linked_list_link *node= node_ptr(obj);
+    node->obj= obj;
+    add(head.prev, &head, node);
+  }
+
+  /** Remove an element from the list. */
+  void remove(T *obj)
+  {
+    I_Linked_list_link *node= node_ptr(obj);
+    rem(node->prev, node->next);
+  }
+
+  /** Returns the first element of the list. */
+  T *front() const
+  {
+    return (T*) head.next->obj;
+  }
+
+  /** Returns the last element of the list. */
+  T *back() const
+  {
+    return (T*) head.prev->obj;
+  }
+};
+
+#endif

=== modified file 'sql/sql_cache.cc'
--- a/sql/sql_cache.cc	2009-05-15 12:57:51 +0000
+++ b/sql/sql_cache.cc	2009-06-10 18:32:26 +0000
@@ -333,6 +333,7 @@ TODO list:
 #include <hash.h>
 #include "../storage/myisammrg/ha_myisammrg.h"
 #include "../storage/myisammrg/myrg_def.h"
+#include "sql_cache.h"
 
 #ifdef EMBEDDED_LIBRARY
 #include "emb_qcache.h"
@@ -418,6 +419,115 @@ TYPELIB query_cache_type_typelib=
   array_elements(query_cache_type_names)-1,"", query_cache_type_names, NULL
 };
 
+Query_cache_queue_element::Query_cache_queue_element(void)
+{
+  pthread_cond_init(&m_cond, NULL);
+}
+
+Query_cache_queue_element::~Query_cache_queue_element(void)
+{
+  pthread_cond_destroy(&m_cond);
+}
+
+/**
+  Serialize access to the query cache.
+
+  @param lock_type LOCKED or SUSPENDING
+
+  The lock type affects other threads using try_lock_query_cache(). If the
+  lock_type is SUSPENDING then try_lock_query_cache() will fail. This lock_type
+  is used if the query cache is undergoing a full cache flush.
+
+  @return
+   @retval FALSE An exclusive lock was taken
+   @retval TRUE The locking attempt failed
+*/
+
+bool Query_cache::try_lock_query_cache(Query_cache::Cache_lock_status lock_type=
+                                       Query_cache::LOCKED)
+{
+  DBUG_ENTER("Query_cache::try_lock_query_cache");
+
+  bool interrupt= FALSE;
+  pthread_mutex_lock(&structure_guard_mutex);
+  while (1)
+  {
+    if (m_cache_lock_status == Query_cache::UNLOCKED)
+    {
+      DBUG_ASSERT(lock_type == Query_cache::LOCKED_NO_WAIT ||
+                  lock_type == Query_cache::LOCKED);
+      m_cache_lock_status= lock_type;
+      break;
+    }
+    else if (m_cache_lock_status == Query_cache::LOCKED_NO_WAIT)
+    {
+      /*
+        If query cache is protected by a SUSPENDING lock this thread
+        should avoid using the query cache as it is being invalidated.
+        Threads which are already in waiting on the query cache are not
+        affected.
+      */
+      interrupt= TRUE;
+      break;
+    }
+    else if (m_cache_lock_status == Query_cache::LOCKED)
+    {
+      Query_cache_queue_element queue_element;
+      m_wait_queue.push_back(&queue_element);
+      pthread_cond_wait(&queue_element.m_cond, &structure_guard_mutex);
+      m_wait_queue.remove(&queue_element);
+    }
+  }
+
+  pthread_mutex_unlock(&structure_guard_mutex);
+  DBUG_RETURN(interrupt);
+}
+
+
+/**
+  Serialize access to the query cache.
+  @param lock_type LOCKED or SUSPENDING
+
+  The lock type affects other threads using try_lock_query_cache(). If the
+  lock_type is SUSPENDING then try_lock_query_cache() will fail. This lock_type
+  is used if the query cache is undergoing a full cache flush.
+ */
+
+void Query_cache::lock_query_cache(Query_cache::Cache_lock_status lock_type=
+                                   Query_cache::LOCKED)
+{
+  DBUG_ENTER("Query_cache::lock_query_cache");
+  pthread_mutex_lock(&structure_guard_mutex);
+  while (is_locked())
+  {
+    Query_cache_queue_element queue_element;
+    m_wait_queue.push_back(&queue_element);
+    pthread_cond_wait(&queue_element.m_cond, &structure_guard_mutex);
+    m_wait_queue.remove(&queue_element);
+  }
+  m_cache_lock_status= lock_type;
+  pthread_mutex_unlock(&structure_guard_mutex);
+  DBUG_VOID_RETURN;
+}
+
+
+void Query_cache::unlock_query_cache(void)
+{
+  DBUG_ENTER("Query_cache::unlock_query_cache");
+  pthread_mutex_lock(&structure_guard_mutex);
+  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;
+  if (!m_wait_queue.is_empty())
+  {
+    Query_cache_queue_element *queue_element= m_wait_queue.front();
+    DBUG_PRINT("Query_cache",("Sending signal"));
+    pthread_cond_signal(&queue_element->m_cond);
+  }
+  pthread_mutex_unlock(&structure_guard_mutex);
+  DBUG_VOID_RETURN;
+}
+
 
 /**
   Helper function for determine if a SELECT statement has a SQL_NO_CACHE
@@ -713,14 +823,8 @@ void query_cache_insert(NET *net, const 
   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_query_cache())
     DBUG_VOID_RETURN;
-  }
 
   Query_cache_block *query_block= (Query_cache_block*)net->query_cache_query;
   if (!query_block)
@@ -729,7 +833,7 @@ void query_cache_insert(NET *net, const 
       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_query_cache();
     DBUG_VOID_RETURN;
   }
 
@@ -755,7 +859,7 @@ void query_cache_insert(NET *net, const 
     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_query_cache();
     DBUG_VOID_RETURN;
   }
 
@@ -777,14 +881,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_query_cache())
     DBUG_VOID_RETURN;
-  }
 
   /*
     While we were waiting another thread might have changed the status
@@ -803,8 +901,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_query_cache();
   DBUG_VOID_RETURN;
 }
 
@@ -832,15 +929,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_query_cache())
     DBUG_VOID_RETURN;
-  }
 
   query_block= ((Query_cache_block*) thd->net.query_cache_query);
   if (query_block)
@@ -869,10 +959,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_query_cache();
       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 +974,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_query_cache();
   DBUG_VOID_RETURN;
 }
 
@@ -950,11 +1037,7 @@ ulong Query_cache::resize(ulong query_ca
 			query_cache_size_arg));
   DBUG_ASSERT(initialized);
 
-  STRUCT_LOCK(&structure_guard_mutex);
-  while (is_flushing())
-    pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
-  m_cache_status= Query_cache::FLUSH_IN_PROGRESS;
-  STRUCT_UNLOCK(&structure_guard_mutex);
+  lock_query_cache(Query_cache::LOCKED_NO_WAIT);
 
   /*
     Wait for all readers and writers to exit. When the list of all queries
@@ -986,13 +1069,10 @@ ulong Query_cache::resize(ulong query_ca
   query_cache_size= query_cache_size_arg;
   new_query_cache_size= init_cache();
 
-  STRUCT_LOCK(&structure_guard_mutex);
-  m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
-  pthread_cond_signal(&COND_cache_status_changed);
   if (new_query_cache_size)
     DBUG_EXECUTE("check_querycache",check_integrity(1););
-  STRUCT_UNLOCK(&structure_guard_mutex);
 
+  unlock_query_cache();
   DBUG_RETURN(new_query_cache_size);
 }
 
@@ -1089,15 +1169,16 @@ def_week_frmt: %lu, in_trans: %d, autoco
     */
     ha_release_temporary_latches(thd);
 
-    STRUCT_LOCK(&structure_guard_mutex);
-    if (query_cache_size == 0 || is_flushing())
+    if (try_lock_query_cache())
+      DBUG_VOID_RETURN;
+    if (query_cache_size == 0)
     {
       /*
         A table- or a full flush operation can potentially take a long time to 
         finish. We choose not to wait for them and skip caching statements
         instead.
       */
-      STRUCT_UNLOCK(&structure_guard_mutex);
+      unlock_query_cache();
       DBUG_VOID_RETURN;
     }
     DUMP(this);
@@ -1105,7 +1186,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
     if (ask_handler_allowance(thd, tables_used))
     {
       refused++;
-      STRUCT_UNLOCK(&structure_guard_mutex);
+      unlock_query_cache();
       DBUG_VOID_RETURN;
     }
 
@@ -1153,7 +1234,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
 	  DBUG_PRINT("qcache", ("insertion in query hash"));
 	  header->unlock_n_destroy();
 	  free_memory_block(query_block);
-	  STRUCT_UNLOCK(&structure_guard_mutex);
+          unlock_query_cache();
 	  goto end;
 	}
 	if (!register_all_tables(query_block, tables_used, local_tables))
@@ -1163,7 +1244,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
 	  hash_delete(&queries, (uchar *) query_block);
 	  header->unlock_n_destroy();
 	  free_memory_block(query_block);
-	  STRUCT_UNLOCK(&structure_guard_mutex);
+          unlock_query_cache();
 	  goto end;
 	}
 	double_linked_list_simple_include(query_block, &queries_blocks);
@@ -1173,7 +1254,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
 	header->writer(net);
 	header->tables_type(tables_type);
 
-	STRUCT_UNLOCK(&structure_guard_mutex);
+        unlock_query_cache();
 
 	// init_n_lock make query block locked
 	BLOCK_UNLOCK_WR(query_block);
@@ -1182,7 +1263,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
       {
 	// We have not enough memory to store query => do nothing
 	refused++;
-	STRUCT_UNLOCK(&structure_guard_mutex);
+        unlock_query_cache();
 	DBUG_PRINT("warning", ("Can't allocate query"));
       }
     }
@@ -1190,7 +1271,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
     {
       // Another thread is processing the same query => do nothing
       refused++;
-      STRUCT_UNLOCK(&structure_guard_mutex);
+      unlock_query_cache();
       DBUG_PRINT("qcache", ("Another thread process same query"));
     }
   }
@@ -1282,17 +1363,16 @@ Query_cache::send_result_to_client(THD *
     }
   }
 
-  STRUCT_LOCK(&structure_guard_mutex);
-
-  if (query_cache_size == 0)
+  /*
+    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_query_cache())
     goto err_unlock;
 
-  if (is_flushing())
-  {
-    /* Return; Query cache is temporarily disabled while we flush. */
-    DBUG_PRINT("qcache",("query cache disabled"));
+  if (query_cache_size == 0)
     goto err_unlock;
-  }
 
   /*
     Check that we haven't forgot to reset the query cache variables;
@@ -1427,7 +1507,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
         DBUG_PRINT("qcache",
                    ("Temporary table detected: '%s.%s'",
                     table_list.db, table_list.alias));
-        STRUCT_UNLOCK(&structure_guard_mutex);
+        unlock_query_cache();
         /*
           We should not store result of this query because it contain
           temporary tables => assign following variable to make check
@@ -1448,7 +1528,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
       DBUG_PRINT("qcache",
 		 ("probably no SELECT access to %s.%s =>  return to normal processing",
 		  table_list.db, table_list.alias));
-      STRUCT_UNLOCK(&structure_guard_mutex);
+      unlock_query_cache();
       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 +1571,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
   }
   move_to_query_list_end(query_block);
   hits++;
-  STRUCT_UNLOCK(&structure_guard_mutex);
+  unlock_query_cache();
 
   /*
     Send cached result to client
@@ -1530,7 +1610,7 @@ def_week_frmt: %lu, in_trans: %d, autoco
   DBUG_RETURN(1);				// Result sent to client
 
 err_unlock:
-  STRUCT_UNLOCK(&structure_guard_mutex);
+  unlock_query_cache();
 err:
   DBUG_RETURN(0);				// Query was not cached
 }
@@ -1651,47 +1731,6 @@ void Query_cache::invalidate(THD *thd, c
 
 
 /**
-  Synchronize the thread with any flushing operations.
-
-  This helper function is called whenever a thread needs to operate on the
-  query cache structure (example: during invalidation). If a table flush is in
-  progress this function will wait for it to stop. If a full flush is in
-  progress, the function will set the interrupt parameter to indicate that the
-  current operation is redundant and should be interrupted.
-
-  @param[out] interrupt This out-parameter will be set to TRUE if the calling
-    function is redundant and should be interrupted.
-
-  @return If the interrupt-parameter is TRUE then m_cache_status is set to
-    NO_FLUSH_IN_PROGRESS. If the interrupt-parameter is FALSE then
-    m_cache_status is set to FLUSH_IN_PROGRESS.
-    The structure_guard_mutex will in any case be locked.
-*/
-
-void Query_cache::wait_while_table_flush_is_in_progress(bool *interrupt)
-{
-  while (is_flushing())
-  {
-    /*
-      If there already is a full flush in progress query cache isn't enabled
-      and additional flushes are redundant; just return instead.
-    */
-    if (m_cache_status == Query_cache::FLUSH_IN_PROGRESS)
-    {
-      *interrupt= TRUE;
-      return;
-    }
-    /*
-      If a table flush is in progress; wait on cache status to change.
-    */
-    if (m_cache_status == Query_cache::TABLE_FLUSH_IN_PROGRESS)
-      pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
-  }
-  *interrupt= FALSE;
-}
-
-
-/**
    Remove all cached queries that uses the given database.
 */
 
@@ -1700,14 +1739,13 @@ 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;
-  }
+  /*
+    try_lock_query_cache is a blocking attempt to get a lock.
+    If a lock can't be obtained it is because the cache is either disabled or
+    it is being flushed.
+  */
+  if (try_lock_query_cache())
+    DBUG_VOID_RETURN;
 
   THD *thd= current_thd;
 
@@ -1763,7 +1801,7 @@ void Query_cache::invalidate(char *db)
       } while (restart);
     } // end if( tables_blocks )
   }
-  STRUCT_UNLOCK(&structure_guard_mutex);
+  unlock_query_cache();
 
   DBUG_VOID_RETURN;
 }
@@ -1787,7 +1825,10 @@ void Query_cache::invalidate_by_MyISAM_f
 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_query_cache(Query_cache::LOCKED_NO_WAIT);
   if (query_cache_size > 0)
   {
     DUMP(this);
@@ -1796,7 +1837,7 @@ void Query_cache::flush()
   }
 
   DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
-  STRUCT_UNLOCK(&structure_guard_mutex);
+  unlock_query_cache();
   DBUG_VOID_RETURN;
 }
 
@@ -1815,18 +1856,12 @@ void Query_cache::pack(ulong join_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);
-    DBUG_VOID_RETURN;
-  }
+  lock_query_cache();
+
 
   if (query_cache_size == 0)
   {
-    STRUCT_UNLOCK(&structure_guard_mutex);
+    unlock_query_cache();
     DBUG_VOID_RETURN;
   }
 
@@ -1836,7 +1871,7 @@ void Query_cache::pack(ulong join_limit,
     pack_cache();
   } while ((++i < iteration_limit) && join_results(join_limit));
 
-  STRUCT_UNLOCK(&structure_guard_mutex);
+  unlock_query_cache();
   DBUG_VOID_RETURN;
 }
 
@@ -1851,11 +1886,10 @@ void Query_cache::destroy()
   else
   {
     /* Underlying code expects the lock. */
-    STRUCT_LOCK(&structure_guard_mutex);
+    lock_query_cache();
     free_cache();
-    STRUCT_UNLOCK(&structure_guard_mutex);
+    unlock_query_cache();
 
-    pthread_cond_destroy(&COND_cache_status_changed);
     pthread_mutex_destroy(&structure_guard_mutex);
     initialized = 0;
   }
@@ -1871,8 +1905,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 +2145,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 +2155,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);
 }
 
 /*
@@ -2394,7 +2409,7 @@ Query_cache::append_result_data(Query_ca
   else
   {
     // It is success (nobody can prevent us write data)
-    STRUCT_UNLOCK(&structure_guard_mutex);
+    unlock_query_cache();
   }
 
   // Now finally write data to the last block
@@ -2432,7 +2447,7 @@ my_bool Query_cache::write_result_data(Q
   if (success)
   {
     // It is success (nobody can prevent us write data)
-    STRUCT_UNLOCK(&structure_guard_mutex);
+    unlock_query_cache();
     uint headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
 			ALIGN_SIZE(sizeof(Query_cache_result)));
 #ifndef EMBEDDED_LIBRARY
@@ -2590,36 +2605,20 @@ void Query_cache::invalidate_table(THD *
 
 void Query_cache::invalidate_table(THD *thd, uchar * key, uint32  key_length)
 {
-  bool interrupt;
-  STRUCT_LOCK(&structure_guard_mutex);
-  wait_while_table_flush_is_in_progress(&interrupt);
-  if (interrupt)
-  {
-    STRUCT_UNLOCK(&structure_guard_mutex);
+  DBUG_EXECUTE_IF("wait_in_query_cache_invalidate1",
+                   debug_wait_for_kill("wait_in_query_cache_invalidate1"); );
+
+  if (try_lock_query_cache())
     return;
-  }
 
-  /*
-    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.
-  */
-  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);
 
-  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_query_cache();
 }
 
 
@@ -2628,7 +2627,7 @@ void Query_cache::invalidate_table(THD *
   The caller must ensure that no other thread is trying to work with
   the query cache when this function is executed.
 
-  @pre structure_guard_mutex is acquired or TABLE_FLUSH_IN_PROGRESS is set.
+  @pre structure_guard_mutex is acquired or LOCKED is set.
 */
 
 void
@@ -2646,7 +2645,7 @@ Query_cache::invalidate_table_internal(T
 /**
   Invalidate a linked list of query cache blocks.
 
-  Each block tries to aquire a block level lock before
+  Each block tries to acquire a block level lock before
   free_query is a called. This function will in turn affect
   related table- and result-blocks.
 
@@ -4170,10 +4169,7 @@ my_bool Query_cache::check_integrity(boo
   DBUG_ENTER("check_integrity");
 
   if (!locked)
-    STRUCT_LOCK(&structure_guard_mutex);
-
-  while (is_flushing())
-    pthread_cond_wait(&COND_cache_status_changed,&structure_guard_mutex);
+    lock_query_cache();
 
   if (hash_check(&queries))
   {
@@ -4422,7 +4418,7 @@ my_bool Query_cache::check_integrity(boo
   }
   DBUG_ASSERT(result == 0);
   if (!locked)
-    STRUCT_UNLOCK(&structure_guard_mutex);
+    unlock_query_cache();
   DBUG_RETURN(result);
 }
 

=== modified file 'sql/sql_cache.h'
--- a/sql/sql_cache.h	2008-07-24 13:41:55 +0000
+++ b/sql/sql_cache.h	2009-06-10 18:32:26 +0000
@@ -65,6 +65,8 @@ struct Query_cache_query;
 struct Query_cache_result;
 class Query_cache;
 
+#include "ilist.h";
+
 /**
   This class represents a node in the linked chain of queries
   belonging to one table.
@@ -261,6 +263,29 @@ struct Query_cache_memory_bin_step
   }
 };
 
+
+/**
+  Encapsulates a condition variable for each waiting thread and participates
+  in an intrusive linked list.
+  @see Wait_queue_list
+*/
+
+class Query_cache_queue_element
+{
+public:
+  Query_cache_queue_element(void);
+  ~Query_cache_queue_element(void);
+  pthread_cond_t m_cond;
+  
+  /**
+    Intrusive list traits used by I_Linked_list interface.
+  */
+  I_Linked_list_link link;
+};
+
+typedef I_Linked_list<Query_cache_queue_element, &Query_cache_queue_element::link>
+  Wait_queue_list;
+
 class Query_cache
 {
 public:
@@ -272,12 +297,16 @@ public:
 
 
 private:
-  pthread_cond_t COND_cache_status_changed;
 
-  enum Cache_status { NO_FLUSH_IN_PROGRESS, FLUSH_IN_PROGRESS,
-                      TABLE_FLUSH_IN_PROGRESS };
+  enum Cache_lock_status { UNLOCKED, LOCKED_NO_WAIT, LOCKED };
 
-  Cache_status m_cache_status;
+  Cache_lock_status m_cache_lock_status;
+
+  /**
+    A list of client threads which wait on the query cache to set an exclusive
+    lock.
+  */
+  Wait_queue_list m_wait_queue;
 
   void free_query_internal(Query_cache_block *point);
   void invalidate_table_internal(THD *thd, uchar *key, uint32 key_length);
@@ -380,8 +409,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,9 +496,9 @@ protected:
   friend void query_cache_end_of_result(THD *thd);
   friend void query_cache_abort(NET *net);
 
-  bool is_flushing(void) 
+  bool is_locked(void)
   { 
-    return (m_cache_status != Query_cache::NO_FLUSH_IN_PROGRESS);
+    return (m_cache_lock_status != Query_cache::UNLOCKED);
   }
 
   /*
@@ -491,6 +518,10 @@ protected:
 			Query_cache_block_table * point,
 			const char *name);
   my_bool in_blocks(Query_cache_block * point);
+
+  bool try_lock_query_cache(Query_cache::Cache_lock_status lock_type);
+  void lock_query_cache(Query_cache::Cache_lock_status lock_type);
+  void unlock_query_cache(void);
 };
 
 extern Query_cache query_cache;


Attachment: [text/bzr-bundle]
Thread
bzr commit into mysql-5.1-bugteam branch (kristofer.pettersson:2924)Bug#43758Kristofer Pettersson10 Jun