------------------------------------------------------------ revno: 3807 committer: Sergey Vojtovich branch nick: 10.0 timestamp: Tue 2013-08-27 16:12:33 +0400 message: MDEV-4956 - Reduce usage of LOCK_open: TABLE_SHARE::tdc.used_tables - tc_acquire_table and tc_release_table do not access TABLE_SHARE::tdc.used_tables anymore - in tc_acquire_table(): release LOCK_tdc after we relase LOCK_open (saves a few CPU cycles in critical section) - in tc_release_table(): if we reached table cache threshold, evict to-be-released table without moving it to unused_tables. unused_tables must be empty at this point (added assertion). === modified file 'sql/sql_base.cc' --- sql/sql_base.cc 2013-08-14 08:48:50 +0000 +++ sql/sql_base.cc 2013-08-27 12:12:33 +0000 @@ -308,9 +308,11 @@ OPEN_TABLE_LIST *list_open_tables(THD *t share->table_name.str); (*start_list)->in_use= 0; mysql_mutex_lock(&LOCK_open); - TABLE_SHARE::TABLE_list::Iterator it(share->tdc.used_tables); - while (it++) - ++(*start_list)->in_use; + 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(&LOCK_open); (*start_list)->locked= 0; /* Obsolete. */ start_list= &(*start_list)->next; @@ -370,7 +372,7 @@ void free_io_cache(TABLE *table) void kill_delayed_threads_for_table(TABLE_SHARE *share) { - TABLE_SHARE::TABLE_list::Iterator it(share->tdc.used_tables); + TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables); TABLE *tab; mysql_mutex_assert_owner(&LOCK_open); @@ -379,7 +381,7 @@ void kill_delayed_threads_for_table(TABL { THD *in_use= tab->in_use; - if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && + if (in_use && (in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && ! in_use->killed) { in_use->killed= KILL_SYSTEM_THREAD; === modified file 'sql/sql_test.cc' --- sql/sql_test.cc 2013-08-14 08:48:50 +0000 +++ sql/sql_test.cc 2013-08-27 12:12:33 +0000 @@ -92,21 +92,23 @@ static void print_cached_tables(void) mysql_mutex_lock(&LOCK_open); while ((share= tdc_it.next())) { - TABLE_SHARE::TABLE_list::Iterator it(share->tdc.used_tables); + TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables); while ((entry= it++)) { - printf("%-14.14s %-32s%6ld%8ld%6d %s\n", - entry->s->db.str, entry->s->table_name.str, entry->s->version, - entry->in_use->thread_id, entry->db_stat ? 1 : 0, - lock_descriptions[(int)entry->reginfo.lock_type]); - } - it.init(share->tdc.free_tables); - while ((entry= it++)) - { - unused++; - printf("%-14.14s %-32s%6ld%8ld%6d %s\n", - entry->s->db.str, entry->s->table_name.str, entry->s->version, - 0L, entry->db_stat ? 1 : 0, "Not in use"); + if (entry->in_use) + { + printf("%-14.14s %-32s%6ld%8ld%6d %s\n", + entry->s->db.str, entry->s->table_name.str, entry->s->version, + entry->in_use->thread_id, entry->db_stat ? 1 : 0, + lock_descriptions[(int)entry->reginfo.lock_type]); + } + else + { + unused++; + printf("%-14.14s %-32s%6ld%8ld%6d %s\n", + entry->s->db.str, entry->s->table_name.str, entry->s->version, + 0L, entry->db_stat ? 1 : 0, "Not in use"); + } } } tdc_it.deinit(); === modified file 'sql/table.cc' --- sql/table.cc 2013-08-14 08:48:50 +0000 +++ sql/table.cc 2013-08-27 12:12:33 +0000 @@ -3728,7 +3728,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_fo if (gvisitor->m_lock_open_count++ == 0) mysql_mutex_lock(&LOCK_open); - TABLE_list::Iterator tables_it(tdc.used_tables); + All_share_tables_list::Iterator tables_it(tdc.all_tables); /* In case of multiple searches running in parallel, avoid going @@ -3746,7 +3746,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_fo while ((table= tables_it++)) { - if (gvisitor->inspect_edge(&table->in_use->mdl_context)) + if (table->in_use && gvisitor->inspect_edge(&table->in_use->mdl_context)) { goto end_leave_node; } @@ -3755,7 +3755,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_fo tables_it.rewind(); while ((table= tables_it++)) { - if (table->in_use->mdl_context.visit_subgraph(gvisitor)) + if (table->in_use && table->in_use->mdl_context.visit_subgraph(gvisitor)) { goto end_leave_node; } === modified file 'sql/table.h' --- sql/table.h 2013-08-14 08:48:50 +0000 +++ sql/table.h 2013-08-27 12:12:33 +0000 @@ -478,6 +478,7 @@ TABLE_CATEGORY get_table_category(const struct TABLE_share; +struct All_share_tables; extern ulong tdc_refresh_version(void); @@ -601,6 +602,7 @@ struct TABLE_SHARE mysql_mutex_t LOCK_share; /* To protect TABLE_SHARE */ typedef I_P_List TABLE_list; + typedef I_P_List All_share_tables_list; struct { /** @@ -617,7 +619,7 @@ struct TABLE_SHARE Doubly-linked (back-linked) lists of used and unused TABLE objects for this share. */ - TABLE_list used_tables; + All_share_tables_list all_tables; TABLE_list free_tables; } tdc; @@ -1022,8 +1024,10 @@ struct TABLE One should use methods of I_P_List template instead. */ TABLE *share_next, **share_prev; + TABLE *share_all_next, **share_all_prev; friend struct TABLE_share; + friend struct All_share_tables; public: @@ -1382,6 +1386,19 @@ struct TABLE_share } }; + +struct All_share_tables +{ + static inline TABLE **next_ptr(TABLE *l) + { + return &l->share_all_next; + } + static inline TABLE ***prev_ptr(TABLE *l) + { + return &l->share_all_prev; + } +}; + enum enum_schema_table_state { === modified file 'sql/table_cache.cc' --- sql/table_cache.cc 2013-08-15 14:15:32 +0000 +++ sql/table_cache.cc 2013-08-27 12:12:33 +0000 @@ -43,11 +43,8 @@ - free_table_share() Table cache invariants: - - TABLE_SHARE::used_tables shall not contain objects with TABLE::in_use == 0 - TABLE_SHARE::free_tables shall not contain objects with TABLE::in_use != 0 - unused_tables shall not contain objects with TABLE::in_use != 0 - - cached TABLE object must be either in TABLE_SHARE::used_tables or in - TABLE_SHARE::free_tables */ #include "my_global.h" @@ -65,7 +62,7 @@ static HASH tdc_hash; /**< Collection of static TABLE_SHARE *oldest_unused_share, end_of_unused_share; TABLE *unused_tables; /**< Collection of unused TABLE objects. */ -static int64 tdc_version; /* Increments on each reload */ +static int64 tdc_version; /* Increments on each reload */ static int64 last_table_id; static bool tdc_inited; @@ -81,7 +78,7 @@ static uint tc_count; /**< Number of TAB TABLE::next TABLE::prev TABLE_SHARE::tdc.free_tables - TABLE_SHARE::tdc.used_tables + TABLE_SHARE::tdc.all_tables */ mysql_mutex_t LOCK_open; @@ -159,7 +156,8 @@ uint tc_records(void) Free all unused TABLE objects. While locked: - - remove unused objects from TABLE_SHARE::tdc.free_tables lists + - remove unused objects from TABLE_SHARE::tdc.free_tables and + TABLE_SHARE::tdc.all_tables - reset unused_tables - decrement tc_count @@ -180,6 +178,7 @@ void tc_purge(void) do { unused_tables->s->tdc.free_tables.remove(unused_tables); + unused_tables->s->tdc.all_tables.remove(unused_tables); tc_count--; } while ((unused_tables= unused_tables->next)); mysql_rwlock_rdlock(&LOCK_flush); @@ -249,14 +248,11 @@ static void check_unused(THD *thd) count--; open_files++; } - it.init(share->tdc.used_tables); - while ((entry= it++)) + TABLE_SHARE::All_share_tables_list::Iterator it2(share->tdc.all_tables); + while ((entry= it2++)) { - if (!entry->in_use) - { - DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */ - } - open_files++; + if (entry->in_use) + open_files++; } } mysql_mutex_unlock(&LOCK_open); @@ -278,7 +274,8 @@ static void check_unused(THD *thd) @pre LOCK_open is locked, table is not used. While locked: - - remove object from TABLE_SHARE::tdc.free_tables + - remove object from TABLE_SHARE::tdc.free_tables and + TABLE_SHARE::tdc.all_tables - remove object from unused_tables @note This is helper routine, supposed to be used by table cache @@ -291,6 +288,7 @@ static void tc_remove_table(TABLE *table DBUG_ASSERT(!table->in_use); /* Remove from per-share chain of unused TABLE objects. */ table->s->tdc.free_tables.remove(table); + table->s->tdc.all_tables.remove(table); /* And global unused chain. */ table->next->prev= table->prev; @@ -313,11 +311,11 @@ static void tc_remove_table(TABLE *table Added object cannot be evicted or acquired. While locked: - - add object to TABLE_SHARE::tdc.used_tables + - add object to TABLE_SHARE::tdc.all_tables - increment tc_count - evict LRU object from table cache if we reached threshold - While unlocked: + While unlocked: - free evicted object */ @@ -325,7 +323,7 @@ void tc_add_table(THD *thd, TABLE *table { DBUG_ASSERT(table->in_use == thd); mysql_mutex_lock(&LOCK_open); - table->s->tdc.used_tables.push_front(table); + table->s->tdc.all_tables.push_front(table); tc_count++; /* If we have too many TABLE instances around, try to get rid of them */ if (tc_count > tc_size && unused_tables) @@ -352,13 +350,9 @@ void tc_add_table(THD *thd, TABLE *table While locked: - pop object from TABLE_SHARE::tdc.free_tables() - - remove share protection - remove object from unused_tables - - add object to TABLE_SHARE::tdc.used_tables() - mark object used by thd - @note share protection is kept if there are no unused objects. - @return TABLE object, or NULL if no unused objects. */ @@ -372,7 +366,6 @@ static TABLE *tc_acquire_table(THD *thd, mysql_mutex_unlock(&LOCK_open); return 0; } - mysql_rwlock_unlock(&LOCK_tdc); DBUG_ASSERT(!table->in_use); /* Unlink table from global unused tables list. */ @@ -385,15 +378,12 @@ static TABLE *tc_acquire_table(THD *thd, table->prev->next=table->next; /* Remove from unused list */ table->next->prev=table->prev; table->in_use= thd; - /* Add table to list of used tables for this share. */ - table->s->tdc.used_tables.push_front(table); mysql_mutex_unlock(&LOCK_open); /* 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)); - check_unused(thd); return table; } @@ -407,13 +397,12 @@ static TABLE *tc_acquire_table(THD *thd, While locked: - mark object not in use by any thread - - remove object from TABLE_SHARE::tdc.used_tables - if object is marked for purge, decrement tc_count - add object to TABLE_SHARE::tdc.free_tables - add object to unused_tables - evict LRU object from table cache if we reached threshold - While unlocked: + While unlocked: - free evicted/purged object @note Another thread may mark share for purge any moment (even @@ -433,12 +422,13 @@ bool tc_release_table(TABLE *table) DBUG_ASSERT(table->file); mysql_mutex_lock(&LOCK_open); - /* Remove table from the list of tables used in this share. */ - table->s->tdc.used_tables.remove(table); table->in_use= 0; - if (table->s->has_old_version() || table->needs_reopen() || !tdc_size) + if (table->s->has_old_version() || table->needs_reopen() || !tdc_size || + tc_count > tc_size) { + DBUG_ASSERT(!unused_tables || tc_count <= tc_size); tc_count--; + table->s->tdc.all_tables.remove(table); mysql_rwlock_rdlock(&LOCK_flush); mysql_mutex_unlock(&LOCK_open); intern_close_table(table); @@ -457,21 +447,7 @@ bool tc_release_table(TABLE *table) } else unused_tables=table->next=table->prev=table; - /* - We free the least used table, not the subject table, - to keep the LRU order. - */ - if (tc_count > tc_size) - { - TABLE *purge_table= unused_tables; - tc_remove_table(purge_table); - mysql_rwlock_rdlock(&LOCK_flush); - mysql_mutex_unlock(&LOCK_open); - intern_close_table(purge_table); - mysql_rwlock_unlock(&LOCK_flush); - } - else - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&LOCK_open); check_unused(thd); return false; } @@ -636,19 +612,9 @@ ulong tdc_records(void) void tdc_purge(bool all) { DBUG_ENTER("tdc_purge"); - for (;;) + while (all || tdc_records() > tdc_size) { TABLE_SHARE *share; - if (!all) - { - mysql_rwlock_rdlock(&LOCK_tdc); - if (tdc_hash.records <= tdc_size) - { - mysql_rwlock_unlock(&LOCK_tdc); - break; - } - mysql_rwlock_unlock(&LOCK_tdc); - } mysql_mutex_lock(&LOCK_unused_shares); if (!oldest_unused_share->tdc.next) @@ -684,7 +650,7 @@ void tdc_init_share(TABLE_SHARE *share) mysql_mutex_init(key_TABLE_SHARE_LOCK_table_share, &share->tdc.LOCK_table_share, MY_MUTEX_INIT_FAST); share->tdc.m_flush_tickets.empty(); - share->tdc.used_tables.empty(); + share->tdc.all_tables.empty(); share->tdc.free_tables.empty(); tdc_assign_new_table_id(share); share->version= tdc_refresh_version(); @@ -701,7 +667,7 @@ 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.used_tables.is_empty()); + DBUG_ASSERT(share->tdc.all_tables.is_empty()); DBUG_ASSERT(share->tdc.free_tables.is_empty()); mysql_mutex_destroy(&share->tdc.LOCK_table_share); DBUG_VOID_RETURN; @@ -721,7 +687,7 @@ 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 Pointer to locked table share */ TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name) @@ -846,6 +812,8 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, { if ((*out_table= tc_acquire_table(thd, share))) { + mysql_rwlock_unlock(&LOCK_tdc); + check_unused(thd); DBUG_ASSERT(!(flags & GTS_NOLOCK)); DBUG_ASSERT(!share->error); DBUG_ASSERT(!share->is_view); @@ -894,7 +862,7 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, share->tdc.next->tdc.prev= share->tdc.prev; share->tdc.next= 0; share->tdc.prev= 0; - } + } mysql_mutex_unlock(&LOCK_unused_shares); } @@ -928,7 +896,7 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, void tdc_release_share(TABLE_SHARE *share) { DBUG_ENTER("tdc_release_share"); - + mysql_mutex_lock(&share->tdc.LOCK_table_share); DBUG_PRINT("enter", ("share: 0x%lx table: %s.%s ref_count: %u version: %lu", @@ -998,7 +966,7 @@ static TABLE_SHARE *tdc_delete_share(con /* 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)) @@ -1072,13 +1040,11 @@ bool tdc_remove_table(THD *thd, enum_tdc TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables); #ifndef DBUG_OFF - if (remove_type == TDC_RT_REMOVE_ALL) - DBUG_ASSERT(share->tdc.used_tables.is_empty()); - else if (remove_type == TDC_RT_REMOVE_NOT_OWN) + if (remove_type == TDC_RT_REMOVE_NOT_OWN) { - TABLE_SHARE::TABLE_list::Iterator it2(share->tdc.used_tables); + TABLE_SHARE::All_share_tables_list::Iterator it2(share->tdc.all_tables); while ((table= it2++)) - DBUG_ASSERT(table->in_use == thd); + DBUG_ASSERT(!table->in_use || table->in_use == thd); } #endif /* @@ -1105,6 +1071,7 @@ bool tdc_remove_table(THD *thd, enum_tdc mysql_rwlock_unlock(&LOCK_flush); check_unused(thd); + DBUG_ASSERT(share->tdc.all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL); tdc_release_share(share); /* Wait for concurrent threads to free unused objects. */