2013-08-14 10:48:50 +02:00
|
|
|
/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
|
|
|
|
Copyright (c) 2010, 2011 Monty Program Ab
|
|
|
|
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-1301 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:
|
|
|
|
- intern_close_table(): frees TABLE object
|
|
|
|
- kill_delayed_threads_for_table()
|
|
|
|
- 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
|
2014-02-13 07:44:10 +01:00
|
|
|
- TABLE_SHARE::free_tables shall not receive new objects if
|
|
|
|
TABLE_SHARE::tdc.flushed is true
|
2013-08-14 10:48:50 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "my_global.h"
|
2014-12-28 16:42:17 +01:00
|
|
|
#include "lf.h"
|
2013-08-14 10:48:50 +02:00
|
|
|
#include "table.h"
|
|
|
|
#include "sql_base.h"
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
/** Configuration. */
|
|
|
|
ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */
|
|
|
|
ulong tc_size; /**< Table cache threshold for LRU eviction. */
|
|
|
|
|
|
|
|
/** Data collections. */
|
2014-12-28 16:42:17 +01:00
|
|
|
static LF_HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */
|
2013-08-14 10:48:50 +02:00
|
|
|
/** Collection of unused TABLE_SHARE objects. */
|
2014-12-28 16:42:17 +01:00
|
|
|
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;
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2013-12-10 16:00:36 +01:00
|
|
|
static int64 tdc_version; /* Increments on each reload */
|
2013-08-14 10:48:50 +02:00
|
|
|
static bool tdc_inited;
|
|
|
|
|
2014-02-13 07:19:37 +01:00
|
|
|
static int32 tc_count; /**< Number of TABLE objects in table cache. */
|
2013-08-14 10:48:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Protects unused shares list.
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
TDC_element::prev
|
|
|
|
TDC_element::next
|
|
|
|
unused_shares
|
2013-08-14 10:48:50 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
static mysql_mutex_t LOCK_unused_shares;
|
|
|
|
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
2016-06-01 15:12:38 +02:00
|
|
|
static PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share;
|
2013-08-14 10:48:50 +02:00
|
|
|
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 }
|
|
|
|
};
|
|
|
|
|
2016-06-01 15:12:38 +02:00
|
|
|
static PSI_cond_key key_TABLE_SHARE_COND_release;
|
2014-02-13 08:13:55 +01:00
|
|
|
static PSI_cond_info all_tc_conds[]=
|
|
|
|
{
|
|
|
|
{ &key_TABLE_SHARE_COND_release, "TABLE_SHARE::tdc.COND_release", 0 }
|
2013-08-14 10:48:50 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void init_tc_psi_keys(void)
|
|
|
|
{
|
|
|
|
const char *category= "sql";
|
|
|
|
int count;
|
|
|
|
|
|
|
|
count= array_elements(all_tc_mutexes);
|
|
|
|
mysql_mutex_register(category, all_tc_mutexes, count);
|
|
|
|
|
2014-02-13 08:13:55 +01:00
|
|
|
count= array_elements(all_tc_conds);
|
|
|
|
mysql_cond_register(category, all_tc_conds, count);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
static int fix_thd_pins(THD *thd)
|
|
|
|
{
|
|
|
|
return thd->tdc_hash_pins ? 0 :
|
|
|
|
(thd->tdc_hash_pins= lf_hash_get_pins(&tdc_hash)) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
/*
|
2014-02-13 07:19:37 +01:00
|
|
|
Auxiliary routines for manipulating with per-share all/unused lists
|
|
|
|
and tc_count counter.
|
2013-08-14 10:48:50 +02:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Get number of TABLE objects (used and unused) in table cache.
|
|
|
|
*/
|
|
|
|
|
|
|
|
uint tc_records(void)
|
|
|
|
{
|
2015-01-12 17:03:45 +01:00
|
|
|
return my_atomic_load32_explicit(&tc_count, MY_MEMORY_ORDER_RELAXED);
|
2014-02-13 07:19:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-06-01 15:12:38 +02:00
|
|
|
/**
|
|
|
|
Wait for MDL deadlock detector to complete traversing tdc.all_tables.
|
|
|
|
|
|
|
|
Must be called before updating TABLE_SHARE::tdc.all_tables.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void tc_wait_for_mdl_deadlock_detector(TDC_element *element)
|
|
|
|
{
|
|
|
|
while (element->all_tables_refs)
|
|
|
|
mysql_cond_wait(&element->COND_release, &element->LOCK_table_share);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-13 07:19:37 +01:00
|
|
|
/**
|
|
|
|
Remove TABLE object from table cache.
|
|
|
|
|
|
|
|
- decrement tc_count
|
|
|
|
- remove object from TABLE_SHARE::tdc.all_tables
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void tc_remove_table(TABLE *table)
|
|
|
|
{
|
2016-06-01 15:54:23 +02:00
|
|
|
mysql_mutex_assert_owner(&table->s->tdc->LOCK_table_share);
|
|
|
|
tc_wait_for_mdl_deadlock_detector(table->s->tdc);
|
2014-12-02 11:02:48 +01:00
|
|
|
my_atomic_add32_explicit(&tc_count, -1, MY_MEMORY_ORDER_RELAXED);
|
2014-12-28 16:42:17 +01:00
|
|
|
table->s->tdc->all_tables.remove(table);
|
2014-03-20 08:11:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-12 14:29:17 +02:00
|
|
|
static void tc_remove_all_unused_tables(TDC_element *element,
|
2016-06-01 15:12:38 +02:00
|
|
|
TDC_element::TABLE_list *purge_tables,
|
2016-05-12 14:29:17 +02:00
|
|
|
bool mark_flushed)
|
|
|
|
{
|
|
|
|
TABLE *table;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Mark share flushed in order to ensure that it gets
|
|
|
|
automatically deleted once it is no longer referenced.
|
|
|
|
|
|
|
|
Note that code in TABLE_SHARE::wait_for_old_version() assumes that
|
|
|
|
marking share flushed is followed by purge of unused table
|
|
|
|
shares.
|
|
|
|
*/
|
|
|
|
if (mark_flushed)
|
|
|
|
element->flushed= true;
|
|
|
|
while ((table= element->free_tables.pop_front()))
|
|
|
|
{
|
|
|
|
tc_remove_table(table);
|
|
|
|
purge_tables->push_front(table);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
/**
|
|
|
|
Free all unused TABLE objects.
|
|
|
|
|
|
|
|
While locked:
|
2013-12-10 16:00:36 +01:00
|
|
|
- remove unused objects from TABLE_SHARE::tdc.free_tables and
|
|
|
|
TABLE_SHARE::tdc.all_tables
|
2013-08-14 10:48:50 +02:00
|
|
|
- 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.
|
|
|
|
*/
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
struct tc_purge_arg
|
|
|
|
{
|
|
|
|
TDC_element::TABLE_list purge_tables;
|
|
|
|
bool mark_flushed;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static my_bool tc_purge_callback(TDC_element *element, tc_purge_arg *arg)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_lock(&element->LOCK_table_share);
|
2016-05-12 14:29:17 +02:00
|
|
|
tc_remove_all_unused_tables(element, &arg->purge_tables, arg->mark_flushed);
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2013-12-12 18:49:14 +01:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
|
|
|
|
void tc_purge(bool mark_flushed)
|
|
|
|
{
|
|
|
|
tc_purge_arg argument;
|
|
|
|
TABLE *table;
|
|
|
|
|
|
|
|
argument.mark_flushed= mark_flushed;
|
|
|
|
tdc_iterate(0, (my_hash_walk_action) tc_purge_callback, &argument);
|
|
|
|
while ((table= argument.purge_tables.pop_front()))
|
2013-12-12 18:49:14 +01:00
|
|
|
intern_close_table(table);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-06-01 15:12:38 +02:00
|
|
|
/**
|
|
|
|
Get last element of free_tables.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static TABLE *tc_free_tables_back(TDC_element *element)
|
|
|
|
{
|
|
|
|
TDC_element::TABLE_list::Iterator it(element->free_tables);
|
|
|
|
TABLE *entry, *last= 0;
|
|
|
|
while ((entry= it++))
|
|
|
|
last= entry;
|
|
|
|
return last;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
/**
|
|
|
|
Add new TABLE object to table cache.
|
|
|
|
|
|
|
|
@pre TABLE object is used by caller.
|
|
|
|
|
|
|
|
Added object cannot be evicted or acquired.
|
|
|
|
|
|
|
|
While locked:
|
2013-12-10 16:00:36 +01:00
|
|
|
- add object to TABLE_SHARE::tdc.all_tables
|
2013-08-14 10:48:50 +02:00
|
|
|
- increment tc_count
|
|
|
|
- evict LRU object from table cache if we reached threshold
|
|
|
|
|
2013-12-10 16:00:36 +01:00
|
|
|
While unlocked:
|
2013-08-14 10:48:50 +02:00
|
|
|
- free evicted object
|
|
|
|
*/
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
struct tc_add_table_arg
|
|
|
|
{
|
|
|
|
char key[MAX_DBKEY_LENGTH];
|
|
|
|
uint key_length;
|
|
|
|
ulonglong purge_time;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-06-01 15:12:38 +02:00
|
|
|
static my_bool tc_add_table_callback(TDC_element *element, tc_add_table_arg *arg)
|
2014-12-28 16:42:17 +01:00
|
|
|
{
|
|
|
|
TABLE *table;
|
|
|
|
|
|
|
|
mysql_mutex_lock(&element->LOCK_table_share);
|
2016-06-01 15:12:38 +02:00
|
|
|
if ((table= tc_free_tables_back(element)) && table->tc_time < arg->purge_time)
|
2014-12-28 16:42:17 +01:00
|
|
|
{
|
|
|
|
memcpy(arg->key, element->m_key, element->m_key_length);
|
|
|
|
arg->key_length= element->m_key_length;
|
|
|
|
arg->purge_time= table->tc_time;
|
|
|
|
}
|
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
void tc_add_table(THD *thd, TABLE *table)
|
|
|
|
{
|
2014-02-13 07:19:37 +01:00
|
|
|
bool need_purge;
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_ASSERT(table->in_use == thd);
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_lock(&table->s->tdc->LOCK_table_share);
|
2016-06-01 15:12:38 +02:00
|
|
|
tc_wait_for_mdl_deadlock_detector(table->s->tdc);
|
2014-12-28 16:42:17 +01:00
|
|
|
table->s->tdc->all_tables.push_front(table);
|
|
|
|
mysql_mutex_unlock(&table->s->tdc->LOCK_table_share);
|
2014-02-13 07:19:37 +01:00
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
/* If we have too many TABLE instances around, try to get rid of them */
|
2014-12-02 11:02:48 +01:00
|
|
|
need_purge= my_atomic_add32_explicit(&tc_count, 1, MY_MEMORY_ORDER_RELAXED) >=
|
|
|
|
(int32) tc_size;
|
2014-02-13 07:19:37 +01:00
|
|
|
|
|
|
|
if (need_purge)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
tc_add_table_arg argument;
|
|
|
|
argument.purge_time= ULONGLONG_MAX;
|
|
|
|
tdc_iterate(thd, (my_hash_walk_action) tc_add_table_callback, &argument);
|
2014-02-13 07:19:37 +01:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
if (argument.purge_time != ULONGLONG_MAX)
|
2014-02-13 07:19:37 +01:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
TDC_element *element= (TDC_element*) lf_hash_search(&tdc_hash,
|
|
|
|
thd->tdc_hash_pins,
|
|
|
|
argument.key,
|
|
|
|
argument.key_length);
|
|
|
|
if (element)
|
2014-03-20 08:11:13 +01:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
TABLE *entry;
|
|
|
|
mysql_mutex_lock(&element->LOCK_table_share);
|
|
|
|
lf_hash_search_unpin(thd->tdc_hash_pins);
|
|
|
|
|
|
|
|
/*
|
|
|
|
It may happen that oldest table was acquired meanwhile. In this case
|
|
|
|
just go ahead, number of objects in table cache will normalize
|
|
|
|
eventually.
|
|
|
|
*/
|
2016-06-01 15:12:38 +02:00
|
|
|
if ((entry= tc_free_tables_back(element)) &&
|
2014-12-28 16:42:17 +01:00
|
|
|
entry->tc_time == argument.purge_time)
|
|
|
|
{
|
|
|
|
element->free_tables.remove(entry);
|
|
|
|
tc_remove_table(entry);
|
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
|
|
|
intern_close_table(entry);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
2014-03-20 08:11:13 +01:00
|
|
|
}
|
2014-02-13 07:19:37 +01:00
|
|
|
}
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-06-01 15:12:38 +02:00
|
|
|
/**
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static TABLE *tc_acquire_table(THD *thd, TDC_element *element)
|
|
|
|
{
|
|
|
|
TABLE *table;
|
|
|
|
|
|
|
|
mysql_mutex_lock(&element->LOCK_table_share);
|
|
|
|
table= element->free_tables.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));
|
|
|
|
}
|
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
|
|
|
return table;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
/**
|
|
|
|
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
|
|
|
|
|
2013-12-10 16:00:36 +01:00
|
|
|
While unlocked:
|
2014-02-13 07:44:10 +01:00
|
|
|
- mark object not in use by any thread
|
2013-08-14 10:48:50 +02:00
|
|
|
- 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(),
|
2014-03-20 08:11:13 +01:00
|
|
|
which is synchronized with us on TABLE_SHARE::tdc.LOCK_table_share.
|
2013-08-14 10:48:50 +02:00
|
|
|
|
|
|
|
@return
|
|
|
|
@retval true object purged
|
|
|
|
@retval false object released
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool tc_release_table(TABLE *table)
|
|
|
|
{
|
|
|
|
DBUG_ASSERT(table->in_use);
|
|
|
|
DBUG_ASSERT(table->file);
|
|
|
|
|
2014-02-13 07:19:37 +01:00
|
|
|
if (table->needs_reopen() || tc_records() > tc_size)
|
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_lock(&table->s->tdc->LOCK_table_share);
|
2014-02-13 07:19:37 +01:00
|
|
|
goto purge;
|
|
|
|
}
|
|
|
|
|
2013-12-12 18:49:14 +01:00
|
|
|
table->tc_time= my_interval_timer();
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_lock(&table->s->tdc->LOCK_table_share);
|
|
|
|
if (table->s->tdc->flushed)
|
2014-02-13 07:19:37 +01:00
|
|
|
goto purge;
|
2014-02-13 07:44:10 +01:00
|
|
|
/*
|
2014-03-20 08:11:13 +01:00
|
|
|
in_use doesn't really need mutex protection, but must be reset after
|
2014-02-13 07:44:10 +01:00
|
|
|
checking tdc.flushed and before this table appears in free_tables.
|
|
|
|
Resetting in_use is needed only for print_cached_tables() and
|
|
|
|
list_open_tables().
|
|
|
|
*/
|
|
|
|
table->in_use= 0;
|
2013-08-14 10:48:50 +02:00
|
|
|
/* Add table to the list of unused TABLE objects for this share. */
|
2014-12-28 16:42:17 +01:00
|
|
|
table->s->tdc->free_tables.push_front(table);
|
|
|
|
mysql_mutex_unlock(&table->s->tdc->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
return false;
|
2014-02-13 07:19:37 +01:00
|
|
|
|
|
|
|
purge:
|
|
|
|
tc_remove_table(table);
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_unlock(&table->s->tdc->LOCK_table_share);
|
2014-02-13 07:44:10 +01:00
|
|
|
table->in_use= 0;
|
2014-02-13 07:19:37 +01:00
|
|
|
intern_close_table(table);
|
|
|
|
return true;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-06-01 15:12:38 +02:00
|
|
|
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());
|
|
|
|
DBUG_ASSERT(element->free_tables.is_empty());
|
|
|
|
DBUG_ASSERT(element->all_tables_refs == 0);
|
|
|
|
DBUG_ASSERT(element->next == 0);
|
|
|
|
DBUG_ASSERT(element->prev == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
/**
|
|
|
|
Delete share from hash and free share object.
|
|
|
|
*/
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
static void tdc_delete_share_from_hash(TDC_element *element)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
THD *thd= current_thd;
|
|
|
|
LF_PINS *pins;
|
|
|
|
TABLE_SHARE *share;
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_ENTER("tdc_delete_share_from_hash");
|
2014-12-28 16:42:17 +01:00
|
|
|
|
|
|
|
mysql_mutex_assert_owner(&element->LOCK_table_share);
|
|
|
|
share= element->share;
|
|
|
|
DBUG_ASSERT(share);
|
|
|
|
element->share= 0;
|
2013-08-14 10:48:50 +02:00
|
|
|
PSI_CALL_release_table_share(share->m_psi);
|
|
|
|
share->m_psi= 0;
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
if (!element->m_flush_tickets.is_empty())
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
Wait_for_flush_list::Iterator it(element->m_flush_tickets);
|
2013-08-14 10:48:50 +02:00
|
|
|
Wait_for_flush *ticket;
|
|
|
|
while ((ticket= it++))
|
|
|
|
(void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED);
|
2014-12-28 16:42:17 +01:00
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
mysql_cond_wait(&element->COND_release, &element->LOCK_table_share);
|
|
|
|
} while (!element->m_flush_tickets.is_empty());
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
|
|
|
|
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?
|
2016-06-01 15:12:38 +02:00
|
|
|
tdc_assert_clean_share(element);
|
2014-12-28 16:42:17 +01:00
|
|
|
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;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-06-01 15:12:38 +02:00
|
|
|
/**
|
|
|
|
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();
|
|
|
|
element->free_tables.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 *hash __attribute__((unused)),
|
|
|
|
TDC_element *element, LEX_STRING *key)
|
|
|
|
{
|
|
|
|
memcpy(element->m_key, key->str, key->length);
|
|
|
|
element->m_key_length= key->length;
|
|
|
|
tdc_assert_clean_share(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static uchar *tdc_hash_key(const TDC_element *element, size_t *length,
|
|
|
|
my_bool not_used __attribute__((unused)))
|
|
|
|
{
|
|
|
|
*length= element->m_key_length;
|
|
|
|
return (uchar*) element->m_key;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-08-14 10:48:50 +02:00
|
|
|
/**
|
|
|
|
Initialize table definition cache.
|
|
|
|
*/
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
void tdc_init(void)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
|
|
|
DBUG_ENTER("tdc_init");
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
|
|
init_tc_psi_keys();
|
|
|
|
#endif
|
|
|
|
tdc_inited= true;
|
|
|
|
mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares,
|
|
|
|
MY_MUTEX_INIT_FAST);
|
|
|
|
tdc_version= 1L; /* Increments on each reload */
|
2014-12-28 16:42:17 +01:00
|
|
|
lf_hash_init(&tdc_hash, sizeof(TDC_element), LF_HASH_UNIQUE, 0, 0,
|
2016-06-01 15:12:38 +02:00
|
|
|
(my_hash_get_key) tdc_hash_key,
|
2014-12-28 16:42:17 +01:00
|
|
|
&my_charset_bin);
|
2016-06-01 15:12:38 +02:00
|
|
|
tdc_hash.alloc.constructor= lf_alloc_constructor;
|
|
|
|
tdc_hash.alloc.destructor= lf_alloc_destructor;
|
|
|
|
tdc_hash.initializer= (lf_hash_initializer) tdc_hash_initializer;
|
2014-12-28 16:42:17 +01:00
|
|
|
DBUG_VOID_RETURN;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
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("table_def_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;
|
2013-12-12 18:49:14 +01:00
|
|
|
tc_size= 0;
|
2013-08-14 10:48:50 +02:00
|
|
|
/* Free all cached but unused TABLEs and TABLE_SHAREs. */
|
|
|
|
close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
|
|
|
|
}
|
|
|
|
DBUG_VOID_RETURN;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Deinitialize table definition cache.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void tdc_deinit(void)
|
|
|
|
{
|
|
|
|
DBUG_ENTER("tdc_deinit");
|
|
|
|
if (tdc_inited)
|
|
|
|
{
|
|
|
|
tdc_inited= false;
|
2014-12-28 16:42:17 +01:00
|
|
|
lf_hash_destroy(&tdc_hash);
|
2013-08-14 10:48:50 +02:00
|
|
|
mysql_mutex_destroy(&LOCK_unused_shares);
|
|
|
|
}
|
|
|
|
DBUG_VOID_RETURN;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Get number of cached table definitions.
|
|
|
|
|
|
|
|
@return Number of cached table definitions
|
|
|
|
*/
|
|
|
|
|
|
|
|
ulong tdc_records(void)
|
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
return my_atomic_load32_explicit(&tdc_hash.count, MY_MEMORY_ORDER_RELAXED);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void tdc_purge(bool all)
|
|
|
|
{
|
|
|
|
DBUG_ENTER("tdc_purge");
|
2013-12-10 16:00:36 +01:00
|
|
|
while (all || tdc_records() > tdc_size)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
TDC_element *element;
|
2013-08-14 10:48:50 +02:00
|
|
|
|
|
|
|
mysql_mutex_lock(&LOCK_unused_shares);
|
2014-12-28 16:42:17 +01:00
|
|
|
if (!(element= unused_shares.pop_front()))
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Concurrent thread may start using share again, reset prev and next. */
|
2014-12-28 16:42:17 +01:00
|
|
|
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;
|
|
|
|
}
|
2013-08-14 10:48:50 +02:00
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
tdc_delete_share_from_hash(element);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
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().
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
@retval 0 Share not found
|
|
|
|
@retval MY_ERRPTR OOM
|
|
|
|
@retval ptr Pointer to locked table share
|
2013-08-14 10:48:50 +02:00
|
|
|
*/
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
TDC_element *tdc_lock_share(THD *thd, const char *db, const char *table_name)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
TDC_element *element;
|
2013-08-14 10:48:50 +02:00
|
|
|
char key[MAX_DBKEY_LENGTH];
|
|
|
|
|
|
|
|
DBUG_ENTER("tdc_lock_share");
|
2014-12-28 16:42:17 +01:00
|
|
|
if (fix_thd_pins(thd))
|
|
|
|
DBUG_RETURN((TDC_element*) MY_ERRPTR);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
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 (!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);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Unlock share locked by tdc_lock_share().
|
|
|
|
*/
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
void tdc_unlock_share(TDC_element *element)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
|
|
|
DBUG_ENTER("tdc_unlock_share");
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_VOID_RETURN;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Get TABLE_SHARE for a table.
|
|
|
|
|
|
|
|
tdc_acquire_share()
|
|
|
|
thd Thread handle
|
|
|
|
table_list Table that should be opened
|
|
|
|
key Table cache key
|
|
|
|
key_length Length of key
|
|
|
|
flags operation: what to open table or view
|
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2015-12-29 11:58:17 +01:00
|
|
|
TABLE_SHARE *tdc_acquire_share(THD *thd, TABLE_LIST *tl, uint flags,
|
2013-08-14 10:48:50 +02:00
|
|
|
TABLE **out_table)
|
|
|
|
{
|
|
|
|
TABLE_SHARE *share;
|
2014-12-28 16:42:17 +01:00
|
|
|
TDC_element *element;
|
2015-12-29 11:58:17 +01:00
|
|
|
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();
|
2013-08-14 10:48:50 +02:00
|
|
|
bool was_unused;
|
|
|
|
DBUG_ENTER("tdc_acquire_share");
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
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)))
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2015-03-04 08:01:32 +01:00
|
|
|
LEX_STRING tmp= { const_cast<char*>(key), key_length };
|
2014-12-28 16:42:17 +01:00
|
|
|
int res= lf_hash_insert(&tdc_hash, thd->tdc_hash_pins, (uchar*) &tmp);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
if (res == -1)
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_RETURN(0);
|
2014-12-28 16:42:17 +01:00
|
|
|
else if (res == 1)
|
|
|
|
continue;
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash,
|
|
|
|
thd->tdc_hash_pins, hash_value, (uchar*) key, key_length);
|
|
|
|
lf_hash_search_unpin(thd->tdc_hash_pins);
|
|
|
|
DBUG_ASSERT(element);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2015-12-29 11:58:17 +01:00
|
|
|
if (!(share= alloc_table_share(tl->db, tl->table_name, key, key_length)))
|
2014-12-28 16:42:17 +01:00
|
|
|
{
|
|
|
|
lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length);
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
/* note that tdc_acquire_share() *always* uses discovery */
|
|
|
|
open_table_def(thd, share, flags | GTS_USE_DISCOVERY);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
if (share->error)
|
|
|
|
{
|
|
|
|
free_table_share(share);
|
|
|
|
lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length);
|
|
|
|
DBUG_RETURN(0);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
|
|
|
|
mysql_mutex_lock(&element->LOCK_table_share);
|
|
|
|
element->share= share;
|
|
|
|
share->tdc= element;
|
|
|
|
element->ref_count++;
|
|
|
|
element->version= tdc_refresh_version();
|
|
|
|
element->flushed= false;
|
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
|
|
|
|
|
|
|
tdc_purge(false);
|
|
|
|
if (out_table)
|
|
|
|
*out_table= 0;
|
|
|
|
share->m_psi= PSI_CALL_get_table_share(false, share);
|
|
|
|
goto end;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* cannot force discovery of a cached share */
|
|
|
|
DBUG_ASSERT(!(flags & GTS_FORCE_DISCOVERY));
|
|
|
|
|
|
|
|
if (out_table && (flags & GTS_TABLE))
|
|
|
|
{
|
2016-06-01 15:12:38 +02:00
|
|
|
if ((*out_table= tc_acquire_table(thd, element)))
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
lf_hash_search_unpin(thd->tdc_hash_pins);
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_ASSERT(!(flags & GTS_NOLOCK));
|
2014-12-28 16:42:17 +01:00
|
|
|
DBUG_ASSERT(element->share);
|
|
|
|
DBUG_ASSERT(!element->share->error);
|
|
|
|
DBUG_ASSERT(!element->share->is_view);
|
|
|
|
DBUG_RETURN(element->share);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
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);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
We found an existing table definition. Return it if we didn't get
|
|
|
|
an error when reading the table definition from file.
|
|
|
|
*/
|
|
|
|
if (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;
|
|
|
|
}
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
was_unused= !element->ref_count;
|
|
|
|
element->ref_count++;
|
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
if (was_unused)
|
|
|
|
{
|
|
|
|
mysql_mutex_lock(&LOCK_unused_shares);
|
2014-12-28 16:42:17 +01:00
|
|
|
if (element->prev)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
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"));
|
2014-12-28 16:42:17 +01:00
|
|
|
unused_shares.remove(element);
|
|
|
|
element->next= 0;
|
|
|
|
element->prev= 0;
|
2013-12-10 16:00:36 +01:00
|
|
|
}
|
2013-08-14 10:48:50 +02:00
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u",
|
2014-12-28 16:42:17 +01:00
|
|
|
(ulong) share, share->tdc->ref_count));
|
2013-08-14 10:48:50 +02:00
|
|
|
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= (TABLE_SHARE*) 1;
|
|
|
|
}
|
|
|
|
DBUG_RETURN(share);
|
|
|
|
|
|
|
|
err:
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Release table share acquired by tdc_acquire_share().
|
|
|
|
*/
|
|
|
|
|
|
|
|
void tdc_release_share(TABLE_SHARE *share)
|
|
|
|
{
|
|
|
|
DBUG_ENTER("tdc_release_share");
|
2013-12-10 16:00:36 +01:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_lock(&share->tdc->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_PRINT("enter",
|
|
|
|
("share: 0x%lx table: %s.%s ref_count: %u version: %lu",
|
|
|
|
(ulong) share, share->db.str, share->table_name.str,
|
2014-12-28 16:42:17 +01:00
|
|
|
share->tdc->ref_count, share->tdc->version));
|
|
|
|
DBUG_ASSERT(share->tdc->ref_count);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
if (share->tdc->ref_count > 1)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
share->tdc->ref_count--;
|
2014-03-20 08:11:13 +01:00
|
|
|
if (!share->is_view)
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_cond_broadcast(&share->tdc->COND_release);
|
|
|
|
mysql_mutex_unlock(&share->tdc->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_VOID_RETURN;
|
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_unlock(&share->tdc->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
|
|
|
mysql_mutex_lock(&LOCK_unused_shares);
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_lock(&share->tdc->LOCK_table_share);
|
|
|
|
if (--share->tdc->ref_count)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_unlock(&share->tdc->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
|
|
|
DBUG_VOID_RETURN;
|
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
if (share->tdc->flushed || tdc_records() > tdc_size)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
2014-12-28 16:42:17 +01:00
|
|
|
tdc_delete_share_from_hash(share->tdc);
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_VOID_RETURN;
|
|
|
|
}
|
|
|
|
/* Link share last in used_table_share list */
|
|
|
|
DBUG_PRINT("info", ("moving share to unused list"));
|
2014-12-28 16:42:17 +01:00
|
|
|
DBUG_ASSERT(share->tdc->next == 0);
|
|
|
|
unused_shares.push_back(share->tdc);
|
|
|
|
mysql_mutex_unlock(&share->tdc->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
|
|
|
DBUG_VOID_RETURN;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Remove all or some (depending on parameter) instances of TABLE and
|
|
|
|
TABLE_SHARE from the table definition cache.
|
|
|
|
|
|
|
|
@param thd Thread context
|
|
|
|
@param remove_type Type of removal:
|
|
|
|
TDC_RT_REMOVE_ALL - remove all TABLE instances and
|
|
|
|
TABLE_SHARE instance. There
|
|
|
|
should be no used TABLE objects
|
|
|
|
and caller should have exclusive
|
|
|
|
metadata lock on the table.
|
|
|
|
TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances
|
|
|
|
except those that belong to
|
|
|
|
this thread. There should be
|
|
|
|
no TABLE objects used by other
|
|
|
|
threads and caller should have
|
|
|
|
exclusive metadata lock on the
|
|
|
|
table.
|
|
|
|
TDC_RT_REMOVE_UNUSED - remove all unused TABLE
|
|
|
|
instances (if there are no
|
|
|
|
used instances will also
|
|
|
|
remove TABLE_SHARE).
|
|
|
|
TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE -
|
|
|
|
remove all TABLE instances
|
|
|
|
except those that belong to
|
|
|
|
this thread, but don't mark
|
|
|
|
TABLE_SHARE as old. There
|
|
|
|
should be no TABLE objects
|
|
|
|
used by other threads and
|
|
|
|
caller should have exclusive
|
|
|
|
metadata lock on the table.
|
|
|
|
@param db Name of database
|
|
|
|
@param table_name Name of table
|
|
|
|
@param kill_delayed_threads If TRUE, kill INSERT DELAYED threads
|
|
|
|
|
|
|
|
@note It assumes that table instances are already not used by any
|
|
|
|
(other) thread (this should be achieved by using meta-data locks).
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
|
|
|
|
const char *db, const char *table_name,
|
|
|
|
bool kill_delayed_threads)
|
|
|
|
{
|
2016-03-22 15:01:40 +01:00
|
|
|
TDC_element::TABLE_list purge_tables;
|
2013-08-14 10:48:50 +02:00
|
|
|
TABLE *table;
|
2014-12-28 16:42:17 +01:00
|
|
|
TDC_element *element;
|
|
|
|
uint my_refs= 1;
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_ENTER("tdc_remove_table");
|
|
|
|
DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type));
|
|
|
|
|
|
|
|
DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED ||
|
|
|
|
thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
|
|
|
|
MDL_EXCLUSIVE));
|
|
|
|
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_lock(&LOCK_unused_shares);
|
|
|
|
if (!(element= tdc_lock_share(thd, db, table_name)))
|
|
|
|
{
|
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
|
|
|
DBUG_ASSERT(remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE);
|
|
|
|
DBUG_RETURN(false);
|
|
|
|
}
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
DBUG_ASSERT(element != MY_ERRPTR); // What can we do about it?
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
if (!element->ref_count)
|
|
|
|
{
|
|
|
|
if (element->prev)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
unused_shares.remove(element);
|
|
|
|
element->prev= 0;
|
|
|
|
element->next= 0;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
2014-02-13 07:44:10 +01:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
tdc_delete_share_from_hash(element);
|
|
|
|
DBUG_RETURN(true);
|
|
|
|
}
|
|
|
|
mysql_mutex_unlock(&LOCK_unused_shares);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
element->ref_count++;
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2016-05-12 14:29:17 +02:00
|
|
|
tc_remove_all_unused_tables(element, &purge_tables,
|
|
|
|
remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE);
|
2014-12-28 16:42:17 +01:00
|
|
|
|
|
|
|
if (kill_delayed_threads)
|
|
|
|
kill_delayed_threads_for_table(element);
|
|
|
|
|
|
|
|
if (remove_type == TDC_RT_REMOVE_NOT_OWN ||
|
|
|
|
remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE)
|
|
|
|
{
|
|
|
|
TDC_element::All_share_tables_list::Iterator it(element->all_tables);
|
|
|
|
while ((table= it++))
|
2014-02-13 08:13:55 +01:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
my_refs++;
|
|
|
|
DBUG_ASSERT(table->in_use == thd);
|
2014-02-13 08:13:55 +01:00
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
}
|
|
|
|
DBUG_ASSERT(element->all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL);
|
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
while ((table= purge_tables.pop_front()))
|
|
|
|
intern_close_table(table);
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
if (remove_type != TDC_RT_REMOVE_UNUSED)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Even though current thread holds exclusive metadata lock on this share
|
|
|
|
(asserted above), 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.
|
|
|
|
*/
|
|
|
|
mysql_mutex_lock(&element->LOCK_table_share);
|
|
|
|
while (element->ref_count > my_refs)
|
|
|
|
mysql_cond_wait(&element->COND_release, &element->LOCK_table_share);
|
|
|
|
mysql_mutex_unlock(&element->LOCK_table_share);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
|
|
|
|
tdc_release_share(element->share);
|
|
|
|
|
|
|
|
DBUG_RETURN(true);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
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,
|
2014-02-13 07:44:10 +01:00
|
|
|
ulong wait_timeout, uint deadlock_weight,
|
|
|
|
ulong refresh_version)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
TDC_element *element;
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
if (!(element= tdc_lock_share(thd, db, table_name)))
|
|
|
|
return FALSE;
|
|
|
|
else if (element == MY_ERRPTR)
|
|
|
|
return TRUE;
|
|
|
|
else if (element->flushed && refresh_version > element->version)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
struct timespec abstime;
|
|
|
|
set_timespec(abstime, wait_timeout);
|
|
|
|
return element->share->wait_for_old_version(thd, &abstime, deadlock_weight);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
tdc_unlock_share(element);
|
|
|
|
return FALSE;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ulong tdc_refresh_version(void)
|
|
|
|
{
|
2015-01-12 17:03:45 +01:00
|
|
|
return my_atomic_load64_explicit(&tdc_version, MY_MEMORY_ORDER_RELAXED);
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-13 07:44:10 +01:00
|
|
|
ulong tdc_increment_refresh_version(void)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-02 11:02:48 +01:00
|
|
|
ulong v= my_atomic_add64_explicit(&tdc_version, 1, MY_MEMORY_ORDER_RELAXED);
|
2013-08-14 10:48:50 +02:00
|
|
|
DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", v));
|
2014-02-13 07:44:10 +01:00
|
|
|
return v + 1;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2014-12-28 16:42:17 +01:00
|
|
|
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.
|
2013-08-14 10:48:50 +02:00
|
|
|
*/
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
struct eliminate_duplicates_arg
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
HASH hash;
|
|
|
|
MEM_ROOT root;
|
|
|
|
my_hash_walk_action action;
|
|
|
|
void *argument;
|
|
|
|
};
|
2013-08-14 10:48:50 +02:00
|
|
|
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
static uchar *eliminate_duplicates_get_key(const uchar *element, size_t *length,
|
|
|
|
my_bool not_used __attribute__((unused)))
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
LEX_STRING *key= (LEX_STRING *) element;
|
|
|
|
*length= key->length;
|
|
|
|
return (uchar *) key->str;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
static my_bool eliminate_duplicates(TDC_element *element,
|
|
|
|
eliminate_duplicates_arg *arg)
|
|
|
|
{
|
|
|
|
LEX_STRING *key= (LEX_STRING *) alloc_root(&arg->root, sizeof(LEX_STRING));
|
2013-08-14 10:48:50 +02:00
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
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);
|
|
|
|
}
|
2013-08-14 10:48:50 +02:00
|
|
|
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
int tdc_iterate(THD *thd, my_hash_walk_action action, void *argument,
|
|
|
|
bool no_dups)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
eliminate_duplicates_arg no_dups_argument;
|
|
|
|
LF_PINS *pins;
|
|
|
|
myf alloc_flags= 0;
|
|
|
|
uint hash_flags= HASH_UNIQUE;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (thd)
|
2013-08-14 10:48:50 +02:00
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
fix_thd_pins(thd);
|
|
|
|
pins= thd->tdc_hash_pins;
|
|
|
|
alloc_flags= MY_THREAD_SPECIFIC;
|
|
|
|
hash_flags|= HASH_THREAD_SPECIFIC;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|
2014-12-28 16:42:17 +01:00
|
|
|
else
|
|
|
|
pins= lf_hash_get_pins(&tdc_hash);
|
|
|
|
|
|
|
|
if (!pins)
|
|
|
|
return ER_OUTOFMEMORY;
|
|
|
|
|
|
|
|
if (no_dups)
|
|
|
|
{
|
|
|
|
init_alloc_root(&no_dups_argument.root, 4096, 4096, MYF(alloc_flags));
|
|
|
|
my_hash_init(&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= (my_hash_walk_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;
|
2013-08-14 10:48:50 +02:00
|
|
|
}
|