Skip to content

Commit 453c29c

Browse files
committed
MDEV-6321: close_temporary_tables() in format description event not serialised correctly
Follow-up patch, fixing a possible deadlock issue. If the master crashes in the middle of an event group, there can be an active transaction in a worker thread when we encounter the following master restart format description event. In this case, we need to notify that worker thread to abort and roll back the partial event group. Otherwise a deadlock occurs: the worker thread waits for the commit that never arrives, and the SQL driver thread waits for the worker thread to complete its event group, which it never does.
1 parent 4cb1e0e commit 453c29c

File tree

4 files changed

+205
-4
lines changed

4 files changed

+205
-4
lines changed

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,43 @@ COUNT(*)
7979
SHOW STATUS LIKE 'Slave_open_temp_tables';
8080
Variable_name Value
8181
Slave_open_temp_tables 0
82+
*** Test that if master logged partial event group before crash, we finish that group correctly before executing format description event ***
83+
include/stop_slave.inc
84+
CALL mtr.add_suppression("Statement accesses nontransactional table as well as transactional or temporary table, and writes to any of them");
85+
SET gtid_domain_id= 1;
86+
DELETE FROM t1;
87+
ALTER TABLE t1 ENGINE=InnoDB;
88+
CREATE TEMPORARY TABLE t2 (a INT PRIMARY KEY);
89+
INSERT INTO t2 VALUES (1);
90+
INSERT INTO t2 VALUES (2);
91+
SET gtid_domain_id= 2;
92+
CREATE TEMPORARY TABLE t3 (a INT PRIMARY KEY);
93+
INSERT INTO t3 VALUES (10);
94+
INSERT INTO t3 VALUES (20);
95+
INSERT INTO t1 SELECT a, 'server_1' FROM t2;
96+
INSERT INTO t1 SELECT a, 'default' FROM t3;
97+
INSERT INTO t1 SELECT a+2, '+server_1' FROM t2;
98+
FLUSH TABLES;
99+
SET SESSION debug_dbug="+d,crash_before_writing_xid";
100+
INSERT INTO t1 SELECT a+4, '++server_1' FROM t2;
101+
Got one of the listed errors
102+
INSERT INTO t1 VALUES (0, 1);
103+
include/save_master_gtid.inc
104+
include/start_slave.inc
105+
include/sync_with_master_gtid.inc
106+
SELECT * FROM t1 ORDER BY a;
107+
a b
108+
0 1
109+
1 server_1
110+
2 server_1
111+
3 +server_1
112+
4 +server_1
113+
10 default
114+
20 default
115+
SHOW STATUS LIKE 'Slave_open_temp_tables';
116+
Variable_name Value
117+
Slave_open_temp_tables 0
118+
FLUSH LOGS;
82119
include/stop_slave.inc
83120
SET GLOBAL slave_parallel_threads=@old_parallel_threads;
84121
include/start_slave.inc

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

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
--source include/have_innodb.inc
12
--source include/have_binlog_format_statement.inc
23
--let $rpl_topology=1->2
34
--source include/rpl_init.inc
@@ -132,6 +133,83 @@ SELECT COUNT(*) FROM t1 WHERE a BETWEEN 100+0 AND 100+256;
132133
SHOW STATUS LIKE 'Slave_open_temp_tables';
133134

134135

