List:Commits« Previous MessageNext Message »
From:Dmitry Lenev Date:May 24 2012 10:29am
Subject:bzr push into mysql-trunk branch (Dmitry.Lenev:3899 to 3901) WL#5772
View as plain text  
 3901 Dmitry Lenev	2012-05-23
      WL#5772 "Add partitioned Table Definition Cache to avoid using
      LOCK_open and its derivatives in DML queries".
      
      Follow-up patch #2: Added unit tests for Table_cache,
      Table_cache_manager and Table_cache_iterator classes.

    added:
      unittest/gunit/table_cache-t.cc
    modified:
      unittest/gunit/CMakeLists.txt
 3900 Dmitry Lenev	2012-05-22
      WL#5772 "Add partitioned Table Definition Cache to avoid using
      LOCK_open and its derivatives in DML queries".
      
      Follow-up patch #1:
      - Move definition of Table_cache, Table_cache_manager and
        Table_cache_iterator classes and their methods to a separate
        files - table_cache.h and table_cache.cc.
      - Made methods of Table_cache_iterator inline.

    added:
      sql/table_cache.cc
      sql/table_cache.h
    modified:
      sql/CMakeLists.txt
      sql/mysqld.cc
      sql/sql_base.cc
      sql/sql_base.h
      sql/sql_parse.cc
      sql/sql_test.cc
      sql/sys_vars.cc
      sql/table.cc
      sql/table.h
 3899 Dmitry Lenev	2012-05-21
      WL#5772 "Add partitioned Table Definition Cache to avoid using
      LOCK_open and its derivatives in DML queries".
      
      Re-introduced table cache - a separate cache holding TABLE objects
      ready for use by statements protected by its own mutex. In the most
      common case a connection opening/closing a table only needs to go
      to this cache for getting/releasing TABLE object and do not need
      to go to the table definition cache. By having several instances of
      such cache (each protected by its own lock) and partitioning
      user connections between these instances (based on connection id)
      we can greatly improve scalability in some scenarios.
      
      Added new start-up parameter --table_open_cache_instances for
      setting number of table cache instances in the system.
      
      Existing --table_open_cache parameter now limits total number of
      TABLE object in all table cache instances combined.
      
      Added Table_open_cache_hits, Table_open_cache_misses,
      Table_open_cache_overflows - per-connection and global status
      variables for tracking and tuning performance of new cache.
      (The latter allows to track how often we have to expel tables
      from the cache since the table cache instance contains more
      than table_open_cache/table_open_cache_instances objects.)
     @ mysql-test/r/mysqld--help-notwin.result
        Adjusted test results after adding new --table-open-cache-instances
        parameter.
     @ mysql-test/r/mysqld--help-win.result
        Adjusted test results after adding new --table-open-cache-instances
        parameter.
     @ mysql-test/r/status.result
        Added test coverage for status variables added by WL#5772 "Add
        partitioned Table Definition Cache to avoid using LOCK_open and
        its derivatives in DML queries".
     @ mysql-test/suite/perfschema/r/func_mutex.result
        Adjusted test case to use mutex for table cache instance instead
        of LOCK_open. The latter is no longer locked in the common case,
        when table is present in table cache.
     @ mysql-test/suite/perfschema/t/func_mutex.test
        Adjusted test case to use mutex for table cache instance instead
        of LOCK_open. The latter is no longer locked in the common case,
        when table is present in table cache.
     @ mysql-test/suite/sys_vars/r/table_open_cache_instances_basic.result
        Added test coverage for new table_open_cache_instances
        global variable.
     @ mysql-test/suite/sys_vars/t/table_open_cache_instances_basic.test
        Added test coverage for new table_open_cache_instances
        global variable.
     @ mysql-test/t/status.test
        Added test coverage for status variables added by WL#5772 "Add
        partitioned Table Definition Cache to avoid using LOCK_open and
        its derivatives in DML queries".
     @ sql/mysqld.cc
        Added global variable for table_cache_instances parameter. Also
        added global table_cache_size_per_instance variable for size of
        single instance of table cache.
        Added new status variables for table cache - Table_open_cache_hits,
        Table_open_cache_misses, Table_open_cache_overflows.
     @ sql/mysqld.h
        Added global variable for table_cache_instances parameter. Also
        added global table_cache_size_per_instance variable for size of
        single instance of table cache.
     @ sql/sql_base.cc
        Implemented a table cache:
        * Added Table_cache object representing a table cache instance.
          Made counter of TABLE objects and list of unused TABLE objects
          members of this class, protected by its lock.
        * Introduced Table_cache_element, a structure representing table
          in the specific table cache instances. Moved lists of used and
          unused TABLE instances for the table from TABLE_SHARE to this
          structure.
        * Made table_def_add_used_table(), table_def_remove_table(),
          table_def_use_table() and table_def_unuse_table() methods of
          Table_cache class.
        * Moved common code trimming list of unused tables to auxiliary
          method in this class. Moved all code dealing with global list
          of unused tables to methods of Table_cache.
        * Introduced Table_cache_manager class - a container for all
          Table_cache instances in the system and incapsulating operations
          on all of them.
        * Changed code in open_table() to try to get TABLE object from
          appropriate Table_cache instance, before going to the table
          definition cache for TABLE_SHARE and creating a TABLE object
          based on it.
        * Changed close_thread_table() to deal with Table_cache instead
          of table definition cache.
        * Changed code that needs to deal with all TABLE instances for
          a table to acquire locks on all table caches.
        * Introduced Table_cache_iterator, an iterator over all used TABLE
          instances for the table. Changed code that earlier used iterator
          over TABLE_SHARE::used_tables to use this new iterator.
        * Moved code dealing with lists of used and unused TABLE objects
          from tdc_remove_table() to corresponding method in
          Table_cache_manager.
     @ sql/sql_base.h
        Implemented a table cache:
        * Added Table_cache object representing a table cache instance.
        * Introduced Table_cache_manager class, a container for all
          Table_cache instances in the system and incapsulating
          operations on all of them.
        * Introduced Table_cache_iterator, an iterator over all
          used TABLE instances for the table.
     @ sql/sql_class.h
        Added new status variables for table cache - Table_open_cache_hits,
        Table_open_cache_misses, Table_open_cache_overflows.
     @ sql/sql_handler.cc
        close_thread_table() no longer has any return value.
     @ sql/sql_parse.cc
        cached_open_tables() function was superceded by
        Table_cache_manager::cached_tables() method.
     @ sql/sql_partition.cc
        Do not set TABLE_SHARE::version without holding LOCK_open mutex and
        locks on all table cache instances. close_all_tables_for_name() will
        do it anyway (and in the correct fashion).
     @ sql/sql_table.cc
        intern_close_table() now also does my_free() of the TABLE object.
     @ sql/sql_test.cc
        Moved most of contents of print_cached_tables() method to
        Table_cache_manager/Table_cache::print_tables() methods.
        To support this change made lock_descriptions[] array visible
        outside of sql_test.cc file. Changed code to use
        Table_cache_manager::cached_tables() method instead of
        cached_open_tables() function.
     @ sql/sql_test.h
        Made lock_descriptions[] array visible outside of sql_test.cc file.
     @ sql/sys_vars.cc
        Introduced new global, non-dynamic variable/start-up parameter for
        number of table cache instances in server -
        table_open_cache_instances.
        Adjusted code handling existing table_open_cache parameter to take
        into account that now it limits total number of TABLE objects in
        all table cache instances in the server.
     @ sql/table.cc
        Added allocation of TABLE_SHARE::cache_element[] array, which
        allows quick access to Table_cache_element objects corresponding
        to this table in each of the table cache instances, to
        alloc_table_share().
        Changed TABLE_SHARE::visit_subgraph() method:
        * to acquire not only LOCK_open but also locks on all table cache
          instances, since now information about used TABLE objects is
          protected by the latter
        * use new Table_cache_iterator for iterating over all used TABLE
          objects in all table cache instances
     @ sql/table.h
        TABLE_SHARE no longer contains lists of all used and unused TABLE
        objects for this table. These lists are now local to table cache
        instance and reside in corresponding Table_cache_element object
        in each table cache.
        Consequently TABLE::share_prev/share_next pointers were renamed
        to TABLE::cache_prev/cache_next.
        Added TABLE_SHARE::cache_element[] array for quick access to
        Table_cache_element objects corresponding to this table in each
        of table cache instances.

    added:
      mysql-test/suite/sys_vars/r/table_open_cache_instances_basic.result
      mysql-test/suite/sys_vars/t/table_open_cache_instances_basic.test
    modified:
      mysql-test/r/mysqld--help-notwin.result
      mysql-test/r/mysqld--help-win.result
      mysql-test/r/status.result
      mysql-test/suite/perfschema/r/func_mutex.result
      mysql-test/suite/perfschema/t/func_mutex.test
      mysql-test/t/status.test
      sql/mysqld.cc
      sql/mysqld.h
      sql/sql_base.cc
      sql/sql_base.h
      sql/sql_class.h
      sql/sql_handler.cc
      sql/sql_parse.cc
      sql/sql_partition.cc
      sql/sql_table.cc
      sql/sql_test.cc
      sql/sql_test.h
      sql/sys_vars.cc
      sql/table.cc
      sql/table.h
=== modified file 'sql/CMakeLists.txt'
--- a/sql/CMakeLists.txt	2012-05-18 14:39:25 +0000
+++ b/sql/CMakeLists.txt	2012-05-22 10:41:38 +0000
@@ -176,6 +176,7 @@ SET(SQL_SHARED_SOURCES
   strfunc.cc
   sys_vars.cc
   table.cc
+  table_cache.cc
   thr_malloc.cc 
   transaction.cc
   tztime.cc

=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc	2012-05-21 17:56:02 +0000
+++ b/sql/mysqld.cc	2012-05-22 10:41:38 +0000
@@ -111,6 +111,7 @@
 #ifdef HAVE_FESETROUND
 #include <fenv.h>
 #endif
+#include "table_cache.h" // table_cache_manager
 
 using std::min;
 using std::max;

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2012-05-21 17:56:02 +0000
+++ b/sql/sql_base.cc	2012-05-22 10:41:38 +0000
@@ -57,7 +57,7 @@
 #ifdef  __WIN__
 #include <io.h>
 #endif
-#include "sql_test.h" // lock_descriptions[]
+#include "table_cache.h" // Table_cache_manager, Table_cache
 
 
 bool
@@ -195,9 +195,7 @@ mysql_mutex_t LOCK_open;
 
 #ifdef HAVE_PSI_INTERFACE
 static PSI_mutex_key key_LOCK_open;
-static PSI_mutex_key key_LOCK_table_cache;
 static PSI_mutex_info all_tdc_mutexes[]= {
-  { &key_LOCK_table_cache, "LOCK_table_cache", 0},
   { &key_LOCK_open, "LOCK_open", PSI_FLAG_GLOBAL }
 };
 
@@ -231,262 +229,6 @@ static void modify_slave_open_temp_table
 }
 
 
