From 00d2c7f7f480c58c5e406c7c1a875f83bd3bb1fc Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Wed, 10 Jul 2024 19:34:53 +0530 Subject: [PATCH] MDEV-34542 Assertion `lock_trx_has_sys_table_locks(trx) == __null' failed in void row_mysql_unfreeze_data_dictionary(trx_t*) - During XA PREPARE, InnoDB releases the non-exclusive locks. But it fails to remove the non-exclusive table lock from the transaction table locks. In the mean time, main thread evicts the table from the LRU cache. While rollbacking the XA transaction, InnoDB iterates through the table locks to check whether it holds lock on any system tables and wrongly assumes the evicted table as system table since the table id is 0 Fix: === During XA PREPARE, remove the table locks of the transaction while releasing the non-exclusive locks. --- mysql-test/suite/innodb/r/lock_release.result | 23 +++ mysql-test/suite/innodb/t/lock_release.test | 26 +++ storage/innobase/lock/lock0lock.cc | 149 +++++++++--------- 3 files changed, 124 insertions(+), 74 deletions(-) create mode 100644 mysql-test/suite/innodb/r/lock_release.result create mode 100644 mysql-test/suite/innodb/t/lock_release.test diff --git a/mysql-test/suite/innodb/r/lock_release.result b/mysql-test/suite/innodb/r/lock_release.result new file mode 100644 index 00000000000..6bafe79a36c --- /dev/null +++ b/mysql-test/suite/innodb/r/lock_release.result @@ -0,0 +1,23 @@ +# +# MDEV-34542 Assertion `lock_trx_has_sys_table_locks(trx) == __null' +# failed in void row_mysql_unfreeze_data_dictionary(trx_t*) +# +# +CREATE TABLE t1 (c1 CHAR(1) ,c2 INT) ENGINE=INNODB +PARTITION BY LINEAR HASH ((c2)) PARTITIONS 512; +CREATE TABLE t2 (a INT) ENGINE=INNODB; +set @old_table_open_cache= @@table_open_cache; +XA START 'a'; +INSERT INTO mysql.innodb_index_stats SELECT * FROM mysql.innodb_index_stats WHERE table_name=''; +SET GLOBAL table_open_cache=10; +INSERT into t2 (a) VALUES (1); +SELECT * FROM t1; +c1 c2 +XA END 'a'; +XA PREPARE 'a'; +SELECT sleep(3); +sleep(3) +0 +XA ROLLBACK 'a'; +DROP TABLE t1, t2; +SET GLOBAL table_open_cache=@old_table_open_cache; diff --git a/mysql-test/suite/innodb/t/lock_release.test b/mysql-test/suite/innodb/t/lock_release.test new file mode 100644 index 00000000000..6620bf69d14 --- /dev/null +++ b/mysql-test/suite/innodb/t/lock_release.test @@ -0,0 +1,26 @@ +--source include/have_innodb.inc +--source include/have_partition.inc + +--echo # +--echo # MDEV-34542 Assertion `lock_trx_has_sys_table_locks(trx) == __null' +--echo # failed in void row_mysql_unfreeze_data_dictionary(trx_t*) +--echo # +--echo # +CREATE TABLE t1 (c1 CHAR(1) ,c2 INT) ENGINE=INNODB + PARTITION BY LINEAR HASH ((c2)) PARTITIONS 512; +CREATE TABLE t2 (a INT) ENGINE=INNODB; + +set @old_table_open_cache= @@table_open_cache; +XA START 'a'; +INSERT INTO mysql.innodb_index_stats SELECT * FROM mysql.innodb_index_stats WHERE table_name=''; +SET GLOBAL table_open_cache=10; +INSERT into t2 (a) VALUES (1); +SELECT * FROM t1; +XA END 'a'; +XA PREPARE 'a'; +# Added sleep to make sure that InnoDB main thread is to remove +# the innodb_index_stats from table cache +SELECT sleep(3); +XA ROLLBACK 'a'; +DROP TABLE t1, t2; +SET GLOBAL table_open_cache=@old_table_open_cache; diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index d0b4105d9c6..3dd4b5fa6f6 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -4382,80 +4382,6 @@ static void lock_rec_unlock_supremum(lock_t *lock) trx_mutex_exit(lock->trx); } -/** Release non-exclusive locks on XA PREPARE, -and release possible other transactions waiting because of these locks. */ -void lock_release_on_prepare(trx_t *trx) -{ - trx->set_skip_lock_inheritance(); - - ulint count= 0; - lock_mutex_enter(); - ut_ad(!trx_mutex_own(trx)); - - for (lock_t *lock= UT_LIST_GET_LAST(trx->lock.trx_locks); lock; ) - { - ut_ad(lock->trx == trx); - - if (lock_get_type_low(lock) == LOCK_REC) - { - ut_ad(!lock->index->table->is_temporary()); - if ((lock->type_mode & (LOCK_MODE_MASK | LOCK_GAP)) != LOCK_X) - lock_rec_dequeue_from_page(lock); - else if (lock_rec_get_nth_bit(lock, PAGE_HEAP_NO_SUPREMUM)) - lock_rec_unlock_supremum(lock); - else - { - ut_ad(trx->dict_operation || - lock->index->table->id >= DICT_HDR_FIRST_ID); - ut_ad(lock->trx->isolation_level > TRX_ISO_READ_COMMITTED || - /* Insert-intention lock is valid for supremum for isolation - level > TRX_ISO_READ_COMMITTED */ - lock_get_mode(lock) == LOCK_X || - !lock_rec_get_nth_bit(lock, PAGE_HEAP_NO_SUPREMUM)); -retain_lock: - lock= UT_LIST_GET_PREV(trx_locks, lock); - continue; - } - } - else - { - ut_ad(lock_get_type_low(lock) & LOCK_TABLE); - ut_d(dict_table_t *table= lock->un_member.tab_lock.table); - ut_ad(!table->is_temporary()); - - switch (lock_get_mode(lock)) { - case LOCK_IS: - case LOCK_S: - lock_table_dequeue(lock); - break; - case LOCK_IX: - case LOCK_X: - ut_ad(table->id >= DICT_HDR_FIRST_ID || trx->dict_operation); - /* fall through */ - default: - goto retain_lock; - } - } - - if (++count == LOCK_RELEASE_INTERVAL) - { - lock_mutex_exit(); - count= 0; - lock_mutex_enter(); - } - - lock= UT_LIST_GET_LAST(trx->lock.trx_locks); - } - - lock_mutex_exit(); - -} - -/* True if a lock mode is S or X */ -#define IS_LOCK_S_OR_X(lock) \ - (lock_get_mode(lock) == LOCK_S \ - || lock_get_mode(lock) == LOCK_X) - /*********************************************************************//** Removes table locks of the transaction on a table to be dropped. */ static @@ -4502,6 +4428,81 @@ lock_trx_table_locks_remove( ut_error; } +/** Release non-exclusive locks on XA PREPARE, +and release possible other transactions waiting because of these locks. */ +void lock_release_on_prepare(trx_t *trx) +{ + trx->set_skip_lock_inheritance(); + + ulint count= 0; + lock_mutex_enter(); + ut_ad(!trx_mutex_own(trx)); + + for (lock_t *lock= UT_LIST_GET_LAST(trx->lock.trx_locks); lock; ) + { + ut_ad(lock->trx == trx); + + if (lock_get_type_low(lock) == LOCK_REC) + { + ut_ad(!lock->index->table->is_temporary()); + if ((lock->type_mode & (LOCK_MODE_MASK | LOCK_GAP)) != LOCK_X) + lock_rec_dequeue_from_page(lock); + else if (lock_rec_get_nth_bit(lock, PAGE_HEAP_NO_SUPREMUM)) + lock_rec_unlock_supremum(lock); + else + { + ut_ad(trx->dict_operation || + lock->index->table->id >= DICT_HDR_FIRST_ID); + ut_ad(lock->trx->isolation_level > TRX_ISO_READ_COMMITTED || + /* Insert-intention lock is valid for supremum for isolation + level > TRX_ISO_READ_COMMITTED */ + lock_get_mode(lock) == LOCK_X || + !lock_rec_get_nth_bit(lock, PAGE_HEAP_NO_SUPREMUM)); +retain_lock: + lock= UT_LIST_GET_PREV(trx_locks, lock); + continue; + } + } + else + { + ut_ad(lock_get_type_low(lock) & LOCK_TABLE); + ut_d(dict_table_t *table= lock->un_member.tab_lock.table); + ut_ad(!table->is_temporary()); + + switch (lock_get_mode(lock)) { + case LOCK_IS: + case LOCK_S: + lock_table_dequeue(lock); + lock_trx_table_locks_remove(lock); + break; + case LOCK_IX: + case LOCK_X: + ut_ad(table->id >= DICT_HDR_FIRST_ID || trx->dict_operation); + /* fall through */ + default: + goto retain_lock; + } + } + + if (++count == LOCK_RELEASE_INTERVAL) + { + lock_mutex_exit(); + count= 0; + lock_mutex_enter(); + } + + lock= UT_LIST_GET_LAST(trx->lock.trx_locks); + } + + lock_mutex_exit(); + +} + +/* True if a lock mode is S or X */ +#define IS_LOCK_S_OR_X(lock) \ + (lock_get_mode(lock) == LOCK_S \ + || lock_get_mode(lock) == LOCK_X) + /*===================== VALIDATION AND DEBUGGING ====================*/ /** Print info of a table lock.