Skip to content

Commit 60d094a

Browse files
committed
MDEV-7936: Assertion `!table || table->in_use == _current_thd()' failed on parallel replication in optimistic mode
Make sure that in parallel replication, we execute wait_for_prior_commit() before setting table->in_use for a temporary table. Otherwise we can end up with two parallel replication worker threads competing with each other for use of a temporary table. Re-factor the use of find_temporary_table() to be able to handle errors in the caller (as wait_for_prior_commit() can return error in case of deadlock kill).
1 parent c47fe0e commit 60d094a

File tree

5 files changed

+116
-41
lines changed

5 files changed

+116
-41
lines changed

mysql-test/suite/rpl/r/rpl_parallel_temptable.result

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,26 @@ SHOW STATUS LIKE 'Slave_open_temp_tables';
116116
Variable_name Value
117117
Slave_open_temp_tables 0
118118
FLUSH LOGS;
119+
*** MDEV-7936: Assertion `!table || table->in_use == _current_thd()' failed on parallel replication in optimistic mode ***
120+
CREATE TEMPORARY TABLE t4 (a INT PRIMARY KEY) ENGINE=InnoDB;
121+
SET @old_dbug= @@SESSION.debug_dbug;
122+
SET SESSION debug_dbug="+d,binlog_force_commit_id";
123+
SET @commit_id= 10000;
124+
INSERT INTO t4 VALUES (30);
125+
INSERT INTO t4 VALUES (31);
126+
SET SESSION debug_dbug= @old_dbug;
127+
INSERT INTO t1 SELECT a, "conservative" FROM t4;
128+
DROP TEMPORARY TABLE t4;
129+
SELECT * FROM t1 WHERE a >= 30 ORDER BY a;
130+
a b
131+
30 conservative
132+
31 conservative
133+
include/save_master_pos.inc
134+
include/sync_with_master_gtid.inc
135+
SELECT * FROM t1 WHERE a >= 30 ORDER BY a;
136+
a b
137+
30 conservative
138+
31 conservative
119139
include/stop_slave.inc
120140
SET GLOBAL slave_parallel_threads=@old_parallel_threads;
121141
include/start_slave.inc

mysql-test/suite/rpl/t/rpl_parallel_temptable.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,29 @@ SHOW STATUS LIKE 'Slave_open_temp_tables';
213213
FLUSH LOGS;
214214

215215