-/**
-  Container for all table cache instances in the system.
-*/
-Table_cache_manager table_cache_manager;
-
-
-/**
-  Element that represents the table in the specific table cache.
-  Plays for table cache instance role similar to role of TABLE_SHARE
-  for table definition cache.
-*/
-
-struct Table_cache_element
-{
-  /*
-    Doubly-linked (back-linked) lists of used and unused TABLE objects
-    for this table in this table cache (one such list per table cache).
-  */
-  typedef I_P_List <TABLE,
-                    I_P_List_adapter<TABLE,
-                                     &TABLE::cache_next,
-                                     &TABLE::cache_prev> > TABLE_list;
-
-  TABLE_list used_tables;
-  TABLE_list free_tables;
-  TABLE_SHARE *share;
-
-  Table_cache_element(TABLE_SHARE *share_arg)
-    : share(share_arg)
-  {
-  }
-};
-
-
-extern "C" uchar *table_cache_key(const uchar *record,
-                                  size_t *length,
-                                  my_bool not_used __attribute__((unused)))
-{
-  Table_cache_element *entry= (Table_cache_element*) record;
-  *length= entry->share->table_cache_key.length;
-  return (uchar*) entry->share->table_cache_key.str;
-}
-
-
-static void table_cache_free_entry(Table_cache_element *element)
-{
-  delete element;
-}
-
-
-/**
-  Initialize instance of table cache.
-
-  @retval false - success.
-  @retval true  - failure.
-*/
-
-bool Table_cache::init()
-{
-  mysql_mutex_init(key_LOCK_table_cache, &m_lock, MY_MUTEX_INIT_FAST);
-  m_unused_tables= NULL;
-  m_table_count= 0;
-
-  if (my_hash_init(&m_cache, &my_charset_bin,
-                   table_cache_size_per_instance, 0, 0,
-                   table_cache_key, (my_hash_free_key) table_cache_free_entry,
-                   0))
-  {
-    mysql_mutex_destroy(&m_lock);
-    return true;
-  }
-  return false;
-}
-
-
-/** Destroy instance of table cache. */
-
-void Table_cache::destroy()
-{
-  my_hash_free(&m_cache);
-  mysql_mutex_destroy(&m_lock);
-}
-
-
-/**
-  Initialize all instances of table cache to be used by server.
-
-  @retval false - success.
-  @retval true  - failure.
-*/
-
-bool Table_cache_manager::init()
-{
-  for (uint i= 0; i < table_cache_instances; i++)
-  {
-    if (m_table_cache[i].init())
-    {
-      for (uint j= 0; j < i; j++)
-        m_table_cache[i].destroy();
-      return true;
-    }
-  }
-
-  return false;
-}
-
-
-/** Destroy all instances of table cache which were used by server. */
-
-void Table_cache_manager::destroy()
-{
-  for (uint i= 0; i < table_cache_instances; i++)
-    m_table_cache[i].destroy();
-}
-
-
-/**
-  Get total number of used and unused TABLE objects in all table caches.
-
-  @note Doesn't require acquisition of table cache locks if inexact number
-        of tables is acceptable.
-*/
-
-uint Table_cache_manager::cached_tables()
-{
-  uint result= 0;
-
-  for (uint i= 0; i < table_cache_instances; i++)
-    result+= m_table_cache[i].cached_tables();
-
-  return result;
-}
-
-
-/**
-  Acquire locks on all instances of table cache and table definition
-  cache (i.e. LOCK_open).
-*/
-
-void Table_cache_manager::lock_all_and_tdc()
-{
-  for (uint i= 0; i < table_cache_instances; i++)
-    m_table_cache[i].lock();
-
-  mysql_mutex_lock(&LOCK_open);
-}
-
-
-/**
-  Release locks on all instances of table cache and table definition
-  cache.
-*/
-
-void Table_cache_manager::unlock_all_and_tdc()
-{
-  mysql_mutex_unlock(&LOCK_open);
-
-  for (uint i= 0; i < table_cache_instances; i++)
-    m_table_cache[i].unlock();
-}
-
-
-/**
-  Assert that caller owns locks on all instances of table cache.
-*/
-
-void Table_cache_manager::assert_owner_all()
-{
-  for (uint i= 0; i < table_cache_instances; i++)
-    m_table_cache[i].assert_owner();
-}
-
-
-/**
-  Assert that caller owns locks on all instances of table cache
-  and table definition cache.
-*/
-
-void Table_cache_manager::assert_owner_all_and_tdc()
-{
-  assert_owner_all();
-
-  mysql_mutex_assert_owner(&LOCK_open);
-}
-
-
-/**
-  Construct iterator over all used TABLE objects for the table share.
-
-  @note Assumes that caller owns locks on all table caches.
-*/
-
-Table_cache_iterator::Table_cache_iterator(const TABLE_SHARE *share_arg)
-  : share(share_arg), current_cache_index(0), current_table(NULL)
-{
-  table_cache_manager.assert_owner_all();
-
-  move_to_next_table();
-}
-
-
-/** Helper that moves iterator to the next used TABLE for the table share. */
-
-void Table_cache_iterator::move_to_next_table()
-{
-  for (; current_cache_index < table_cache_instances; ++current_cache_index)
-  {
-    Table_cache_element *el;
-
-    if ((el= share->cache_element[current_cache_index]))
-    {
-      if ((current_table= el->used_tables.front()))
-        break;
-    }
-  }
-}
-
-
-/**
-  Get current used TABLE instance and move iterator to the next one.
-
-  @note Assumes that caller owns locks on all table caches.
-*/
-
-TABLE* Table_cache_iterator::operator ++(int)
-{
-  table_cache_manager.assert_owner_all();
-
-  TABLE *result= current_table;
-
-  if (current_table)
-  {
-    Table_cache_element::TABLE_list::Iterator
-      it(share->cache_element[current_cache_index]->used_tables, current_table);
-
-    current_table= ++it;
-
-    if (!current_table)
-    {
-      ++current_cache_index;
-      move_to_next_table();
-    }
-  }
-
-  return result;
-}
-
-
-void Table_cache_iterator::rewind()
-{
-  current_cache_index= 0;
-  current_table= NULL;
-  move_to_next_table();
-}
-
-
 HASH table_def_cache;
 static TABLE_SHARE *oldest_unused_share, end_of_unused_share;
 static bool table_def_inited= false;