136+
--echo *** Test that if master logged partial event group before crash, we finish that group correctly before executing format description event ***
137+
138+
--source include/stop_slave.inc
139+
140+
--connection server_1
141+
CALL mtr.add_suppression("Statement accesses nontransactional table as well as transactional or temporary table, and writes to any of them");
142+
SET gtid_domain_id= 1;
143+
DELETE FROM t1;
144+
ALTER TABLE t1 ENGINE=InnoDB;
145+
CREATE TEMPORARY TABLE t2 (a INT PRIMARY KEY);
146+
INSERT INTO t2 VALUES (1);
147+
INSERT INTO t2 VALUES (2);
148+
149+
--connection default
150+
SET gtid_domain_id= 2;
151+
CREATE TEMPORARY TABLE t3 (a INT PRIMARY KEY);
152+
INSERT INTO t3 VALUES (10);
153+
INSERT INTO t3 VALUES (20);
154+
155+
--connection server_1
156+
INSERT INTO t1 SELECT a, 'server_1' FROM t2;
157+
158+
--connection default
159+
INSERT INTO t1 SELECT a, 'default' FROM t3;
160+
161+
--connection server_1
162+
INSERT INTO t1 SELECT a+2, '+server_1' FROM t2;
163+
164+
# Crash the master server in the middle of writing an event group.
165+
--write_file $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
166+
wait
167+
EOF
168+
169+
FLUSH TABLES;
170+
SET SESSION debug_dbug="+d,crash_before_writing_xid";
171+
--error 2006,2013
172+
INSERT INTO t1 SELECT a+4, '++server_1' FROM t2;
173+
174+
--source include/wait_until_disconnected.inc
175+
--connection default
176+
--source include/wait_until_disconnected.inc
177+
178+
--append_file $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
179+
restart
180+
EOF
181+
182+
--connection default
183+
--enable_reconnect
184+
--source include/wait_until_connected_again.inc
185+
186+
--connection server_1
187+
--enable_reconnect
188+
--source include/wait_until_connected_again.inc
189+
190+
INSERT INTO t1 VALUES (0, 1);
191+
#--save_master_pos
192+
--source include/save_master_gtid.inc
193+
194+
--connection server_2
195+
# Start the slave replicating the events.
196+
# The main thing to test here is that the slave will know that it
197+
# needs to abort the partially received event group, so that the
198+
# execution of format_description event will not wait infinitely
199+
# for a commit of the incomplete group that never happens.
200+
201+
--source include/start_slave.inc
202+
#--sync_with_master
203+
--source include/sync_with_master_gtid.inc
204+
205+
SELECT * FROM t1 ORDER BY a;
206+
SHOW STATUS LIKE 'Slave_open_temp_tables';
207+
208+
--connection server_1
209+
# This FLUSH can be removed once MDEV-6608 is fixed.
210+
FLUSH LOGS;
211+
212+
135213
--connection server_2
136214
--source include/stop_slave.inc
137215
SET GLOBAL slave_parallel_threads=@old_parallel_threads;

sql/rpl_parallel.cc

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,30 @@ handle_rpl_parallel_thread(void *arg)
587587
events= next;
588588
continue;
589589
}
590+
else if (events->typ ==
591+
rpl_parallel_thread::queued_event::QUEUED_MASTER_RESTART)
592+
{
593+
if (in_event_group)
594+
{
595+
/*
596+
Master restarted (crashed) in the middle of an event group.
597+
So we need to roll back and discard that event group.
598+
*/
599+
group_rgi->cleanup_context(thd, 1);
600+
in_event_group= false;
601+
finish_event_group(thd, group_rgi->gtid_sub_id,
602+
events->entry_for_queued, group_rgi);
603+
604+
group_rgi->next= rgis_to_free;
605+
rgis_to_free= group_rgi;
606+
thd->rgi_slave= group_rgi= NULL;
607+
}
608+
609+
events->next= qevs_to_free;
610+
qevs_to_free= events;
611+
events= next;
612+
continue;
613+
}
590614
DBUG_ASSERT(events->typ==rpl_parallel_thread::queued_event::QUEUED_EVENT);
591615

592616
thd->rgi_slave= group_rgi= rgi;
@@ -1617,6 +1641,54 @@ rpl_parallel::workers_idle()
16171641
}
16181642

16191643

