mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 10:56:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1319 lines
		
	
	
	
		
			37 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1319 lines
		
	
	
	
		
			37 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
 | |
|    Copyright (c) 2010, 2022, MariaDB Corporation.
 | |
|    Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation
 | |
| 
 | |
|    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-1335  USA */
 | |
| 
 | |
| /**
 | |
|   @file
 | |
|   Table definition cache and table cache implementation.
 | |
| 
 | |
|   Table definition cache actions:
 | |
|   - add new TABLE_SHARE object to cache (tdc_acquire_share())
 | |
|   - acquire TABLE_SHARE object from cache (tdc_acquire_share())
 | |
|   - release TABLE_SHARE object to cache (tdc_release_share())
 | |
|   - purge unused TABLE_SHARE objects from cache (tdc_purge())
 | |
|   - remove TABLE_SHARE object from cache (tdc_remove_table())
 | |
|   - get number of TABLE_SHARE objects in cache (tdc_records())
 | |
| 
 | |
|   Table cache actions:
 | |
|   - add new TABLE object to cache (tc_add_table())
 | |
|   - acquire TABLE object from cache (tc_acquire_table())
 | |
|   - release TABLE object to cache (tc_release_table())
 | |
|   - purge unused TABLE objects from cache (tc_purge())
 | |
|   - purge unused TABLE objects of a table from cache (tdc_remove_table())
 | |
|   - get number of TABLE objects in cache (tc_records())
 | |
| 
 | |
|   Dependencies:
 | |
|   - close_cached_tables(): flush tables on shutdown
 | |
|   - alloc_table_share()
 | |
|   - free_table_share()
 | |
| 
 | |
|   Table cache invariants:
 | |
|   - TABLE_SHARE::free_tables shall not contain objects with TABLE::in_use != 0
 | |
|   - TABLE_SHARE::free_tables shall not receive new objects if
 | |
|     TABLE_SHARE::tdc.flushed is true
 | |
| */
 | |
| 
 | |
| #include "mariadb.h"
 | |
| #include "lf.h"
 | |
| #include "table.h"
 | |
| #include "sql_base.h"
 | |
| #include "aligned.h"
 | |
| 
 | |
| 
 | |
| /** Configuration. */
 | |
| ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */
 | |
| ulong tc_size; /**< Table cache threshold for LRU eviction. */
 | |
| uint32 tc_instances;
 | |
| static size_t tc_allocated_size;
 | |
| static std::atomic<uint32_t> tc_active_instances(1);
 | |
| static std::atomic<bool> tc_contention_warning_reported;
 | |
| 
 | |
| /** Data collections. */
 | |
| static LF_HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */
 | |
| /** Collection of unused TABLE_SHARE objects. */
 | |
| static
 | |
| I_P_List <TDC_element,
 | |
|           I_P_List_adapter<TDC_element, &TDC_element::next, &TDC_element::prev>,
 | |
|           I_P_List_null_counter,
 | |
|           I_P_List_fast_push_back<TDC_element> > unused_shares;
 | |
| 
 | |
| static bool tdc_inited;
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Protects unused shares list.
 | |
| 
 | |
|   TDC_element::prev
 | |
|   TDC_element::next
 | |
|   unused_shares
 | |
| */
 | |
| 
 | |
| static mysql_mutex_t LOCK_unused_shares;
 | |
| 
 | |
| #ifdef HAVE_PSI_INTERFACE
 | |
| static PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share,
 | |
|                      key_LOCK_table_cache;
 | |
| static PSI_mutex_info all_tc_mutexes[]=
 | |
| {
 | |
|   { &key_LOCK_unused_shares, "LOCK_unused_shares", PSI_FLAG_GLOBAL },
 | |
|   { &key_TABLE_SHARE_LOCK_table_share, "TABLE_SHARE::tdc.LOCK_table_share", 0 },
 | |
|   { &key_LOCK_table_cache, "LOCK_table_cache", 0 }
 | |
| };
 | |
| 
 | |
| static PSI_cond_key key_TABLE_SHARE_COND_release;
 | |
| static PSI_cond_info all_tc_conds[]=
 | |
| {
 | |
|   { &key_TABLE_SHARE_COND_release, "TABLE_SHARE::tdc.COND_release", 0 }
 | |
| };
 | |
| #endif
 | |
| 
 | |
| 
 | |
| static int fix_thd_pins(THD *thd)
 | |