@@ -504,171 +246,6 @@ static bool has_write_table_auto_increme
 
 
 /**
-  Add table to the tail of unused tables list for table cache
-  (i.e. as the most recently used table in this list).
-*/
-
-void Table_cache::link_unused_table(TABLE *table)
-{
-  if (m_unused_tables)
-  {
-    table->next= m_unused_tables;
-    table->prev= m_unused_tables->prev;
-    m_unused_tables->prev= table;
-    table->prev->next= table;
-  }
-  else
-    m_unused_tables= table->next= table->prev= table;
-  check_unused();
-}
-
-
-/** Remove table from the unused tables list for table cache. */
-
-void Table_cache::unlink_unused_table(TABLE *table)
-{
-  table->next->prev= table->prev;
-  table->prev->next= table->next;
-  if (table == m_unused_tables)
-  {
-    m_unused_tables= m_unused_tables->next;
-    if (table == m_unused_tables)
-      m_unused_tables= NULL;
-  }
-  check_unused();
-}
-
-
-#ifdef EXTRA_DEBUG
-void Table_cache::check_unused()
-{
-  uint count= 0;
-
-  if (m_unused_tables != NULL)
-  {
-    TABLE *cur_link= m_unused_tables;
-    TABLE *start_link= m_unused_tables;
-    do
-    {
-      if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next)
-      {
-	DBUG_PRINT("error",("Unused_links aren't linked properly"));
-	return;
-      }
-    } while (count++ < m_table_count &&
-	     (cur_link= cur_link->next) != start_link);
-    if (cur_link != start_link)
-      DBUG_PRINT("error",("Unused_links aren't connected"));
-  }
-
-  for (uint idx= 0; idx < m_cache.records; idx++)
-  {
-    Table_cache_element *el=
-      (Table_cache_element*) my_hash_element(&m_cache, idx);
-
-    Table_cache_element::TABLE_list::Iterator it(el->free_tables);
-    TABLE *entry;
-    while ((entry= it++))
-    {
-      /* We must not have TABLEs in the free list that have their file closed. */
-      DBUG_ASSERT(entry->db_stat && entry->file);
-      /* Merge children should be detached from a merge parent */
-      DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
-
-      if (entry->in_use)
-        DBUG_PRINT("error",("Used table is in share's list of unused tables"));
-      count--;
-    }
-    it.init(el->used_tables);
-    while ((entry= it++))
-    {
-      if (!entry->in_use)
-        DBUG_PRINT("error",("Unused table is in share's list of used tables"));
-    }
-  }
-
-  if (count != 0)
-    DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d",
-                        count));
-}
-#endif
-
-
-#ifndef DBUG_OFF
-
-/**
-  Print debug information for the contents of all table cache instances.
-*/
-
-void Table_cache_manager::print_tables()
-{
-  puts("DB             Table                            Version  Thread  Open  Lock");
-
-  for (uint i= 0; i < table_cache_instances; i++)
-    m_table_cache[i].print_tables();
-}
-
-
-/**
-  Print debug information for the contents of the table cache.
-*/
-
-void Table_cache::print_tables()
-{
-  uint unused= 0;
-  uint count=0;
-
-  compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
-
-  for (uint idx= 0; idx < m_cache.records; idx++)
-  {
-    Table_cache_element *el=
-      (Table_cache_element*) my_hash_element(&m_cache, idx);
-
-    Table_cache_element::TABLE_list::Iterator it(el->used_tables);
-    TABLE *entry;
-    while ((entry= it++))
-    {
-      printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
-             entry->s->db.str, entry->s->table_name.str, entry->s->version,
-             entry->in_use->thread_id, entry->db_stat ? 1 : 0,
-             lock_descriptions[(int)entry->reginfo.lock_type]);
-    }
-    it.init(el->free_tables);
-    while ((entry= it++))
-    {
-      unused++;
-      printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
-             entry->s->db.str, entry->s->table_name.str, entry->s->version,
-             0L, entry->db_stat ? 1 : 0, "Not in use");
-    }
-  }
-
-  if (m_unused_tables != NULL)
-  {
-    TABLE *start_link= m_unused_tables;
-    TABLE *lnk= m_unused_tables;
-    do
-    {
-      if (lnk != lnk->next->prev || lnk != lnk->prev->next)
-      {
-	printf("unused_links isn't linked properly\n");
-	return;
-      }
-    } while (count++ < m_table_count && (lnk= lnk->next) != start_link);
-    if (lnk != start_link)
-      printf("Unused_links aren't connected\n");
-  }
-
-  if (count != unused)
-    printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count,
-           unused);
-}
-
-#endif
-
-
-/**
   Create a table cache/table definition cache key
 
   @param thd        Thread context
@@ -845,252 +422,6 @@ uint cached_table_definitions(void)
 }
 
 
-/**
-  Free unused TABLE instances if total number of TABLE objects
-  in table cache has exceeded table_cache_size_per_instance
-  limit.
-
-  @note That we might need to free more than one instance during
-        this call if table_cache_size was changed dynamically.
-*/
-
-void Table_cache::free_unused_tables_if_necessary(THD *thd)
-{
-  /*
-    We have too many TABLE instances around let us try to get rid of them.
-
-    Note that we might need to free more than one TABLE object, and thus
-    need the below loop, in case when table_cache_size is changed dynamically,
-    at server run time.
-  */
-  if (m_table_count > table_cache_size_per_instance && m_unused_tables)
-  {
-    mysql_mutex_lock(&LOCK_open);
-    while (m_table_count > table_cache_size_per_instance &&
-           m_unused_tables)
-    {
-      TABLE *table_to_free= m_unused_tables;
-      remove_table(table_to_free);
-      intern_close_table(table_to_free);
-      thd->status_var.table_open_cache_overflows++;
-    }
-    mysql_mutex_unlock(&LOCK_open);
-  }
-}
-
-
-/**
-   Add newly created TABLE object which is going to be used right away
-   to the table cache.
-
-   @note Caller should own lock on the table cache.
-
-   @note Sets TABLE::in_use member as side effect.
-
-   @retval false - success.
-   @retval true  - failure.
-*/
-
-bool Table_cache::add_used_table(THD *thd, TABLE *table)
-{
-  Table_cache_element *el;
-
-  assert_owner();
-
-  DBUG_ASSERT(table->in_use == thd);
-
-  /*
-    Try to get Table_cache_element representing this table in the cache
-    from array in the TABLE_SHARE.
-  */
-  el= table->s->cache_element[table_cache_manager.cache_index(this)];
-
-  if (!el)
-  {
-    /*
-      If TABLE_SHARE doesn't have pointer to the element representing table
-      in this cache, the element for the table must be absent from table the
-      cache.
-
-      Allocate new Table_cache_element object and add it to the cache
-      and array in TABLE_SHARE.
-    */
-    DBUG_ASSERT(! my_hash_search(&m_cache,
-                                 (uchar*)table->s->table_cache_key.str,
-                                 table->s->table_cache_key.length));
-
-    if (!(el= new Table_cache_element(table->s)))
-      return true;
-
-    if (my_hash_insert(&m_cache, (uchar*)el))
-    {
-      delete el;
-      return true;
-    }
-
-    table->s->cache_element[table_cache_manager.cache_index(this)]= el;
-  }
-
-  /* Add table to the used tables list */
-  el->used_tables.push_front(table);
-
-  m_table_count++;
-
-  free_unused_tables_if_necessary(thd);
-
-  return false;
-}
-
-
-/**
-   Prepare used or unused TABLE instance for destruction by removing
-   it from the table cache.
-
-   @note Caller should own lock on the table cache.
-*/
-
-void Table_cache::remove_table(TABLE *table)
-{
-  Table_cache_element *el=
-    table->s->cache_element[table_cache_manager.cache_index(this)];
-
-  assert_owner();
-
-  if (table->in_use)
-  {
-    /* Remove from per-table chain of used TABLE objects. */
-    el->used_tables.remove(table);
-  }
-  else
-  {
-    /* Remove from per-table chain of unused TABLE objects. */
-    el->free_tables.remove(table);
-
-    /* And per-cache unused chain. */
-    unlink_unused_table(table);
-  }
-
-  m_table_count--;
-
-  if (el->used_tables.is_empty() && el->free_tables.is_empty())
-  {
-    (void) my_hash_delete(&m_cache, (uchar*) el);
-    /*
-      Remove reference to deleted cache element from array
-      in the TABLE_SHARE.
-    */
-    table->s->cache_element[table_cache_manager.cache_index(this)]= NULL;
-  }
-}
-
-
-/**
-  Get an unused TABLE instance from the table cache.
-
-  @param      thd         Thread context.
-  @param      hash_value  Hash value for the key identifying table.
-  @param      key         Key identifying table.
-  @param      key_length  Length of key for the table.
-  @param[out] share       NULL - if table cache doesn't contain any
-                          information about the table (i.e. doesn't have
-                          neither used nor unused TABLE objects for it).
-                          Pointer to TABLE_SHARE for the table otherwise.
-
-  @note Caller should own lock on the table cache.
-  @note Sets TABLE::in_use member as side effect.
-
-  @retval non-NULL - pointer to unused TABLE object, "share" out-parameter
-                     contains pointer to TABLE_SHARE for this table.
-  @retval NULL     - no unused TABLE object was found, "share" parameter
-                     contains pointer to TABLE_SHARE for this table if there
-                     are used TABLE objects in cache and NULL otherwise.
-*/
-
-TABLE* Table_cache::get_table(THD *thd, my_hash_value_type hash_value,
-                              const char *key, uint key_length,
-                              TABLE_SHARE **share)
-{
-  Table_cache_element *el;
-  TABLE *table;
-
-  assert_owner();
-
-  *share= NULL;
-
-  if (!(el= (Table_cache_element*) my_hash_search_using_hash_value(&m_cache,
-                                     hash_value, (uchar*) key, key_length)))
-    return NULL;
-
-  *share= el->share;
-
-  if ((table= el->free_tables.front()))
-  {
-    DBUG_ASSERT(!table->in_use);
-
-    /*
-      Unlink table from list of unused TABLE objects for this
-      table in this cache.
-    */
-    el->free_tables.remove(table);
-
-    /* Unlink table from unused tables list for this cache. */
-    unlink_unused_table(table);
-
-    /*
-      Add table to list of used TABLE objects for this table
-      in the table cache.
-    */
-    el->used_tables.push_front(table);
-
-    table->in_use= thd;
-    /* The ex-unused table must be fully functional. */
-    DBUG_ASSERT(table->db_stat && table->file);
-    /* The children must be detached from the table. */
-    DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
-  }
-
-  return table;
-}
-
-
-/**
-  Put used TABLE instance back to the table cache and mark
-  it as unused.
-
-  @note Caller should own lock on the table cache.
-  @note Sets TABLE::in_use member as side effect.
-*/
-
-void Table_cache::release_table(THD *thd, TABLE *table)
-{
-  Table_cache_element *el=
-    table->s->cache_element[table_cache_manager.cache_index(this)];
-
-  assert_owner();
-
-  DBUG_ASSERT(table->in_use);
-  DBUG_ASSERT(table->file);
-
-  /* We shouldn't put the table to 'unused' list if the share is old. */
-  DBUG_ASSERT(! table->s->has_old_version());
-
-  table->in_use= NULL;
-
-  /* Remove TABLE from the list of used objects for the table in this cache. */
-  el->used_tables.remove(table);
-  /* Add TABLE to the list of unused objects for the table in this cache. */
-  el->free_tables.push_front(table);
-  /* Also link it last in the list of unused TABLE objects for the cache. */
-  link_unused_table(table);
-
-  /*
-    We free the least used tables, not the subject table, to keep the LRU order.
-    Note that in most common case the below call won't free anything.
-  */
-  free_unused_tables_if_necessary(thd);
-}
-
-
 /*
   Get TABLE_SHARE for a table.
 
@@ -1536,32 +867,6 @@ static void kill_delayed_threads_for_tab
 }
 
 
-/** Free all unused TABLE objects in the table cache. */
-
-void Table_cache::free_all_unused_tables()
-{
-  assert_owner();
-
-  while (m_unused_tables)
-  {
-    TABLE *table_to_free= m_unused_tables;
-    remove_table(table_to_free);
-    intern_close_table(table_to_free);
-  }
-}
-
-
-/** Free all unused TABLE objects in all table cache instances. */
-
-void Table_cache_manager::free_all_unused_tables()
-{
-  assert_owner_all_and_tdc();
-
-  for (uint i= 0; i < table_cache_instances; i++)
-    m_table_cache[i].free_all_unused_tables();
-}
-
-
 /*
   Close all tables which aren't in use by any thread
 
@@ -9792,66 +9097,6 @@ void tdc_flush_unused_tables()
 }
 
 
-/**
-   Remove and free all or some (depending on parameter) TABLE objects
-   for the table from all table cache instances.
-
-   @param  thd          Thread context
-   @param  remove_type  Type of removal. @sa tdc_remove_table().
-   @param  share        TABLE_SHARE for the table to be removed.
-
-   @note Caller should own LOCK_open and locks on all table cache
-         instances.
-*/
-void Table_cache_manager::free_table(THD *thd,
-                                     enum_tdc_remove_table_type remove_type,
-                                     TABLE_SHARE *share)
-{
-  Table_cache_element *cache_el[MAX_TABLE_CACHES];
-
-  assert_owner_all_and_tdc();
-
-  /*
-    Freeing last TABLE instance for the share will destroy the share
-    and corresponding TABLE_SHARE::cache_element[] array. To make
-    iteration over this array safe, even when share is destroyed in
-    the middle of iteration, we create copy of this array on the stack
-    and iterate over it.
-  */
-  memcpy(&cache_el, share->cache_element,
-         table_cache_instances * sizeof(Table_cache_element *));
-
-  for (uint i= 0; i < table_cache_instances; i++)
-  {
-    if (cache_el[i])
-    {
-      Table_cache_element::TABLE_list::Iterator it(cache_el[i]->free_tables);
-      TABLE *table;
-
-#ifndef DBUG_OFF
-      if (remove_type == TDC_RT_REMOVE_ALL)
-        DBUG_ASSERT(cache_el[i]->used_tables.is_empty());
-      else if (remove_type == TDC_RT_REMOVE_NOT_OWN)
-      {
-        Table_cache_element::TABLE_list::Iterator it2(cache_el[i]->used_tables);
-        while ((table= it2++))
-        {
-          if (table->in_use != thd)
-            DBUG_ASSERT(0);
-        }
-      }
-#endif
-
-      while ((table= it++))
-      {
-        m_table_cache[i].remove_table(table);
-        intern_close_table(table);
-      }
-    }
-  }
-}
-
-
 /**
    Remove all or some (depending on parameter) instances of TABLE and
    TABLE_SHARE from the table definition cache.

=== modified file 'sql/sql_base.h'
--- a/sql/sql_base.h	2012-05-21 17:56:02 +0000
+++ b/sql/sql_base.h	2012-05-22 10:41:38 +0000
@@ -609,194 +609,4 @@ private:
   int m_unhandled_errors;
 };
 
-
-/**
-  Cache for open TABLE objects.
-
-  The idea behind this cache is that most statements don't need to
-  go to a central table definition cache to get a TABLE object and
-  therefore don't need to lock LOCK_open mutex.
-  Instead they only need to go to one Table_cache instance (the
-  specific instance is determined by thread id) and only lock the
-  mutex protecting this cache.
-  DDL statements that need to remove all TABLE objects from all caches
-  need to lock mutexes for all Table_cache instances, but they are rare.
-
-  This significantly increases scalability in some scenarios.
-*/
-
-class Table_cache
-{
-private:
-  /**
-    The table cache lock protects the following data:
-
-    1) m_unused_tables list.
-    2) m_cache hash.
-    3) used_tables, free_tables lists in Table_cache_element objects in
-       this cache.
-    4) m_table_count - total number of TABLE objects in this cache.
-    5) the element in TABLE_SHARE::cache_element[] array that corresponds
-       to this cache,
-    6) in_use member in TABLE object.
-    7) Also ownership of mutexes for all caches are required to update
-       the refresh_version and table_def_shutdown_in_progress variables
-       and TABLE_SHARE::version member.
-
-    The intention is that any query that finds a cached table object in
-    its designated table cache should only need to lock this mutex
-    instance and there should be no need to lock LOCK_open. LOCK_open is
-    still required however to create and release TABLE objects. However
-    most usage of the MySQL Server should be able to set the cache size
-    big enough so that the majority of the queries only need to lock this
-    mutex instance and not LOCK_open.
-  */
-  mysql_mutex_t m_lock;
-
-  /**
-    The hash of Table_cache_element objects, each table/table share that
-    has any TABLE object in the Table_cache has a Table_cache_element from
-    which the list of free TABLE objects in this table cache AND the list
-    of used TABLE objects in this table cache is stored.
-    We use Table_cache_element::share::table_cache_key as key for this hash.
-  */
-  HASH m_cache;
-
-  /**
-    List that contains all TABLE instances for tables in this particular
-    table cache that are in not use by any thread. Recently used TABLE
-    instances are appended to the end of the list. Thus the beginning of
-    the list contains which have been least recently used.
-  */
-  TABLE *m_unused_tables;
-
-  /**
-    Total number of TABLE instances for tables in this particular table
-    cache (both in use by threads and not in use).
-    This value summed over all table caches is accessible to users as
-    Open_tables status variable.
-  */
-  uint m_table_count;
-
-private:
-
-#ifdef EXTRA_DEBUG
-  void check_unused();
-#else
-  void check_unused() {}
-#endif
-  inline void link_unused_table(TABLE *table);
-  inline void unlink_unused_table(TABLE *table);
-
-  inline void free_unused_tables_if_necessary(THD *thd);
-
-public:
-
-  bool init();
-  void destroy();
-
-  /** Acquire lock on table cache instance. */
-  void lock() { mysql_mutex_lock(&m_lock); }
-  /** Release lock on table cache instance. */
-  void unlock() { mysql_mutex_unlock(&m_lock); }
-  /** Assert that caller owns lock on the table cache. */
-  void assert_owner() { mysql_mutex_assert_owner(&m_lock); }
-
-  inline TABLE* get_table(THD *thd, my_hash_value_type hash_value,
-                          const char *key, uint key_length,
-                          TABLE_SHARE **share);
-
-  inline void release_table(THD *thd, TABLE *table);
-
-  inline bool add_used_table(THD *thd, TABLE *table);
-  inline void remove_table(TABLE *table);
-
-  /** Get number of TABLE instances in the cache. */
-  uint cached_tables() const { return m_table_count; }
-
-  void free_all_unused_tables();
-
-#ifndef DBUG_OFF
-  void print_tables();
-#endif
-};
-
-
-/**
-  Container class for all table cache instances in the system.
-*/
-
-class Table_cache_manager
-{
-public:
-
-  /** Maximum supported number of table cache instances. */
-  static const int MAX_TABLE_CACHES= 64;
-
-  bool init();
-  void destroy();
-
-  /** Get instance of table cache to be used by particular connection. */
-  Table_cache* get_cache(THD *thd)
-  {
-    return &m_table_cache[thd->thread_id % table_cache_instances];
-  }
-
-  /** Get index for the table cache in container. */
-  uint cache_index(Table_cache *cache) const
-  {
-    return (cache - &m_table_cache[0]);
-  }
-
-  uint cached_tables();
-
-  void lock_all_and_tdc();
-  void unlock_all_and_tdc();
-  void assert_owner_all();
-  void assert_owner_all_and_tdc();
-
-  void free_table(THD *thd,
-                  enum_tdc_remove_table_type remove_type,
-                  TABLE_SHARE *share);
-
-  void free_all_unused_tables();
-
-#ifndef DBUG_OFF
-  void print_tables();
-#endif
-
-  friend class Table_cache_iterator;
-
-private:
-
-  /**
-    An array of Table_cache instances.
-    Only the first table_cache_instances elements in it are used.
-  */
-  Table_cache m_table_cache[MAX_TABLE_CACHES];
-};
-
-
-/**
-  Iterator which allows to go through all used TABLE instances
-  for the table in all table caches.
-*/
-
-class Table_cache_iterator
-{
-  const TABLE_SHARE *share;
-  uint current_cache_index;
-  TABLE *current_table;
-
-  void move_to_next_table();
-
-public:
-  Table_cache_iterator(const TABLE_SHARE *share);
-  TABLE* operator++(int);
-  void rewind();
-};
-
-
-extern Table_cache_manager table_cache_manager;
-
 #endif /* SQL_BASE_INCLUDED */

=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc	2012-05-21 17:56:02 +0000
+++ b/sql/sql_parse.cc	2012-05-22 10:41:38 +0000
@@ -98,6 +98,7 @@
 #include "opt_explain.h"
 #include "sql_rewrite.h"
 #include "global_threads.h"
+#include "table_cache.h" // table_cache_manager
 
 #include <algorithm>
 using std::max;

=== modified file 'sql/sql_test.cc'
--- a/sql/sql_test.cc	2012-05-21 17:56:02 +0000
+++ b/sql/sql_test.cc	2012-05-22 10:41:38 +0000
@@ -40,6 +40,7 @@
 #endif
 
 #include "global_threads.h"
