Skip to content

Commit 260373b

Browse files
author
Aditya A
committed
Bug #30215068 BINLOG ROTATION DEADLOCK WHEN INNODB CONCURRENCY LIMIT SETTED
PROBLEM ------- 1. In SBR, replication related mutexes are acquired while rotating a binlog. These mutexes are released only after updating a gtid table. 2. Before the rotate thread can commit ,many update transactions which are not updating any rows are fired which enter innodb and increase the srv_conc.n_active connection. Since no rows are updated it will not call MYSQL_BIN_LOG::prepare() which would have decremnted the srv_conc.n_active. These update threads later wait on the replication mutexes held by the rotate thread. 3. Since the innodb_thread_concurrency is set to very low value the rotate thread cannot enter innodb since innodb_thread_concurrency has been reached ,so there is a deadlock 4. This issue is not seen in RBR. FIX --- 1. Consider rotate thread as a special thread which updates the gtid implicitly and and skip innodb thread concurrency check for it. The fix is backport from Bug #30427369. 2. Patch even skips threads having active attacable transactions. Even these can result in deadlock in low innodb_thread_concurrency setting. This fix is a backport of Bug#3109077. Reviewed by : Praveenkumar Hulakund <praveenkumar.hulakund@oracle.com> Reviewed by : Debarun Banerjee <debarun.banerjee@oracle.com>
1 parent e891d72 commit 260373b

File tree

8 files changed

+155
-7
lines changed

8 files changed

+155
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#
2+
# Bug #30215068 BINLOG ROTATION DEADLOCK WHEN INNODB CONCURRENCY LIMIT SETTED
3+
#
4+
connect con1, localhost, root,,;
5+
select @@innodb_thread_concurrency;
6+
@@innodb_thread_concurrency
7+
1
8+
CREATE TABLE t1 (c1 INT) Engine=InnoDB;
9+
CREATE TABLE t2 (c1 INT, c2 int, c3 varchar(200)) Engine=InnoDB;
10+
SET @debug_save= @@SESSION.DEBUG;
11+
SET SESSION DEBUG = '+d,force_rotate';
12+
SET DEBUG_SYNC = 'stop_binlog_rotation_after_acquiring_lock_log SIGNAL rotate_stopped WAIT_FOR proceed_rotate';
13+
INSERT INTO t1 VALUES (1);;
14+
connect con2, localhost, root,,;
15+
SET DEBUG_SYNC= 'now WAIT_FOR rotate_stopped';
16+
UPDATE t2 SET C2=1 WHERE C1 = 1;;
17+
connect con3, localhost, root,,;
18+
SET DEBUG_SYNC= 'now SIGNAL proceed_rotate';
19+
connection con1;
20+
SET @@SESSION.DEBUG= @debug_save;
21+
connection con2;
22+
connection default;
23+
disconnect con1;
24+
disconnect con2;
25+
disconnect con3;
26+
DROP TABLE t1,t2;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
--binlog-format=MIXED
2+
--max_binlog_size=4096
3+
--innodb_thread_concurrency=1
4+
--innodb_thread_sleep_delay=0
5+
--enforce_gtid_consistency=ON
6+
--gtid_mode=ON
7+
--master-info-repository=TABLE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--source include/have_debug.inc
2+
--source include/have_debug_sync.inc
3+
--source include/have_binlog_format_statement.inc
4+
5+
--echo #
6+
--echo # Bug #30215068 BINLOG ROTATION DEADLOCK WHEN INNODB CONCURRENCY LIMIT SETTED
7+
--echo #
8+
9+
--enable_connect_log
10+
--connect (con1, localhost, root,,)
11+
select @@innodb_thread_concurrency;
12+
13+
CREATE TABLE t1 (c1 INT) Engine=InnoDB;
14+
CREATE TABLE t2 (c1 INT, c2 int, c3 varchar(200)) Engine=InnoDB;
15+
SET @debug_save= @@SESSION.DEBUG;
16+
SET SESSION DEBUG = '+d,force_rotate';
17+
SET DEBUG_SYNC = 'stop_binlog_rotation_after_acquiring_lock_log SIGNAL rotate_stopped WAIT_FOR proceed_rotate';
18+
--send INSERT INTO t1 VALUES (1);
19+
20+
# Since innodb_thread_concurrency=1, Send txn from a different connection
21+
22+
--connect (con2, localhost, root,,)
23+
SET DEBUG_SYNC= 'now WAIT_FOR rotate_stopped';
24+
--SEND UPDATE t2 SET C2=1 WHERE C1 = 1;
25+
26+
--connect (con3, localhost, root,,)
27+
# Verify that con1 is waiting in stop_binlog_rotation_after_acquiring_lock_log sync point and con 2 is waiting for con1 to commit
28+
--let $wait_condition=SELECT COUNT(*)=1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE State = 'debug sync point: stop_binlog_rotation_after_acquiring_lock_log' and info = 'INSERT INTO t1 VALUES (1)'
29+
--source include/wait_condition.inc
30+
--let $wait_condition=SELECT COUNT(*)=1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE State = 'query end' and info = 'UPDATE t2 SET C2=1 WHERE C1 = 1'
31+
--source include/wait_condition.inc
32+
33+
# Continue rotate thread (con1)
34+
SET DEBUG_SYNC= 'now SIGNAL proceed_rotate';
35+
36+
--connection con1
37+
#without fix con1 will hang because it cannot get innodb concurrency ticket
38+
--reap
39+
SET @@SESSION.DEBUG= @debug_save;
40+
41+
--connection con2
42+
--reap
43+
44+
# Cleanup
45+
connection default;
46+
47+
--disconnect con1
48+
--disconnect con2
49+
--disconnect con3
50+
--disable_connect_log
51+
DROP TABLE t1,t2;

