Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-21205

Assertion failure in btr_sec_min_rec_mark

Details

    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]
      

      Attachments

        Issue Links

          Activity

            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.

            marko Marko Mäkelä added a comment - 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.

            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.

            marko Marko Mäkelä added a comment - 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.

            People

              marko Marko Mäkelä
              marko Marko Mäkelä
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Git Integration

                  Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.