diff options
author | Sergey Vojtovich <svoj@mariadb.org> | 2014-12-28 19:42:17 +0400 |
---|---|---|
committer | Sergey Vojtovich <svoj@mariadb.org> | 2014-12-28 19:46:18 +0400 |
commit | 6dbc48ca79e5fdd8d4022b00b862e08a4198155b (patch) | |
tree | 1904e477f09dd958af2b6696d4b7c8dbb0a1772d | |
parent | 8883c54ac08a555bc7d9b09395f49893ad4d80b5 (diff) | |
download | mariadb-git-6dbc48ca79e5fdd8d4022b00b862e08a4198155b.tar.gz |
MDEV-7324 - Lock-free hash for table definition cache
-rw-r--r-- | include/lf.h | 3 | ||||
-rw-r--r-- | mysql-test/r/ps.result | 16 | ||||
-rw-r--r-- | mysql-test/r/show_check.result | 2 | ||||
-rw-r--r-- | mysql-test/suite/perfschema/r/dml_setup_instruments.result | 2 | ||||
-rw-r--r-- | mysql-test/suite/perfschema/r/func_mutex.result | 14 | ||||
-rw-r--r-- | mysql-test/suite/perfschema/t/func_mutex.test | 12 | ||||
-rw-r--r-- | mysql-test/t/ps.test | 15 | ||||
-rw-r--r-- | mysql-test/t/show_check.test | 3 | ||||
-rw-r--r-- | mysys/lf_hash.c | 16 | ||||
-rw-r--r-- | sql/handler.cc | 8 | ||||
-rw-r--r-- | sql/mysqld.cc | 3 | ||||
-rw-r--r-- | sql/sql_base.cc | 285 | ||||
-rw-r--r-- | sql/sql_base.h | 2 | ||||
-rw-r--r-- | sql/sql_class.cc | 5 | ||||
-rw-r--r-- | sql/sql_class.h | 2 | ||||
-rw-r--r-- | sql/sql_handler.cc | 2 | ||||
-rw-r--r-- | sql/sql_insert.cc | 2 | ||||
-rw-r--r-- | sql/sql_test.cc | 44 | ||||
-rw-r--r-- | sql/table.cc | 52 | ||||
-rw-r--r-- | sql/table.h | 28 | ||||
-rw-r--r-- | sql/table_cache.cc | 944 | ||||
-rw-r--r-- | sql/table_cache.h | 180 |
22 files changed, 889 insertions, 751 deletions
diff --git a/include/lf.h b/include/lf.h index 5ca777dd680..b776c152ee5 100644 --- a/include/lf.h +++ b/include/lf.h @@ -231,6 +231,9 @@ void lf_hash_init(LF_HASH *hash, uint element_size, uint flags, void lf_hash_destroy(LF_HASH *hash); int lf_hash_insert(LF_HASH *hash, LF_PINS *pins, const void *data); void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen); +void *lf_hash_search_using_hash_value(LF_HASH *hash, LF_PINS *pins, + my_hash_value_type hash_value, + const void *key, uint keylen); int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen); int lf_hash_iterate(LF_HASH *hash, LF_PINS *pins, my_hash_walk_action action, void *argument); diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index e30fa9a1966..95a9a299bb5 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -2119,9 +2119,9 @@ select Host, Db from mysql.host limit 0; Host Db show open tables from mysql; Database Table In_use Name_locked -mysql user 0 0 mysql general_log 0 0 mysql host 0 0 +mysql user 0 0 call proc_1(); show open tables from mysql; Database Table In_use Name_locked @@ -2132,9 +2132,9 @@ select Host, Db from mysql.host limit 0; Host Db show open tables from mysql; Database Table In_use Name_locked -mysql user 0 0 mysql general_log 0 0 mysql host 0 0 +mysql user 0 0 call proc_1(); show open tables from mysql; Database Table In_use Name_locked @@ -2145,9 +2145,9 @@ select Host, Db from mysql.host limit 0; Host Db show open tables from mysql; Database Table In_use Name_locked -mysql user 0 0 mysql general_log 0 0 mysql host 0 0 +mysql user 0 0 call proc_1(); show open tables from mysql; Database Table In_use Name_locked @@ -2158,9 +2158,9 @@ select Host, Db from mysql.host limit 0; Host Db show open tables from mysql; Database Table In_use Name_locked -mysql user 0 0 mysql general_log 0 0 mysql host 0 0 +mysql user 0 0 flush tables; create function func_1() returns int begin flush tables; return 1; end| ERROR 0A000: FLUSH is not allowed in stored function or trigger @@ -2176,9 +2176,9 @@ select Host, Db from mysql.host limit 0; Host Db show open tables from mysql; Database Table In_use Name_locked -mysql user 0 0 mysql general_log 0 0 mysql host 0 0 +mysql user 0 0 prepare abc from "flush tables"; execute abc; show open tables from mysql; @@ -2190,9 +2190,9 @@ select Host, Db from mysql.host limit 0; Host Db show open tables from mysql; Database Table In_use Name_locked -mysql user 0 0 mysql general_log 0 0 mysql host 0 0 +mysql user 0 0 execute abc; show open tables from mysql; Database Table In_use Name_locked @@ -2203,9 +2203,9 @@ select Host, Db from mysql.host limit 0; Host Db show open tables from mysql; Database Table In_use Name_locked -mysql user 0 0 mysql general_log 0 0 mysql host 0 0 +mysql user 0 0 execute abc; show open tables from mysql; Database Table In_use Name_locked @@ -2216,9 +2216,9 @@ select Host, Db from mysql.host limit 0; Host Db show open tables from mysql; Database Table In_use Name_locked -mysql user 0 0 mysql general_log 0 0 mysql host 0 0 +mysql user 0 0 flush tables; deallocate prepare abc; create procedure proc_1() flush logs; diff --git a/mysql-test/r/show_check.result b/mysql-test/r/show_check.result index 371b2de1d80..a829cbd2aa3 100644 --- a/mysql-test/r/show_check.result +++ b/mysql-test/r/show_check.result @@ -259,8 +259,8 @@ create table t1(n int); insert into t1 values (1); show open tables; Database Table In_use Name_locked -test t1 0 0 mysql general_log 0 0 +test t1 0 0 drop table t1; create table t1 (a int not null, b VARCHAR(10), INDEX (b) ) AVG_ROW_LENGTH=10 CHECKSUM=1 COMMENT="test" ENGINE=MYISAM MIN_ROWS=10 MAX_ROWS=100 PACK_KEYS=1 DELAY_KEY_WRITE=1 ROW_FORMAT=fixed; show create table t1; diff --git a/mysql-test/suite/perfschema/r/dml_setup_instruments.result b/mysql-test/suite/perfschema/r/dml_setup_instruments.result index 08edb1c3b01..ce1cdf1884e 100644 --- a/mysql-test/suite/perfschema/r/dml_setup_instruments.result +++ b/mysql-test/suite/perfschema/r/dml_setup_instruments.result @@ -24,11 +24,11 @@ wait/synch/rwlock/sql/LOCK_grant YES YES wait/synch/rwlock/sql/LOCK_system_variables_hash YES YES wait/synch/rwlock/sql/LOCK_sys_init_connect YES YES wait/synch/rwlock/sql/LOCK_sys_init_slave YES YES -wait/synch/rwlock/sql/LOCK_tdc YES YES wait/synch/rwlock/sql/LOGGER::LOCK_logger YES YES wait/synch/rwlock/sql/MDL_context::LOCK_waiting_for YES YES wait/synch/rwlock/sql/MDL_lock::rwlock YES YES wait/synch/rwlock/sql/Query_cache_query::lock YES YES +wait/synch/rwlock/sql/THR_LOCK_servers YES YES select * from performance_schema.setup_instruments where name like 'Wait/Synch/Cond/sql/%' and name not in ( diff --git a/mysql-test/suite/perfschema/r/func_mutex.result b/mysql-test/suite/perfschema/r/func_mutex.result index 86967a2a63f..1bd70b16811 100644 --- a/mysql-test/suite/perfschema/r/func_mutex.result +++ b/mysql-test/suite/perfschema/r/func_mutex.result @@ -5,9 +5,9 @@ WHERE name LIKE 'wait/synch/mutex/%' truncate table performance_schema.events_statements_summary_by_digest; flush status; select NAME from performance_schema.mutex_instances -where NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share' GROUP BY NAME; +where NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex' GROUP BY NAME; NAME -wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share +wait/synch/mutex/mysys/THR_LOCK::mutex select NAME from performance_schema.rwlock_instances where NAME = 'wait/synch/rwlock/sql/LOCK_grant'; NAME @@ -24,7 +24,7 @@ id b 1 initial value SET @before_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long -WHERE (EVENT_NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share')); +WHERE (EVENT_NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex')); SELECT * FROM t1; id b 1 initial value @@ -37,12 +37,12 @@ id b 8 initial value SET @after_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long -WHERE (EVENT_NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share')); +WHERE (EVENT_NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex')); SELECT IF((@after_count - @before_count) > 0, 'Success', 'Failure') test_fm1_timed; test_fm1_timed Success UPDATE performance_schema.setup_instruments SET enabled = 'NO' -WHERE NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share'; +WHERE NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex'; TRUNCATE TABLE performance_schema.events_waits_history_long; TRUNCATE TABLE performance_schema.events_waits_history; TRUNCATE TABLE performance_schema.events_waits_current; @@ -51,7 +51,7 @@ id b 1 initial value SET @before_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long -WHERE (EVENT_NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share')); +WHERE (EVENT_NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex')); SELECT * FROM t1; id b 1 initial value @@ -64,7 +64,7 @@ id b 8 initial value SET @after_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long -WHERE (EVENT_NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share')); +WHERE (EVENT_NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex')); SELECT IF((COALESCE(@after_count, 0) - COALESCE(@before_count, 0)) = 0, 'Success', 'Failure') test_fm2_timed; test_fm2_timed Success diff --git a/mysql-test/suite/perfschema/t/func_mutex.test b/mysql-test/suite/perfschema/t/func_mutex.test index ca1feb68091..66bcb68accb 100644 --- a/mysql-test/suite/perfschema/t/func_mutex.test +++ b/mysql-test/suite/perfschema/t/func_mutex.test @@ -19,7 +19,7 @@ flush status; # Make sure objects are instrumented select NAME from performance_schema.mutex_instances - where NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share' GROUP BY NAME; + where NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex' GROUP BY NAME; select NAME from performance_schema.rwlock_instances where NAME = 'wait/synch/rwlock/sql/LOCK_grant'; @@ -49,18 +49,18 @@ SELECT * FROM t1 WHERE id = 1; SET @before_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long - WHERE (EVENT_NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share')); + WHERE (EVENT_NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex')); SELECT * FROM t1; SET @after_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long - WHERE (EVENT_NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share')); + WHERE (EVENT_NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex')); SELECT IF((@after_count - @before_count) > 0, 'Success', 'Failure') test_fm1_timed; UPDATE performance_schema.setup_instruments SET enabled = 'NO' -WHERE NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share'; +WHERE NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex'; TRUNCATE TABLE performance_schema.events_waits_history_long; TRUNCATE TABLE performance_schema.events_waits_history; @@ -70,13 +70,13 @@ SELECT * FROM t1 WHERE id = 1; SET @before_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long - WHERE (EVENT_NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share')); + WHERE (EVENT_NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex')); SELECT * FROM t1; SET @after_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long - WHERE (EVENT_NAME = 'wait/synch/mutex/sql/TABLE_SHARE::tdc.LOCK_table_share')); + WHERE (EVENT_NAME = 'wait/synch/mutex/mysys/THR_LOCK::mutex')); SELECT IF((COALESCE(@after_count, 0) - COALESCE(@before_count, 0)) = 0, 'Success', 'Failure') test_fm2_timed; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 491594a3045..84c9874260a 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -2224,24 +2224,32 @@ deallocate prepare abc; create procedure proc_1() flush tables; flush tables; +--sorted_result show open tables from mysql; select Host, User from mysql.user limit 0; select Host, Db from mysql.host limit 0; +--sorted_result show open tables from mysql; call proc_1(); +--sorted_result show open tables from mysql; select Host, User from mysql.user limit 0; select Host, Db from mysql.host limit 0; +--sorted_result show open tables from mysql; call proc_1(); +--sorted_result show open tables from mysql; select Host, User from mysql.user limit 0; select Host, Db from mysql.host limit 0; +--sorted_result show open tables from mysql; call proc_1(); +--sorted_result show open tables from mysql; select Host, User from mysql.user limit 0; select Host, Db from mysql.host limit 0; +--sorted_result show open tables from mysql; flush tables; delimiter |; @@ -2261,24 +2269,31 @@ drop procedure proc_1; flush tables; select Host, User from mysql.user limit 0; select Host, Db from mysql.host limit 0; +--sorted_result show open tables from mysql; --enable_ps_protocol prepare abc from "flush tables"; execute abc; +--sorted_result show open tables from mysql; select Host, User from mysql.user limit 0; select Host, Db from mysql.host limit 0; +--sorted_result show open tables from mysql; execute abc; +--sorted_result show open tables from mysql; select Host, User from mysql.user limit 0; select Host, Db from mysql.host limit 0; +--sorted_result show open tables from mysql; execute abc; +--sorted_result show open tables from mysql; select Host, User from mysql.user limit 0; select Host, Db from mysql.host limit 0; +--sorted_result show open tables from mysql; flush tables; deallocate prepare abc; diff --git a/mysql-test/t/show_check.test b/mysql-test/t/show_check.test index 14d50709921..d2788ae3bad 100644 --- a/mysql-test/t/show_check.test +++ b/mysql-test/t/show_check.test @@ -135,9 +135,11 @@ show create table t1; drop table t1; flush tables; +--sorted_result show open tables; create table t1(n int); insert into t1 values (1); +--sorted_result show open tables; drop table t1; @@ -617,6 +619,7 @@ show databases; show tables; show events; show table status; +--sorted_result show open tables; show plugins; show columns in t1; diff --git a/mysys/lf_hash.c b/mysys/lf_hash.c index 782ebc00f24..53860bea49d 100644 --- a/mysys/lf_hash.c +++ b/mysys/lf_hash.c @@ -122,7 +122,7 @@ retry: { if (unlikely(callback)) { - if (callback(cursor->curr + 1, (void*)key)) + if (cur_hashnr & 1 && callback(cursor->curr + 1, (void*)key)) return 1; } else if (cur_hashnr >= hashnr) @@ -467,12 +467,13 @@ int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen) NOTE see lsearch() for pin usage notes */ -void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen) +void *lf_hash_search_using_hash_value(LF_HASH *hash, LF_PINS *pins, + my_hash_value_type hashnr, + const void *key, uint keylen) { LF_SLIST * volatile *el, *found; - uint bucket, hashnr= calc_hash(hash, (uchar *)key, keylen); + uint bucket= hashnr % hash->size; - bucket= hashnr % hash->size; lf_rwlock_by_pins(pins); el= _lf_dynarray_lvalue(&hash->array, bucket); if (unlikely(!el)) @@ -521,6 +522,13 @@ int lf_hash_iterate(LF_HASH *hash, LF_PINS *pins, return res; } +void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen) +{ + return lf_hash_search_using_hash_value(hash, pins, + calc_hash(hash, (uchar*) key, keylen), + key, keylen); +} + static const uchar *dummy_key= (uchar*)""; /* diff --git a/sql/handler.cc b/sql/handler.cc index 4bb8e0b4397..c512f2d0eb9 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -5001,12 +5001,12 @@ bool ha_table_exists(THD *thd, const char *db, const char *table_name, else if (engines_with_discover) hton= &dummy; - TABLE_SHARE *share= tdc_lock_share(db, table_name); - if (share) + TDC_element *element= tdc_lock_share(thd, db, table_name); + if (element && element != MY_ERRPTR) { if (hton) - *hton= share->db_type(); - tdc_unlock_share(share); + *hton= element->share->db_type(); + tdc_unlock_share(element); DBUG_RETURN(TRUE); } diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 9fd02e70767..9fcb382df70 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -4789,7 +4789,8 @@ static int init_server_components() all things are initialized so that unireg_abort() doesn't fail */ mdl_init(); - if (tdc_init() | hostname_cache_init()) + tdc_init(); + if (hostname_cache_init()) unireg_abort(1); query_cache_set_min_res_unit(query_cache_min_res_unit); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 88b35f79b93..73a027772fa 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -270,58 +270,75 @@ uint get_table_def_key(const TABLE_LIST *table_list, const char **key) # Pointer to list of names of open tables. */ -OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) +struct list_open_tables_arg { - OPEN_TABLE_LIST **start_list, *open_list; + THD *thd; + const char *db; + const char *wild; TABLE_LIST table_list; - TABLE_SHARE *share; - TDC_iterator tdc_it; - DBUG_ENTER("list_open_tables"); + OPEN_TABLE_LIST **start_list, *open_list; +}; - bzero((char*) &table_list,sizeof(table_list)); - start_list= &open_list; - open_list=0; - tdc_it.init(); - while ((share= tdc_it.next())) - { - if (db && my_strcasecmp(system_charset_info, db, share->db.str)) - continue; - if (wild && wild_compare(share->table_name.str, wild, 0)) - continue; +static my_bool list_open_tables_callback(TDC_element *element, + list_open_tables_arg *arg) +{ + char *db= (char*) element->m_key; + char *table_name= (char*) element->m_key + strlen((char*) element->m_key) + 1; - /* Check if user has SELECT privilege for any column in the table */ - table_list.db= share->db.str; - table_list.table_name= share->table_name.str; - table_list.grant.privilege=0; + if (arg->db && my_strcasecmp(system_charset_info, arg->db, db)) + return FALSE; + if (arg->wild && wild_compare(table_name, arg->wild, 0)) + return FALSE; - if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE)) - continue; + /* Check if user has SELECT privilege for any column in the table */ + arg->table_list.db= db; + arg->table_list.table_name= table_name; + arg->table_list.grant.privilege= 0; - if (!(*start_list = (OPEN_TABLE_LIST *) - sql_alloc(sizeof(**start_list)+share->table_cache_key.length))) - { - open_list=0; // Out of memory - break; - } - strmov((*start_list)->table= - strmov(((*start_list)->db= (char*) ((*start_list)+1)), - share->db.str)+1, - share->table_name.str); - (*start_list)->in_use= 0; - mysql_mutex_lock(&share->tdc.LOCK_table_share); - TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables); - TABLE *table; - while ((table= it++)) - if (table->in_use) - ++(*start_list)->in_use; - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - (*start_list)->locked= 0; /* Obsolete. */ - start_list= &(*start_list)->next; - *start_list=0; - } - tdc_it.deinit(); - DBUG_RETURN(open_list); + if (check_table_access(arg->thd, SELECT_ACL, &arg->table_list, TRUE, 1, TRUE)) + return FALSE; + + if (!(*arg->start_list= (OPEN_TABLE_LIST *) arg->thd->alloc( + sizeof(**arg->start_list) + element->m_key_length))) + return TRUE; + + strmov((*arg->start_list)->table= + strmov(((*arg->start_list)->db= (char*) ((*arg->start_list) + 1)), + db) + 1, table_name); + (*arg->start_list)->in_use= 0; + + mysql_mutex_lock(&element->LOCK_table_share); + TDC_element::All_share_tables_list::Iterator it(element->all_tables); + TABLE *table; + while ((table= it++)) + if (table->in_use) + ++(*arg->start_list)->in_use; + mysql_mutex_unlock(&element->LOCK_table_share); + (*arg->start_list)->locked= 0; /* Obsolete. */ + arg->start_list= &(*arg->start_list)->next; + *arg->start_list= 0; + return FALSE; +} + + +OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) +{ + list_open_tables_arg argument; + DBUG_ENTER("list_open_tables"); + + argument.thd= thd; + argument.db= db; + argument.wild= wild; + bzero((char*) &argument.table_list, sizeof(argument.table_list)); + argument.start_list= &argument.open_list; + argument.open_list= 0; + + if (tdc_iterate(thd, (my_hash_walk_action) list_open_tables_callback, + &argument, true)) + DBUG_RETURN(0); + + DBUG_RETURN(argument.open_list); } /***************************************************************************** @@ -371,12 +388,12 @@ void free_io_cache(TABLE *table) @pre Caller should have TABLE_SHARE::tdc.LOCK_table_share mutex. */ -void kill_delayed_threads_for_table(TABLE_SHARE *share) +void kill_delayed_threads_for_table(TDC_element *element) { - TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables); + TDC_element::All_share_tables_list::Iterator it(element->all_tables); TABLE *tab; - mysql_mutex_assert_owner(&share->tdc.LOCK_table_share); + mysql_mutex_assert_owner(&element->LOCK_table_share); if (!delayed_insert_threads) return; @@ -385,7 +402,7 @@ void kill_delayed_threads_for_table(TABLE_SHARE *share) { THD *in_use= tab->in_use; - DBUG_ASSERT(in_use && tab->s->tdc.flushed); + DBUG_ASSERT(in_use && tab->s->tdc->flushed); if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && ! in_use->killed) { @@ -422,6 +439,30 @@ void kill_delayed_threads_for_table(TABLE_SHARE *share) lock taken by thread trying to obtain global read lock. */ + +struct close_cached_tables_arg +{ + ulong refresh_version; + TDC_element *element; +}; + + +static my_bool close_cached_tables_callback(TDC_element *element, + close_cached_tables_arg *arg) +{ + mysql_mutex_lock(&element->LOCK_table_share); + if (element->share && element->flushed && + element->version < arg->refresh_version) + { + /* wait_for_old_version() will unlock mutex and free share */ + arg->element= element; + return TRUE; + } + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} + + bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool wait_for_refresh, ulong timeout) { @@ -517,38 +558,21 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, if (!tables) { - bool found= true; + int r= 0; + close_cached_tables_arg argument; + argument.refresh_version= refresh_version; set_timespec(abstime, timeout); - while (found && !thd->killed) - { - TABLE_SHARE *share; - TDC_iterator tdc_it; - found= false; - tdc_it.init(); - while ((share= tdc_it.next())) - { - mysql_mutex_lock(&share->tdc.LOCK_table_share); - if (share->tdc.flushed && share->tdc.version < refresh_version) - { - /* wait_for_old_version() will unlock mutex and free share */ - found= true; - break; - } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - } - tdc_it.deinit(); + while (!thd->killed && + (r= tdc_iterate(thd, + (my_hash_walk_action) close_cached_tables_callback, + &argument)) == 1 && + !argument.element->share->wait_for_old_version(thd, &abstime, + MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL)) + /* no-op */; - if (found) - { - if (share->wait_for_old_version(thd, &abstime, - MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL)) - { - result= TRUE; - break; - } - } - } + if (r) + result= TRUE; } else { @@ -592,53 +616,72 @@ err_with_reopen: if specified string is NULL, then any table with a connection string. */ -bool close_cached_connection_tables(THD *thd, LEX_STRING *connection) +struct close_cached_connection_tables_arg { - TABLE_LIST tmp, *tables= NULL; - bool result= FALSE; - TABLE_SHARE *share; - TDC_iterator tdc_it; - DBUG_ENTER("close_cached_connections"); - DBUG_ASSERT(thd); + THD *thd; + LEX_STRING *connection; + TABLE_LIST *tables; +}; - bzero(&tmp, sizeof(TABLE_LIST)); - tdc_it.init(); - while ((share= tdc_it.next())) - { - mysql_mutex_lock(&share->tdc.LOCK_table_share); - /* Ignore if table is not open or does not have a connect_string */ - if (!share->connect_string.length || !share->tdc.ref_count) - { - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - continue; - } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); +static my_bool close_cached_connection_tables_callback( + TDC_element *element, close_cached_connection_tables_arg *arg) +{ + TABLE_LIST *tmp; - /* Compare the connection string */ - if (connection && - (connection->length > share->connect_string.length || - (connection->length < share->connect_string.length && - (share->connect_string.str[connection->length] != '/' && - share->connect_string.str[connection->length] != '\\')) || - strncasecmp(connection->str, share->connect_string.str, - connection->length))) - continue; + mysql_mutex_lock(&element->LOCK_table_share); + /* Ignore if table is not open or does not have a connect_string */ + if (!element->share || !element->share->connect_string.length || + !element->ref_count) + { + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; + } + + /* Compare the connection string */ + if (arg->connection && + (arg->connection->length > element->share->connect_string.length || + (arg->connection->length < element->share->connect_string.length && + (element->share->connect_string.str[arg->connection->length] != '/' && + element->share->connect_string.str[arg->connection->length] != '\\')) || + strncasecmp(arg->connection->str, element->share->connect_string.str, + arg->connection->length))) + return FALSE; + + /* close_cached_tables() only uses these elements */ + if (!(tmp= (TABLE_LIST*) alloc_root(arg->thd->mem_root, sizeof(TABLE_LIST))) || + !(tmp->db= strdup_root(arg->thd->mem_root, element->share->db.str)) || + !(tmp->table_name= strdup_root(arg->thd->mem_root, + element->share->table_name.str))) + return TRUE; - /* close_cached_tables() only uses these elements */ - tmp.db= share->db.str; - tmp.table_name= share->table_name.str; - tmp.next_local= tables; + tmp->next_local= arg->tables; + arg->tables= tmp; - tables= (TABLE_LIST *) memdup_root(thd->mem_root, (char*)&tmp, - sizeof(TABLE_LIST)); - } - tdc_it.deinit(); + mysql_mutex_unlock(&element->LOCK_table_share); + + return FALSE; +} - if (tables) - result= close_cached_tables(thd, tables, FALSE, LONG_TIMEOUT); - DBUG_RETURN(result); +bool close_cached_connection_tables(THD *thd, LEX_STRING *connection) +{ + close_cached_connection_tables_arg argument; + DBUG_ENTER("close_cached_connections"); + DBUG_ASSERT(thd); + + argument.thd= thd; + argument.connection= connection; + argument.tables= NULL; + + if (tdc_iterate(thd, + (my_hash_walk_action) close_cached_connection_tables_callback, + &argument)) + DBUG_RETURN(true); + + DBUG_RETURN(argument.tables ? + close_cached_tables(thd, argument.tables, FALSE, LONG_TIMEOUT) : + false); } @@ -1775,7 +1818,7 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, DBUG_ENTER("wait_while_table_is_used"); DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", table->s->table_name.str, (ulong) table->s, - table->db_stat, table->s->tdc.version)); + table->db_stat, table->s->tdc->version)); if (thd->mdl_context.upgrade_shared_lock( table->mdl_ticket, MDL_EXCLUSIVE, @@ -2388,10 +2431,10 @@ retry_share: if (!(flags & MYSQL_OPEN_IGNORE_FLUSH)) { - if (share->tdc.flushed) + if (share->tdc->flushed) { DBUG_PRINT("info", ("Found old share version: %lu current: %lu", - share->tdc.version, tdc_refresh_version())); + share->tdc->version, tdc_refresh_version())); /* We already have an MDL lock. But we have encountered an old version of table in the table definition cache which is possible @@ -2422,7 +2465,7 @@ retry_share: goto retry_share; } - if (thd->open_tables && thd->open_tables->s->tdc.flushed) + if (thd->open_tables && thd->open_tables->s->tdc->flushed) { /* If the version changes while we're opening the tables, diff --git a/sql/sql_base.h b/sql/sql_base.h index 0041972f101..211aaee72b2 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -271,7 +271,7 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags); int decide_logging_format(THD *thd, TABLE_LIST *tables); void free_io_cache(TABLE *entry); void intern_close_table(TABLE *entry); -void kill_delayed_threads_for_table(TABLE_SHARE *share); +void kill_delayed_threads_for_table(TDC_element *element); void close_thread_table(THD *thd, TABLE **table_ptr); bool close_temporary_tables(THD *thd); TABLE_LIST *unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 246dab715ab..4fddb2158bc 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -912,7 +912,8 @@ THD::THD(bool is_wsrep_applier) #endif /* defined(ENABLED_DEBUG_SYNC) */ wait_for_commit_ptr(0), main_da(0, false, false), - m_stmt_da(&main_da) + m_stmt_da(&main_da), + tdc_hash_pins(0) #ifdef WITH_WSREP , wsrep_applier(is_wsrep_applier), @@ -1701,6 +1702,8 @@ THD::~THD() free_root(&main_mem_root, MYF(0)); main_da.free_memory(); + if (tdc_hash_pins) + lf_hash_put_pins(tdc_hash_pins); if (status_var.memory_used != 0) { DBUG_PRINT("error", ("memory_used: %lld", status_var.memory_used)); diff --git a/sql/sql_class.h b/sql/sql_class.h index 65210c8432f..60ed0793bef 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3769,6 +3769,8 @@ public: (rgi_slave && rgi_have_temporary_tables())); } + LF_PINS *tdc_hash_pins; + inline ulong wsrep_binlog_format() const { return WSREP_FORMAT(variables.binlog_format); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 110bca96530..7dcc6fa0e95 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -1134,7 +1134,7 @@ void mysql_ha_flush(THD *thd) ((hash_tables->table->mdl_ticket && hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) || (!hash_tables->table->s->tmp_table && - hash_tables->table->s->tdc.flushed))) + hash_tables->table->s->tdc->flushed))) mysql_ha_close_table(hash_tables); } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 429b51969a8..050e28f98b4 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3048,7 +3048,7 @@ bool Delayed_insert::handle_inserts(void) THD_STAGE_INFO(&thd, stage_insert); max_rows= delayed_insert_limit; - if (thd.killed || table->s->tdc.flushed) + if (thd.killed || table->s->tdc->flushed) { thd.killed= KILL_SYSTEM_THREAD; max_rows= ULONG_MAX; // Do as much as possible diff --git a/sql/sql_test.cc b/sql/sql_test.cc index 8992ff24a1e..cad2bf4ba56 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -76,35 +76,37 @@ print_where(COND *cond,const char *info, enum_query_type query_type) /* This is for debugging purposes */ -static void print_cached_tables(void) +static my_bool print_cached_tables_callback(TDC_element *element, + void *arg __attribute__((unused))) { - TABLE_SHARE *share; TABLE *entry; - TDC_iterator tdc_it; + mysql_mutex_lock(&element->LOCK_table_share); + TDC_element::All_share_tables_list::Iterator it(element->all_tables); + while ((entry= it++)) + { + THD *in_use= entry->in_use; + printf("%-14.14s %-32s%6ld%8ld%6d %s\n", + entry->s->db.str, entry->s->table_name.str, element->version, + in_use ? in_use->thread_id : 0, + entry->db_stat ? 1 : 0, + in_use ? lock_descriptions[(int)entry->reginfo.lock_type] : + "Not in use"); + } + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} + + +static void print_cached_tables(void) +{ compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions)); /* purecov: begin tested */ puts("DB Table Version Thread Open Lock"); - tdc_it.init(); - while ((share= tdc_it.next())) - { - mysql_mutex_lock(&share->tdc.LOCK_table_share); - TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables); - while ((entry= it++)) - { - THD *in_use= entry->in_use; - printf("%-14.14s %-32s%6ld%8ld%6d %s\n", - entry->s->db.str, entry->s->table_name.str, entry->s->tdc.version, - in_use ? in_use->thread_id : 0, - entry->db_stat ? 1 : 0, - in_use ? lock_descriptions[(int)entry->reginfo.lock_type] : - "Not in use"); - } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - } - tdc_it.deinit(); + tdc_iterate(0, (my_hash_walk_action) print_cached_tables_callback, NULL, true); + printf("\nCurrent refresh version: %ld\n", tdc_refresh_version()); fflush(stdout); /* purecov: end */ diff --git a/sql/table.cc b/sql/table.cc index ebe058472ca..d0cffc8e78e 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -325,7 +325,7 @@ TABLE_SHARE *alloc_table_share(const char *db, const char *table_name, &share->LOCK_share, MY_MUTEX_INIT_SLOW); mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data, &share->LOCK_ha_data, MY_MUTEX_INIT_FAST); - tdc_init_share(share); + tdc_assign_new_table_id(share); } DBUG_RETURN(share); } @@ -422,7 +422,6 @@ void TABLE_SHARE::destroy() { mysql_mutex_destroy(&LOCK_share); mysql_mutex_destroy(&LOCK_ha_data); - tdc_deinit_share(this); } my_hash_free(&name_hash); @@ -3866,11 +3865,11 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, because we won't try to acquire tdc.LOCK_table_share while holding a write-lock on MDL_lock::m_rwlock. */ - mysql_mutex_lock(&tdc.LOCK_table_share); - tdc.all_tables_refs++; - mysql_mutex_unlock(&tdc.LOCK_table_share); + mysql_mutex_lock(&tdc->LOCK_table_share); + tdc->all_tables_refs++; + mysql_mutex_unlock(&tdc->LOCK_table_share); - All_share_tables_list::Iterator tables_it(tdc.all_tables); + TDC_element::All_share_tables_list::Iterator tables_it(tdc->all_tables); /* In case of multiple searches running in parallel, avoid going @@ -3888,7 +3887,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, while ((table= tables_it++)) { - DBUG_ASSERT(table->in_use && tdc.flushed); + DBUG_ASSERT(table->in_use && tdc->flushed); if (gvisitor->inspect_edge(&table->in_use->mdl_context)) { goto end_leave_node; @@ -3898,7 +3897,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, tables_it.rewind(); while ((table= tables_it++)) { - DBUG_ASSERT(table->in_use && tdc.flushed); + DBUG_ASSERT(table->in_use && tdc->flushed); if (table->in_use->mdl_context.visit_subgraph(gvisitor)) { goto end_leave_node; @@ -3911,10 +3910,10 @@ end_leave_node: gvisitor->leave_node(src_ctx); end: - mysql_mutex_lock(&tdc.LOCK_table_share); - if (!--tdc.all_tables_refs) - mysql_cond_broadcast(&tdc.COND_release); - mysql_mutex_unlock(&tdc.LOCK_table_share); + mysql_mutex_lock(&tdc->LOCK_table_share); + if (!--tdc->all_tables_refs) + mysql_cond_broadcast(&tdc->COND_release); + mysql_mutex_unlock(&tdc->LOCK_table_share); return result; } @@ -3949,14 +3948,14 @@ bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime, Wait_for_flush ticket(mdl_context, this, deadlock_weight); MDL_wait::enum_wait_status wait_status; - mysql_mutex_assert_owner(&tdc.LOCK_table_share); - DBUG_ASSERT(tdc.flushed); + mysql_mutex_assert_owner(&tdc->LOCK_table_share); + DBUG_ASSERT(tdc->flushed); - tdc.m_flush_tickets.push_front(&ticket); + tdc->m_flush_tickets.push_front(&ticket); mdl_context->m_wait.reset_status(); - mysql_mutex_unlock(&tdc.LOCK_table_share); + mysql_mutex_unlock(&tdc->LOCK_table_share); mdl_context->will_wait_for(&ticket); @@ -3967,21 +3966,10 @@ bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime, mdl_context->done_waiting_for(); - mysql_mutex_lock(&tdc.LOCK_table_share); - - tdc.m_flush_tickets.remove(&ticket); - - if (tdc.m_flush_tickets.is_empty() && tdc.ref_count == 0) - { - /* - If our thread was the last one using the share, - we must destroy it here. - */ - mysql_mutex_unlock(&tdc.LOCK_table_share); - destroy(); - } - else - mysql_mutex_unlock(&tdc.LOCK_table_share); + mysql_mutex_lock(&tdc->LOCK_table_share); + tdc->m_flush_tickets.remove(&ticket); + mysql_cond_broadcast(&tdc->COND_release); + mysql_mutex_unlock(&tdc->LOCK_table_share); /* @@ -4027,7 +4015,7 @@ bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime, void TABLE::init(THD *thd, TABLE_LIST *tl) { - DBUG_ASSERT(s->tdc.ref_count > 0 || s->tmp_table != NO_TMP_TABLE); + DBUG_ASSERT(s->tmp_table != NO_TMP_TABLE || s->tdc->ref_count > 0); if (thd->lex->need_correct_ident()) alias_name_used= my_strcasecmp(table_alias_charset, diff --git a/sql/table.h b/sql/table.h index 75c118f7374..3720d2e6b56 100644 --- a/sql/table.h +++ b/sql/table.h @@ -47,6 +47,7 @@ class ACL_internal_schema_access; class ACL_internal_table_access; class Field; class Table_statistics; +class TDC_element; /* Used to identify NESTED_JOIN structures within a join (applicable only to @@ -611,32 +612,7 @@ struct TABLE_SHARE mysql_mutex_t LOCK_ha_data; /* To protect access to ha_data */ mysql_mutex_t LOCK_share; /* To protect TABLE_SHARE */ - typedef I_P_List <TABLE, TABLE_share> TABLE_list; - typedef I_P_List <TABLE, All_share_tables> All_share_tables_list; - struct - { - /** - Protects ref_count, m_flush_tickets, all_tables, free_tables, flushed, - all_tables_refs. - */ - mysql_mutex_t LOCK_table_share; - mysql_cond_t COND_release; - TABLE_SHARE *next, **prev; /* Link to unused shares */ - uint ref_count; /* How many TABLE objects uses this */ - uint all_tables_refs; /* Number of refs to all_tables */ - /** - List of tickets representing threads waiting for the share to be flushed. - */ - Wait_for_flush_list m_flush_tickets; - /* - Doubly-linked (back-linked) lists of used and unused TABLE objects - for this share. - */ - All_share_tables_list all_tables; - TABLE_list free_tables; - ulong version; - bool flushed; - } tdc; + TDC_element *tdc; LEX_CUSTRING tabledef_version; diff --git a/sql/table_cache.cc b/sql/table_cache.cc index 9e2246a5846..f12c031f91a 100644 --- a/sql/table_cache.cc +++ b/sql/table_cache.cc @@ -49,18 +49,22 @@ */ #include "my_global.h" -#include "hash.h" +#include "lf.h" #include "table.h" #include "sql_base.h" + /** Configuration. */ ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */ ulong tc_size; /**< Table cache threshold for LRU eviction. */ /** Data collections. */ -static HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */ +static LF_HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */ /** Collection of unused TABLE_SHARE objects. */ -static TABLE_SHARE *oldest_unused_share, end_of_unused_share; +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 int64 tdc_version; /* Increments on each reload */ static int64 last_table_id; @@ -72,32 +76,23 @@ static int32 tc_count; /**< Number of TABLE objects in table cache. */ /** Protects unused shares list. - TABLE_SHARE::tdc.prev - TABLE_SHARE::tdc.next - oldest_unused_share - end_of_unused_share + TDC_element::prev + TDC_element::next + unused_shares */ static mysql_mutex_t LOCK_unused_shares; -static mysql_rwlock_t LOCK_tdc; /**< Protects tdc_hash. */ my_atomic_rwlock_t LOCK_tdc_atomics; /**< Protects tdc_version. */ #ifdef HAVE_PSI_INTERFACE -static PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share; +PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share; 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 } }; -static PSI_rwlock_key key_rwlock_LOCK_tdc; -static PSI_rwlock_info all_tc_rwlocks[]= -{ - { &key_rwlock_LOCK_tdc, "LOCK_tdc", PSI_FLAG_GLOBAL } -}; - - -static PSI_cond_key key_TABLE_SHARE_COND_release; +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 } @@ -112,15 +107,19 @@ static void init_tc_psi_keys(void) count= array_elements(all_tc_mutexes); mysql_mutex_register(category, all_tc_mutexes, count); - count= array_elements(all_tc_rwlocks); - mysql_rwlock_register(category, all_tc_rwlocks, count); - count= array_elements(all_tc_conds); mysql_cond_register(category, all_tc_conds, count); } #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. @@ -157,34 +156,7 @@ static void tc_remove_table(TABLE *table) my_atomic_rwlock_wrlock(&LOCK_tdc_atomics); my_atomic_add32_explicit(&tc_count, -1, MY_MEMORY_ORDER_RELAXED); my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics); - table->s->tdc.all_tables.remove(table); -} - - -/** - 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(TABLE_SHARE *share) -{ - while (share->tdc.all_tables_refs) - mysql_cond_wait(&share->tdc.COND_release, &share->tdc.LOCK_table_share); -} - - -/** - Get last element of tdc.free_tables. -*/ - -static TABLE *tc_free_tables_back(TABLE_SHARE *share) -{ - TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables); - TABLE *entry, *last= 0; - while ((entry= it++)) - last= entry; - return last; + table->s->tdc->all_tables.remove(table); } @@ -203,31 +175,39 @@ static TABLE *tc_free_tables_back(TABLE_SHARE *share) periodicly flush all not used tables. */ -void tc_purge(bool mark_flushed) +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) { - TABLE_SHARE *share; TABLE *table; - TDC_iterator tdc_it; - TABLE_SHARE::TABLE_list purge_tables; - tdc_it.init(); - while ((share= tdc_it.next())) + mysql_mutex_lock(&element->LOCK_table_share); + element->wait_for_mdl_deadlock_detector(); + if (arg->mark_flushed) + element->flushed= true; + while ((table= element->free_tables.pop_front())) { - mysql_mutex_lock(&share->tdc.LOCK_table_share); - tc_wait_for_mdl_deadlock_detector(share); - - if (mark_flushed) - share->tdc.flushed= true; - while ((table= share->tdc.free_tables.pop_front())) - { - tc_remove_table(table); - purge_tables.push_front(table); - } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + tc_remove_table(table); + arg->purge_tables.push_front(table); } - tdc_it.deinit(); + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} - while ((table= purge_tables.pop_front())) + +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())) intern_close_table(table); } @@ -248,14 +228,38 @@ void tc_purge(bool mark_flushed) - free evicted object */ +struct tc_add_table_arg +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + ulonglong purge_time; +}; + + +my_bool tc_add_table_callback(TDC_element *element, tc_add_table_arg *arg) +{ + TABLE *table; + + mysql_mutex_lock(&element->LOCK_table_share); + if ((table= element->free_tables_back()) && table->tc_time < arg->purge_time) + { + 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; +} + + void tc_add_table(THD *thd, TABLE *table) { bool need_purge; DBUG_ASSERT(table->in_use == thd); - mysql_mutex_lock(&table->s->tdc.LOCK_table_share); - tc_wait_for_mdl_deadlock_detector(table->s); - table->s->tdc.all_tables.push_front(table); - mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); + table->s->tdc->wait_for_mdl_deadlock_detector(); + table->s->tdc->all_tables.push_front(table); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); /* If we have too many TABLE instances around, try to get rid of them */ my_atomic_rwlock_wrlock(&LOCK_tdc_atomics); @@ -265,89 +269,45 @@ void tc_add_table(THD *thd, TABLE *table) if (need_purge) { - TABLE_SHARE *purge_share= 0; - TABLE_SHARE *share; - TABLE *entry; - ulonglong UNINIT_VAR(purge_time); - TDC_iterator tdc_it; - - tdc_it.init(); - while ((share= tdc_it.next())) - { - mysql_mutex_lock(&share->tdc.LOCK_table_share); - if ((entry= tc_free_tables_back(share)) && - (!purge_share || entry->tc_time < purge_time)) - { - purge_share= share; - purge_time= entry->tc_time; - } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - } + tc_add_table_arg argument; + argument.purge_time= ULONGLONG_MAX; + tdc_iterate(thd, (my_hash_walk_action) tc_add_table_callback, &argument); - if (purge_share) + if (argument.purge_time != ULONGLONG_MAX) { - mysql_mutex_lock(&purge_share->tdc.LOCK_table_share); - tc_wait_for_mdl_deadlock_detector(purge_share); - tdc_it.deinit(); - /* - It may happen that oldest table was acquired meanwhile. In this case - just go ahead, number of objects in table cache will normalize - eventually. - */ - if ((entry= tc_free_tables_back(purge_share)) && - entry->tc_time == purge_time) + TDC_element *element= (TDC_element*) lf_hash_search(&tdc_hash, + thd->tdc_hash_pins, + argument.key, + argument.key_length); + if (element) { - entry->s->tdc.free_tables.remove(entry); - tc_remove_table(entry); - mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share); - intern_close_table(entry); + TABLE *entry; + mysql_mutex_lock(&element->LOCK_table_share); + lf_hash_search_unpin(thd->tdc_hash_pins); + element->wait_for_mdl_deadlock_detector(); + + /* + It may happen that oldest table was acquired meanwhile. In this case + just go ahead, number of objects in table cache will normalize + eventually. + */ + if ((entry= element->free_tables_back()) && + 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); } - else - mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share); } - else - tdc_it.deinit(); } } /** - Acquire TABLE object from table cache. - - @pre share must be protected against removal. - - Acquired object cannot be evicted or acquired again. - - While locked: - - pop object from TABLE_SHARE::tdc.free_tables - - While unlocked: - - mark object used by thd - - @return TABLE object, or NULL if no unused objects. -*/ - -static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share) -{ - TABLE *table; - - mysql_mutex_lock(&share->tdc.LOCK_table_share); - table= share->tdc.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(&share->tdc.LOCK_table_share); - return table; -} - - -/** Release TABLE object to table cache. @pre object is used by caller. @@ -380,14 +340,14 @@ bool tc_release_table(TABLE *table) if (table->needs_reopen() || tc_records() > tc_size) { - mysql_mutex_lock(&table->s->tdc.LOCK_table_share); + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); goto purge; } table->tc_time= my_interval_timer(); - mysql_mutex_lock(&table->s->tdc.LOCK_table_share); - if (table->s->tdc.flushed) + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); + if (table->s->tdc->flushed) goto purge; /* in_use doesn't really need mutex protection, but must be reset after @@ -397,88 +357,76 @@ bool tc_release_table(TABLE *table) */ table->in_use= 0; /* Add table to the list of unused TABLE objects for this share. */ - table->s->tdc.free_tables.push_front(table); - mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); + table->s->tdc->free_tables.push_front(table); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); return false; purge: - tc_wait_for_mdl_deadlock_detector(table->s); + table->s->tdc->wait_for_mdl_deadlock_detector(); tc_remove_table(table); - mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); table->in_use= 0; intern_close_table(table); return true; } -extern "C" uchar *tdc_key(const uchar *record, size_t *length, - my_bool not_used __attribute__((unused))) -{ - TABLE_SHARE *entry= (TABLE_SHARE*) record; - *length= entry->table_cache_key.length; - return (uchar*) entry->table_cache_key.str; -} - - /** Delete share from hash and free share object. - - @return - @retval 0 Success - @retval 1 Share is referenced */ -static int tdc_delete_share_from_hash(TABLE_SHARE *share) +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_rwlock_wrlock(&LOCK_tdc); - mysql_mutex_lock(&share->tdc.LOCK_table_share); - if (--share->tdc.ref_count) - { - mysql_cond_broadcast(&share->tdc.COND_release); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - mysql_rwlock_unlock(&LOCK_tdc); - DBUG_RETURN(1); - } - my_hash_delete(&tdc_hash, (uchar*) share); - /* Notify PFS early, while still locked. */ + + 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; - mysql_rwlock_unlock(&LOCK_tdc); - if (share->tdc.m_flush_tickets.is_empty()) - { - /* No threads are waiting for this share to be flushed, destroy it. */ - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - free_table_share(share); - } - else + if (!element->m_flush_tickets.is_empty()) { - Wait_for_flush_list::Iterator it(share->tdc.m_flush_tickets); + 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); - /* - If there are threads waiting for this share to be flushed, - the last one to receive the notification will destroy the - share. At this point the share is removed from the table - definition cache, so is OK to proceed here without waiting - for this thread to do the work. - */ - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + + do + { + mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); + } while (!element->m_flush_tickets.is_empty()); } - DBUG_RETURN(0); + + 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? + element->assert_clean_share(); + 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; } /** Initialize table definition cache. - - @retval 0 Success - @retval !0 Error */ -int tdc_init(void) +void tdc_init(void) { DBUG_ENTER("tdc_init"); #ifdef HAVE_PSI_INTERFACE @@ -487,13 +435,15 @@ int tdc_init(void) tdc_inited= true; mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares, MY_MUTEX_INIT_FAST); - mysql_rwlock_init(key_rwlock_LOCK_tdc, &LOCK_tdc); my_atomic_rwlock_init(&LOCK_tdc_atomics); - oldest_unused_share= &end_of_unused_share; - end_of_unused_share.tdc.prev= &oldest_unused_share; tdc_version= 1L; /* Increments on each reload */ - DBUG_RETURN(my_hash_init(&tdc_hash, &my_charset_bin, tdc_size, 0, 0, tdc_key, - 0, 0)); + lf_hash_init(&tdc_hash, sizeof(TDC_element), LF_HASH_UNIQUE, 0, 0, + (my_hash_get_key) TDC_element::key, + &my_charset_bin); + tdc_hash.alloc.constructor= TDC_element::lf_alloc_constructor; + tdc_hash.alloc.destructor= TDC_element::lf_alloc_destructor; + tdc_hash.element_size= offsetof(TDC_element, version); + DBUG_VOID_RETURN; } @@ -533,9 +483,8 @@ void tdc_deinit(void) if (tdc_inited) { tdc_inited= false; - my_hash_free(&tdc_hash); + lf_hash_destroy(&tdc_hash); my_atomic_rwlock_destroy(&LOCK_tdc_atomics); - mysql_rwlock_destroy(&LOCK_tdc); mysql_mutex_destroy(&LOCK_unused_shares); } DBUG_VOID_RETURN; @@ -550,12 +499,7 @@ void tdc_deinit(void) ulong tdc_records(void) { - ulong records; - DBUG_ENTER("tdc_records"); - mysql_rwlock_rdlock(&LOCK_tdc); - records= tdc_hash.records; - mysql_rwlock_unlock(&LOCK_tdc); - DBUG_RETURN(records); + return my_atomic_load32_explicit(&tdc_hash.count, MY_MEMORY_ORDER_RELAXED); } @@ -564,72 +508,34 @@ void tdc_purge(bool all) DBUG_ENTER("tdc_purge"); while (all || tdc_records() > tdc_size) { - TABLE_SHARE *share; + TDC_element *element; mysql_mutex_lock(&LOCK_unused_shares); - if (!oldest_unused_share->tdc.next) + if (!(element= unused_shares.pop_front())) { mysql_mutex_unlock(&LOCK_unused_shares); break; } - share= oldest_unused_share; - *share->tdc.prev= share->tdc.next; - share->tdc.next->tdc.prev= share->tdc.prev; /* Concurrent thread may start using share again, reset prev and next. */ - share->tdc.prev= 0; - share->tdc.next= 0; - mysql_mutex_lock(&share->tdc.LOCK_table_share); - share->tdc.ref_count++; - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + 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(share); + tdc_delete_share_from_hash(element); } DBUG_VOID_RETURN; } /** - Prepeare table share for use with table definition cache. -*/ - -void tdc_init_share(TABLE_SHARE *share) -{ - DBUG_ENTER("tdc_init_share"); - mysql_mutex_init(key_TABLE_SHARE_LOCK_table_share, - &share->tdc.LOCK_table_share, MY_MUTEX_INIT_FAST); - mysql_cond_init(key_TABLE_SHARE_COND_release, &share->tdc.COND_release, 0); - share->tdc.m_flush_tickets.empty(); - share->tdc.all_tables.empty(); - share->tdc.free_tables.empty(); - tdc_assign_new_table_id(share); - share->tdc.version= tdc_refresh_version(); - share->tdc.flushed= false; - share->tdc.all_tables_refs= 0; - DBUG_VOID_RETURN; -} - - -/** - Release table definition cache specific resources of table share. -*/ - -void tdc_deinit_share(TABLE_SHARE *share) -{ - DBUG_ENTER("tdc_deinit_share"); - DBUG_ASSERT(share->tdc.ref_count == 0); - DBUG_ASSERT(share->tdc.m_flush_tickets.is_empty()); - DBUG_ASSERT(share->tdc.all_tables.is_empty()); - DBUG_ASSERT(share->tdc.free_tables.is_empty()); - DBUG_ASSERT(share->tdc.all_tables_refs == 0); - mysql_cond_destroy(&share->tdc.COND_release); - mysql_mutex_destroy(&share->tdc.LOCK_table_share); - DBUG_VOID_RETURN; -} - - -/** Lock table share. Find table share with given db.table_name in table definition cache. Return @@ -641,27 +547,35 @@ void tdc_deinit_share(TABLE_SHARE *share) Caller is expected to unlock table share with tdc_unlock_share(). - @retval 0 Share not found - @retval !0 Pointer to locked table share + @retval 0 Share not found + @retval MY_ERRPTR OOM + @retval ptr Pointer to locked table share */ -TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name) +TDC_element *tdc_lock_share(THD *thd, const char *db, const char *table_name) { + TDC_element *element; char key[MAX_DBKEY_LENGTH]; - uint key_length; DBUG_ENTER("tdc_lock_share"); - key_length= tdc_create_key(key, db, table_name); + if (fix_thd_pins(thd)) + DBUG_RETURN((TDC_element*) MY_ERRPTR); - mysql_rwlock_rdlock(&LOCK_tdc); - TABLE_SHARE* share= (TABLE_SHARE*) my_hash_search(&tdc_hash, - (uchar*) key, key_length); - if (share && !share->error) - mysql_mutex_lock(&share->tdc.LOCK_table_share); - else - share= 0; - mysql_rwlock_unlock(&LOCK_tdc); - DBUG_RETURN(share); + 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); } @@ -669,10 +583,10 @@ TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name) Unlock share locked by tdc_lock_share(). */ -void tdc_unlock_share(TABLE_SHARE *share) +void tdc_unlock_share(TDC_element *element) { DBUG_ENTER("tdc_unlock_share"); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&element->LOCK_table_share); DBUG_VOID_RETURN; } @@ -702,60 +616,60 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, TABLE **out_table) { TABLE_SHARE *share; + TDC_element *element; bool was_unused; DBUG_ENTER("tdc_acquire_share"); - mysql_rwlock_rdlock(&LOCK_tdc); - share= (TABLE_SHARE*) my_hash_search_using_hash_value(&tdc_hash, hash_value, - (uchar*) key, - key_length); - if (!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))) { - TABLE_SHARE *new_share; - mysql_rwlock_unlock(&LOCK_tdc); + TDC_element tmp(key, key_length); + int res= lf_hash_insert(&tdc_hash, thd->tdc_hash_pins, (uchar*) &tmp); - if (!(new_share= alloc_table_share(db, table_name, key, key_length))) + if (res == -1) DBUG_RETURN(0); - new_share->error= OPEN_FRM_OPEN_ERROR; + else if (res == 1) + continue; - mysql_rwlock_wrlock(&LOCK_tdc); - share= (TABLE_SHARE*) my_hash_search_using_hash_value(&tdc_hash, hash_value, - (uchar*) key, - key_length); - if (!share) - { - bool need_purge; + 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); + element->assert_clean_share(); - share= new_share; - mysql_mutex_lock(&share->tdc.LOCK_table_share); - if (my_hash_insert(&tdc_hash, (uchar*) share)) - { - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - mysql_rwlock_unlock(&LOCK_tdc); - free_table_share(share); - DBUG_RETURN(0); - } - need_purge= tdc_hash.records > tdc_size; - mysql_rwlock_unlock(&LOCK_tdc); + if (!(share= alloc_table_share(db, table_name, 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); - share->tdc.ref_count++; - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + /* note that tdc_acquire_share() *always* uses discovery */ + open_table_def(thd, share, flags | GTS_USE_DISCOVERY); - if (share->error) - { - tdc_delete_share_from_hash(share); - DBUG_RETURN(0); - } - else if (need_purge) - tdc_purge(false); - if (out_table) - *out_table= 0; - share->m_psi= PSI_CALL_get_table_share(false, share); - goto end; + if (share->error) + { + free_table_share(share); + lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length); + DBUG_RETURN(0); } - free_table_share(new_share); + + 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; } /* cannot force discovery of a cached share */ @@ -763,18 +677,25 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, if (out_table && (flags & GTS_TABLE)) { - if ((*out_table= tc_acquire_table(thd, share))) + if ((*out_table= element->acquire_table(thd))) { - mysql_rwlock_unlock(&LOCK_tdc); + lf_hash_search_unpin(thd->tdc_hash_pins); DBUG_ASSERT(!(flags & GTS_NOLOCK)); - DBUG_ASSERT(!share->error); - DBUG_ASSERT(!share->is_view); - DBUG_RETURN(share); + DBUG_ASSERT(element->share); + DBUG_ASSERT(!element->share->error); + DBUG_ASSERT(!element->share->is_view); + DBUG_RETURN(element->share); } } - mysql_mutex_lock(&share->tdc.LOCK_table_share); - mysql_rwlock_unlock(&LOCK_tdc); + 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 @@ -797,30 +718,29 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, goto err; } - was_unused= !share->tdc.ref_count; - share->tdc.ref_count++; - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + 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 (share->tdc.prev) + 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")); - *share->tdc.prev= share->tdc.next; - share->tdc.next->tdc.prev= share->tdc.prev; - share->tdc.next= 0; - share->tdc.prev= 0; + unused_shares.remove(element); + element->next= 0; + element->prev= 0; } mysql_mutex_unlock(&LOCK_unused_shares); } end: DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", - (ulong) share, share->tdc.ref_count)); + (ulong) share, share->tdc->ref_count)); if (flags & GTS_NOLOCK) { tdc_release_share(share); @@ -836,7 +756,7 @@ end: DBUG_RETURN(share); err: - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&element->LOCK_table_share); DBUG_RETURN(0); } @@ -849,87 +769,47 @@ void tdc_release_share(TABLE_SHARE *share) { DBUG_ENTER("tdc_release_share"); - mysql_mutex_lock(&share->tdc.LOCK_table_share); + mysql_mutex_lock(&share->tdc->LOCK_table_share); DBUG_PRINT("enter", ("share: 0x%lx table: %s.%s ref_count: %u version: %lu", (ulong) share, share->db.str, share->table_name.str, - share->tdc.ref_count, share->tdc.version)); - DBUG_ASSERT(share->tdc.ref_count); + share->tdc->ref_count, share->tdc->version)); + DBUG_ASSERT(share->tdc->ref_count); - if (share->tdc.ref_count > 1) + if (share->tdc->ref_count > 1) { - share->tdc.ref_count--; + share->tdc->ref_count--; if (!share->is_view) - mysql_cond_broadcast(&share->tdc.COND_release); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + 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_unlock(&share->tdc->LOCK_table_share); mysql_mutex_lock(&LOCK_unused_shares); - mysql_mutex_lock(&share->tdc.LOCK_table_share); - if (share->tdc.flushed) + mysql_mutex_lock(&share->tdc->LOCK_table_share); + if (--share->tdc->ref_count) { - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&share->tdc->LOCK_table_share); mysql_mutex_unlock(&LOCK_unused_shares); - tdc_delete_share_from_hash(share); DBUG_VOID_RETURN; } - if (--share->tdc.ref_count) + if (share->tdc->flushed || tdc_records() > tdc_size) { - mysql_mutex_unlock(&share->tdc.LOCK_table_share); 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); - share->tdc.prev= end_of_unused_share.tdc.prev; - *end_of_unused_share.tdc.prev= share; - end_of_unused_share.tdc.prev= &share->tdc.next; - share->tdc.next= &end_of_unused_share; - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + 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); - - /* Delete the least used share to preserve LRU order. */ - tdc_purge(false); DBUG_VOID_RETURN; } -static TABLE_SHARE *tdc_delete_share(const char *db, const char *table_name) -{ - TABLE_SHARE *share; - DBUG_ENTER("tdc_delete_share"); - - while ((share= tdc_lock_share(db, table_name))) - { - share->tdc.ref_count++; - if (share->tdc.ref_count > 1) - { - tdc_unlock_share(share); - DBUG_RETURN(share); - } - tdc_unlock_share(share); - - mysql_mutex_lock(&LOCK_unused_shares); - if (share->tdc.prev) - { - *share->tdc.prev= share->tdc.next; - share->tdc.next->tdc.prev= share->tdc.prev; - /* Concurrent thread may start using share again, reset prev and next. */ - share->tdc.prev= 0; - share->tdc.next= 0; - } - mysql_mutex_unlock(&LOCK_unused_shares); - - if (!tdc_delete_share_from_hash(share)) - break; - } - DBUG_RETURN(0); -} - - /** Remove all or some (depending on parameter) instances of TABLE and TABLE_SHARE from the table definition cache. @@ -973,9 +853,10 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, const char *db, const char *table_name, bool kill_delayed_threads) { + I_P_List <TABLE, TABLE_share> purge_tables; TABLE *table; - TABLE_SHARE *share; - bool found= false; + TDC_element *element; + uint my_refs= 1; DBUG_ENTER("tdc_remove_table"); DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type)); @@ -983,83 +864,102 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, MDL_EXCLUSIVE)); - if ((share= tdc_delete_share(db, table_name))) - { - I_P_List <TABLE, TABLE_share> purge_tables; - uint my_refs= 1; - mysql_mutex_lock(&share->tdc.LOCK_table_share); - tc_wait_for_mdl_deadlock_detector(share); - /* - Mark share flushed in order to ensure that it gets - automatically deleted once it is no longer referenced. + 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); + } - Note that code in TABLE_SHARE::wait_for_old_version() assumes that - marking share flushed is followed by purge of unused table - shares. - */ - if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) - share->tdc.flushed= true; + DBUG_ASSERT(element != MY_ERRPTR); // What can we do about it? - while ((table= share->tdc.free_tables.pop_front())) + if (!element->ref_count) + { + if (element->prev) { - tc_remove_table(table); - purge_tables.push_front(table); + unused_shares.remove(element); + element->prev= 0; + element->next= 0; } - if (kill_delayed_threads) - kill_delayed_threads_for_table(share); + mysql_mutex_unlock(&LOCK_unused_shares); - if (remove_type == TDC_RT_REMOVE_NOT_OWN || - remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) - { - TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables); - while ((table= it++)) - { - my_refs++; - DBUG_ASSERT(table->in_use == thd); - } - } - DBUG_ASSERT(share->tdc.all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + tdc_delete_share_from_hash(element); + DBUG_RETURN(true); + } + mysql_mutex_unlock(&LOCK_unused_shares); - while ((table= purge_tables.pop_front())) - intern_close_table(table); + element->ref_count++; - if (remove_type != TDC_RT_REMOVE_UNUSED) + element->wait_for_mdl_deadlock_detector(); + /* + 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 (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) + element->flushed= true; + + while ((table= element->free_tables.pop_front())) + { + tc_remove_table(table); + purge_tables.push_front(table); + } + 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++)) { - /* - 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(&share->tdc.LOCK_table_share); - while (share->tdc.ref_count > my_refs) - mysql_cond_wait(&share->tdc.COND_release, &share->tdc.LOCK_table_share); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + my_refs++; + DBUG_ASSERT(table->in_use == thd); } + } + DBUG_ASSERT(element->all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL); + mysql_mutex_unlock(&element->LOCK_table_share); - tdc_release_share(share); + while ((table= purge_tables.pop_front())) + intern_close_table(table); - found= true; + 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); } - DBUG_ASSERT(found || remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE); - DBUG_RETURN(found); + + tdc_release_share(element->share); + + DBUG_RETURN(true); } @@ -1081,21 +981,20 @@ int tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name, ulong wait_timeout, uint deadlock_weight, ulong refresh_version) { - TABLE_SHARE *share; - int res= FALSE; + TDC_element *element; - if ((share= tdc_lock_share(db, table_name))) + 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) { - if (share->tdc.flushed && refresh_version > share->tdc.version) - { - struct timespec abstime; - set_timespec(abstime, wait_timeout); - res= share->wait_for_old_version(thd, &abstime, deadlock_weight); - } - else - tdc_unlock_share(share); + struct timespec abstime; + set_timespec(abstime, wait_timeout); + return element->share->wait_for_old_version(thd, &abstime, deadlock_weight); } - return res; + tdc_unlock_share(element); + return FALSE; } @@ -1119,50 +1018,95 @@ ulong tdc_increment_refresh_version(void) /** - Initialize table definition cache iterator. + 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. */ -void TDC_iterator::init(void) +struct eliminate_duplicates_arg { - DBUG_ENTER("TDC_iterator::init"); - idx= 0; - mysql_rwlock_rdlock(&LOCK_tdc); - DBUG_VOID_RETURN; -} - + HASH hash; + MEM_ROOT root; + my_hash_walk_action action; + void *argument; +}; -/** - Deinitialize table definition cache iterator. -*/ -void TDC_iterator::deinit(void) +static uchar *eliminate_duplicates_get_key(const uchar *element, size_t *length, + my_bool not_used __attribute__((unused))) { - DBUG_ENTER("TDC_iterator::deinit"); - mysql_rwlock_unlock(&LOCK_tdc); - DBUG_VOID_RETURN; + LEX_STRING *key= (LEX_STRING *) element; + *length= key->length; + return (uchar *) key->str; } -/** - Get next TABLE_SHARE object from table definition cache. +static my_bool eliminate_duplicates(TDC_element *element, + eliminate_duplicates_arg *arg) +{ + LEX_STRING *key= (LEX_STRING *) alloc_root(&arg->root, sizeof(LEX_STRING)); - Object is protected against removal from table definition cache. + 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); +} - @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. -*/ -TABLE_SHARE *TDC_iterator::next(void) +int tdc_iterate(THD *thd, my_hash_walk_action action, void *argument, + bool no_dups) { - TABLE_SHARE *share= 0; - DBUG_ENTER("TDC_iterator::next"); - if (idx < tdc_hash.records) + eliminate_duplicates_arg no_dups_argument; + LF_PINS *pins; + myf alloc_flags= 0; + uint hash_flags= HASH_UNIQUE; + int res; + + if (thd) { - share= (TABLE_SHARE*) my_hash_element(&tdc_hash, idx); - idx++; + fix_thd_pins(thd); + pins= thd->tdc_hash_pins; + alloc_flags= MY_THREAD_SPECIFIC; + hash_flags|= HASH_THREAD_SPECIFIC; } - DBUG_RETURN(share); + 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; } diff --git a/sql/table_cache.h b/sql/table_cache.h index ea3822f9f68..b829e4de752 100644 --- a/sql/table_cache.h +++ b/sql/table_cache.h @@ -16,6 +16,165 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +extern PSI_mutex_key key_TABLE_SHARE_LOCK_table_share; +extern PSI_cond_key key_TABLE_SHARE_COND_release; + +class TDC_element +{ +public: + uchar m_key[NAME_LEN + 1 + NAME_LEN + 1]; + uint m_key_length; + ulong version; + bool flushed; + TABLE_SHARE *share; + + typedef I_P_List <TABLE, TABLE_share> TABLE_list; + typedef I_P_List <TABLE, All_share_tables> All_share_tables_list; + /** + Protects ref_count, m_flush_tickets, all_tables, free_tables, flushed, + all_tables_refs. + */ + mysql_mutex_t LOCK_table_share; + mysql_cond_t COND_release; + TDC_element *next, **prev; /* Link to unused shares */ + uint ref_count; /* How many TABLE objects uses this */ + uint all_tables_refs; /* Number of refs to all_tables */ + /** + List of tickets representing threads waiting for the share to be flushed. + */ + Wait_for_flush_list m_flush_tickets; + /* + Doubly-linked (back-linked) lists of used and unused TABLE objects + for this share. + */ + All_share_tables_list all_tables; + TABLE_list free_tables; + + TDC_element() {} + + TDC_element(const char *key, uint key_length) : m_key_length(key_length) + { + memcpy(m_key, key, key_length); + } + + + void assert_clean_share() + { + DBUG_ASSERT(share == 0); + DBUG_ASSERT(ref_count == 0); + DBUG_ASSERT(m_flush_tickets.is_empty()); + DBUG_ASSERT(all_tables.is_empty()); + DBUG_ASSERT(free_tables.is_empty()); + DBUG_ASSERT(all_tables_refs == 0); + DBUG_ASSERT(next == 0); + DBUG_ASSERT(prev == 0); + } + + + /** + 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 *acquire_table(THD *thd) + { + TABLE *table; + + mysql_mutex_lock(&LOCK_table_share); + table= 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(&LOCK_table_share); + return table; + } + + + /** + Get last element of free_tables. + */ + + TABLE *free_tables_back() + { + TABLE_list::Iterator it(share->tdc->free_tables); + TABLE *entry, *last= 0; + while ((entry= it++)) + last= entry; + return last; + } + + + /** + Wait for MDL deadlock detector to complete traversing tdc.all_tables. + + Must be called before updating TABLE_SHARE::tdc.all_tables. + */ + + void wait_for_mdl_deadlock_detector() + { + while (all_tables_refs) + mysql_cond_wait(&COND_release, &LOCK_table_share); + } + + + /** + 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"); + element->assert_clean_share(); + mysql_cond_destroy(&element->COND_release); + mysql_mutex_destroy(&element->LOCK_table_share); + DBUG_VOID_RETURN; + } + + + static uchar *key(const TDC_element *element, size_t *length, + my_bool not_used __attribute__((unused))) + { + *length= element->m_key_length; + return (uchar*) element->m_key; + } +}; + + enum enum_tdc_remove_table_type { TDC_RT_REMOVE_ALL, @@ -27,15 +186,14 @@ enum enum_tdc_remove_table_type extern ulong tdc_size; extern ulong tc_size; -extern int tdc_init(void); +extern void tdc_init(void); extern void tdc_start_shutdown(void); extern void tdc_deinit(void); extern ulong tdc_records(void); extern void tdc_purge(bool all); -extern void tdc_init_share(TABLE_SHARE *share); -extern void tdc_deinit_share(TABLE_SHARE *share); -extern TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name); -extern void tdc_unlock_share(TABLE_SHARE *share); +extern TDC_element *tdc_lock_share(THD *thd, const char *db, + const char *table_name); +extern void tdc_unlock_share(TDC_element *element); extern TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, const char *key, uint key_length, @@ -52,6 +210,8 @@ extern int tdc_wait_for_old_version(THD *thd, const char *db, extern ulong tdc_refresh_version(void); extern ulong tdc_increment_refresh_version(void); extern void tdc_assign_new_table_id(TABLE_SHARE *share); +extern int tdc_iterate(THD *thd, my_hash_walk_action action, void *argument, + bool no_dups= false); extern uint tc_records(void); extern void tc_purge(bool mark_flushed= false); @@ -125,13 +285,3 @@ static inline TABLE_SHARE *tdc_acquire_share_shortlived(THD *thd, TABLE_LIST *tl return tdc_acquire_share(thd, tl->db, tl->table_name, key, key_length, tl->mdl_request.key.tc_hash_value(), flags, 0); } - - -class TDC_iterator -{ - ulong idx; -public: - void init(void); - void deinit(void); - TABLE_SHARE *next(void); -}; |