+#include "table_cache.h" // table_cache_manager
 
 const char *lock_descriptions[TL_WRITE_ONLY + 1] =
 {

=== modified file 'sql/sys_vars.cc'
--- a/sql/sys_vars.cc	2012-05-21 17:56:02 +0000
+++ b/sql/sys_vars.cc	2012-05-22 10:41:38 +0000
@@ -55,6 +55,7 @@
 #include "sql_base.h"                           // close_cached_tables
 #include "hostname.h"                           // host_cache_size
 #include "sql_show.h"                           // opt_ignore_db_dirs
+#include "table_cache.h"                        // Table_cache_manager
 
 #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
 #include "../storage/perfschema/pfs_server.h"

=== modified file 'sql/table.cc'
--- a/sql/table.cc	2012-05-21 17:56:02 +0000
+++ b/sql/table.cc	2012-05-22 10:41:38 +0000
@@ -38,6 +38,7 @@
 #include "sql_select.h"
 #include "mdl.h"                 // MDL_wait_for_graph_visitor
 #include "opt_trace.h"           // opt_trace_disable_if_no_security_...
+#include "table_cache.h"         // table_cache_manager
 
 /* INFORMATION_SCHEMA name */
 LEX_STRING INFORMATION_SCHEMA_NAME= {C_STRING_WITH_LEN("information_schema")};

=== modified file 'sql/table.h'
--- a/sql/table.h	2012-05-21 17:56:02 +0000
+++ b/sql/table.h	2012-05-22 10:41:38 +0000
@@ -46,7 +46,7 @@ class ACL_internal_schema_access;
 class ACL_internal_table_access;
 class Field;
 class Field_temporal_with_date_and_time;
-struct Table_cache_element;
+class Table_cache_element;
 
 /*
   Used to identify NESTED_JOIN structures within a join (applicable only to
@@ -934,7 +934,7 @@ private:
     Give Table_cache_element access to the above two members to allow
     using them for linking TABLE objects in a list.
   */
-  friend struct Table_cache_element;
+  friend class Table_cache_element;
 
   Field_temporal_with_date_and_time *timestamp_field;
 

=== added file 'sql/table_cache.cc'
--- a/sql/table_cache.cc	1970-01-01 00:00:00 +0000
+++ b/sql/table_cache.cc	2012-05-22 10:41:38 +0000
@@ -0,0 +1,410 @@
+/* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+
+   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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */
+
+#include "table_cache.h"
+#include "sql_test.h" // lock_descriptions[]
+
+
+/**
+  Container for all table cache instances in the system.
+*/
+Table_cache_manager table_cache_manager;
+
+
+#ifdef HAVE_PSI_INTERFACE
+PSI_mutex_key Table_cache::m_lock_key;
+PSI_mutex_info Table_cache::m_mutex_keys[]= {
+  { &m_lock_key, "LOCK_table_cache", 0}
+};
+#endif
+
+
+extern "C" uchar *table_cache_key(const uchar *record,
+                                  size_t *length,
+                                  my_bool not_used __attribute__((unused)))
+{
+  TABLE_SHARE *share= ((Table_cache_element*)record)->get_share();
+  *length= share->table_cache_key.length;
+  return (uchar*) share->table_cache_key.str;
+}
+
+
+extern "C" void table_cache_free_entry(Table_cache_element *element)
+{
+  delete element;
+}
+
+
+/**
+  Initialize instance of table cache.
+
+  @retval false - success.
+  @retval true  - failure.
+*/
+
+bool Table_cache::init()
+{
+  mysql_mutex_init(m_lock_key, &m_lock, MY_MUTEX_INIT_FAST);
+  m_unused_tables= NULL;
+  m_table_count= 0;
+
+  if (my_hash_init(&m_cache, &my_charset_bin,
+                   table_cache_size_per_instance, 0, 0,
+                   table_cache_key, (my_hash_free_key) table_cache_free_entry,
+                   0))
+  {
+    mysql_mutex_destroy(&m_lock);
+    return true;
+  }
+  return false;
+}
+
+
+/** Destroy instance of table cache. */
+
+void Table_cache::destroy()
+{
+  my_hash_free(&m_cache);
+  mysql_mutex_destroy(&m_lock);
+}
+
+
+/** Init P_S instrumentation key for mutex protecting Table_cache instance. */
+
+void Table_cache::init_psi_keys()
+{
+#ifdef HAVE_PSI_INTERFACE
+  mysql_mutex_register("sql", m_mutex_keys, array_elements(m_mutex_keys));
+#endif
+}
+
+
+#ifdef EXTRA_DEBUG
+void Table_cache::check_unused()
+{
+  uint count= 0;
+
+  if (m_unused_tables != NULL)
+  {
+    TABLE *cur_link= m_unused_tables;
+    TABLE *start_link= m_unused_tables;
+    do
+    {
+      if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next)
+      {
+	DBUG_PRINT("error",("Unused_links aren't linked properly"));
+	return;
+      }
+    } while (count++ < m_table_count &&
+	     (cur_link= cur_link->next) != start_link);
+    if (cur_link != start_link)
+      DBUG_PRINT("error",("Unused_links aren't connected"));
+  }
+
+  for (uint idx= 0; idx < m_cache.records; idx++)
+  {
+    Table_cache_element *el=
+      (Table_cache_element*) my_hash_element(&m_cache, idx);
+
+    Table_cache_element::TABLE_list::Iterator it(el->free_tables);
+    TABLE *entry;
+    while ((entry= it++))
+    {
+      /* We must not have TABLEs in the free list that have their file closed. */
+      DBUG_ASSERT(entry->db_stat && entry->file);
+      /* Merge children should be detached from a merge parent */
+      DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
+
+      if (entry->in_use)
+        DBUG_PRINT("error",("Used table is in share's list of unused tables"));
+      count--;
+    }
+    it.init(el->used_tables);
+    while ((entry= it++))
+    {
+      if (!entry->in_use)
+        DBUG_PRINT("error",("Unused table is in share's list of used tables"));
+    }
+  }
+
+  if (count != 0)
+    DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d",
+                        count));
+}
+#endif
+
+
+/** Free all unused TABLE objects in the table cache. */
+
+void Table_cache::free_all_unused_tables()
+{
+  assert_owner();
+
+  while (m_unused_tables)
+  {
+    TABLE *table_to_free= m_unused_tables;
+    remove_table(table_to_free);
+    intern_close_table(table_to_free);
+  }
+}
+
+
+#ifndef DBUG_OFF
+/**
+  Print debug information for the contents of the table cache.
+*/
+
+void Table_cache::print_tables()
+{
+  uint unused= 0;
+  uint count=0;
+
+  compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
+
+  for (uint idx= 0; idx < m_cache.records; idx++)
+  {
+    Table_cache_element *el=
+      (Table_cache_element*) my_hash_element(&m_cache, idx);
+
+    Table_cache_element::TABLE_list::Iterator it(el->used_tables);
+    TABLE *entry;
+    while ((entry= it++))
+    {
+      printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
+             entry->s->db.str, entry->s->table_name.str, entry->s->version,
+             entry->in_use->thread_id, entry->db_stat ? 1 : 0,
+             lock_descriptions[(int)entry->reginfo.lock_type]);
+    }
+    it.init(el->free_tables);
+    while ((entry= it++))
+    {
+      unused++;
+      printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
+             entry->s->db.str, entry->s->table_name.str, entry->s->version,
+             0L, entry->db_stat ? 1 : 0, "Not in use");
+    }
+  }
+
+  if (m_unused_tables != NULL)
+  {
+    TABLE *start_link= m_unused_tables;
+    TABLE *lnk= m_unused_tables;
+    do
+    {
+      if (lnk != lnk->next->prev || lnk != lnk->prev->next)
+      {
+	printf("unused_links isn't linked properly\n");
+	return;
+      }
+    } while (count++ < m_table_count && (lnk= lnk->next) != start_link);
+    if (lnk != start_link)
+      printf("Unused_links aren't connected\n");
+  }
+
+  if (count != unused)
+    printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count,
+           unused);
+}
+#endif
+
+
+/**
+  Initialize all instances of table cache to be used by server.
+
+  @retval false - success.
+  @retval true  - failure.
+*/
+
+bool Table_cache_manager::init()
+{
+  Table_cache::init_psi_keys();
+  for (uint i= 0; i < table_cache_instances; i++)
+  {
+    if (m_table_cache[i].init())
+    {
+      for (uint j= 0; j < i; j++)
+        m_table_cache[i].destroy();
+      return true;
+    }
+  }
+
+  return false;
+}
+
+
+/** Destroy all instances of table cache which were used by server. */
+
+void Table_cache_manager::destroy()
+{
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].destroy();
+}
+
+
+/**
+  Get total number of used and unused TABLE objects in all table caches.
+
+  @note Doesn't require acquisition of table cache locks if inexact number
+        of tables is acceptable.
+*/
+
+uint Table_cache_manager::cached_tables()
+{
+  uint result= 0;
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    result+= m_table_cache[i].cached_tables();
+
+  return result;
+}
+
+
+/**
+  Acquire locks on all instances of table cache and table definition
+  cache (i.e. LOCK_open).
+*/
+
+void Table_cache_manager::lock_all_and_tdc()
+{
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].lock();
+
+  mysql_mutex_lock(&LOCK_open);
+}
+
+
+/**
+  Release locks on all instances of table cache and table definition
+  cache.
+*/
+
+void Table_cache_manager::unlock_all_and_tdc()
+{
+  mysql_mutex_unlock(&LOCK_open);
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].unlock();
+}
+
+
+/**
+  Assert that caller owns locks on all instances of table cache.
+*/
+
+void Table_cache_manager::assert_owner_all()
+{
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].assert_owner();
+}
+
+
+/**
+  Assert that caller owns locks on all instances of table cache
+  and table definition cache.
+*/
+
+void Table_cache_manager::assert_owner_all_and_tdc()
+{
+  assert_owner_all();
+
+  mysql_mutex_assert_owner(&LOCK_open);
+}
+
+
+/**
+   Remove and free all or some (depending on parameter) TABLE objects
+   for the table from all table cache instances.
+
+   @param  thd          Thread context
+   @param  remove_type  Type of removal. @sa tdc_remove_table().
+   @param  share        TABLE_SHARE for the table to be removed.
+
+   @note Caller should own LOCK_open and locks on all table cache
+         instances.
+*/
+void Table_cache_manager::free_table(THD *thd,
+                                     enum_tdc_remove_table_type remove_type,
+                                     TABLE_SHARE *share)
+{
+  Table_cache_element *cache_el[MAX_TABLE_CACHES];
+
+  assert_owner_all_and_tdc();
+
+  /*
+    Freeing last TABLE instance for the share will destroy the share
+    and corresponding TABLE_SHARE::cache_element[] array. To make
+    iteration over this array safe, even when share is destroyed in
+    the middle of iteration, we create copy of this array on the stack
+    and iterate over it.
+  */
+  memcpy(&cache_el, share->cache_element,
+         table_cache_instances * sizeof(Table_cache_element *));
+
+  for (uint i= 0; i < table_cache_instances; i++)
+  {
+    if (cache_el[i])
+    {
+      Table_cache_element::TABLE_list::Iterator it(cache_el[i]->free_tables);
+      TABLE *table;
+
+#ifndef DBUG_OFF
+      if (remove_type == TDC_RT_REMOVE_ALL)
+        DBUG_ASSERT(cache_el[i]->used_tables.is_empty());
+      else if (remove_type == TDC_RT_REMOVE_NOT_OWN)
+      {
+        Table_cache_element::TABLE_list::Iterator it2(cache_el[i]->used_tables);
+        while ((table= it2++))
+        {
+          if (table->in_use != thd)
+            DBUG_ASSERT(0);
+        }
+      }
+#endif
+
+      while ((table= it++))
+      {
+        m_table_cache[i].remove_table(table);
+        intern_close_table(table);
+      }
+    }
+  }
+}
+
+
+/** Free all unused TABLE objects in all table cache instances. */
+
+void Table_cache_manager::free_all_unused_tables()
+{
+  assert_owner_all_and_tdc();
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].free_all_unused_tables();
+}
+
+
+#ifndef DBUG_OFF
+/**
+  Print debug information for the contents of all table cache instances.
+*/
+
+void Table_cache_manager::print_tables()
+{
+  puts("DB             Table                            Version  Thread  Open  Lock");
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].print_tables();
+}
+#endif
+

