[MDEV-21205] Assertion failure in btr_sec_min_rec_mark Created: 2019-12-03  Updated: 2019-12-04  Resolved: 2019-12-04

Status: Closed
Project: MariaDB Server
Component/s: Storage Engine - InnoDB
Affects Version/s: 10.5
Fix Version/s: 10.5.1

Type: Bug Priority: Critical
Reporter: Marko Mäkelä Assignee: Marko Mäkelä
Resolution: Fixed Votes: 0
Labels: None

Issue Links:
Problem/Incident
is caused by MDEV-21174 Refactor mlog_write_ulint, mlog_memse... Closed

 Description   

Observed on kvm-asan:

10.5 2ac0e64cadcff55b8d4b8adcca3895443b2727a8

innodb_zip.page_size '4k,innodb,strict_crc32' w3 [ fail ]
        Test ended at 2019-12-03 07:49:25
 
CURRENT_TEST: innodb_zip.page_size
mysqltest: At line 787: query 'DROP TABLE t1' failed: 2013: Lost connection to MySQL server during query
Version: '10.5.0-MariaDB-debug-log'  socket: '/dev/shm/var/tmp/3/mysqld.1.sock'  port: 16040  Source distribution
2019-12-03  7:49:20 4 [ERROR] InnoDB: Cannot add field `c10` in table `test`.`t1` because after adding it, the row size is 1979 which is greater than maximum allowed size (1979 bytes) for a record on index leaf page.
2019-12-03  7:49:20 4 [ERROR] InnoDB: Cannot add field `c10` in table `test`.`t1` because after adding it, the row size is 1982 which is greater than maximum allowed size (1982 bytes) for a record on index leaf page.
2019-12-03  7:49:20 4 [ERROR] InnoDB: Cannot add field `c10` in table `test`.`t1` because after adding it, the row size is 1902 which is greater than maximum allowed size (1900 bytes) for a record on index leaf page.
2019-12-03  7:49:20 4 [ERROR] InnoDB: Cannot add field `c10` in table `test`.`t1` because after adding it, the row size is 1982 which is greater than maximum allowed size (1982 bytes) for a record on index leaf page.
mysqld: /home/buildbot/buildbot/build/mariadb-10.5.0/storage/innobase/include/btr0btr.h:533: void btr_set_min_rec_mark(rec_t*, const buf_block_t&, mtr_t*): Assertion `block.frame == page_align(rec)' failed.
191203  7:49:22 [ERROR] mysqld got signal 6 ;
include/btr0btr.h:534(btr_set_min_rec_mark(unsigned char*, buf_block_t const&, mtr_t*))[0x226537f]
btr/btr0btr.cc:4188(btr_discard_page(btr_cur_t*, mtr_t*))[0x22172ce]
btr/btr0cur.cc:5973(btr_cur_pessimistic_delete(dberr_t*, unsigned long, btr_cur_t*, unsigned long, bool, mtr_t*))[0x2264889]
btr/btr0cur.cc:6108(btr_cur_node_ptr_delete(btr_cur_t*, mtr_t*))[0x20a997a]
btr/btr0btr.cc:4195(btr_discard_page(btr_cur_t*, mtr_t*))[0x20aa8db]
row/row0purge.cc:474(row_purge_remove_sec_if_poss_tree(purge_node_t*, dict_index_t*, dtuple_t const*))[0x20aad31]
row/row0purge.cc:699(row_purge_remove_sec_if_poss(purge_node_t*, dict_index_t*, dtuple_t const*))[0x20ae1b9]
row/row0purge.cc:766(row_purge_del_mark(purge_node_t*))[0x20ae8e6]
row/row0purge.cc:1196(row_purge_record_func(purge_node_t*, unsigned char*, que_thr_t const*, bool))[0x20aed1f]



 Comments   
Comment by Marko Mäkelä [ 2019-12-04 ]

Waiting for purge before the DROP TABLE dramatically improves reproducibility:

diff --git a/mysql-test/suite/innodb_zip/r/page_size.result b/mysql-test/suite/innodb_zip/r/page_size.result
index e65a57326ec..c823c9d0d3f 100644
--- a/mysql-test/suite/innodb_zip/r/page_size.result
+++ b/mysql-test/suite/innodb_zip/r/page_size.result
@@ -566,6 +566,7 @@ SET @r = repeat('e', 48);
 INSERT INTO t1 VALUES(@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,
 @r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r);
 DELETE from t1;
+InnoDB		0 transactions not purged
 DROP TABLE t1;
 CREATE TABLE t1(
 c text NOT NULL, d text NOT NULL,
diff --git a/mysql-test/suite/innodb_zip/t/page_size.test b/mysql-test/suite/innodb_zip/t/page_size.test
index 0faf4428f62..c8d2a3c4d2a 100644
--- a/mysql-test/suite/innodb_zip/t/page_size.test
+++ b/mysql-test/suite/innodb_zip/t/page_size.test
@@ -3,6 +3,8 @@
 SET default_storage_engine=InnoDB;
 
 --disable_query_log
+SET @save_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency;
+SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
 let $MYSQLD_DATADIR = `select @@datadir`;
 let $INNODB_PAGE_SIZE = `select @@innodb_page_size`;
 
@@ -821,6 +823,7 @@ SET @r = repeat('e', 48);
 INSERT INTO t1 VALUES(@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,
                       @r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r);
 DELETE from t1;
+--source ../../innodb/include/wait_all_purged.inc
 DROP TABLE t1;
 
 #-- disable_query_log
@@ -872,3 +875,6 @@ CREATE TABLE t1(c text, PRIMARY KEY (c(438)))
 ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=1 CHARSET=ASCII;
 INSERT INTO t1 VALUES(REPEAT('A',512)),(REPEAT('B',512));
 DROP TABLE t1;
+--disable_query_log
+SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_frequency;
+--enable_query_log

It will still not fail every time. For a crash that I reproduced, node_ptr-block->frame in btr_discard_page() was 8351, which is more than the configured innodb_page_size=4k.

It requires a high concurrency to fail. I was also able to reproduce it for the 4k,full_crc32 combination.

Comment by Marko Mäkelä [ 2019-12-04 ]

There were two bugs in the code. The second one is an assertion that would fail if we had passed the correct parameter to the function call:

diff --git a/storage/innobase/btr/btr0btr.cc b/storage/innobase/btr/btr0btr.cc
--- a/storage/innobase/btr/btr0btr.cc
+++ b/storage/innobase/btr/btr0btr.cc
@@ -4182,7 +4178,7 @@ btr_discard_page(
 		because everything will take place within a single
 		mini-transaction and because writing to the redo log
 		is an atomic operation (performed by mtr_commit()). */
-		btr_set_min_rec_mark(node_ptr, *block, mtr);
+		btr_set_min_rec_mark(node_ptr, *merge_block, mtr);
 	}
 
 	if (dict_index_is_spatial(index)) {
diff --git a/storage/innobase/include/btr0btr.h b/storage/innobase/include/btr0btr.h
--- a/storage/innobase/include/btr0btr.h
+++ b/storage/innobase/include/btr0btr.h
@@ -532,7 +532,6 @@ inline void btr_set_min_rec_mark(rec_t *rec, const buf_block_t &block,
 {
   ut_ad(block.frame == page_align(rec));
   ut_ad(!page_is_leaf(block.frame));
-  ut_ad(!page_has_prev(block.frame));
 
   rec-= page_rec_is_comp(rec) ? REC_NEW_INFO_BITS : REC_OLD_INFO_BITS;
 

The assertion would fail, because only a subsequent call to btr_level_list_remove() in the same mini-transaction would make merge_block the leftmost on its level. I think that we can retain the assertion by refactoring the code a little more.

Generated at Thu Feb 08 09:05:22 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.