Skip to content

Commit 8155045

Browse files
Dyre TjeldvollDyre Tjeldvoll
authored andcommitted
BUG#22083048: MUTEX REQUEST ON LOCK_OPEN CAUSE HANG IN OPEN_TABLE_UNCACHED()
Problem: Adding a virtual generated column could cause a deadlock in debug builds. This was caused by a virtual column purge thread callback becoming blocked in open_table_uncached() because it could not acquire the LOCK_open mutex. The mutex, in turn, was needed to check if the table being opened was present in the TDC. Solution: Relax condition for assert and associated lock so that it is possible to call open_table_uncached() without taking LOCK_open or checking the TDC provided the table will not be opened in SE. Relaxing the assert is safe in this case as the second share outside the TDC will not have its own SE private data.
1 parent a072b8c commit 8155045

File tree

4 files changed

+52
-52
lines changed

4 files changed

+52
-52
lines changed

sql/handler.cc

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8193,6 +8193,11 @@ bool handler::my_prepare_gcolumn_template(THD *thd,
81938193
DBUG_ASSERT(!was_truncated);
81948194
lex_start(thd);
81958195
bool rc= true;
8196+
8197+
// Note! The last argument to open_table_uncached() must be false,
8198+
// since the table already exists in the TDC. Allowing the table to
8199+
// be opened in the SE in this case is dangerous as the two shares
8200+
// could get conflicting SE private data.
81968201
TABLE *table= open_table_uncached(thd, path, db_name, table_name,
81978202
false, false);
81988203
if (table)
@@ -8207,14 +8212,17 @@ bool handler::my_prepare_gcolumn_template(THD *thd,
82078212

82088213

82098214
/**
8210-
Callback for generated columns processing. Will open the table
8211-
and call my_eval_gcolumn_expr_helper() to do the actual
8215+
Callback for generated columns processing. Will open the table, in the
8216+
server *only*, and call my_eval_gcolumn_expr_helper() to do the actual
82128217
processing. This function is a variant of the other
82138218
handler::my_eval_gcolumn_expr() but is intended for use when no TABLE
82148219
object already exists - e.g. from purge threads.
82158220
8221+
Note! The call to open_table_uncached() must be made with the last
8222+
argument (open_in_engine) set to false. Failing to do so will cause
8223+
deadlocks and incorrect behavior.
8224+
82168225
@param thd Thread handle
8217-
@param open_in_engine Should ha_open() be called?
82188226
@param db_name Database containing the table to open
82198227
@param table_name Name of table to open
82208228
@param fields Bitmap of field index of evaluated generated column
@@ -8223,49 +8231,28 @@ bool handler::my_prepare_gcolumn_template(THD *thd,
82238231
@return true in case of error, false otherwise.
82248232
*/
82258233

8226-
bool handler::my_eval_gcolumn_expr(THD *thd,
8227-
bool open_in_engine,
8228-
const char *db_name,
8229-
const char *table_name,
8230-
const MY_BITMAP *const fields,
8231-
uchar *record)
8234+
bool handler::my_eval_gcolumn_expr_with_open(THD *thd,
8235+
const char *db_name,
8236+
const char *table_name,
8237+
const MY_BITMAP *const fields,
8238+
uchar *record)
82328239
{
82338240
DBUG_ASSERT(thd->system_thread == SYSTEM_THREAD_BACKGROUND);
82348241
bool retval= true;
82358242
lex_start(thd);
82368243

8237-
if (open_in_engine)
8238-
{
8239-
TABLE_LIST table_list;
8240-
table_list.init_one_table(db_name, strlen(db_name),
8241-
table_name, strlen(table_name),
8242-
table_name, TL_READ);
8244+
char path[FN_REFLEN + 1];
8245+
bool was_truncated;
8246+
build_table_filename(path, sizeof(path) - 1 - reg_ext_length,
8247+
db_name, table_name, "", 0, &was_truncated);
8248+
DBUG_ASSERT(!was_truncated);
82438249

8244-
TABLE *table= open_ltable(thd, &table_list, table_list.lock_type,
8245-
MYSQL_LOCK_IGNORE_TIMEOUT);
8246-
if (table)
8247-
{
8248-
retval=
8249-
my_eval_gcolumn_expr_helper(thd, table_list.table, fields, record, true);
8250-
trans_commit_stmt(thd);
8251-
close_thread_tables(thd);
8252-
}
8253-
}
8254-
else
8250+
TABLE *table= open_table_uncached(thd, path, db_name, table_name,
8251+
false, false);
8252+
if (table)
82558253
{
8256-
char path[FN_REFLEN + 1];
8257-
bool was_truncated;
8258-
build_table_filename(path, sizeof(path) - 1 - reg_ext_length,
8259-
db_name, table_name, "", 0, &was_truncated);
8260-
DBUG_ASSERT(!was_truncated);
8261-
8262-
TABLE *table= open_table_uncached(thd, path, db_name, table_name,
8263-
false, false);
8264-
if (table)
8265-
{
8266-
retval= my_eval_gcolumn_expr_helper(thd, table, fields, record, true);
8267-
intern_close_table(table);
8268-
}
8254+
retval= my_eval_gcolumn_expr_helper(thd, table, fields, record, true);
8255+
intern_close_table(table);
82698256
}
82708257

82718258
lex_end(thd->lex);

sql/handler.h

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3693,12 +3693,11 @@ class handler :public Sql_alloc
36933693
const char *table_name,
36943694
my_gcolumn_template_callback_t myc,
36953695
void *ib_table);
3696-
static bool my_eval_gcolumn_expr(THD *thd,
3697-
bool open_in_engine,
3698-
const char *db_name,
3699-
const char *table_name,
3700-
const MY_BITMAP *const fields,
3701-
uchar *record);
3696+
static bool my_eval_gcolumn_expr_with_open(THD *thd,
3697+
const char *db_name,
3698+
const char *table_name,
3699+
const MY_BITMAP *const fields,
3700+
uchar *record);
37023701
static bool my_eval_gcolumn_expr(THD *thd,
37033702
const char *db_name,
37043703
const char *table_name,

sql/sql_base.cc

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6779,10 +6779,25 @@ TABLE *open_table_uncached(THD *thd, const char *path, const char *db,
67796779
DBUG_RETURN(0); /* purecov: inspected */
67806780

67816781
#ifndef DBUG_OFF
6782-
mysql_mutex_lock(&LOCK_open);
6783-
DBUG_ASSERT(!my_hash_search(&table_def_cache, (uchar*) cache_key,
6784-
key_length));
6785-
mysql_mutex_unlock(&LOCK_open);
6782+
// In order to let purge thread callback call open_table_uncached()
6783+
// we cannot grab LOCK_open here, as that will cause a deadlock.
6784+
6785+
// The assert below safeguards against opening a table which is
6786+
// already found in the table definition cache. Iff the table will
6787+
// be opened in the SE below, we may get two conflicting copies of
6788+
// SE private data in the two table_shares.
6789+
6790+
// By only grabbing LOCK_open and check the assert only when
6791+
// open_in_engine is true, we safeguard the engine private data while
6792+
// also allowing the purge threads callbacks since they always call
6793+
// with open_in_engine=false.
6794+
if (open_in_engine)
6795+
{
6796+
mysql_mutex_lock(&LOCK_open);
6797+
DBUG_ASSERT(!my_hash_search(&table_def_cache, (uchar*) cache_key,
6798+
key_length));
6799+
mysql_mutex_unlock(&LOCK_open);
6800+
}
67866801
#endif
67876802

67886803
share= (TABLE_SHARE*) (tmp_table+1);

storage/innobase/handler/ha_innodb.cc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19932,9 +19932,8 @@ innobase_get_computed_value(
1993219932
vctempl->mysql_col_len, blob_mem, max_len);
1993319933
}
1993419934

19935-
ret = handler::my_eval_gcolumn_expr(
19936-
current_thd, false,
19937-
index->table->vc_templ->db_name.c_str(),
19935+
ret = handler::my_eval_gcolumn_expr_with_open(
19936+
current_thd, index->table->vc_templ->db_name.c_str(),
1993819937
index->table->vc_templ->tb_name.c_str(), &column_map,
1993919938
(uchar *)mysql_rec);
1994019939
} else {

0 commit comments

Comments
 (0)