Skip to content

Commit

Permalink
MDEV-33593 Auto increment deadlock error causes ASSERT in subsequent …
Browse files Browse the repository at this point in the history
…save point

The issue here is ha_innobase::get_auto_increment() could cause a
deadlock involving auto-increment lock and rollback the transaction
implicitly. For such cases, storage engines usually call
thd_mark_transaction_to_rollback() to inform SQL engine about it which
in turn takes appropriate actions and close the transaction. In innodb,
we call it while converting Innodb error code to MySQL.

However, since ::innobase_get_autoinc() returns void, we skip the call
for error code conversion and also miss marking the transaction for
rollback for deadlock error. We assert eventually while releasing a
savepoint as the transaction state is not active.

Since convert_error_code_to_mysql() is handling some generic error
handling part, like invoking the callback when needed, we should call
that function in ha_innobase::get_auto_increment() even if we don't
return the resulting mysql error code back.
  • Loading branch information
mariadb-DebarunBanerjee committed Mar 7, 2024
1 parent 6e5333f commit afe9632
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 0 deletions.
55 changes: 55 additions & 0 deletions mysql-test/suite/innodb/r/autoinc_debug.result
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,58 @@ t1 CREATE TABLE `t1` (
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
DROP TABLE t1;
SET DEBUG_SYNC='RESET';
#
# MDEV-33593: Auto increment deadlock error causes ASSERT in subsequent save point
#
CREATE TABLE t1(col1 INT PRIMARY KEY AUTO_INCREMENT, col2 INT) ENGINE=InnoDB;
CREATE TABLE t2(col1 INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1(col2) values(100);
connect con1, localhost, root,,;
START TRANSACTION;
# T1: Acquiring Row X lock on table t2
INSERT INTO t2 values(100);
connect con2, localhost, root,,;
START TRANSACTION;
# T2: Wait for (T1) row lock on t2 after acquiring GAP Lock on t1
UPDATE t1 SET col2 = 20 where col1 = 10;
INSERT INTO t2 values(100);
connection default;
# T3: Wait for (T2) II row Lock on t1 after acquiring Auto Increment Lock on t1
SET DEBUG_SYNC='lock_wait_suspend_thread_enter SIGNAL t3_waiting';
INSERT INTO t1(col2) SELECT col2 from t1;
connection con1;
SAVEPOINT s1;
SET DEBUG_SYNC='now WAIT_FOR t3_waiting';
# T1: Wait for (T3) auto increment lock on t1 causing T1 -> T3 -> T2 -> T1 deadlock
SET debug_dbug = '+d,innodb_deadlock_victim_self';
INSERT INTO t1(col2) VALUES(200);
ERROR HY000: Failed to read auto-increment value from storage engine
# The transaction should have been rolled back
SELECT * FROM t1;
col1 col2
1 100
SELECT * FROM t2;
col1
# Release the previous savepoint using the same name
SAVEPOINT s1;
COMMIT;
connection con2;
COMMIT;
connection default;
COMMIT;
disconnect con1;
disconnect con2;
# Cleanup
SELECT * FROM t1;
col1 col2
1 100
2 100
DROP TABLE t1;
SELECT * FROM t2;
col1
100
DROP TABLE t2;
SET DEBUG_SYNC='RESET';
#
# End of 10.5 tests
#
64 changes: 64 additions & 0 deletions mysql-test/suite/innodb/t/autoinc_debug.test
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,67 @@ SELECT * FROM t1;
SHOW CREATE TABLE t1;
DROP TABLE t1;
SET DEBUG_SYNC='RESET';

--echo #
--echo # MDEV-33593: Auto increment deadlock error causes ASSERT in subsequent save point
--echo #

CREATE TABLE t1(col1 INT PRIMARY KEY AUTO_INCREMENT, col2 INT) ENGINE=InnoDB;
CREATE TABLE t2(col1 INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1(col2) values(100);

--connect(con1, localhost, root,,)
START TRANSACTION;
--echo # T1: Acquiring Row X lock on table t2
INSERT INTO t2 values(100);

--connect(con2, localhost, root,,)
START TRANSACTION;
--echo # T2: Wait for (T1) row lock on t2 after acquiring GAP Lock on t1
UPDATE t1 SET col2 = 20 where col1 = 10;
--send INSERT INTO t2 values(100)

--connection default
--echo # T3: Wait for (T2) II row Lock on t1 after acquiring Auto Increment Lock on t1
SET DEBUG_SYNC='lock_wait_suspend_thread_enter SIGNAL t3_waiting';
--send INSERT INTO t1(col2) SELECT col2 from t1

--connection con1
SAVEPOINT s1;
SET DEBUG_SYNC='now WAIT_FOR t3_waiting';
--echo # T1: Wait for (T3) auto increment lock on t1 causing T1 -> T3 -> T2 -> T1 deadlock
SET debug_dbug = '+d,innodb_deadlock_victim_self';
--error ER_AUTOINC_READ_FAILED
INSERT INTO t1(col2) VALUES(200);

--echo # The transaction should have been rolled back
SELECT * FROM t1;
SELECT * FROM t2;

--echo # Release the previous savepoint using the same name
SAVEPOINT s1;
COMMIT;

--connection con2
--reap
COMMIT;

--connection default
--reap
COMMIT;

--disconnect con1
--disconnect con2

--echo # Cleanup
SELECT * FROM t1;
DROP TABLE t1;

SELECT * FROM t2;
DROP TABLE t2;

SET DEBUG_SYNC='RESET';

--echo #
--echo # End of 10.5 tests
--echo #
7 changes: 7 additions & 0 deletions storage/innobase/handler/ha_innodb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16655,6 +16655,13 @@ ha_innobase::get_auto_increment(

if (error != DB_SUCCESS) {
*first_value = (~(ulonglong) 0);
/* This is an error case. We do the error handling by calling
the error code conversion function. Specifically, we need to
call thd_mark_transaction_to_rollback() to inform sql that we
have rolled back innodb transaction after a deadlock error. We
ignore the returned mysql error code here. */
std::ignore = convert_error_code_to_mysql(
error, m_prebuilt->table->flags, m_user_thd);
return;
}

Expand Down
2 changes: 2 additions & 0 deletions storage/innobase/lock/lock0lock.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6712,6 +6712,8 @@ DeadlockChecker::select_victim() const
ut_ad(m_start->lock.wait_lock != 0);
ut_ad(m_wait_lock->trx != m_start);

DBUG_EXECUTE_IF("innodb_deadlock_victim_self", return m_start;);

if (trx_weight_ge(m_wait_lock->trx, m_start)) {
/* The joining transaction is 'smaller',
choose it as the victim and roll it back. */
Expand Down

0 comments on commit afe9632

Please sign in to comment.