216+
--echo *** MDEV-7936: Assertion `!table || table->in_use == _current_thd()' failed on parallel replication in optimistic mode ***
217+
218+
--connection server_1
219+
CREATE TEMPORARY TABLE t4 (a INT PRIMARY KEY) ENGINE=InnoDB;
220+
SET @old_dbug= @@SESSION.debug_dbug;
221+
SET SESSION debug_dbug="+d,binlog_force_commit_id";
222+
SET @commit_id= 10000;
223+
INSERT INTO t4 VALUES (30);
224+
INSERT INTO t4 VALUES (31);
225+
SET SESSION debug_dbug= @old_dbug;
226+
INSERT INTO t1 SELECT a, "conservative" FROM t4;
227+
DROP TEMPORARY TABLE t4;
228+
SELECT * FROM t1 WHERE a >= 30 ORDER BY a;
229+
--source include/save_master_pos.inc
230+
231+
--connection server_2
232+
--source include/sync_with_master_gtid.inc
233+
234+
SELECT * FROM t1 WHERE a >= 30 ORDER BY a;
235+
236+
237+
# Clean up.
238+
216239
--connection server_2
217240
--source include/stop_slave.inc
218241
SET GLOBAL slave_parallel_threads=@old_parallel_threads;

sql/sql_base.cc

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,69 @@ TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl)
15501550
}
15511551

15521552

1553+
static bool
1554+
use_temporary_table(THD *thd, TABLE *table, TABLE **out_table)
1555+
{
1556+
*out_table= table;
1557+
if (!table)
1558+
return false;
1559+
/*
1560+
Temporary tables are not safe for parallel replication. They were
1561+
designed to be visible to one thread only, so have no table locking.
1562+
Thus there is no protection against two conflicting transactions
1563+
committing in parallel and things like that.
1564+
1565+
So for now, anything that uses temporary tables will be serialised
1566+
with anything before it, when using parallel replication.
1567+
1568+
ToDo: We might be able to introduce a reference count or something
1569+
on temp tables, and have slave worker threads wait for it to reach
1570+
zero before being allowed to use the temp table. Might not be worth
1571+
it though, as statement-based replication using temporary tables is
1572+
in any case rather fragile.
1573+
*/
1574+
if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec &&
1575+
thd->wait_for_prior_commit())
1576+
return true;
1577+
/*
1578+
We need to set the THD as it may be different in case of
1579+
parallel replication
1580+
*/
1581+
if (table->in_use != thd)
1582+
{
1583+
table->in_use= thd;
1584+
#ifdef REMOVE_AFTER_MERGE_WITH_10
1585+
if (thd->rgi_slave)
1586+
{
1587+
/*
1588+
We may be stealing an opened temporary tables from one slave
1589+
thread to another, we need to let the performance schema know that,
1590+
for aggregates per thread to work properly.
1591+
*/
1592+
table->file->unbind_psi();
1593+
table->file->rebind_psi();
1594+
}
1595+
#endif
1596+
}
1597+
return false;
1598+
}
1599+
1600+
bool
1601+
find_and_use_temporary_table(THD *thd, const char *db, const char *table_name,
1602+
TABLE **out_table)
1603+
{
1604+
return use_temporary_table(thd, find_temporary_table(thd, db, table_name),
1605+
out_table);
1606+
}
1607+
1608+
1609+
bool
1610+
find_and_use_temporary_table(THD *thd, const TABLE_LIST *tl, TABLE **out_table)
1611+
{
1612+
return use_temporary_table(thd, find_temporary_table(thd, tl), out_table);
1613+
}
1614+
1615+
15531616
/**
15541617
Find a temporary table specified by a key in the THD::temporary_tables list.
15551618
@@ -1570,26 +1633,6 @@ TABLE *find_temporary_table(THD *thd,
15701633
if (table->s->table_cache_key.length == table_key_length &&
15711634
!memcmp(table->s->table_cache_key.str, table_key, table_key_length))
15721635
{
1573-
/*
1574-
We need to set the THD as it may be different in case of
1575-
parallel replication
1576-
*/
1577-
if (table->in_use != thd)
1578-
{
1579-
table->in_use= thd;
1580-
#ifdef REMOVE_AFTER_MERGE_WITH_10
1581-
if (thd->rgi_slave)
1582-
{
1583-
/*
1584-
We may be stealing an opened temporary tables from one slave
1585-
thread to another, we need to let the performance schema know that,
1586-
for aggregates per thread to work properly.
1587-
*/
1588-
table->file->unbind_psi();
1589-
table->file->rebind_psi();
1590-
}
1591-
#endif
1592-
}
15931636
result= table;
15941637
break;
15951638
}
@@ -5822,7 +5865,9 @@ bool open_temporary_table(THD *thd, TABLE_LIST *tl)
58225865
DBUG_RETURN(FALSE);
58235866
}
58245867

5825-
if (!(table= find_temporary_table(thd, tl)))
5868+
if (find_and_use_temporary_table(thd, tl, &table))
5869+
DBUG_RETURN(TRUE);
5870+
if (!table)
58265871
{
58275872
if (tl->open_type == OT_TEMPORARY_ONLY &&
58285873
tl->open_strategy == TABLE_LIST::OPEN_NORMAL)
@@ -5833,25 +5878,6 @@ bool open_temporary_table(THD *thd, TABLE_LIST *tl)
58335878
DBUG_RETURN(FALSE);
58345879
}
58355880

5836-
/*
5837-
Temporary tables are not safe for parallel replication. They were
5838-
designed to be visible to one thread only, so have no table locking.
5839-
Thus there is no protection against two conflicting transactions
5840-
committing in parallel and things like that.
5841-
5842-
So for now, anything that uses temporary tables will be serialised
5843-
with anything before it, when using parallel replication.
5844-
5845-
ToDo: We might be able to introduce a reference count or something
5846-
on temp tables, and have slave worker threads wait for it to reach
5847-
zero before being allowed to use the temp table. Might not be worth
5848-
it though, as statement-based replication using temporary tables is
5849-
in any case rather fragile.
5850-
*/
5851-
if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec &&
5852-
thd->wait_for_prior_commit())
5853-
DBUG_RETURN(true);
5854-
58555881
#ifdef WITH_PARTITION_STORAGE_ENGINE
58565882
if (tl->partition_names)
58575883
{

sql/sql_base.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,11 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table,
148148
const char *db_name,
149149
const char *table_name);
150150
TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name);
151+
bool find_and_use_temporary_table(THD *thd, const char *db,
152+
const char *table_name, TABLE **out_table);
151153
TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl);
154+
bool find_and_use_temporary_table(THD *thd, const TABLE_LIST *tl,
155+
TABLE **out_table);
152156
TABLE *find_temporary_table(THD *thd, const char *table_key,
153157
uint table_key_length);
154158
void close_thread_tables(THD *thd);

sql/sql_table.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4687,7 +4687,9 @@ int create_table_impl(THD *thd,
46874687
if (create_info->tmp_table())
46884688
{
46894689
TABLE *tmp_table;
4690-
if ((tmp_table= find_temporary_table(thd, db, table_name)))
4690+
if (find_and_use_temporary_table(thd, db, table_name, &tmp_table))
4691+
goto err;
4692+
if (tmp_table)
46914693
{
46924694
bool table_creation_was_logged= tmp_table->s->table_creation_was_logged;
46934695
if (create_info->options & HA_LEX_CREATE_REPLACE)

0 commit comments

Comments
 (0)