1644+
int
1645+
rpl_parallel_entry::queue_master_restart(rpl_group_info *rgi,
1646+
Format_description_log_event *fdev)
1647+
{
1648+
uint32 idx;
1649+
rpl_parallel_thread *thr;
1650+
rpl_parallel_thread::queued_event *qev;
1651+
Relay_log_info *rli= rgi->rli;
1652+
1653+
/*
1654+
We only need to queue the server restart if we still have a thread working
1655+
on a (potentially partial) event group.
1656+
1657+
If the last thread we queued for has finished, then it cannot have any
1658+
partial event group that needs aborting.
1659+
1660+
Thus there is no need for the full complexity of choose_thread(). We only
1661+
need to check if we have a current worker thread, and queue for it if so.
1662+
*/
1663+
idx= rpl_thread_idx;
1664+
thr= rpl_threads[idx];
1665+
if (!thr)
1666+
return 0;
1667+
mysql_mutex_lock(&thr->LOCK_rpl_thread);
1668+
if (thr->current_owner != &rpl_threads[idx])
1669+
{
1670+
/* No active worker thread, so no need to queue the master restart. */
1671+
mysql_mutex_unlock(&thr->LOCK_rpl_thread);
1672+
return 0;
1673+
}
1674+
1675+
if (!(qev= thr->get_qev(fdev, 0, rli)))
1676+
{
1677+
mysql_mutex_unlock(&thr->LOCK_rpl_thread);
1678+
return 1;
1679+
}
1680+
1681+
qev->rgi= rgi;
1682+
qev->typ= rpl_parallel_thread::queued_event::QUEUED_MASTER_RESTART;
1683+
qev->entry_for_queued= this;
1684+
qev->ir= rli->last_inuse_relaylog;
1685+
++qev->ir->queued_count;
1686+
thr->enqueue(qev);
1687+
mysql_mutex_unlock(&thr->LOCK_rpl_thread);
1688+
return 0;
1689+
}
1690+
1691+
16201692
void
16211693
rpl_parallel::wait_for_workers_idle(THD *thd)
16221694
{
@@ -1727,7 +1799,16 @@ rpl_parallel::do_event(rpl_group_info *serial_rgi, Log_event *ev,
17271799
17281800
Thus we need to wait for all prior events to execute to completion,
17291801
in case they need access to any of the temporary tables.
1802+
1803+
We also need to notify the worker thread running the prior incomplete
1804+
event group (if any), as such event group signifies an incompletely
1805+
written group cut short by a master crash, and must be rolled back.
17301806
*/
1807+
if (current->queue_master_restart(serial_rgi, fdev))
1808+
{
1809+
delete ev;
1810+
return 1;
1811+
}
17311812
wait_for_workers_idle(rli->sql_driver_thd);
17321813
}
17331814
}

sql/rpl_parallel.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,15 @@ struct rpl_parallel_thread {
7676
queued_event can hold either an event to be executed, or just a binlog
7777
position to be updated without any associated event.
7878
*/
79-
enum queued_event_t { QUEUED_EVENT, QUEUED_POS_UPDATE } typ;
79+
enum queued_event_t {
80+
QUEUED_EVENT,
81+
QUEUED_POS_UPDATE,
82+
QUEUED_MASTER_RESTART
83+
} typ;
8084
union {
8185
Log_event *ev; /* QUEUED_EVENT */
82-
rpl_parallel_entry *entry_for_queued; /* QUEUED_POS_UPDATE */
86+
rpl_parallel_entry *entry_for_queued; /* QUEUED_POS_UPDATE and
87+
QUEUED_MASTER_RESTART */
8388
};
8489
rpl_group_info *rgi;
8590
inuse_relaylog *ir;
@@ -224,8 +229,8 @@ struct rpl_parallel_entry {
224229

225230
rpl_parallel_thread * choose_thread(rpl_group_info *rgi, bool *did_enter_cond,
226231
PSI_stage_info *old_stage, bool reuse);
227-
group_commit_orderer *get_gco();
228-
void free_gco(group_commit_orderer *gco);
232+
int queue_master_restart(rpl_group_info *rgi,
233+
Format_description_log_event *fdev);
229234
};
230235
struct rpl_parallel {
231236
HASH domain_hash;

0 commit comments

Comments
 (0)