=== added file 'sql/table_cache.h'
--- a/sql/table_cache.h	1970-01-01 00:00:00 +0000
+++ b/sql/table_cache.h	2012-05-22 10:41:38 +0000
@@ -0,0 +1,613 @@
+/* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+
+   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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */
+
+#ifndef TABLE_CACHE_INCLUDED
+#define TABLE_CACHE_INCLUDED
+
+#include "my_global.h"
+#include "sql_class.h"
+#include "sql_base.h"
+
+/**
+  Cache for open TABLE objects.
+
+  The idea behind this cache is that most statements don't need to
+  go to a central table definition cache to get a TABLE object and
+  therefore don't need to lock LOCK_open mutex.
+  Instead they only need to go to one Table_cache instance (the
+  specific instance is determined by thread id) and only lock the
+  mutex protecting this cache.
+  DDL statements that need to remove all TABLE objects from all caches
+  need to lock mutexes for all Table_cache instances, but they are rare.
+
+  This significantly increases scalability in some scenarios.
+*/
+
+class Table_cache
+{
+private:
+  /**
+    The table cache lock protects the following data:
+
+    1) m_unused_tables list.
+    2) m_cache hash.
+    3) used_tables, free_tables lists in Table_cache_element objects in
+       this cache.
+    4) m_table_count - total number of TABLE objects in this cache.
+    5) the element in TABLE_SHARE::cache_element[] array that corresponds
+       to this cache,
+    6) in_use member in TABLE object.
+    7) Also ownership of mutexes for all caches are required to update
+       the refresh_version and table_def_shutdown_in_progress variables
+       and TABLE_SHARE::version member.
+
+    The intention is that any query that finds a cached table object in
+    its designated table cache should only need to lock this mutex
+    instance and there should be no need to lock LOCK_open. LOCK_open is
+    still required however to create and release TABLE objects. However
+    most usage of the MySQL Server should be able to set the cache size
+    big enough so that the majority of the queries only need to lock this
+    mutex instance and not LOCK_open.
+  */
+  mysql_mutex_t m_lock;
+
+  /**
+    The hash of Table_cache_element objects, each table/table share that
+    has any TABLE object in the Table_cache has a Table_cache_element from
+    which the list of free TABLE objects in this table cache AND the list
+    of used TABLE objects in this table cache is stored.
+    We use Table_cache_element::share::table_cache_key as key for this hash.
+  */
+  HASH m_cache;
+
+  /**
+    List that contains all TABLE instances for tables in this particular
+    table cache that are in not use by any thread. Recently used TABLE
+    instances are appended to the end of the list. Thus the beginning of
+    the list contains which have been least recently used.
+  */
+  TABLE *m_unused_tables;
+
+  /**
+    Total number of TABLE instances for tables in this particular table
+    cache (both in use by threads and not in use).
+    This value summed over all table caches is accessible to users as
+    Open_tables status variable.
+  */
+  uint m_table_count;
+
+#ifdef HAVE_PSI_INTERFACE
+  static PSI_mutex_key m_lock_key;
+  static PSI_mutex_info m_mutex_keys[];
+#endif 
+
+private:
+
+#ifdef EXTRA_DEBUG
+  void check_unused();
+#else
+  void check_unused() {}
+#endif
+  inline void link_unused_table(TABLE *table);
+  inline void unlink_unused_table(TABLE *table);
+
+  inline void free_unused_tables_if_necessary(THD *thd);
+
+public:
+
+  bool init();
+  void destroy();
+  static void init_psi_keys();
+
+  /** Acquire lock on table cache instance. */
+  void lock() { mysql_mutex_lock(&m_lock); }
+  /** Release lock on table cache instance. */
+  void unlock() { mysql_mutex_unlock(&m_lock); }
+  /** Assert that caller owns lock on the table cache. */
+  void assert_owner() { mysql_mutex_assert_owner(&m_lock); }
+
+  inline TABLE* get_table(THD *thd, my_hash_value_type hash_value,
+                          const char *key, uint key_length,
+                          TABLE_SHARE **share);
+
+  inline void release_table(THD *thd, TABLE *table);
+
+  inline bool add_used_table(THD *thd, TABLE *table);
+  inline void remove_table(TABLE *table);
+
+  /** Get number of TABLE instances in the cache. */
+  uint cached_tables() const { return m_table_count; }
+
+  void free_all_unused_tables();
+
+#ifndef DBUG_OFF
+  void print_tables();
+#endif
+};
+
+
+/**
+  Container class for all table cache instances in the system.
+*/
+
+class Table_cache_manager
+{
+public:
+
+  /** Maximum supported number of table cache instances. */
+  static const int MAX_TABLE_CACHES= 64;
+
+  bool init();
+  void destroy();
+
+  /** Get instance of table cache to be used by particular connection. */
+  Table_cache* get_cache(THD *thd)
+  {
+    return &m_table_cache[thd->thread_id % table_cache_instances];
+  }
+
+  /** Get index for the table cache in container. */
+  uint cache_index(Table_cache *cache) const
+  {
+    return (cache - &m_table_cache[0]);
+  }
+
+  uint cached_tables();
+
+  void lock_all_and_tdc();
+  void unlock_all_and_tdc();
+  void assert_owner_all();
+  void assert_owner_all_and_tdc();
+
+  void free_table(THD *thd,
+                  enum_tdc_remove_table_type remove_type,
+                  TABLE_SHARE *share);
+
+  void free_all_unused_tables();
+
+#ifndef DBUG_OFF
+  void print_tables();
+#endif
+
+  friend class Table_cache_iterator;
+
+private:
+
+  /**
+    An array of Table_cache instances.
+    Only the first table_cache_instances elements in it are used.
+  */
+  Table_cache m_table_cache[MAX_TABLE_CACHES];
+};
+
+
+extern Table_cache_manager table_cache_manager;
+
+
+/**
+  Element that represents the table in the specific table cache.
+  Plays for table cache instance role similar to role of TABLE_SHARE
+  for table definition cache.
+
+  It is an implementation detail of Table_cache and is present
+  in the header file only to allow inlining of some methods.
+*/
+
+class Table_cache_element
+{
+private:
+  /*
+    Doubly-linked (back-linked) lists of used and unused TABLE objects
+    for this table in this table cache (one such list per table cache).
+  */
+  typedef I_P_List <TABLE,
+                    I_P_List_adapter<TABLE,
+                                     &TABLE::cache_next,
+                                     &TABLE::cache_prev> > TABLE_list;
+
+  TABLE_list used_tables;
+  TABLE_list free_tables;
+  TABLE_SHARE *share;
+
+public:
+
+  Table_cache_element(TABLE_SHARE *share_arg)
+    : share(share_arg)
+  {
+  }
+
+  TABLE_SHARE * get_share() const { return share; };
+
+  friend class Table_cache;
+  friend class Table_cache_manager;
+  friend class Table_cache_iterator;
+};
+
+
+/**
+  Iterator which allows to go through all used TABLE instances
+  for the table in all table caches.
+*/
+
+class Table_cache_iterator
+{
+  const TABLE_SHARE *share;
+  uint current_cache_index;
+  TABLE *current_table;
+
+  inline void move_to_next_table();
+
+public:
+  /**
+    Construct iterator over all used TABLE objects for the table share.
+
+    @note Assumes that caller owns locks on all table caches.
+  */
+  inline Table_cache_iterator(const TABLE_SHARE *share_arg);
+  inline TABLE* operator++(int);
+  inline void rewind();
+};
+
+
+/**
+  Add table to the tail of unused tables list for table cache
+  (i.e. as the most recently used table in this list).
+*/
+
+void Table_cache::link_unused_table(TABLE *table)
+{
+  if (m_unused_tables)
+  {
+    table->next= m_unused_tables;
+    table->prev= m_unused_tables->prev;
+    m_unused_tables->prev= table;
+    table->prev->next= table;
+  }
+  else
+    m_unused_tables= table->next= table->prev= table;
+  check_unused();
+}
+
+
+/** Remove table from the unused tables list for table cache. */
+
+void Table_cache::unlink_unused_table(TABLE *table)
+{
+  table->next->prev= table->prev;
+  table->prev->next= table->next;
+  if (table == m_unused_tables)
+  {
+    m_unused_tables= m_unused_tables->next;
+    if (table == m_unused_tables)
+      m_unused_tables= NULL;
+  }
+  check_unused();
+}
+
+
+/**
+  Free unused TABLE instances if total number of TABLE objects
+  in table cache has exceeded table_cache_size_per_instance
+  limit.
+
+  @note That we might need to free more than one instance during
+        this call if table_cache_size was changed dynamically.
+*/
+
+void Table_cache::free_unused_tables_if_necessary(THD *thd)
+{
+  /*
+    We have too many TABLE instances around let us try to get rid of them.
+
+    Note that we might need to free more than one TABLE object, and thus
+    need the below loop, in case when table_cache_size is changed dynamically,
+    at server run time.
+  */
+  if (m_table_count > table_cache_size_per_instance && m_unused_tables)
+  {
+    mysql_mutex_lock(&LOCK_open);
+    while (m_table_count > table_cache_size_per_instance &&
+           m_unused_tables)
+    {
+      TABLE *table_to_free= m_unused_tables;
+      remove_table(table_to_free);
+      intern_close_table(table_to_free);
+      thd->status_var.table_open_cache_overflows++;
+    }
+    mysql_mutex_unlock(&LOCK_open);
+  }
+}
+
+
+/**
+   Add newly created TABLE object which is going to be used right away
+   to the table cache.
+
+   @note Caller should own lock on the table cache.
+
+   @note Sets TABLE::in_use member as side effect.
+
+   @retval false - success.
+   @retval true  - failure.
+*/
+
+bool Table_cache::add_used_table(THD *thd, TABLE *table)
+{
+  Table_cache_element *el;
+
+  assert_owner();
+
+  DBUG_ASSERT(table->in_use == thd);
+
+  /*
+    Try to get Table_cache_element representing this table in the cache
+    from array in the TABLE_SHARE.
+  */
+  el= table->s->cache_element[table_cache_manager.cache_index(this)];
+
+  if (!el)
+  {
+    /*
+      If TABLE_SHARE doesn't have pointer to the element representing table
+      in this cache, the element for the table must be absent from table the
+      cache.
+
+      Allocate new Table_cache_element object and add it to the cache
+      and array in TABLE_SHARE.
+    */
+    DBUG_ASSERT(! my_hash_search(&m_cache,
+                                 (uchar*)table->s->table_cache_key.str,
+                                 table->s->table_cache_key.length));
+
+    if (!(el= new Table_cache_element(table->s)))
+      return true;
+
+    if (my_hash_insert(&m_cache, (uchar*)el))
+    {
+      delete el;
+      return true;
+    }
+
+    table->s->cache_element[table_cache_manager.cache_index(this)]= el;
+  }
+
+  /* Add table to the used tables list */
+  el->used_tables.push_front(table);
+
+  m_table_count++;
+
+  free_unused_tables_if_necessary(thd);
+
+  return false;
+}
+
+
+/**
+   Prepare used or unused TABLE instance for destruction by removing
+   it from the table cache.
+
+   @note Caller should own lock on the table cache.
+*/
+
+void Table_cache::remove_table(TABLE *table)
+{
+  Table_cache_element *el=
+    table->s->cache_element[table_cache_manager.cache_index(this)];
+
+  assert_owner();
+
+  if (table->in_use)
+  {
+    /* Remove from per-table chain of used TABLE objects. */
+    el->used_tables.remove(table);
+  }
+  else
+  {
+    /* Remove from per-table chain of unused TABLE objects. */
+    el->free_tables.remove(table);
+
+    /* And per-cache unused chain. */
+    unlink_unused_table(table);
+  }
+
+  m_table_count--;
+
+  if (el->used_tables.is_empty() && el->free_tables.is_empty())
+  {
+    (void) my_hash_delete(&m_cache, (uchar*) el);
+    /*
+      Remove reference to deleted cache element from array
+      in the TABLE_SHARE.
+    */
+    table->s->cache_element[table_cache_manager.cache_index(this)]= NULL;
+  }
+}
+
+
+/**
+  Get an unused TABLE instance from the table cache.
+
+  @param      thd         Thread context.
+  @param      hash_value  Hash value for the key identifying table.
+  @param      key         Key identifying table.
+  @param      key_length  Length of key for the table.
+  @param[out] share       NULL - if table cache doesn't contain any
+                          information about the table (i.e. doesn't have
+                          neither used nor unused TABLE objects for it).
+                          Pointer to TABLE_SHARE for the table otherwise.
+
+  @note Caller should own lock on the table cache.
+  @note Sets TABLE::in_use member as side effect.
+
+  @retval non-NULL - pointer to unused TABLE object, "share" out-parameter
+                     contains pointer to TABLE_SHARE for this table.
+  @retval NULL     - no unused TABLE object was found, "share" parameter
+                     contains pointer to TABLE_SHARE for this table if there
+                     are used TABLE objects in cache and NULL otherwise.
+*/
+
+TABLE* Table_cache::get_table(THD *thd, my_hash_value_type hash_value,
+                              const char *key, uint key_length,
+                              TABLE_SHARE **share)
+{
+  Table_cache_element *el;
+  TABLE *table;
+
+  assert_owner();
+
+  *share= NULL;
+
+  if (!(el= (Table_cache_element*) my_hash_search_using_hash_value(&m_cache,
+                                     hash_value, (uchar*) key, key_length)))
+    return NULL;
+
+  *share= el->share;
+
+  if ((table= el->free_tables.front()))
+  {
+    DBUG_ASSERT(!table->in_use);
+
+    /*
+      Unlink table from list of unused TABLE objects for this
+      table in this cache.
+    */
+    el->free_tables.remove(table);
+
+    /* Unlink table from unused tables list for this cache. */
+    unlink_unused_table(table);
+
+    /*
+      Add table to list of used TABLE objects for this table
+      in the table cache.
+    */
+    el->used_tables.push_front(table);
+
+    table->in_use= thd;
+    /* The ex-unused table must be fully functional. */
+    DBUG_ASSERT(table->db_stat && table->file);
+    /* The children must be detached from the table. */
+    DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
+  }
+
+  return table;
+}
+
+
+/**
+  Put used TABLE instance back to the table cache and mark
+  it as unused.
+
+  @note Caller should own lock on the table cache.
+  @note Sets TABLE::in_use member as side effect.
+*/
+
+void Table_cache::release_table(THD *thd, TABLE *table)
+{
+  Table_cache_element *el=
+    table->s->cache_element[table_cache_manager.cache_index(this)];
+
+  assert_owner();
+
+  DBUG_ASSERT(table->in_use);
+  DBUG_ASSERT(table->file);
+
+  /* We shouldn't put the table to 'unused' list if the share is old. */
+  DBUG_ASSERT(! table->s->has_old_version());
+
+  table->in_use= NULL;
+
+  /* Remove TABLE from the list of used objects for the table in this cache. */
+  el->used_tables.remove(table);
+  /* Add TABLE to the list of unused objects for the table in this cache. */
+  el->free_tables.push_front(table);
+  /* Also link it last in the list of unused TABLE objects for the cache. */
+  link_unused_table(table);
+
+  /*
+    We free the least used tables, not the subject table, to keep the LRU order.
+    Note that in most common case the below call won't free anything.
+  */
+  free_unused_tables_if_necessary(thd);
+}
+
+
+/**
+  Construct iterator over all used TABLE objects for the table share.
+
+  @note Assumes that caller owns locks on all table caches.
+*/
+Table_cache_iterator::Table_cache_iterator(const TABLE_SHARE *share_arg)
+  : share(share_arg), current_cache_index(0), current_table(NULL)
+{
+  table_cache_manager.assert_owner_all();
+  move_to_next_table();
+}
+
+
+/** Helper that moves iterator to the next used TABLE for the table share. */
+
+void Table_cache_iterator::move_to_next_table()
+{
+  for (; current_cache_index < table_cache_instances; ++current_cache_index)
+  {
+    Table_cache_element *el;
+
+    if ((el= share->cache_element[current_cache_index]))
+    {
+      if ((current_table= el->used_tables.front()))
+        break;
+    }
+  }
+}
+
+
+/**
+  Get current used TABLE instance and move iterator to the next one.
+
+  @note Assumes that caller owns locks on all table caches.
+*/
+
+TABLE* Table_cache_iterator::operator ++(int)
+{
+  table_cache_manager.assert_owner_all();
+
+  TABLE *result= current_table;
+
+  if (current_table)
+  {
+    Table_cache_element::TABLE_list::Iterator
+      it(share->cache_element[current_cache_index]->used_tables, current_table);
+
+    current_table= ++it;
+
+    if (!current_table)
+    {
+      ++current_cache_index;
+      move_to_next_table();
+    }
+  }
+
+  return result;
+}
+
+
+void Table_cache_iterator::rewind()
+{
+  current_cache_index= 0;
+  current_table= NULL;
+  move_to_next_table();
+}
+
+#endif /* TABLE_CACHE_INCLUDED */