sql/binlog.cc

+2
Original file line numberDiff line numberDiff line change
@@ -7580,6 +7580,8 @@ int MYSQL_BIN_LOG::rotate(bool force_rotate, bool* check_purge)
75807580
assert(!is_relay_log);
75817581
mysql_mutex_assert_owner(&LOCK_log);
75827582

7583+
DEBUG_SYNC(current_thd,"stop_binlog_rotation_after_acquiring_lock_log");
7584+
75837585
*check_purge= false;
75847586

75857587
if (DBUG_EVALUATE_IF("force_rotate", 1, 0) || force_rotate ||

sql/sql_class.cc

+10
Original file line numberDiff line numberDiff line change
@@ -3897,6 +3897,16 @@ extern "C" int thd_non_transactional_update(const MYSQL_THD thd)
38973897
Transaction_ctx::SESSION);
38983898
}
38993899

3900+
extern "C" int thd_has_active_attachable_trx(const MYSQL_THD thd)
3901+
{
3902+
return thd->is_attachable_transaction_active();
3903+
}
3904+
3905+
extern "C" int thd_is_operating_gtid_table_implicitly(const MYSQL_THD thd)
3906+
{
3907+
return thd->is_operating_gtid_table_implicitly;
3908+
}
3909+
39003910
extern "C" int thd_binlog_format(const MYSQL_THD thd)
39013911
{
39023912
if (mysql_bin_log.is_open() && (thd->variables.option_bits & OPTION_BIN_LOG))

sql/sql_class.h

+6
Original file line numberDiff line numberDiff line change
@@ -3698,6 +3698,12 @@ class THD :public MDL_context_owner,
36983698
/**
36993699
@return true if there is an active attachable transaction.
37003700
*/
3701+
int is_attachable_transaction_active() const
3702+
{ return m_attachable_trx != NULL; }
3703+
3704+
/**
3705+
@return true if there is an active attachable readonly transaction.
3706+
*/
37013707
bool is_attachable_ro_transaction_active() const
37023708
{ return m_attachable_trx != NULL && m_attachable_trx->is_read_only(); }
37033709

storage/innobase/handler/ha_innodb.cc

+35-7
Original file line numberDiff line numberDiff line change
@@ -1507,13 +1507,12 @@ innobase_srv_conc_enter_innodb(
15071507
row_prebuilt_t* prebuilt)
15081508
{
15091509
/* We rely on server to do external_lock(F_UNLCK) to reset the
1510-
srv_conc.n_active counter. Since there are no locks on instrinsic
1511-
tables, we should skip this for intrinsic temporary tables. */
1512-
if (dict_table_is_intrinsic(prebuilt->table)) {
1510+
srv_conc.n_active counter.*/
1511+
if (skip_concurrency_ticket(prebuilt)) {
15131512
return;
15141513
}
15151514

1516-
trx_t* trx = prebuilt->trx;
1515+
trx_t* trx = prebuilt->trx;
15171516
if (srv_thread_concurrency) {
15181517
if (trx->n_tickets_to_enter_innodb > 0) {
15191518

@@ -1545,9 +1544,8 @@ innobase_srv_conc_exit_innodb(
15451544
row_prebuilt_t* prebuilt)
15461545
{
15471546
/* We rely on server to do external_lock(F_UNLCK) to reset the
1548-
srv_conc.n_active counter. Since there are no locks on instrinsic
1549-
tables, we should skip this for intrinsic temporary tables. */
1550-
if (dict_table_is_intrinsic(prebuilt->table)) {
1547+
srv_conc.n_active counter.*/
1548+
if (skip_concurrency_ticket(prebuilt)) {
15511549
return;
15521550
}
15531551

@@ -1586,6 +1584,36 @@ innobase_srv_conc_force_exit_innodb(
15861584
}
15871585
}
15881586

1587+
ibool
1588+
skip_concurrency_ticket(row_prebuilt_t* prebuilt) {
1589+
/* Since there are no locks on instrinsic tables,
1590+
we should skip this for intrinsic temporary tables.*/
1591+
if (dict_table_is_intrinsic(prebuilt->table)) {
1592+
return true;
1593+
}
1594+
1595+
THD *thd = prebuilt->trx->mysql_thd;
1596+
if (thd == NULL) {
1597+
thd = current_thd;
1598+
}
1599+
/* Skip concurrency ticket in following cases
1600+
(a) while implicitly updating GTID table.This
1601+
is to avoid deadlock otherwise possible with
1602+
low innodb_thread_concurrency.
1603+
Session: RESET MASTER -> FLUSH LOGS -> get innodb ticket
1604+
-> wait for GTID flush.
1605+
GTID Background: Write to GTID table -> wait for innodb ticket.
1606+
(b) For attachable transaction. If a attachable trx in
1607+
thread asks for a ticket while the main transaction
1608+
has reserved a ticket. */
1609+
if (thd && (thd_has_active_attachable_trx(thd)
1610+
|| thd_is_operating_gtid_table_implicitly(thd))) {
1611+
return true;
1612+
}
1613+
1614+
return false;
1615+
}
1616+
15891617
/******************************************************************//**
15901618
Returns the NUL terminated value of glob_hostname.
15911619
@return pointer to glob_hostname. */

storage/innobase/handler/ha_innodb.h

+18
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,18 @@ int thd_slave_thread(const MYSQL_THD thd);
547547
@retval 1 the user thread is running a non-transactional update */
548548
int thd_non_transactional_update(const MYSQL_THD thd);
549549

550+
/** Check if the thread has attachable transaction
551+
@param thd user thread
552+
@retval 0 thd doesn't have attachable trx
553+
@retval 1 thd has attachable trx */
554+
int thd_has_active_attachable_trx(const MYSQL_THD thd);
555+
556+
/** Check if the thread is doing a gtid operation implicitly
557+
@param thd user thread
558+
@retval 0 thd is not doing any gtid implicit operation
559+
@retval 1 thd is doing gtid implicit operation */
560+
int thd_is_operating_gtid_table_implicitly(const MYSQL_THD thd);
561+
550562
/** Get the user thread's binary logging format
551563
@param thd user thread
552564
@return Value to be used as index into the binlog_format_names array */
@@ -600,6 +612,12 @@ typedef struct new_ft_info
600612
fts_result_t* ft_result;
601613
} NEW_FT_INFO;
602614

615+
/* Check if the thread can skip innodb concurrency check
616+
@param[in] row_prebuilt_t* prebuilt
617+
@return true if thread can skip innodb concurrency check*/
618+
ibool
619+
skip_concurrency_ticket(row_prebuilt_t* prebuilt);
620+
603621
/**
604622
Allocates an InnoDB transaction for a MySQL handler object.
605623
@return InnoDB transaction handle */

0 commit comments

Comments
 (0)