| {
 | |
|   return thd->tdc_hash_pins ? 0 :
 | |
|          (thd->tdc_hash_pins= lf_hash_get_pins(&tdc_hash)) == 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Auxiliary routines for manipulating with per-share all/unused lists
 | |
|   and tc_count counter.
 | |
|   Responsible for preserving invariants between those lists, counter
 | |
|   and TABLE::in_use member.
 | |
|   In fact those routines implement sort of implicit table cache as
 | |
|   part of table definition cache.
 | |
| */
 | |
| 
 | |
| struct Table_cache_instance
 | |
| {
 | |
|   /**
 | |
|     Protects free_tables (TABLE::global_free_next and TABLE::global_free_prev),
 | |
|     records, Share_free_tables::List (TABLE::prev and TABLE::next),
 | |
|     TABLE::in_use.
 | |
|   */
 | |
|   alignas(CPU_LEVEL1_DCACHE_LINESIZE)
 | |
|   mysql_mutex_t LOCK_table_cache;
 | |
|   I_P_List <TABLE, I_P_List_adapter<TABLE, &TABLE::global_free_next,
 | |
|                                     &TABLE::global_free_prev>,
 | |
|             I_P_List_null_counter, I_P_List_fast_push_back<TABLE> >
 | |
|     free_tables;
 | |
|   ulong records;
 | |
|   uint mutex_waits;
 | |
|   uint mutex_nowaits;
 | |
| 
 | |
|   Table_cache_instance(): records(0), mutex_waits(0), mutex_nowaits(0)
 | |
|   {
 | |
|     static_assert(!(sizeof(*this) % CPU_LEVEL1_DCACHE_LINESIZE), "alignment");
 | |
|     mysql_mutex_init(key_LOCK_table_cache, &LOCK_table_cache,
 | |
|                      MY_MUTEX_INIT_FAST);
 | |
|   }
 | |
| 
 | |
|   ~Table_cache_instance()
 | |
|   {
 | |
|     mysql_mutex_destroy(&LOCK_table_cache);
 | |
|     DBUG_ASSERT(free_tables.is_empty());
 | |
|     DBUG_ASSERT(records == 0);
 | |
|   }
 | |
| 
 | |
|   static void *operator new[](size_t size)
 | |
|   { return aligned_malloc(size, CPU_LEVEL1_DCACHE_LINESIZE); }
 | |
|   static void operator delete[](void *ptr) { aligned_free(ptr); }
 | |
|   static void mark_memory_freed()
 | |
|   {
 | |
|     update_malloc_size(-(longlong) tc_allocated_size, 0);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|     Lock table cache mutex and check contention.
 | |
| 
 | |
|     Instance is considered contested if more than 20% of mutex acquisiotions
 | |
|     can't be served immediately. Up to 100 000 probes may be performed to avoid
 | |
|     instance activation on short sporadic peaks. 100 000 is estimated maximum
 | |
|     number of queries one instance can serve in one second.
 | |
| 
 | |
|     These numbers work well on a 2 socket / 20 core / 40 threads Intel Broadwell
 | |
|     system, that is expected number of instances is activated within reasonable
 | |
|     warmup time. It may have to be adjusted for other systems.
 | |
| 
 | |
|     Only TABLE object acquistion is instrumented. We intentionally avoid this
 | |
|     overhead on TABLE object release. All other table cache mutex acquistions
 | |
|     are considered out of hot path and are not instrumented either.
 | |
|   */
 | |
|   void lock_and_check_contention(uint32_t n_instances, uint32_t instance)
 | |
|   {
 | |
|     if (mysql_mutex_trylock(&LOCK_table_cache))
 | |
|     {
 | |
|       mysql_mutex_lock(&LOCK_table_cache);
 | |
|       if (++mutex_waits == 20000)
 | |
|       {
 | |
|         if (n_instances < tc_instances)
 | |
|         {
 | |
|           if (tc_active_instances.
 | |
|               compare_exchange_weak(n_instances, n_instances + 1,
 | |
|                                     std::memory_order_relaxed,
 | |
|                                     std::memory_order_relaxed))
 | |
|           {
 | |
|             sql_print_information("Detected table cache mutex contention at instance %d: "
 | |
|                                   "%d%% waits. Additional table cache instance "
 | |
|                                   "activated. Number of instances after "
 | |
|                                   "activation: %d.",
 | |
|                                   instance + 1,
 | |
|                                   mutex_waits * 100 / (mutex_nowaits + mutex_waits),
 | |
|                                   n_instances + 1);
 | |
|           }
 | |
|         }
 | |
|         else if (!tc_contention_warning_reported.exchange(true,
 | |
|                                                  std::memory_order_relaxed))
 | |
|         {
 | |
|           sql_print_warning("Detected table cache mutex contention at instance %d: "
 | |
|                             "%d%% waits. Additional table cache instance "
 | |
|                             "cannot be activated: consider raising "
 | |
|                             "table_open_cache_instances. Number of active "
 | |
|                             "instances: %d.",
 | |
|                             instance + 1,
 | |
|                             mutex_waits * 100 / (mutex_nowaits + mutex_waits),
 | |
|                             n_instances);
 | |
|         }
 | |
|         mutex_waits= 0;
 | |
|         mutex_nowaits= 0;
 | |
|       }
 | |
|     }
 | |
|     else if (++mutex_nowaits == 80000)
 | |
|     {
 | |
|       mutex_waits= 0;
 | |
|       mutex_nowaits= 0;
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| static Table_cache_instance *tc;
 | |
| 
 | |
| 
 | |
| static void intern_close_table(TABLE *table)
 | |
| {
 | |
|   delete table->triggers;
 | |
|   DBUG_ASSERT(table->file);
 | |
|   closefrm(table);
 | |
|   tdc_release_share(table->s);
 | |
|   my_free(table);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Get number of TABLE objects (used and unused) in table cache.
 | |
| */
 | |
| 
 | |
| uint tc_records(void)
 | |
| {
 | |
|   ulong total= 0;
 | |
|   for (uint32 i= 0; i < tc_instances; i++)
 | |
|   {
 | |
|     mysql_mutex_lock(&tc[i].LOCK_table_cache);
 | |
|     total+= tc[i].records;
 | |
|     mysql_mutex_unlock(&tc[i].LOCK_table_cache);
 | |
|   }
 | |
|   return total;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Remove TABLE object from table cache.
 | |
| */
 | |
| 
 | |
| static void tc_remove_table(TABLE *table)
 | |
| {
 | |
|   TDC_element *element= table->s->tdc;
 | |
| 
 | |
|   mysql_mutex_lock(&element->LOCK_table_share);
 | |
|   /* Wait for MDL deadlock detector to complete traversing tdc.all_tables. */
 | |
|   while (element->all_tables_refs)
 | |
|     mysql_cond_wait(&element->COND_release, &element->LOCK_table_share);
 | |
|   element->all_tables.remove(table);
 | |
|   mysql_mutex_unlock(&element->LOCK_table_share);
 | |
| 
 | |
|   intern_close_table(table);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void tc_remove_all_unused_tables(TDC_element *element,
 | |
|                                         Share_free_tables::List *purge_tables)
 | |
| {
 | |
|   for (uint32 i= 0; i < tc_instances; i++)
 | |
|   {
 | |
|     mysql_mutex_lock(&tc[i].LOCK_table_cache);
 | |
|     while (auto table= element->free_tables[i].list.pop_front())
 | |
|     {
 | |
|       tc[i].records--;
 | |
|       tc[i].free_tables.remove(table);
 | |
|       DBUG_ASSERT(element->all_tables_refs == 0);
 | |
|       element->all_tables.remove(table);
 | |
|       purge_tables->push_front(table);
 | |
|     }
 | |
|     mysql_mutex_unlock(&tc[i].LOCK_table_cache);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Free all unused TABLE objects.
 | |
| 
 | |
|   While locked:
 | |
|   - remove unused objects from TABLE_SHARE::tdc.free_tables and
 | |
|     TABLE_SHARE::tdc.all_tables
 | |
|   - decrement tc_count
 | |
| 
 | |
|   While unlocked:
 | |
|   - free resources related to unused objects
 | |
| 
 | |
|   @note This is called by 'handle_manager' when one wants to
 | |
|         periodicly flush all not used tables.
 | |
| */
 | |
| 
 | |
| static my_bool tc_purge_callback(void *_element, void *_purge_tables)
 | |
| {
 | |
|   TDC_element *element= static_cast<TDC_element *>(_element);
 | |
|   Share_free_tables::List *purge_tables=
 | |
|       static_cast<Share_free_tables::List *>(_purge_tables);
 | |
|   mysql_mutex_lock(&element->LOCK_table_share);
 | |
|   tc_remove_all_unused_tables(element, purge_tables);
 | |
|   mysql_mutex_unlock(&element->LOCK_table_share);
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| void tc_purge()
 | |
| {
 | |
|   Share_free_tables::List purge_tables;
 | |
| 
 | |
|   tdc_iterate(0, tc_purge_callback, &purge_tables);
 | |
|   while (auto table= purge_tables.pop_front())
 | |
|     intern_close_table(table);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Add new TABLE object to table cache.
 | |
| 
 | |
|   @pre TABLE object is used by caller.
 | |
| 
 | |
|   Added object cannot be evicted or acquired.
 | |
| 
 | |
|   While locked:
 | |
|   - add object to TABLE_SHARE::tdc.all_tables
 | |
|   - increment tc_count
 | |
|   - evict LRU object from table cache if we reached threshold
 | |
| 
 | |
|   While unlocked:
 | |
|   - free evicted object
 | |
| */
 | |
| 
 | |
| void tc_add_table(THD *thd, TABLE *table)
 | |
| {
 | |
|   uint32_t i=
 | |
|     thd->thread_id % tc_active_instances.load(std::memory_order_relaxed);
 | |
|   TABLE *LRU_table= 0;
 | |
|   TDC_element *element= table->s->tdc;
 | |
| 
 | |
|   DBUG_ASSERT(table->in_use == thd);
 | |
|   table->instance= i;
 | |
|   mysql_mutex_lock(&element->LOCK_table_share);
 | |
|   /* Wait for MDL deadlock detector to complete traversing tdc.all_tables. */
 | |
|   while (element->all_tables_refs)
 | |
|     mysql_cond_wait(&element->COND_release, &element->LOCK_table_share);
 | |
|   element->all_tables.push_front(table);
 | |
|   mysql_mutex_unlock(&element->LOCK_table_share);
 | |
| 
 | |
|   mysql_mutex_lock(&tc[i].LOCK_table_cache);
 | |
|   if (tc[i].records == tc_size)
 | |
|   {
 | |
|     if ((LRU_table= tc[i].free_tables.pop_front()))
 | |
|     {
 | |
|       LRU_table->s->tdc->free_tables[i].list.remove(LRU_table);
 | |
|       /* Needed if MDL deadlock detector chimes in before tc_remove_table() */
 | |
|       LRU_table->in_use= thd;
 | |
|       mysql_mutex_unlock(&tc[i].LOCK_table_cache);
 | |
|       /* Keep out of locked LOCK_table_cache */
 | |
|       tc_remove_table(LRU_table);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       tc[i].records++;
 | |
|       mysql_mutex_unlock(&tc[i].LOCK_table_cache);
 | |
|     }
 | |
|     /* Keep out of locked LOCK_table_cache */
 | |
|     status_var_increment(thd->status_var.table_open_cache_overflows);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     tc[i].records++;
 | |
|     mysql_mutex_unlock(&tc[i].LOCK_table_cache);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Acquire TABLE object from table cache.
 | |
| 
 | |
|   @pre share must be protected against removal.
 | |
| 
 | |
|   Acquired object cannot be evicted or acquired again.
 | |
| 
 | |
|   @return TABLE object, or NULL if no unused objects.
 | |
| */
 | |
| 
 | |
| TABLE *tc_acquire_table(THD *thd, TDC_element *element)
 | |
| {
 | |
|   uint32_t n_instances= tc_active_instances.load(std::memory_order_relaxed);
 | |
|   uint32_t i= thd->thread_id % n_instances;
 | |
|   TABLE *table;
 | |
| 
 | |
|   tc[i].lock_and_check_contention(n_instances, i);
 | |
|   table= element->free_tables[i].list.pop_front();
 | |
|   if (table)
 | |
|   {
 | |
|     DBUG_ASSERT(!table->in_use);
 | |
|     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));
 | |
|     tc[i].free_tables.remove(table);
 | |
|   }
 | |
|   mysql_mutex_unlock(&tc[i].LOCK_table_cache);
 | |
|   return table;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Release TABLE object to table cache.
 | |
| 
 | |
|   @pre object is used by caller.
 | |
| 
 | |
|   Released object may be evicted or acquired again.
 | |
| 
 | |
|   While locked:
 | |
|   - if object is marked for purge, decrement tc_count
 | |
|   - add object to TABLE_SHARE::tdc.free_tables
 | |
|   - evict LRU object from table cache if we reached threshold
 | |
| 
 | |
|   While unlocked:
 | |
|   - mark object not in use by any thread
 | |
|   - free evicted/purged object
 | |
| 
 | |
|   @note Another thread may mark share for purge any moment (even
 | |
|   after version check). It means to-be-purged object may go to
 | |
|   unused lists. This other thread is expected to call tc_purge(),
 | |
|   which is synchronized with us on TABLE_SHARE::tdc.LOCK_table_share.
 | |
| 
 | |
|   @return
 | |
|     @retval true  object purged
 | |
|     @retval false object released
 | |
| */
 | |
| 
 | |
| void tc_release_table(TABLE *table)
 | |
| {
 | |
|   uint32 i= table->instance;
 | |
|   DBUG_ENTER("tc_release_table");
 | |
|   DBUG_ASSERT(table->in_use);
 | |
|   DBUG_ASSERT(table->file);
 | |
|   DBUG_ASSERT(!table->pos_in_locked_tables);
 | |
| 
 | |
|   mysql_mutex_lock(&tc[i].LOCK_table_cache);
 | |
|   if (table->needs_reopen() || table->s->tdc->flushed ||
 | |
|       tc[i].records > tc_size)
 | |
|   {
 | |
|     tc[i].records--;
 | |
|     mysql_mutex_unlock(&tc[i].LOCK_table_cache);
 | |
|     tc_remove_table(table);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     table->in_use= 0;
 | |
|     table->s->tdc->free_tables[i].list.push_front(table);
 | |
|     tc[i].free_tables.push_back(table);
 | |
|     mysql_mutex_unlock(&tc[i].LOCK_table_cache);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void tdc_assert_clean_share(TDC_element *element)
 | |
| {
 | |
|   DBUG_ASSERT(element->share == 0);
 | |
|   DBUG_ASSERT(element->ref_count == 0);
 | |
|   DBUG_ASSERT(element->m_flush_tickets.is_empty());
 | |
|   DBUG_ASSERT(element->all_tables.is_empty());
 | |
| #ifndef DBUG_OFF
 | |
|   for (uint32 i= 0; i < tc_instances; i++)
 | |
|     DBUG_ASSERT(element->free_tables[i].list.is_empty());
 | |
| #endif
 | |
|   DBUG_ASSERT(element->all_tables_refs == 0);
 | |
|   DBUG_ASSERT(element->next == 0);
 | |
|   DBUG_ASSERT(element->prev == 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Delete share from hash and free share object.
 | |
| */
 | |
| 
 | |
| static void tdc_delete_share_from_hash(TDC_element *element)
 | |
| {
 | |
|   THD *thd= current_thd;
 | |
|   LF_PINS *pins;
 | |
|   TABLE_SHARE *share;
 | |
|   DBUG_ENTER("tdc_delete_share_from_hash");
 | |
| 
 | |
|   mysql_mutex_assert_owner(&element->LOCK_table_share);
 | |
|   share= element->share;
 | |
|   DBUG_ASSERT(share);
 | |
|   element->share= 0;
 | |
|   PSI_CALL_release_table_share(share->m_psi);
 | |
|   share->m_psi= 0;
 | |
| 
 | |
|   if (!element->m_flush_tickets.is_empty())
 | |
|   {
 | |
|     Wait_for_flush_list::Iterator it(element->m_flush_tickets);
 | |
|     Wait_for_flush *ticket;
 | |
|     while ((ticket= it++))
 | |
|       (void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED);
 | |
| 
 | |
|     do
 | |
|     {
 | |
|       mysql_cond_wait(&element->COND_release, &element->LOCK_table_share);
 | |
|     } while (!element->m_flush_tickets.is_empty());
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&element->LOCK_table_share);
 | |
| 
 | |
|   if (thd)
 | |
|   {
 | |
|     fix_thd_pins(thd);
 | |
|     pins= thd->tdc_hash_pins;
 | |
|   }
 | |
|   else
 | |
|     pins= lf_hash_get_pins(&tdc_hash);
 | |
| 
 | |
|   DBUG_ASSERT(pins); // What can we do about it?
 | |
|   tdc_assert_clean_share(element);
 | |
|   lf_hash_delete(&tdc_hash, pins, element->m_key, element->m_key_length);
 | |
|   if (!thd)
 | |
|     lf_hash_put_pins(pins);
 | |
|   free_table_share(share);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Prepeare table share for use with table definition cache.
 | |
| */
 | |
| 
 | |
| static void lf_alloc_constructor(uchar *arg)
 | |
| {
 | |
|   TDC_element *element= (TDC_element*) (arg + LF_HASH_OVERHEAD);
 | |
|   DBUG_ENTER("lf_alloc_constructor");
 | |
|   mysql_mutex_init(key_TABLE_SHARE_LOCK_table_share,
 | |
|                    &element->LOCK_table_share, MY_MUTEX_INIT_FAST);
 | |
|   mysql_cond_init(key_TABLE_SHARE_COND_release, &element->COND_release, 0);
 | |
|   element->m_flush_tickets.empty();
 | |
|   element->all_tables.empty();
 | |
|   for (uint32 i= 0; i < tc_instances; i++)
 | |
|     element->free_tables[i].list.empty();
 | |
|   element->all_tables_refs= 0;
 | |
|   element->share= 0;
 | |
|   element->ref_count= 0;
 | |
|   element->next= 0;
 | |
|   element->prev= 0;
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Release table definition cache specific resources of table share.
 | |
| */
 | |
| 
 | |
| static void lf_alloc_destructor(uchar *arg)
 | |
| {
 | |
|   TDC_element *element= (TDC_element*) (arg + LF_HASH_OVERHEAD);
 | |
|   DBUG_ENTER("lf_alloc_destructor");
 | |
|   tdc_assert_clean_share(element);
 | |
|   mysql_cond_destroy(&element->COND_release);
 | |
|   mysql_mutex_destroy(&element->LOCK_table_share);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void tdc_hash_initializer(LF_HASH *,
 | |
|                                  void *_element, const void *_key)
 | |
| {
 | |
|   TDC_element *element= static_cast<TDC_element *>(_element);
 | |
|   const LEX_STRING *key= static_cast<const LEX_STRING *>(_key);
 | |
|   memcpy(element->m_key, key->str, key->length);
 | |
|   element->m_key_length= (uint)key->length;
 | |
|   tdc_assert_clean_share(element);
 | |
| }
 | |
| 
 | |
| 
 | |
| static const uchar *tdc_hash_key(const void *element_, size_t *length, my_bool)
 | |
| {
 | |
|   auto element= static_cast<const TDC_element *>(element_);
 | |
|   *length= element->m_key_length;
 | |
|   return reinterpret_cast<const uchar *>(element->m_key);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Initialize table definition cache.
 | |
| */
 | |
| 
 | |
| bool tdc_init(void)
 | |
| {
 | |
|   DBUG_ENTER("tdc_init");
 | |
| #ifdef HAVE_PSI_INTERFACE
 | |
|   mysql_mutex_register("sql", all_tc_mutexes, array_elements(all_tc_mutexes));
 | |
|   mysql_cond_register("sql", all_tc_conds, array_elements(all_tc_conds));
 | |
| #endif
 | |
|   /* Extra instance is allocated to avoid false sharing */
 | |
|   if (!(tc= new Table_cache_instance[tc_instances + 1]))
 | |
|     DBUG_RETURN(true);
 | |
|   tc_allocated_size= (tc_instances + 1) * sizeof *tc;
 | |
|   update_malloc_size(tc_allocated_size, 0);
 | |
|   tdc_inited= true;
 | |
|   mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares,
 | |
|                    MY_MUTEX_INIT_FAST);
 | |
|   lf_hash_init(&tdc_hash,
 | |
|                sizeof(TDC_element) +
 | |
|                    sizeof(Share_free_tables) * (tc_instances - 1),
 | |
|                LF_HASH_UNIQUE, 0, 0, tdc_hash_key, &my_charset_bin);
 | |
|   tdc_hash.alloc.constructor= lf_alloc_constructor;
 | |
|   tdc_hash.alloc.destructor= lf_alloc_destructor;
 | |
|   tdc_hash.initializer= tdc_hash_initializer;
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Notify table definition cache that process of shutting down server
 | |
|   has started so it has to keep number of TABLE and TABLE_SHARE objects
 | |
|   minimal in order to reduce number of references to pluggable engines.
 | |
| */
 | |
| 
 | |
| void tdc_start_shutdown(void)
 | |
| {
 | |
|   DBUG_ENTER("tdc_start_shutdown");
 | |
|   if (tdc_inited)
 | |
|   {
 | |
|     /*
 | |
|       Ensure that TABLE and TABLE_SHARE objects which are created for
 | |
|       tables that are open during process of plugins' shutdown are
 | |
|       immediately released. This keeps number of references to engine
 | |
|       plugins minimal and allows shutdown to proceed smoothly.
 | |
|     */
 | |
|     tdc_size= 0;
 | |
|     tc_size= 0;
 | |
|     /* Free all cached but unused TABLEs and TABLE_SHAREs. */
 | |
|     purge_tables();
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Deinitialize table definition cache.
 | |
| */
 | |
| 
 | |
| void tdc_deinit(void)
 | |
| {
 | |
|   DBUG_ENTER("tdc_deinit");
 | |
|   if (tdc_inited)
 | |
|   {
 | |
|     tdc_inited= false;
 | |
|     lf_hash_destroy(&tdc_hash);
 | |
|     mysql_mutex_destroy(&LOCK_unused_shares);
 | |
|     if (tc)
 | |
|     {
 | |
|       tc->mark_memory_freed();
 | |
|       delete [] tc;
 | |
|       tc= 0;
 | |
|     }
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Get number of cached table definitions.
 | |
| 
 | |
|   @return Number of cached table definitions
 | |
| */
 | |
| 
 | |
| ulong tdc_records(void)
 | |
| {
 | |
|   return lf_hash_size(&tdc_hash);
 | |
| }
 | |
| 
 | |
| 
 | |
| void tdc_purge(bool all)
 | |
| {
 | |
|   DBUG_ENTER("tdc_purge");
 | |
|   while (all || tdc_records() > tdc_size)
 | |
|   {
 | |
|     TDC_element *element;
 | |
| 
 | |
|     mysql_mutex_lock(&LOCK_unused_shares);
 | |
|     if (!(element= unused_shares.pop_front()))
 | |
|     {
 | |
|       mysql_mutex_unlock(&LOCK_unused_shares);
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     /* Concurrent thread may start using share again, reset prev and next. */
 | |
|     element->prev= 0;
 | |
|     element->next= 0;
 | |
|     mysql_mutex_lock(&element->LOCK_table_share);
 | |
|     if (element->ref_count)
 | |
|     {
 | |
|       mysql_mutex_unlock(&element->LOCK_table_share);
 | |
|       mysql_mutex_unlock(&LOCK_unused_shares);
 | |
|       continue;
 | |
|     }
 | |
|     mysql_mutex_unlock(&LOCK_unused_shares);
 | |
| 
 | |
|     tdc_delete_share_from_hash(element);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Lock table share.
 | |
| 
 | |
|   Find table share with given db.table_name in table definition cache. Return
 | |
|   locked table share if found.
 | |
| 
 | |
|   Locked table share means:
 | |
|   - table share is protected against removal from table definition cache
 | |
|   - no other thread can acquire/release table share
 | |
| 
 | |
|   Caller is expected to unlock table share with tdc_unlock_share().
 | |
| 
 | |
|   @retval 0 Share not found
 | |
|   @retval MY_ERRPTR OOM
 | |
|   @retval ptr Pointer to locked table share
 | |
| */
 | |
| 
 | |
| TDC_element *tdc_lock_share(THD *thd, const char *db, const char *table_name)
 | |
| {
 | |
|   TDC_element *element;
 | |
|   char key[MAX_DBKEY_LENGTH];
 | |
| 
 | |
|   DBUG_ENTER("tdc_lock_share");
 | |
|   if (unlikely(fix_thd_pins(thd)))
 | |
|     DBUG_RETURN((TDC_element*) MY_ERRPTR);
 | |
| 
 | |
|   element= (TDC_element *) lf_hash_search(&tdc_hash, thd->tdc_hash_pins,
 | |
|                                           (uchar*) key,
 | |
|                                           tdc_create_key(key, db, table_name));
 | |
|   if (element)
 | |
|   {
 | |
|     mysql_mutex_lock(&element->LOCK_table_share);
 | |
|     if (unlikely(!element->share || element->share->error))
 | |
|     {
 | |
|       mysql_mutex_unlock(&element->LOCK_table_share);
 | |
|       element= 0;
 | |
|     }
 | |
|     lf_hash_search_unpin(thd->tdc_hash_pins);
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(element);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Unlock share locked by tdc_lock_share().
 | |
| */
 | |
| 
 | |
| void tdc_unlock_share(TDC_element *element)
 | |
| {
 | |
|   DBUG_ENTER("tdc_unlock_share");
 | |
|   mysql_mutex_unlock(&element->LOCK_table_share);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| int tdc_share_is_cached(THD *thd, const char *db, const char *table_name)
 | |
| {
 | |
|   char key[MAX_DBKEY_LENGTH];
 | |
| 
 | |
|   if (unlikely(fix_thd_pins(thd)))
 | |
|     return -1;
 | |
| 
 | |
|   if (lf_hash_search(&tdc_hash, thd->tdc_hash_pins, (uchar*) key,
 | |
|                      tdc_create_key(key, db, table_name)))
 | |
|   {
 | |
|     lf_hash_search_unpin(thd->tdc_hash_pins);
 | |
|     return 1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Get TABLE_SHARE for a table.
 | |
| 
 | |
|   tdc_acquire_share()
 | |
|   thd                   Thread handle
 | |
|   tl                    Table that should be opened
 | |
|   flags                 operation: what to open table or view
 | |
|   out_table             TABLE for the requested table
 | |
| 
 | |
|   IMPLEMENTATION
 | |
|     Get a table definition from the table definition cache.
 | |
|     If it doesn't exist, create a new from the table definition file.
 | |
| 
 | |
|   RETURN
 | |
|    0  Error
 | |
|    #  Share for table
 | |
| */
 | |
| 
 | |
| TABLE_SHARE *tdc_acquire_share(THD *thd, TABLE_LIST *tl, uint flags,
 | |
|                                TABLE **out_table)
 | |
| {
 | |
|   TABLE_SHARE *share;
 | |
|   TDC_element *element;
 | |
|   const char *key;
 | |
|   uint key_length= get_table_def_key(tl, &key);
 | |
|   my_hash_value_type hash_value= tl->mdl_request.key.tc_hash_value();
 | |
|   bool was_unused;
 | |
|   DBUG_ENTER("tdc_acquire_share");
 | |
| 
 | |
|   if (fix_thd_pins(thd))
 | |
|     DBUG_RETURN(0);
 | |
| 
 | |
| retry:
 | |
|   while (!(element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash,
 | |
|                     thd->tdc_hash_pins, hash_value, (uchar*) key, key_length)))
 | |
|   {
 | |
|     LEX_STRING tmp= { const_cast<char*>(key), key_length };
 | |
|     int res= lf_hash_insert(&tdc_hash, thd->tdc_hash_pins, (uchar*) &tmp);
 | |
| 
 | |
|     if (res == -1)
 | |
|       DBUG_RETURN(0);
 | |
|     else if (res == 1)
 | |
|       continue;
 | |
| 
 | |
|     element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash,
 | |
|              thd->tdc_hash_pins, hash_value, (uchar*) key, key_length);
 | |
|     /* It's safe to unpin the pins here, because an empty element was inserted
 | |
|     above, "empty" means at least element->share = 0. Some other thread can't
 | |
|     delete it while element->share == 0. And element->share is also protected
 | |
|     with element->LOCK_table_share mutex. */
 | |
|     lf_hash_search_unpin(thd->tdc_hash_pins);
 | |
|     DBUG_ASSERT(element);
 | |
| 
 | |
|     if (!(share= alloc_table_share(tl->db.str, tl->table_name.str, key, key_length)))
 | |
|     {
 | |
|       lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length);
 | |
|       DBUG_RETURN(0);
 | |
|     }
 | |
| 
 | |
|     /* note that tdc_acquire_share() *always* uses discovery */
 | |
|     open_table_def(thd, share, flags | GTS_USE_DISCOVERY);
 | |
| 
 | |
|     if (checked_unlikely(share->error))
 | |
|     {
 | |
|       free_table_share(share);
 | |
|       lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length);
 | |
|       DBUG_RETURN(0);
 | |
|     }
 | |
| 
 | |
|     mysql_mutex_lock(&element->LOCK_table_share);
 | |
|     element->share= share;
 | |
|     share->tdc= element;
 | |
|     element->ref_count++;
 | |
|     element->flushed= false;
 | |
|     mysql_mutex_unlock(&element->LOCK_table_share);
 | |
| 
 | |
|     tdc_purge(false);
 | |
|     if (out_table)
 | |
|     {
 | |
|       status_var_increment(thd->status_var.table_open_cache_misses);
 | |
|       *out_table= 0;
 | |
|     }
 | |
|     share->m_psi= PSI_CALL_get_table_share(false, share);
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   /* cannot force discovery of a cached share */
 | |
|   DBUG_ASSERT(!(flags & GTS_FORCE_DISCOVERY));
 | |
| 
 | |
|   if (out_table && (flags & GTS_TABLE))
 | |
|   {
 | |
|     if ((*out_table= tc_acquire_table(thd, element)))
 | |
|     {
 | |
|       lf_hash_search_unpin(thd->tdc_hash_pins);
 | |
|       DBUG_ASSERT(!(flags & GTS_NOLOCK));
 | |
|       DBUG_ASSERT(element->share);
 | |
|       DBUG_ASSERT(!element->share->error);
 | |
|       DBUG_ASSERT(!element->share->is_view);
 | |
|       status_var_increment(thd->status_var.table_open_cache_hits);
 | |
|       DBUG_RETURN(element->share);
 | |
|     }
 | |
|     status_var_increment(thd->status_var.table_open_cache_misses);
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_lock(&element->LOCK_table_share);
 | |
|   if (!(share= element->share))
 | |
|   {
 | |
|     mysql_mutex_unlock(&element->LOCK_table_share);
 | |
|     lf_hash_search_unpin(thd->tdc_hash_pins);
 | |
|     goto retry;
 | |
|   }
 | |
|   lf_hash_search_unpin(thd->tdc_hash_pins);
 | |
| 
 | |
|   /*
 | |
|      We found an existing table definition. Return it if we didn't get
 | |
|      an error when reading the table definition from file.
 | |
|   */
 | |
|   if (unlikely(share->error))
 | |
|   {
 | |
|     open_table_error(share, share->error, share->open_errno);
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   if (share->is_view && !(flags & GTS_VIEW))
 | |
|   {
 | |
|     open_table_error(share, OPEN_FRM_NOT_A_TABLE, ENOENT);
 | |
|     goto err;
 | |
|   }
 | |
|   if (!share->is_view && !(flags & GTS_TABLE))
 | |
|   {
 | |
|     open_table_error(share, OPEN_FRM_NOT_A_VIEW, ENOENT);
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   was_unused= !element->ref_count;
 | |
|   element->ref_count++;
 | |
|   mysql_mutex_unlock(&element->LOCK_table_share);
 | |
|   if (was_unused)
 | |
|   {
 | |
|     mysql_mutex_lock(&LOCK_unused_shares);
 | |
|     if (element->prev)
 | |
|     {
 | |
|       /*
 | |
|         Share was not used before and it was in the old_unused_share list
 | |
|         Unlink share from this list
 | |
|       */
 | |
|       DBUG_PRINT("info", ("Unlinking from not used list"));
 | |
|       unused_shares.remove(element);
 | |
|       element->next= 0;
 | |
|       element->prev= 0;
 | |
|     }
 | |
|     mysql_mutex_unlock(&LOCK_unused_shares);
 | |
|   }
 | |
| 
 | |
| end:
 | |
|   DBUG_PRINT("exit", ("share: %p  ref_count: %u",
 | |
|                       share, share->tdc->ref_count));
 | |
|   if (flags & GTS_NOLOCK)
 | |
|   {
 | |
|     tdc_release_share(share);
 | |
|     /*
 | |
|       if GTS_NOLOCK is requested, the returned share pointer cannot be used,
 | |
|       the share it points to may go away any moment.
 | |
|       But perhaps the caller is only interested to know whether a share or
 | |
|       table existed?
 | |
|       Let's return an invalid pointer here to catch dereferencing attempts.
 | |
|     */
 | |
|     share= UNUSABLE_TABLE_SHARE;
 | |
|   }
 | |
|   DBUG_RETURN(share);
 | |
| 
 | |
| err:
 | |
|   mysql_mutex_unlock(&element->LOCK_table_share);
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Release table share acquired by tdc_acquire_share().
 | |
| */
 | |
| 
 | |
| void tdc_release_share(TABLE_SHARE *share)
 | |
| {
 | |
|   DBUG_ENTER("tdc_release_share");
 | |
| 
 | |
|   mysql_mutex_lock(&share->tdc->LOCK_table_share);
 | |
|   DBUG_PRINT("enter",
 | |
|              ("share: %p  table: %s.%s  ref_count: %u",
 | |
|               share, share->db.str, share->table_name.str,
 | |
|               share->tdc->ref_count));
 | |
|   DBUG_ASSERT(share->tdc->ref_count);
 | |
| 
 | |
|   if (share->tdc->ref_count > 1)
 | |
|   {
 | |
|     share->tdc->ref_count--;
 | |
|     if (!share->is_view)
 | |
|       mysql_cond_broadcast(&share->tdc->COND_release);
 | |
|     mysql_mutex_unlock(&share->tdc->LOCK_table_share);
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
|   mysql_mutex_unlock(&share->tdc->LOCK_table_share);
 | |
| 
 | |
|   mysql_mutex_lock(&LOCK_unused_shares);
 | |
|   mysql_mutex_lock(&share->tdc->LOCK_table_share);
 | |
|   if (--share->tdc->ref_count)
 | |
|   {
 | |
|     if (!share->is_view)
 | |
|       mysql_cond_broadcast(&share->tdc->COND_release);
 | |
|     mysql_mutex_unlock(&share->tdc->LOCK_table_share);
 | |
|     mysql_mutex_unlock(&LOCK_unused_shares);
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
|   if (share->tdc->flushed || tdc_records() > tdc_size)
 | |
|   {
 | |
|     mysql_mutex_unlock(&LOCK_unused_shares);
 | |
|     tdc_delete_share_from_hash(share->tdc);
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
|   /* Link share last in used_table_share list */
 | |
|   DBUG_PRINT("info", ("moving share to unused list"));
 | |
|   DBUG_ASSERT(share->tdc->next == 0);
 | |
|   unused_shares.push_back(share->tdc);
 | |
|   mysql_mutex_unlock(&share->tdc->LOCK_table_share);
 | |
|   mysql_mutex_unlock(&LOCK_unused_shares);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| void tdc_remove_referenced_share(THD *thd, TABLE_SHARE *share)
 | |
| {
 | |
|   DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, share->db.str,
 | |
|                                              share->table_name.str,
 | |
|                                              MDL_EXCLUSIVE));
 | |
|   share->tdc->flush_unused(true);
 | |
|   mysql_mutex_lock(&share->tdc->LOCK_table_share);
 | |
|   DEBUG_SYNC(thd, "before_wait_for_refs");
 | |
|   share->tdc->wait_for_refs(1);
 | |
|   DBUG_ASSERT(share->tdc->all_tables.is_empty());
 | |
|   share->tdc->ref_count--;
 | |
|   tdc_delete_share_from_hash(share->tdc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    Removes all TABLE instances and corresponding TABLE_SHARE
 | |
| 
 | |
|    @param  thd          Thread context
 | |
|    @param  db           Name of database
 | |
|    @param  table_name   Name of table
 | |
| 
 | |
|    @note It assumes that table instances are already not used by any
 | |
|    (other) thread (this should be achieved by using meta-data locks).
 | |
| */
 | |
| 
 | |
| void tdc_remove_table(THD *thd, const char *db, const char *table_name)
 | |
| {
 | |
|   TDC_element *element;
 | |
|   DBUG_ENTER("tdc_remove_table");
 | |
|   DBUG_PRINT("enter", ("name: %s", table_name));
 | |
| 
 | |
|   DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
 | |
|                                              MDL_EXCLUSIVE));
 | |
| 
 | |
|   mysql_mutex_lock(&LOCK_unused_shares);
 | |
|   if (!(element= tdc_lock_share(thd, db, table_name)))
 | |
|   {
 | |
|     mysql_mutex_unlock(&LOCK_unused_shares);
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
| 
 | |
|   DBUG_ASSERT(element != MY_ERRPTR); // What can we do about it?
 | |
| 
 | |
|   if (!element->ref_count)
 | |
|   {
 | |
|     if (element->prev)
 | |
|     {
 | |
|       unused_shares.remove(element);
 | |
|       element->prev= 0;
 | |
|       element->next= 0;
 | |
|     }
 | |
|     mysql_mutex_unlock(&LOCK_unused_shares);
 | |
| 
 | |
|     tdc_delete_share_from_hash(element);
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
|   mysql_mutex_unlock(&LOCK_unused_shares);
 | |
| 
 | |
|   element->ref_count++;
 | |
|   mysql_mutex_unlock(&element->LOCK_table_share);
 | |
| 
 | |
|   /* We have to relock the mutex to avoid code duplication. Sigh. */
 | |
|   tdc_remove_referenced_share(thd, element->share);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check if table's share is being removed from the table definition
 | |
|   cache and, if yes, wait until the flush is complete.
 | |
| 
 | |
|   @param thd             Thread context.
 | |
|   @param table_list      Table which share should be checked.
 | |
|   @param timeout         Timeout for waiting.
 | |
|   @param deadlock_weight Weight of this wait for deadlock detector.
 | |
| 
 | |
|   @retval 0       Success. Share is up to date or has been flushed.
 | |
|   @retval 1       Error (OOM, was killed, the wait resulted
 | |
|                   in a deadlock or timeout). Reported.
 | |
| */
 | |
| 
 | |
| int tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name,
 | |
|                              ulong wait_timeout, uint deadlock_weight)
 | |
| {
 | |
|   TDC_element *element;
 | |
| 
 | |
|   if (!(element= tdc_lock_share(thd, db, table_name)))
 | |
|     return FALSE;
 | |
|   else if (element == MY_ERRPTR)
 | |
|     return TRUE;
 | |
|   else if (element->flushed)
 | |
|   {
 | |
|     struct timespec abstime;
 | |
|     set_timespec(abstime, wait_timeout);
 | |
|     return element->share->wait_for_old_version(thd, &abstime, deadlock_weight);
 | |
|   }
 | |
|   tdc_unlock_share(element);
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Iterate table definition cache.
 | |
| 
 | |
|   Object is protected against removal from table definition cache.
 | |
| 
 | |
|   @note Returned TABLE_SHARE is not guaranteed to be fully initialized:
 | |
|   tdc_acquire_share() added new share, but didn't open it yet. If caller
 | |
|   needs fully initializer share, it must lock table share mutex.
 | |
| */
 | |
| 
 | |
| struct eliminate_duplicates_arg
 | |
| {
 | |
|   HASH hash;
 | |
|   MEM_ROOT root;
 | |
|   my_hash_walk_action action;
 | |
|   void *argument;
 | |
| };
 | |
| 
 | |
| 
 | |
| static const uchar *eliminate_duplicates_get_key(const void *element,
 | |
|                                                  size_t *length, my_bool)
 | |
| {
 | |
|   auto key= static_cast<const LEX_STRING *>(element);
 | |
|   *length= key->length;
 | |
|   return reinterpret_cast<const uchar *>(key->str);
 | |
| }
 | |
| 
 | |
| 
 | |
| static my_bool eliminate_duplicates(void *el, void *a)
 | |
| {
 | |
|   TDC_element *element= static_cast<TDC_element*>(el);
 | |
|   eliminate_duplicates_arg *arg= static_cast<eliminate_duplicates_arg*>(a);
 | |
|   LEX_STRING *key= (LEX_STRING *) alloc_root(&arg->root, sizeof(LEX_STRING));
 | |
| 
 | |
|   if (!key || !(key->str= (char*) memdup_root(&arg->root, element->m_key,
 | |
|                                               element->m_key_length)))
 | |
|     return TRUE;
 | |
| 
 | |
|   key->length= element->m_key_length;
 | |
| 
 | |
|   if (my_hash_insert(&arg->hash, (uchar *) key))
 | |
|     return FALSE;
 | |
| 
 | |
|   return arg->action(element, arg->argument);
 | |
| }
 | |
| 
 | |
| 
 | |
| int tdc_iterate(THD *thd, my_hash_walk_action action, void *argument,
 | |
|                 bool no_dups)
 | |
| {
 | |
|   eliminate_duplicates_arg no_dups_argument;
 | |
|   LF_PINS *pins;
 | |
|   myf alloc_flags= 0;
 | |
|   uint hash_flags= HASH_UNIQUE;
 | |
|   int res;
 | |
| 
 | |
|   if (thd)
 | |
|   {
 | |
|     fix_thd_pins(thd);
 | |
|     pins= thd->tdc_hash_pins;
 | |
|     alloc_flags= MY_THREAD_SPECIFIC;
 | |
|     hash_flags|= HASH_THREAD_SPECIFIC;
 | |
|   }
 | |
|   else
 | |
|     pins= lf_hash_get_pins(&tdc_hash);
 | |
| 
 | |
|   if (!pins)
 | |
|     return ER_OUTOFMEMORY;
 | |
| 
 | |
|   if (no_dups)
 | |
|   {
 | |
|     init_alloc_root(PSI_INSTRUMENT_ME, &no_dups_argument.root, 4096, 4096, MYF(alloc_flags));
 | |
|     my_hash_init(PSI_INSTRUMENT_ME, &no_dups_argument.hash, &my_charset_bin,
 | |
|                  tdc_records(), 0, 0, eliminate_duplicates_get_key, 0,
 | |
|                  hash_flags);
 | |
|     no_dups_argument.action= action;
 | |
|     no_dups_argument.argument= argument;
 | |
|     action= eliminate_duplicates;
 | |
|     argument= &no_dups_argument;
 | |
|   }
 | |
| 
 | |
|   res= lf_hash_iterate(&tdc_hash, pins, action, argument);
 | |
| 
 | |
|   if (!thd)
 | |
|     lf_hash_put_pins(pins);
 | |
| 
 | |
|   if (no_dups)
 | |
|   {
 | |
|     my_hash_free(&no_dups_argument.hash);
 | |
|     free_root(&no_dups_argument.root, MYF(0));
 | |
|   }
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| int show_tc_active_instances(THD *thd, SHOW_VAR *var, void *buff,
 | |
|                              system_status_var *, enum enum_var_type scope)
 | |
| {
 | |
|   var->type= SHOW_UINT;
 | |
|   var->value= buff;
 | |
|   *(reinterpret_cast<uint32_t*>(buff))=
 | |
|     tc_active_instances.load(std::memory_order_relaxed);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Waits until ref_count goes down to given number
 | |
| 
 | |
|   @param  my_refs  Number of references owned by the caller
 | |
| 
 | |
|   Caller must own at least one TABLE_SHARE reference.
 | |
| 
 | |
|   Even though current thread holds exclusive metadata lock on this share,
 | |
|   concurrent FLUSH TABLES threads may be in process of closing unused table
 | |
|   instances belonging to this share. E.g.:
 | |
|   thr1 (FLUSH TABLES): table= share->tdc.free_tables.pop_front();
 | |
|   thr1 (FLUSH TABLES): share->tdc.all_tables.remove(table);
 | |
|   thr2 (ALTER TABLE): tdc_remove_table();
 | |
|   thr1 (FLUSH TABLES): intern_close_table(table);
 | |
| 
 | |
|   Current remove type assumes that all table instances (except for those
 | |
|   that are owned by current thread) must be closed before
 | |
|   thd_remove_table() returns. Wait for such tables now.
 | |
| 
 | |
|   intern_close_table() decrements ref_count and signals COND_release. When
 | |
|   ref_count drops down to number of references owned by current thread
 | |
|   waiting is completed.
 | |
| 
 | |
|   Unfortunately TABLE_SHARE::wait_for_old_version() cannot be used here
 | |
|   because it waits for all table instances, whereas we have to wait only
 | |
|   for those that are not owned by current thread.
 | |
| */
 | |
| 
 | |
| void TDC_element::wait_for_refs(uint my_refs)
 | |
| {
 | |
|   while (ref_count > my_refs)
 | |
|     mysql_cond_wait(&COND_release, &LOCK_table_share);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Flushes unused TABLE instances
 | |
| 
 | |
|   @param  thd          Thread context
 | |
|   @param  mark_flushed Whether to destroy TABLE_SHARE when released
 | |
| 
 | |
|   Caller is allowed to own used TABLE instances.
 | |
|   There must be no TABLE objects used by other threads and caller must own
 | |
|   exclusive metadata lock on the table.
 | |
| */
 | |
| 
 | |
| void TDC_element::flush(THD *thd, bool mark_flushed)
 | |
| {
 | |
|   DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, share->db.str,
 | |
|                                              share->table_name.str,
 | |
|                                              MDL_EXCLUSIVE));
 | |
| 
 | |
|   flush_unused(mark_flushed);
 | |
| 
 | |
|   mysql_mutex_lock(&LOCK_table_share);
 | |
|   All_share_tables_list::Iterator it(all_tables);
 | |
|   uint my_refs= 0;
 | |
|   while (auto table= it++)
 | |
|   {
 | |
|     if (table->in_use == thd)
 | |
|       my_refs++;
 | |
|   }
 | |
|   wait_for_refs(my_refs);
 | |
| #ifndef DBUG_OFF
 | |
|   it.rewind();
 | |
|   while (auto table= it++)
 | |
|     DBUG_ASSERT(table->in_use == thd);
 | |
| #endif
 | |
|   mysql_mutex_unlock(&LOCK_table_share);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Flushes unused TABLE instances
 | |
| */
 | |
| 
 | |
| void TDC_element::flush_unused(bool mark_flushed)
 | |
| {
 | |
|   Share_free_tables::List purge_tables;
 | |
| 
 | |
|   mysql_mutex_lock(&LOCK_table_share);
 | |
|   if (mark_flushed)
 | |
|     flushed= true;
 | |
|   tc_remove_all_unused_tables(this, &purge_tables);
 | |
|   mysql_mutex_unlock(&LOCK_table_share);
 | |
| 
 | |
|   while (auto table= purge_tables.pop_front())
 | |
|     intern_close_table(table);
 | |
| }
 | 