=== modified file 'unittest/gunit/CMakeLists.txt'
--- a/unittest/gunit/CMakeLists.txt	2012-05-18 22:41:42 +0000
+++ b/unittest/gunit/CMakeLists.txt	2012-05-23 19:23:08 +0000
@@ -192,6 +192,7 @@ INCLUDE_DIRECTORIES(
   ${CMAKE_SOURCE_DIR}/regex
   ${CMAKE_SOURCE_DIR}/sql
   ${CMAKE_SOURCE_DIR}/unittest/mytap
+  ${CMAKE_SOURCE_DIR}/storage/example
 )
 
 # main-wrapper library (with tap-compatible option).
@@ -267,6 +268,7 @@ SET(SERVER_TESTS
   opt_trace
   segfault
   sql_table
+  table_cache
 )
 
 FOREACH(test ${TESTS})
@@ -282,10 +284,20 @@ FOREACH(test ${TESTS})
 ENDFOREACH()
 
 FOREACH(test ${SERVER_TESTS})
-  IF(WIN32)
-    ADD_EXECUTABLE(${test}-t ${test}-t.cc ../../sql/nt_servc.cc)
+  IF(test MATCHES "table_cache")
+    # table_cache-t in addition to server libraries requires EXAMPLE
+    # storage engine.
+    IF(WIN32)
+      ADD_EXECUTABLE(${test}-t ${test}-t.cc ../../storage/example/ha_example.cc ../../sql/nt_servc.cc)
+    ELSE()
+      ADD_EXECUTABLE(${test}-t ${test}-t.cc ../../storage/example/ha_example.cc)
+    ENDIF()
   ELSE()
-    ADD_EXECUTABLE(${test}-t ${test}-t.cc)
+    IF(WIN32)
+      ADD_EXECUTABLE(${test}-t ${test}-t.cc ../../sql/nt_servc.cc)
+    ELSE()
+      ADD_EXECUTABLE(${test}-t ${test}-t.cc)
+    ENDIF()
   ENDIF()
   TARGET_LINK_LIBRARIES(${test}-t sql binlog rpl master slave sql)
   TARGET_LINK_LIBRARIES(${test}-t gunit_large strings dbug regex mysys)

=== added file 'unittest/gunit/table_cache-t.cc'
--- a/unittest/gunit/table_cache-t.cc	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/table_cache-t.cc	2012-05-23 19:23:08 +0000
@@ -0,0 +1,1009 @@
+/* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+
+   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,
+   51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+// First include (the generated) my_config.h, to get correct platform defines.
+#include "my_config.h"
+#include <gtest/gtest.h>
+#include "test_utils.h"
+
+#include "table_cache.h"
+
+#include "ha_example.h"
+
+/*
+  We need example_hton to be able short-cut creation of example
+  handler instances for mock TABLE objects.
+*/
+extern handlerton *example_hton;
+
+namespace {
+
+using my_testing::Server_initializer;
+
+
+/**
+  Test fixture for basic tests involving Table_cache
+  and Table_cache_manager classes.
+
+  Unlike more advanced fixture it doesn't initialize
+  table cache manager, but only prepares THD objects
+  necessary for testing.
+*/
+
+class TableCacheBasicTest : public ::testing::Test
+{
+protected:
+  static const uint MAX_THREADS= 3;
+
+  virtual void SetUp()
+  {
+    for (uint i= 0; i < MAX_THREADS; ++i)
+    {
+      initializer[i].SetUp();
+      initializer[i].thd()->thread_id= i + 1;
+    }
+
+    ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+  }
+  virtual void TearDown()
+  {
+    for (uint i= 0; i < MAX_THREADS; ++i)
+      initializer[i].TearDown();
+  }
+
+  THD *get_thd(uint index) { return initializer[index].thd(); }
+
+  Server_initializer initializer[MAX_THREADS];
+};
+
+
+/**
+  A more advanced fixture that also initializes table_cache_manager
+  with one Table_cache instance and TDC.
+*/
+
+class TableCacheSingleCacheTest : public TableCacheBasicTest
+{
+protected:
+  virtual uint CachesNumber() { return 1; }
+  virtual void SetUp()
+  {
+    TableCacheBasicTest::SetUp();
+
+    /*
+      In addition to table_cache_manager we want to have initialized
+      TDC so we can use its HASH object for calculating hash values
+      and be able to free TABLE objects correctly (we need LOCK_open
+      initialized for this).
+    */
+    table_cache_instances= CachesNumber();
+    table_cache_size_per_instance= 100;
+    ASSERT_FALSE(table_def_init());
+  }
+  virtual void TearDown()
+  {
+    table_def_free();
+    TableCacheBasicTest::TearDown();
+  }
+};
+
+
+/**
+  Another advanced fixture that also initializes table_cache_manager
+  with two Table_cache instances and TDC.
+*/
+
+class TableCacheDoubleCacheTest : public TableCacheSingleCacheTest
+{
+protected:
+  virtual uint CachesNumber() { return 2; }
+};
+
+
+/**
+  Class for mock TABLE_SHARE object which also allows to create
+  associated TABLE objects which are usable with Table_cache.
+*/
+
+class Mock_share : public TABLE_SHARE
+{
+  MEM_ROOT m_mem_root;
+  Table_cache_element *cache_element_arr[Table_cache_manager::MAX_TABLE_CACHES];
+
+public:
+  Mock_share(const char *key)
+  {
+    memset((TABLE_SHARE *)this, 0, sizeof(TABLE_SHARE));
+    /*
+      Both table_cache_key and cache_element array are used by
+      Table_cache code.
+    */
+    table_cache_key.str= (char*)key;
+    table_cache_key.length= strlen(key);
+    memset(cache_element_arr, 0, sizeof(cache_element_arr));
+    cache_element= cache_element_arr;
+    // MEM_ROOT is used for constructing ha_example() instances.
+    init_alloc_root(&m_mem_root, 1024, 0);
+    /*
+      Assertion in some of Table_cache methods check that version of
+      the share is up-to-date.
+    */
+    version= refresh_version;
+    // Ensure that share is never destroyed.
+    ref_count= UINT_MAX;
+  }
+
+  ~Mock_share()
+  {
+    free_root(&m_mem_root, MYF(0));
+  }
+
+  TABLE *create_table(THD *thd)
+  {
+    TABLE *result= (TABLE *)my_malloc(sizeof(TABLE), MYF(0));
+
+    memset(result, 0, sizeof(TABLE));
+    result->s= this;
+    // We create TABLE which is already marked as used
+    result->in_use= thd;
+    /*
+      Assertions in some of Table_cache methods need non-NULL
+      TABLE::file and TABLE::db_stat. Code that frees unused
+      TABLE objects needs proper "handler" instance.
+    */
+    result->file= new (&m_mem_root) ha_example(example_hton, this);
+    result->db_stat= HA_READ_ONLY;
+
+    return result;
+  }
+
+  void destroy_table(TABLE *table)
+  {
+    my_free(table);
+  }
+};
+
+
+/*
+  Test initilization/destruction of Table_cache.
+*/
+
+TEST_F(TableCacheBasicTest, CacheCreateAndDestroy)
+{
+  Table_cache table_cache;
+
+  ASSERT_FALSE(table_cache.init());
+
+  // Cache should be empty after creation
+  EXPECT_EQ(0U, table_cache.cached_tables());
+
+  // Cache should be not locked after creation
+  EXPECT_DEATH_IF_SUPPORTED(table_cache.assert_owner(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+
+  table_cache.destroy();
+}
+
+
+/*
+  Test locking for Table_cache object.
+*/
+
+TEST_F(TableCacheBasicTest, CacheLockAndUnlock)
+{
+  Table_cache table_cache;
+
+  ASSERT_FALSE(table_cache.init());
+
+  // Cache should not be locked after creation
+  EXPECT_DEATH_IF_SUPPORTED(table_cache.assert_owner(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+
+  // And get locked after we call its lock() method
+  table_cache.lock();
+  table_cache.assert_owner();
+
+  // And get unlocked after we call its unlock() method
+  table_cache.unlock();
+  EXPECT_DEATH_IF_SUPPORTED(table_cache.assert_owner(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+
+  table_cache.destroy();
+}
+
+
+/*
+  Tests for the rest of methods of Table_cache need to use an
+  object controlled by the global instance of Table_cache_manager.
+  Let us start testing of Table_cache_manager with test for
+  its initialization/destruction. This test also covers well
+  Table_cache_manager::get_cache() method.
+*/
+
+TEST_F(TableCacheBasicTest, ManagerCreateAndDestroy)
+{
+  // Request two instances of Table_cache
+  table_cache_instances= 2;
+
+  ASSERT_FALSE(table_cache_manager.init());
+
+  // All caches are empty after creation
+  EXPECT_EQ(0U, table_cache_manager.cached_tables());
+
+  // There should be two different caches in the manager
+  Table_cache *cache_1, *cache_2, *cache_3;
+  cache_1= table_cache_manager.get_cache(get_thd(0));
+  cache_2= table_cache_manager.get_cache(get_thd(1));
+  cache_3= table_cache_manager.get_cache(get_thd(2));
+  EXPECT_TRUE(cache_1 != cache_2);
+  // And not three !
+  EXPECT_TRUE(cache_3 == cache_1);
+
+  // Both caches should be empty
+  EXPECT_EQ(0U, cache_1->cached_tables());
+  EXPECT_EQ(0U, cache_2->cached_tables());
+
+  // And not locked
+  EXPECT_DEATH_IF_SUPPORTED(cache_1->assert_owner(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+  EXPECT_DEATH_IF_SUPPORTED(cache_2->assert_owner(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+
+  table_cache_manager.destroy();
+}
+
+
+/*
+  Test addition and removal of TABLE objects to/from the table cache.
+*/
+
+TEST_F(TableCacheSingleCacheTest, CacheAddAndRemove)
+{
+  THD *thd= get_thd(0);
+
+  Mock_share share_1("share_1");
+  TABLE *table_1= share_1.create_table(thd);
+
+  Table_cache *table_cache= table_cache_manager.get_cache(thd);
+  table_cache->lock();
+  EXPECT_FALSE(table_cache->add_used_table(thd, table_1));
+
+  // There should be one TABLE in the cache after we have added table_1.
+  EXPECT_EQ(1U, table_cache->cached_tables());
+
+  // There should be no unused TABLE objects for the same table in the
+  // cache. OTOH it should contain info about table share of table_1.
+  my_hash_value_type hash_value= my_calc_hash(&table_def_cache,
+                                   (uchar*)share_1.table_cache_key.str,
+                                   share_1.table_cache_key.length);
+  TABLE *table_2;
+  TABLE_SHARE *share_2;
+  table_2= table_cache->get_table(thd, hash_value,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_2 == NULL);
+  EXPECT_TRUE(share_2 == &share_1);
+
+  // Table_cache_iterator should be able to find only one TABLE instance
+  // in all caches. And this instance should be table_1.
+  Table_cache_iterator it(&share_1);
+  EXPECT_TRUE(it++ == table_1);
+  EXPECT_TRUE(it++ == NULL);
+
+  // We must be able to release TABLE into table cache and reuse it after
+  // this.
+  table_cache->release_table(thd, table_1);
+  table_2= table_cache->get_table(thd, hash_value,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_2 == table_1);
+  EXPECT_TRUE(share_2 == &share_1);
+
+  table_cache->remove_table(table_1);
+
+  // Once TABLE is removed from the cache the latter should become empty.
+  EXPECT_EQ(0U, table_cache->cached_tables());
+
+  table_2= table_cache->get_table(thd, hash_value,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_2 == NULL);
+  EXPECT_TRUE(share_2 == NULL);
+
+  it.rewind();
+  EXPECT_TRUE(it++ == NULL);
+
+  // Also it should be possible to remove unused TABLE from the cache
+  // Add TABLE instance and mark it as unused
+  EXPECT_FALSE(table_cache->add_used_table(thd, table_1));
+  table_cache->release_table(thd, table_1);
+
+  table_cache->remove_table(table_1);
+
+  // Once TABLE is removed from cache the latter should become empty.
+  EXPECT_EQ(0U, table_cache->cached_tables());
+
+  table_2= table_cache->get_table(thd, hash_value,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_2 == NULL);
+  EXPECT_TRUE(share_2 == NULL);
+
+  table_cache->unlock();
+
+  share_1.destroy_table(table_1);
+}
+
+
+/*
+  Now let us test how Table_cache handles overflows.
+*/
+
+TEST_F(TableCacheSingleCacheTest, CacheOverflow)
+{
+  THD *thd= get_thd(0);
+
+  // Set cache size low so it will overflow quickly.
+  table_cache_size_per_instance= 2;
+
+  Mock_share share_1("share_1");
+  Mock_share share_2("share_2");
+  TABLE *table_1= share_1.create_table(thd);
+  TABLE *table_2= share_1.create_table(thd);
+  TABLE *table_3= share_2.create_table(thd);
+
+  Table_cache *table_cache= table_cache_manager.get_cache(thd);
+
+  table_cache->lock();
+  table_cache->add_used_table(thd, table_1);
+  table_cache->add_used_table(thd, table_2);
+
+  // There should be two TABLE instances in the cache.
+  EXPECT_EQ(2U, table_cache->cached_tables());
+
+  table_cache->release_table(thd, table_1);
+  table_cache->release_table(thd, table_2);
+
+  // Still there should be two TABLE instances in the cache.
+  EXPECT_EQ(2U, table_cache->cached_tables());
+
+  table_cache->add_used_table(thd, table_3);
+
+  // One TABLE was added and one expelled (table_1), so still two TABLE objects.
+  EXPECT_EQ(2U, table_cache->cached_tables());
+
+  // Old value of table_1 points to garbage thanks to expelling
+  table_1= share_1.create_table(thd);
+  table_cache->add_used_table(thd, table_1);
+
+  // Still two TABLE instances (table_2 was expelled).
+  EXPECT_EQ(2U, table_cache->cached_tables());
+
+  // Old value of table_2 points to garbage thanks to expelling
+  table_2= share_1.create_table(thd);
+  table_cache->add_used_table(thd, table_2);
+
+  /*
+    Now we should have three TABLE instances in cache since all
+    of them are used.
+  */
+  EXPECT_EQ(3U, table_cache->cached_tables());
+
+  table_cache->release_table(thd, table_2);
+
+  // The first table that gets released is expelled.
+  EXPECT_EQ(2U, table_cache->cached_tables());
+
+  table_cache->remove_table(table_1);
+  table_cache->remove_table(table_3);
+
+  // Cache should be empty after that
+  EXPECT_EQ(0U, table_cache->cached_tables());
+
+  table_cache->unlock();
+
+  share_1.destroy_table(table_1);
+  share_1.destroy_table(table_3);
+}
+
+
+TEST_F(TableCacheSingleCacheTest, CacheGetAndRelease)
+{
+  THD *thd= get_thd(0);
+
+  Table_cache *table_cache= table_cache_manager.get_cache(thd);
+
+  table_cache->lock();
+
+  TABLE *table_1, *table_2, *table_3;
+  Mock_share share_1("share_1"), share_0("share_0");
+  TABLE_SHARE *share_2;
+
+  // There should be no TABLE in cache, nor information about share.
+  my_hash_value_type hash_value_1= my_calc_hash(&table_def_cache,
+                                     (uchar*)share_1.table_cache_key.str,
+                                     share_1.table_cache_key.length);
+  table_1= table_cache->get_table(thd, hash_value_1,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_1 == NULL);
+  EXPECT_TRUE(share_2 == NULL);
+
+  table_1= share_1.create_table(thd);
+  table_cache->add_used_table(thd, table_1);
+
+  // There should be no unused TABLE in cache, but there should be
+  // information about the share.
+  table_2= table_cache->get_table(thd, hash_value_1,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_2 == NULL);
+  EXPECT_TRUE(share_2 == &share_1);
+
+  // There should be even no information about the share for which
+  // TABLE was not added to cache.
+  my_hash_value_type hash_value_0= my_calc_hash(&table_def_cache,
+                                     (uchar*)share_0.table_cache_key.str,
+                                     share_0.table_cache_key.length);
+  table_2= table_cache->get_table(thd, hash_value_0,
+                                  share_0.table_cache_key.str,
+                                  share_0.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_2 == NULL);
+  EXPECT_TRUE(share_2 == NULL);
+
+  table_2= share_1.create_table(thd);
+  table_cache->add_used_table(thd, table_2);
+
+  // Still there should be no unused TABLE in cache, but there should
+  // be information about the share.
+  table_3= table_cache->get_table(thd, hash_value_1,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_3 == NULL);
+  EXPECT_TRUE(share_2 == &share_1);
+
+  table_cache->release_table(thd, table_1);
+
+  // After releasing one of TABLE objects it should be possible to get
+  // unused TABLE from cache.
+  table_3= table_cache->get_table(thd, hash_value_1,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_3 == table_1);
+  EXPECT_TRUE(share_2 == &share_1);
+
+  // But only once!
+  table_3= table_cache->get_table(thd, hash_value_1,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_3 == NULL);
+  EXPECT_TRUE(share_2 == &share_1);
+
+  // After releasing of both TABLE objects it should be possible to
+  // get two unused TABLE objects from cache (for 'share_1').
+  // There should be nothing for 'share_0'.
+  table_cache->release_table(thd, table_1);
+  table_cache->release_table(thd, table_2);
+
+  table_3= table_cache->get_table(thd, hash_value_0,
+                                  share_0.table_cache_key.str,
+                                  share_0.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_3 == NULL);
+  EXPECT_TRUE(share_2 == NULL);
+
+
+  table_3= table_cache->get_table(thd, hash_value_1,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_3 != NULL);
+  EXPECT_TRUE(share_2 == &share_1);
+  table_3= table_cache->get_table(thd, hash_value_1,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_3 != NULL);
+  EXPECT_TRUE(share_2 == &share_1);
+  table_3= table_cache->get_table(thd, hash_value_1,
+                                  share_1.table_cache_key.str,
+                                  share_1.table_cache_key.length,
+                                  &share_2);
+  EXPECT_TRUE(table_3 == NULL);
+  EXPECT_TRUE(share_2 == &share_1);
+
+  // Clean-up
+  table_cache->remove_table(table_1);
+  table_cache->remove_table(table_2);
+
+  share_1.destroy_table(table_1);
+  share_1.destroy_table(table_2);
+
+  table_cache->unlock();
+}
+
+
+/*
+  Test for Table_cache_manager/Table_cache::free_all_unused_tables().
+*/
+
+TEST_F(TableCacheDoubleCacheTest, ManagerFreeAllUnused)
+{
+  THD *thd_1= get_thd(0);
+  THD *thd_2= get_thd(1);
+
+  Table_cache *table_cache_1= table_cache_manager.get_cache(thd_1);
+  Table_cache *table_cache_2= table_cache_manager.get_cache(thd_2);
+
+  // There should be no TABLE instances in all cachea.
+  EXPECT_EQ(0U, table_cache_manager.cached_tables());
+
+  Mock_share share_1("share_1");
+  Mock_share share_2("share_2");
+  Mock_share share_3("share_2");
+  TABLE *table_1= share_1.create_table(thd_1);
+  TABLE *table_2= share_1.create_table(thd_1);
+  TABLE *table_3= share_2.create_table(thd_1);
+  TABLE *table_4= share_2.create_table(thd_1);
+  TABLE *table_5= share_1.create_table(thd_2);
+  TABLE *table_6= share_3.create_table(thd_2);
+
+  table_cache_manager.lock_all_and_tdc();
+
+  table_cache_1->add_used_table(thd_1, table_1);
+  table_cache_1->add_used_table(thd_1, table_2);
+  table_cache_1->add_used_table(thd_1, table_3);
+  table_cache_1->add_used_table(thd_1, table_4);
+  table_cache_2->add_used_table(thd_2, table_5);
+  table_cache_2->add_used_table(thd_2, table_6);
+
+  EXPECT_EQ(4U, table_cache_1->cached_tables());
+  EXPECT_EQ(2U, table_cache_2->cached_tables());
+  EXPECT_EQ(6U, table_cache_manager.cached_tables());
+
+  table_cache_manager.free_all_unused_tables();
+
+  // All TABLE instances should stay around in caches as
+  // all of them are used.
+  EXPECT_EQ(4U, table_cache_1->cached_tables());
+  EXPECT_EQ(2U, table_cache_2->cached_tables());
+  EXPECT_EQ(6U, table_cache_manager.cached_tables());
+
+  table_cache_1->release_table(thd_1, table_1);
+
+  table_cache_manager.free_all_unused_tables();
+
+  // One table should be freed. So there should be 3 + 2 TABLE instances.
+  EXPECT_EQ(3U, table_cache_1->cached_tables());
+  EXPECT_EQ(2U, table_cache_2->cached_tables());
+  EXPECT_EQ(5U, table_cache_manager.cached_tables());
+
+  table_cache_1->release_table(thd_1, table_2);
+  table_cache_1->release_table(thd_1, table_3);
+  table_cache_2->release_table(thd_2, table_5);
+
+  table_cache_manager.free_all_unused_tables();
+
+  // Now there should be 1 + 1 used TABLE instances left.
+  EXPECT_EQ(1U, table_cache_1->cached_tables());
+  EXPECT_EQ(1U, table_cache_2->cached_tables());
+  EXPECT_EQ(2U, table_cache_manager.cached_tables());
+
+  table_cache_1->release_table(thd_1, table_4);
+
+  table_cache_manager.free_all_unused_tables();
+
+  // There should be 0 + 1 TABLE instances around.
+  EXPECT_EQ(0U, table_cache_1->cached_tables());
+  EXPECT_EQ(1U, table_cache_2->cached_tables());
+  EXPECT_EQ(1U, table_cache_manager.cached_tables());
+
+  table_cache_2->release_table(thd_2, table_6);
+
+  table_cache_manager.free_all_unused_tables();
+
+  // All caches should become empty.
+  EXPECT_EQ(0U, table_cache_1->cached_tables());
+  EXPECT_EQ(0U, table_cache_2->cached_tables());
+  EXPECT_EQ(0U, table_cache_manager.cached_tables());
+
+  table_cache_manager.unlock_all_and_tdc();
+}
+
+
+/*
+  Test for Table_cache_manager/Table_cache::cached_tables().
+*/
+
+TEST_F(TableCacheDoubleCacheTest, ManagerCachedTables)
+{
+  THD *thd_1= get_thd(0);
+  THD *thd_2= get_thd(1);
+
+  Table_cache *table_cache_1= table_cache_manager.get_cache(thd_1);
+  Table_cache *table_cache_2= table_cache_manager.get_cache(thd_2);
+
+  // There should be no TABLE instances in all cachea.
+  EXPECT_EQ(0U, table_cache_1->cached_tables());
+  EXPECT_EQ(0U, table_cache_2->cached_tables());
+  EXPECT_EQ(0U, table_cache_manager.cached_tables());
+
+  Mock_share share_1("share_1");
+  Mock_share share_2("share_2");
+  TABLE *table_1= share_1.create_table(thd_1);
+  TABLE *table_2= share_1.create_table(thd_1);
+  TABLE *table_3= share_2.create_table(thd_1);
+  TABLE *table_4= share_1.create_table(thd_2);
+  TABLE *table_5= share_2.create_table(thd_2);
+
+  table_cache_manager.lock_all_and_tdc();
+
+  table_cache_1->add_used_table(thd_1, table_1);
+  table_cache_1->add_used_table(thd_1, table_2);
+  table_cache_1->add_used_table(thd_1, table_3);
+
+  // There should be 3 + 0 TABLE objects in cache
+  EXPECT_EQ(3U, table_cache_1->cached_tables());
+  EXPECT_EQ(0U, table_cache_2->cached_tables());
+  EXPECT_EQ(3U, table_cache_manager.cached_tables());
+
+  table_cache_2->add_used_table(thd_2, table_4);
+  table_cache_2->add_used_table(thd_2, table_5);
+
+  // There should be 3 + 2 TABLE objects in cache
+  EXPECT_EQ(3U, table_cache_1->cached_tables());
+  EXPECT_EQ(2U, table_cache_2->cached_tables());
+  EXPECT_EQ(5U, table_cache_manager.cached_tables());
+
+  table_cache_1->release_table(thd_1, table_1);
+  table_cache_2->release_table(thd_2, table_4);
+
+  // There should be the same number of TABLE objects - 3 + 2
+  EXPECT_EQ(3U, table_cache_1->cached_tables());
+  EXPECT_EQ(2U, table_cache_2->cached_tables());
+  EXPECT_EQ(5U, table_cache_manager.cached_tables());
+
+  table_cache_2->remove_table(table_5);
+
+  // There should be 3 + 1 TABLE objects in cache
+  EXPECT_EQ(3U, table_cache_1->cached_tables());
+  EXPECT_EQ(1U, table_cache_2->cached_tables());
+  EXPECT_EQ(4U, table_cache_manager.cached_tables());
+
+  table_cache_1->remove_table(table_1);
+  table_cache_2->remove_table(table_4);
+
+  // There should be 2 + 0 TABLE objects in cache
+  EXPECT_EQ(2U, table_cache_1->cached_tables());
+  EXPECT_EQ(0U, table_cache_2->cached_tables());
+  EXPECT_EQ(2U, table_cache_manager.cached_tables());
+
+  table_cache_1->remove_table(table_2);
+  table_cache_1->remove_table(table_3);
+
+  // Caches should be empty
+  EXPECT_EQ(0U, table_cache_1->cached_tables());
+  EXPECT_EQ(0U, table_cache_2->cached_tables());
+  EXPECT_EQ(0U, table_cache_manager.cached_tables());
+
+  table_cache_manager.unlock_all_and_tdc();
+
+  share_1.destroy_table(table_1);
+  share_1.destroy_table(table_2);
+  share_2.destroy_table(table_3);
+  share_1.destroy_table(table_4);
+  share_2.destroy_table(table_5);
+}
+
+
+/*
+  Coverage for lock and unlock methods of Table_cache_manager class.
+*/
+
+TEST_F(TableCacheDoubleCacheTest, ManagerLockAndUnlock)
+{
+  // Nor caches nor LOCK_open should not be locked after initialization
+  EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.assert_owner_all(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+  EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.assert_owner_all_and_tdc(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+
+  // And get locked after we call its lock_all_and_tdc() method.
+  table_cache_manager.lock_all_and_tdc();
+  table_cache_manager.assert_owner_all();
+  table_cache_manager.assert_owner_all_and_tdc();
+
+  // In addition to Table_cache_manager method we check this by
+  // calling Table_cache methods and asserting state of LOCK_open.
+  Table_cache *cache_1= table_cache_manager.get_cache(get_thd(0));
+  Table_cache *cache_2= table_cache_manager.get_cache(get_thd(1));
+
+  cache_1->assert_owner();
+  cache_2->assert_owner();
+  mysql_mutex_assert_owner(&LOCK_open);
+
+  // Locks should be unlocked after we call unlock method
+  table_cache_manager.unlock_all_and_tdc();
+
+  EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.assert_owner_all(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+  EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.assert_owner_all_and_tdc(),
+                            ".*Assertion.*count > 0.*pthread_equal.*");
+}
+
+
+/*
+  Coverage for Table_cache_manager::free_table();
+*/
+
+TEST_F(TableCacheDoubleCacheTest, ManagerFreeTable)
+{
+  THD *thd_1= get_thd(0);
+  THD *thd_2= get_thd(1);
+
+  Table_cache *table_cache_1= table_cache_manager.get_cache(thd_1);
+  Table_cache *table_cache_2= table_cache_manager.get_cache(thd_2);
+
+  Mock_share share_1("share_1");
+  Mock_share share_2("share_2");
+  TABLE *table_1= share_1.create_table(thd_1);
+  TABLE *table_2= share_1.create_table(thd_1);
+  TABLE *table_3= share_2.create_table(thd_1);
+  TABLE *table_4= share_1.create_table(thd_2);
+  TABLE *table_5= share_2.create_table(thd_2);
+
+  table_cache_manager.lock_all_and_tdc();
+
+  /*
+    Coverage for TDC_RT_REMOVE_ALL case.
+  */
+  table_cache_1->add_used_table(thd_1, table_1);
+  table_cache_1->add_used_table(thd_1, table_2);
+  table_cache_1->release_table(thd_1, table_2);
+  table_cache_1->add_used_table(thd_1, table_3);
+  table_cache_2->add_used_table(thd_2, table_4);
+  table_cache_2->add_used_table(thd_2, table_5);
+
+  EXPECT_EQ(5U, table_cache_manager.cached_tables());
+
+  // There should be assert failure since we are trying
+  // to free all tables for share_1, while some tables
+  // are in use.
+  EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.free_table(thd_1,
+                                                           TDC_RT_REMOVE_ALL,
+                                                           &share_1),
+                            ".*Assertion.*is_empty.*");
+
+  table_cache_1->release_table(thd_1, table_1);
+  table_cache_2->release_table(thd_2, table_4);
+
+  // After all tables for share_1 marked as unused freeing
+  // all tables should succeed.
+  table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_ALL, &share_1);
+
+  // We still should have 2 TABLE objects for share_2.
+  EXPECT_EQ(2U, table_cache_manager.cached_tables());
+
+  /*
+    Coverage for TDC_RT_REMOVE_NOT_OWN case.
+  */
+  table_1= share_1.create_table(thd_1);
+  table_2= share_1.create_table(thd_1);
+  table_4= share_1.create_table(thd_2);
+
+  table_cache_1->add_used_table(thd_1, table_1);
+  table_cache_1->add_used_table(thd_1, table_2);
+  table_cache_1->release_table(thd_1, table_2);
+  table_cache_2->add_used_table(thd_2, table_4);
+
+  EXPECT_EQ(5U, table_cache_manager.cached_tables());
+
+  // There should be assert failure since we are trying
+  // to free all not own TABLEs for share_1, while thd_2
+  // has a TABLE object for it in used
+  EXPECT_DEATH_IF_SUPPORTED(table_cache_manager.free_table(thd_1,
+                                                           TDC_RT_REMOVE_NOT_OWN,
+                                                           &share_1),
+                            ".*Assertion.*`0'.*");
+
+  table_cache_2->release_table(thd_2, table_4);
+
+  // After TABLE owned by thd_2 is marked as unused, the below
+  // call should succeed.
+  table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_NOT_OWN, &share_1);
+
+  // We still have 1 TABLE object for share_1 in thd_1 and
+  // 2 TABLE objects for share_2.
+  EXPECT_EQ(3U, table_cache_manager.cached_tables());
+
+  /*
+    Coverage for TDC_RT_REMOVE_UNUSED case.
+  */
+  table_2= share_1.create_table(thd_1);
+  table_4= share_1.create_table(thd_2);
+
+  table_cache_1->add_used_table(thd_1, table_2);
+  table_cache_1->release_table(thd_1, table_2);
+  table_cache_2->add_used_table(thd_2, table_4);
+
+  EXPECT_EQ(5U, table_cache_manager.cached_tables());
+
+  table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_UNUSED, &share_1);
+
+  // The above call should have been freed only 1 table.
+  EXPECT_EQ(4U, table_cache_manager.cached_tables());
+
+  // Mark all remaining TABLE objects for share_1 as unused
+  table_cache_1->release_table(thd_1, table_1);
+  table_cache_2->release_table(thd_2, table_4);
+
+  table_cache_manager.free_table(thd_1, TDC_RT_REMOVE_UNUSED, &share_1);
+
+  // The above call should free all unused TABLE objects for share_1.
+  // Therefore only 2 objects for share_2 should be remaining
+  EXPECT_EQ(2U, table_cache_manager.cached_tables());
+
+  // Clean-up.
+  table_cache_1->remove_table(table_3);
+  table_cache_2->remove_table(table_5);
+
+  share_2.destroy_table(table_3);
+  share_2.destroy_table(table_5);
+
+  table_cache_manager.unlock_all_and_tdc();
+}
+
+
+/*
+  Coverage for Table_cache_iterator
+*/
+
+TEST_F(TableCacheDoubleCacheTest, Iterator)
+{
+  THD *thd_1= get_thd(0);
+  THD *thd_2= get_thd(1);
+
+  table_cache_manager.lock_all_and_tdc();
+
+  Mock_share share_1("share_1");
+  Mock_share share_2("share_2");
+
+  // There is no TABLE objects for share_1 so the below iterator
+  // should not find anything.
+  Table_cache_iterator it(&share_1);
+  EXPECT_TRUE(it++ == NULL);
+  // Attempt to iterate behind the end should not give anything.
+  EXPECT_TRUE(it++ == NULL);
+
+  Table_cache *table_cache_1= table_cache_manager.get_cache(thd_1);
+  Table_cache *table_cache_2= table_cache_manager.get_cache(thd_2);
+  TABLE *table_1= share_1.create_table(thd_1);
+  TABLE *table_2= share_1.create_table(thd_1);
+  TABLE *table_3= share_2.create_table(thd_1);
+  TABLE *table_4= share_1.create_table(thd_2);
+  TABLE *table_5= share_2.create_table(thd_2);
+
+  table_cache_2->add_used_table(thd_2, table_4);
+
+  // Now the iterato should see table_4.
+  it.rewind();
+  TABLE *table_r1= it++;
+  EXPECT_TRUE(table_r1 == table_4);
+  // But only it.
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_1->add_used_table(thd_1, table_1);
+
+  // Now we should see two tables:
+  it.rewind();
+  table_r1= it++;
+  EXPECT_TRUE(table_r1 != NULL);
+  TABLE *table_r2= it++;
+  EXPECT_TRUE(table_r2 != NULL);
+  EXPECT_TRUE(table_r1 != table_r2);
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_1->add_used_table(thd_1, table_2);
+
+  // And now three !
+  it.rewind();
+  table_r1= it++;
+  EXPECT_TRUE(table_r1 != NULL);
+  table_r2= it++;
+  EXPECT_TRUE(table_r2 != NULL);
+  TABLE *table_r3= it++;
+  EXPECT_TRUE(table_r3 != NULL);
+  EXPECT_TRUE(table_r1 != table_r2 && table_r1 != table_r3 && table_r2 != table_r3);
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_1->release_table(thd_1, table_1);
+
+  // We should be seeing only used TABLE objects, so two tables now
+  it.rewind();
+  table_r1= it++;
+  EXPECT_TRUE(table_r1 != NULL);
+  table_r2= it++;
+  EXPECT_TRUE(table_r2 != NULL);
+  EXPECT_TRUE(table_r1 != table_r2);
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_1->add_used_table(thd_1, table_3);
+  table_cache_2->add_used_table(thd_2, table_5);
+
+  // We also should not be seeing TABLE objects for share_2
+  it.rewind();
+  table_r1= it++;
+  EXPECT_TRUE(table_r1 != NULL);
+  table_r2= it++;
+  EXPECT_TRUE(table_r2 != NULL);
+  EXPECT_TRUE(table_r1 != table_r2);
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_1->remove_table(table_2);
+
+  // Now we should se only one used TABLE
+  it.rewind();
+  table_r1= it++;
+  EXPECT_TRUE(table_r1 == table_4);
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_1->remove_table(table_4);
+
+  // And now no used TABLE objects for share_1 at all
+  it.rewind();
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_1->remove_table(table_1);
+
+  // Still the same
+  it.rewind();
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_1->remove_table(table_3);
+  table_cache_2->remove_table(table_5);
+
+  // Cache is empty so iterator should not show any TABLE objects.
+  it.rewind();
+  EXPECT_TRUE(it++ == NULL);
+  EXPECT_TRUE(it++ == NULL);
+
+  table_cache_manager.unlock_all_and_tdc();
+
+  share_1.destroy_table(table_1);
+  share_1.destroy_table(table_2);
+  share_2.destroy_table(table_3);
+  share_1.destroy_table(table_4);
+  share_2.destroy_table(table_5);
+}
+
+}

No bundle (reason: useless for push emails).
Thread
bzr push into mysql-trunk branch (Dmitry.Lenev:3899 to 3901) WL#5772Dmitry Lenev24 May