From fcc3e305470da27f323863cac9e866d6d5042cdc Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Fri, 17 May 2024 02:06:24 +0530 Subject: [PATCH 01/13] Update extra version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 49c8c0d2dc2c..749facb4e961 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=5 MYSQL_VERSION_MINOR=7 MYSQL_VERSION_PATCH=44 -MYSQL_VERSION_EXTRA= +MYSQL_VERSION_EXTRA=-post-eol-1 From 84d1ae9ccd97050a53a75f70d48123c29ffba700 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 9 May 2024 13:43:22 +0530 Subject: [PATCH 02/13] PS-9174 Issue in mysqldump (mysql dump utility) https://perconadev.atlassian.net/browse/PS-9174 Bug#36248967: mysql/mysql-server@f351ea92a5a mysql/mysql-server@c334b7e5f02 Problem: mysqldump not sanitizing the version string obtained from server which may lead to injecting malicious commands to the output. Fix: added function sanitizing the version string by cutting off illegal part and issuing warning. Test: check the server version in the output with and without injected payload. Change-Id: I1f19e1c90bdb8d444285e427092face3bb16da01 --- client/mysqldump.c | 40 ++++++++++++++++++++++-- mysql-test/r/mysqldump_debug_bugs.result | 14 +++++++++ mysql-test/t/mysqldump_debug_bugs.test | 30 ++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 mysql-test/r/mysqldump_debug_bugs.result create mode 100644 mysql-test/t/mysqldump_debug_bugs.test diff --git a/client/mysqldump.c b/client/mysqldump.c index e96df78c5f6c..4a83e8d38567 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -1,5 +1,5 @@ /* - Copyright (c) 2000, 2023, Oracle and/or its affiliates. + Copyright (c) 2000, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -701,6 +701,39 @@ static void short_usage(void) printf("For more options, use %s --help\n", my_progname); } +static void get_safe_server_info(char *safe_server_info, + size_t safe_server_info_len) { + const char *server_info = mysql_get_server_info(&mysql_connection); + size_t i; + if (server_info == NULL) { + safe_server_info[0] = 0; + return; + } + DBUG_EXECUTE_IF("server_version_injection_test", + { + const char *payload= "5.7.0-injection_test\n\\! touch /tmp/xxx"; + server_info= payload; + }); + for (i = 0; i < safe_server_info_len; ++i) { + // End of string. + if (server_info[i] == 0) + { + safe_server_info[i]= 0; + return; + } + // Version may include only alphanumeric and punctuation characters. + // Cut off the rest of the string if incorrect character found. + if (!(my_isalnum(charset_info, server_info[i]) || my_ispunct(charset_info, server_info[i]))) + { + safe_server_info[i] = 0; + fprintf(stderr, + "-- Warning: version string returned by server is incorrect.\n"); + return; + } + safe_server_info[i]= server_info[i]; + } + safe_server_info[safe_server_info_len - 1] = 0; +} static void write_header(FILE *sql_file, char *db_name) { @@ -721,6 +754,8 @@ static void write_header(FILE *sql_file, char *db_name) { my_bool freemem= FALSE; char const* text= fix_identifier_with_newline(db_name, &freemem); + char safe_server_info[SERVER_VERSION_LENGTH]; + get_safe_server_info(safe_server_info, SERVER_VERSION_LENGTH); print_comment(sql_file, 0, "-- MySQL dump %s Distrib %s, for %s (%s)\n--\n", @@ -736,8 +771,7 @@ static void write_header(FILE *sql_file, char *db_name) print_comment(sql_file, 0, "-- ------------------------------------------------------\n" ); - print_comment(sql_file, 0, "-- Server version\t%s\n", - mysql_get_server_info(&mysql_connection)); + print_comment(sql_file, 0, "-- Server version\t%s\n", safe_server_info); if (opt_set_charset) fprintf(sql_file, diff --git a/mysql-test/r/mysqldump_debug_bugs.result b/mysql-test/r/mysqldump_debug_bugs.result new file mode 100644 index 000000000000..872bc0e8d37c --- /dev/null +++ b/mysql-test/r/mysqldump_debug_bugs.result @@ -0,0 +1,14 @@ +# +# Bug#36248967: Security issue in mysqldump (mysql dump utility) +# +CREATE DATABASE test_bug36248967; +-- Run mysqldump with payload injected to server version. +A warning must be issued. +-- Warning: version string returned by server is incorrect. +The test version must be found: +Pattern found. +The test injected string must not be found: +Pattern not found. +-- Run mysqldump with correct server version. +A warning must not be issued. +DROP DATABASE test_bug36248967; diff --git a/mysql-test/t/mysqldump_debug_bugs.test b/mysql-test/t/mysqldump_debug_bugs.test new file mode 100644 index 000000000000..7bfca08978af --- /dev/null +++ b/mysql-test/t/mysqldump_debug_bugs.test @@ -0,0 +1,30 @@ +--source include/mysql_have_debug.inc + +--echo # +--echo # Bug#36248967: Security issue in mysqldump (mysql dump utility) +--echo # + +let $grep_file=$MYSQLTEST_VARDIR/tmp/bug36248967.sql; +let $grep_output=boolean; + +CREATE DATABASE test_bug36248967; + +--echo -- Run mysqldump with payload injected to server version. +--echo A warning must be issued. +--exec $MYSQL_DUMP --debug="d,server_version_injection_test" --result-file=$grep_file test_bug36248967 2>&1 + +--echo The test version must be found: +let $grep_pattern=5.7.0-injection_test; +--source include/grep_pattern.inc + +--echo The test injected string must not be found: +let $grep_pattern=\\! touch /tmp/xxx; +--source include/grep_pattern.inc + +--echo -- Run mysqldump with correct server version. +--exec $MYSQL_DUMP --result-file=$grep_file test_bug36248967 2>&1 +--echo A warning must not be issued. + +#Cleanup +--remove_file $grep_file +DROP DATABASE test_bug36248967; From 4ca7f7c0d6a1cff36934e3df8cdd8ef3ef71ce99 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 9 May 2024 13:46:47 +0530 Subject: [PATCH 03/13] PS-9174 Assertion Failure in /mysql-8.0.34/sql/field.cc:7119 https://perconadev.atlassian.net/browse/PS-9174 Bug#35846221 mysql/mysql-server@3cd7cd2066f Problem is due to missing implementation of Item_func_make_set::fix_after_pullout(), which makes this particular MAKE_SET function be regarded as const and may thus be evaluated during resolving. Fixed by implementing a proper fix_after_pullout() function. Change-Id: I7094869588ce4133c4a925e1a237a37866a5bb3c (cherry picked from commit a9f0b388adeef837811fdba2bce2e4ba5b06863b) --- sql/item_strfunc.cc | 30 ++++++++++++++++++++++++++++++ sql/item_strfunc.h | 12 +++--------- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 2e020f0778b7..653862c953c7 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -3310,6 +3310,36 @@ void Item_func_make_set::fix_length_and_dec() with_sum_func= with_sum_func || item->with_sum_func; } +bool Item_func_make_set::fix_fields(THD *thd, Item **ref) { + assert(!fixed); + if (!item->fixed && item->fix_fields(thd, &item)) { + return true; + } + if (item->check_cols(1)) { + return true; + } + if (Item_func::fix_fields(thd, ref)) { + return true; + } + if (item->maybe_null) { + maybe_null = true; + } + + used_tables_cache|= item->used_tables(); + not_null_tables_cache|= item->not_null_tables(); + const_item_cache&= item->const_item(); + + return false; +} + +void Item_func_make_set::fix_after_pullout(st_select_lex *parent_select, + st_select_lex *removed_select) { + Item_func::fix_after_pullout(parent_select, removed_select); + item->fix_after_pullout(parent_select, removed_select); + used_tables_cache|= item->used_tables(); + not_null_tables_cache|= item->not_null_tables(); + const_item_cache&= item->const_item(); +} void Item_func_make_set::update_used_tables() { diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 5cd0c9eaa34a..d0342d3a7fb8 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -834,15 +834,9 @@ class Item_func_make_set :public Item_str_func virtual bool itemize(Parse_context *pc, Item **res); String *val_str(String *str); - bool fix_fields(THD *thd, Item **ref) - { - assert(fixed == 0); - bool res= ((!item->fixed && item->fix_fields(thd, &item)) || - item->check_cols(1) || - Item_func::fix_fields(thd, ref)); - maybe_null|= item->maybe_null; - return res; - } + bool fix_fields(THD *thd, Item **ref); + void fix_after_pullout(st_select_lex *parent_select, + st_select_lex *removed_select); void split_sum_func(THD *thd, Ref_ptr_array ref_pointer_array, List &fields); void fix_length_and_dec(); From 1fbc8ac9de6dd0b37a90326ee7ce314b94167ec8 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 9 May 2024 13:53:35 +0530 Subject: [PATCH 04/13] PS-9174 Failure in Protocol_classic::send_field_metadata https://perconadev.atlassian.net/browse/PS-9174 Bug#35904044 mysql/mysql-server@271dcf231d0 There may be a failure when returning metadata to the client for certain SQL queries involving dynamic parameters and subqueries in a SELECT clause. The fix is to avoid setting an item name that is a NULL pointer. Change-Id: I1abe206f97060c218de1ae23c63a4da80ffaaae5 --- sql/item_subselect.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 5569cabb9008..60a24bb7a829 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -439,7 +439,10 @@ bool Item_subselect::fix_fields(THD *thd, Item **ref) { int ret= 0; (*ref)= substitution; - substitution->item_name= item_name; + if (item_name.is_set()) + { + substitution->item_name = item_name; + } if (have_to_be_excluded) engine->exclude(); substitution= 0; From 02f4c853698b25cf23ea3a31067cf9da40177fcf Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 15 May 2024 18:32:29 +0530 Subject: [PATCH 05/13] PS-9174 Incorrect results when using group by loose index scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://perconadev.atlassian.net/browse/PS-9174 Bug#35854362 mysql/mysql-server@c7e824d18f7 Description: - Indexes are ordered based on their keys. Loose index scan effectively jumps from one unique value (or set of values) to the next based on the index’s prefix keys. - To “jump” values in an index, we use the handler call: ha_index_read_map(). - the first range read sets an end-of-range value to indicate the end of the first range. - The next range read does not clear the previous end-of-range value and applies it to the current range. - Since the end-of-range value has already been crossed in the previous range read, this causes the reads to stop. So the iteration is finished with the current range without moving onto the next range(unique set of values)resulting in an incorrect query result. Fix: - In order to find the next unique value, the old end-of-range value is cleared. Change-Id: I84290fb794db13ec6f0795dd14a92cf85b9dad09 --- mysql-test/r/select_distinct_debug.result | 74 +++++++++++++++++++ mysql-test/t/select_distinct_debug.test | 88 +++++++++++++++++++++++ sql/opt_range.cc | 2 + 3 files changed, 164 insertions(+) create mode 100644 mysql-test/r/select_distinct_debug.result create mode 100644 mysql-test/t/select_distinct_debug.test diff --git a/mysql-test/r/select_distinct_debug.result b/mysql-test/r/select_distinct_debug.result new file mode 100644 index 000000000000..eccf82b9bf7e --- /dev/null +++ b/mysql-test/r/select_distinct_debug.result @@ -0,0 +1,74 @@ +# +# Bug #35854362: INCORRECT RESULTS WHEN USING GROUP BY LOOSE +# INDEX SCAN +# +CREATE TABLE t1 ( +a1 CHAR(64), +a2 CHAR(64), +b CHAR(16), +c CHAR(16) NOT NULL, +d CHAR(16), +dummy CHAR(248) DEFAULT ' ' + ); +INSERT INTO t1 (a1, a2, b, c, d) VALUES +('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'), +('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'), +('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'), +('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'), +('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'), +('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'), +('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'), +('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'), +('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'), +('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'), +('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'), +('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'), +('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'), +('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'), +('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'), +('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4'), +('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'), +('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'), +('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'), +('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'), +('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'), +('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'), +('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'), +('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'), +('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'), +('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'), +('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'), +('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'), +('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'), +('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'), +('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'), +('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4'); +CREATE INDEX idx_t1_0 ON t1 (a1); +CREATE INDEX idx_t1_1 ON t1 (a1,a2,b,c); +CREATE INDEX idx_t1_2 on t1 (a1,a2,b); +CREATE PROCEDURE insert_multiple_rows(IN num_rows INT) +BEGIN +DECLARE counter INT DEFAULT 0; +WHILE counter < num_rows DO +INSERT INTO t1 (a1, a2, b, c, d, dummy) +VALUES ('b', 'a', 'a', 'a211', 'xy1', ''); +SET counter = counter + 1; +END WHILE; +END $$ +CALL insert_multiple_rows(1000); +SELECT @@innodb_purge_stop_now INTO @old_val; +SET GLOBAL innodb_purge_stop_now = 1; +DELETE FROM t1 WHERE a1 = 'b'; +SET SESSION DEBUG="+d,force_lis_for_group_by"; +SELECT DISTINCT a1 +FROM t1 +WHERE a1 IN ('a', 'd') +AND a2 = 'b'; +a1 +a +d +SET SESSION DEBUG="-d,force_lis_for_group_by"; +SET GLOBAL innodb_purge_stop_now = @old_val; +SET GLOBAL innodb_purge_run_now=1; +DROP PROCEDURE insert_multiple_rows; +DROP TABLE t1; diff --git a/mysql-test/t/select_distinct_debug.test b/mysql-test/t/select_distinct_debug.test new file mode 100644 index 000000000000..6a6a9c0cdad9 --- /dev/null +++ b/mysql-test/t/select_distinct_debug.test @@ -0,0 +1,88 @@ +# for innodb_purge_stop_now=ON +--source include/have_debug.inc + +--echo # +--echo # Bug #35854362: INCORRECT RESULTS WHEN USING GROUP BY LOOSE +--echo # INDEX SCAN +--echo # + +CREATE TABLE t1 ( + a1 CHAR(64), + a2 CHAR(64), + b CHAR(16), + c CHAR(16) NOT NULL, + d CHAR(16), + dummy CHAR(248) DEFAULT ' ' + ); + +INSERT INTO t1 (a1, a2, b, c, d) VALUES +('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'), +('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'), +('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'), +('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'), +('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'), +('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'), +('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'), +('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'), +('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'), +('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'), +('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'), +('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'), +('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'), +('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'), +('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'), +('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4'), +('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'), +('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'), +('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'), +('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'), +('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'), +('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'), +('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'), +('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'), +('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'), +('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'), +('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'), +('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'), +('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'), +('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'), +('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'), +('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4'); + +CREATE INDEX idx_t1_0 ON t1 (a1); +CREATE INDEX idx_t1_1 ON t1 (a1,a2,b,c); +CREATE INDEX idx_t1_2 on t1 (a1,a2,b); + +DELIMITER $$; + +CREATE PROCEDURE insert_multiple_rows(IN num_rows INT) +BEGIN + DECLARE counter INT DEFAULT 0; + WHILE counter < num_rows DO + INSERT INTO t1 (a1, a2, b, c, d, dummy) + VALUES ('b', 'a', 'a', 'a211', 'xy1', ''); + SET counter = counter + 1; + END WHILE; +END $$ + +DELIMITER ;$$ + +CALL insert_multiple_rows(1000); + +SELECT @@innodb_purge_stop_now INTO @old_val; +SET GLOBAL innodb_purge_stop_now = 1; + +DELETE FROM t1 WHERE a1 = 'b'; + +SET SESSION DEBUG="+d,force_lis_for_group_by"; +SELECT DISTINCT a1 +FROM t1 +WHERE a1 IN ('a', 'd') + AND a2 = 'b'; + +SET SESSION DEBUG="-d,force_lis_for_group_by"; +SET GLOBAL innodb_purge_stop_now = @old_val; +SET GLOBAL innodb_purge_run_now=1; + +DROP PROCEDURE insert_multiple_rows; +DROP TABLE t1; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 4eaf49fad382..ace361d79096 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -3073,6 +3073,7 @@ int test_quick_select(THD *thd, key_map keys_to_use, group_trp= get_best_group_min_max(¶m, tree, &best_cost); if (group_trp) { + DBUG_EXECUTE_IF("force_lis_for_group_by", group_trp->cost_est.reset();); param.table->quick_condition_rows= min(group_trp->records, head->file->stats.records); Opt_trace_object grp_summary(trace, @@ -11302,6 +11303,7 @@ int QUICK_RANGE_SELECT::get_next_prefix(uint prefix_length, { /* Read the next record in the same range with prefix after cur_prefix. */ assert(cur_prefix != NULL); + file->set_end_range(NULL, handler::RANGE_SCAN_ASC); result= file->ha_index_read_map(record, cur_prefix, keypart_map, HA_READ_AFTER_KEY); if (result || last_range->max_keypart_map == 0) From 359405b27b4136421b4ba193f3e36b2320aaa6b6 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Tue, 14 May 2024 23:24:49 +0530 Subject: [PATCH 06/13] PS-9174 Signal 11 seen in Gtid_set::~Gtid_set https://perconadev.atlassian.net/browse/PS-9174 BUG#36093405 mysql/mysql-server@6467f70f615 Group Replication maintains a memory structure that keeps track of transactions accepted to commit but not committed on all members yet. This structure, named certification info, is used to detect conflicts and dependencies between transactions. The certification info is cleaned periodically and on Group Replication stop. There was a race identified between these two operations, more precisely: 1) Certifier::garbage_collect() -> while (it != certification_info.end()) { if (it->second->is_subset_not_equals(stable_gtid_set)) { if (it->second->unlink() == 0) delete it->second; 2) Certifier::~Certifier() -> clear_certification_info(); -> for (Certification_info::iterator it = certification_info.begin(); it != certification_info.end(); ++it) { if (it->second->unlink() == 0) delete it->second; `clear_certification_info()` was being called without securing exclusive access to `certification_info` which could cause concurrent access to its items, more precisely `delete it->second`. To solve the above issue, `~Certifier()` (like all other callers) do secure the exclusive access to certification info. Change-Id: I28111d41adb54248d90137ee9d2c17196de045e8 --- .../group_replication/include/certifier.h | 4 +- .../plugin/group_replication/src/certifier.cc | 66 +++++++++++++++++-- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/rapid/plugin/group_replication/include/certifier.h b/rapid/plugin/group_replication/include/certifier.h index d6baffba1068..cac3e221f865 100644 --- a/rapid/plugin/group_replication/include/certifier.h +++ b/rapid/plugin/group_replication/include/certifier.h @@ -433,7 +433,7 @@ class Certifier: public Certifier_interface /** Is certifier initialized. */ - bool initialized; + int32 initialized; /** Variable to store the sidno used for transactions which will be logged @@ -540,7 +540,7 @@ class Certifier: public Certifier_interface bool inline is_initialized() { - return initialized; + return my_atomic_load32(&initialized); } void clear_certification_info(); diff --git a/rapid/plugin/group_replication/src/certifier.cc b/rapid/plugin/group_replication/src/certifier.cc index 41b4700883a4..84bfcfedcfe7 100644 --- a/rapid/plugin/group_replication/src/certifier.cc +++ b/rapid/plugin/group_replication/src/certifier.cc @@ -255,8 +255,7 @@ int Certifier_broadcast_thread::broadcast_gtid_executed() Certifier::Certifier() - :initialized(false), - positive_cert(0), negative_cert(0), + :positive_cert(0), negative_cert(0), parallel_applier_last_committed_global(1), parallel_applier_sequence_number(2), certifying_already_applied_transactions(false), @@ -264,6 +263,7 @@ Certifier::Certifier() gtids_assigned_in_blocks_counter(1), conflict_detection_enable(!local_member_info->in_primary_mode()) { + my_atomic_store32(&initialized, 0); last_conflict_free_transaction.clear(); #if !defined(NDEBUG) @@ -310,16 +310,19 @@ Certifier::Certifier() Certifier::~Certifier() { + mysql_mutex_lock(&LOCK_certification_info); + my_atomic_store32(&initialized, 0); clear_certification_info(); delete certification_info_sid_map; delete stable_gtid_set; delete stable_sid_map; delete stable_gtid_set_lock; - delete broadcast_thread; delete group_gtid_executed; delete group_gtid_extracted; delete group_gtid_sid_map; + mysql_mutex_unlock(&LOCK_certification_info); + delete broadcast_thread; clear_incoming(); delete incoming; @@ -566,6 +569,7 @@ void Certifier::add_to_group_gtid_executed_internal(rpl_sidno sidno, void Certifier::clear_certification_info() { + mysql_mutex_assert_owner(&LOCK_certification_info); for (Certification_info::iterator it= certification_info.begin(); it != certification_info.end(); ++it) @@ -630,7 +634,7 @@ int Certifier::initialize(ulonglong gtid_assignment_block_size) } error= broadcast_thread->initialize(); - initialized= !error; + my_atomic_store32(&initialized, !error); end: mysql_mutex_unlock(&LOCK_certification_info); @@ -931,6 +935,10 @@ int Certifier::add_specified_gtid_to_group_gtid_executed(Gtid_log_event *gle, { DBUG_ENTER("Certifier::add_specified_gtid_to_group_gtid_executed"); + if (!is_initialized()) { + return 1; + } + mysql_mutex_lock(&LOCK_certification_info); rpl_sidno sidno= gle->get_sidno(group_gtid_sid_map); @@ -961,6 +969,11 @@ int Certifier::add_specified_gtid_to_group_gtid_executed(Gtid_log_event *gle, int Certifier::add_group_gtid_to_group_gtid_executed(rpl_gno gno, bool local) { DBUG_ENTER("Certifier::add_group_gtid_to_group_gtid_executed"); + + if (!is_initialized()) { + return 1; + } + mysql_mutex_lock(&LOCK_certification_info); add_to_group_gtid_executed_internal(group_gtid_sid_map_group_sidno, gno, local); mysql_mutex_unlock(&LOCK_certification_info); @@ -1178,6 +1191,10 @@ Certifier::get_group_stable_transactions_set_string(char **buffer, DBUG_ENTER("Certifier::get_group_stable_transactions_set_string"); int error= 1; + if (!is_initialized()) { + return 1; + } + char *m_buffer= NULL; int m_length= stable_gtid_set->to_string(&m_buffer, true); if (m_length >= 0) @@ -1223,6 +1240,11 @@ bool Certifier::set_group_stable_transactions_set(Gtid_set* executed_gtid_set) void Certifier::garbage_collect() { DBUG_ENTER("Certifier::garbage_collect"); + + if (!is_initialized()) { + return; + } + DBUG_EXECUTE_IF("group_replication_do_not_clear_certification_database", { DBUG_VOID_RETURN; };); @@ -1493,6 +1515,11 @@ int Certifier::stable_set_handle() void Certifier::handle_view_change() { DBUG_ENTER("Certifier::handle_view_change"); + + if (!is_initialized()) { + return; + } + clear_incoming(); clear_members(); DBUG_VOID_RETURN; @@ -1502,6 +1529,11 @@ void Certifier::handle_view_change() void Certifier::get_certification_info(std::map *cert_info) { DBUG_ENTER("Certifier::get_certification_info"); + + if (!is_initialized()) { + return; + } + mysql_mutex_lock(&LOCK_certification_info); for(Certification_info::iterator it = certification_info.begin(); @@ -1538,6 +1570,10 @@ rpl_gno Certifier::generate_view_change_group_gno() { DBUG_ENTER("Certifier::generate_view_change_group_gno"); + if (!is_initialized()) { + return -1; + } + mysql_mutex_lock(&LOCK_certification_info); rpl_gno result= get_group_next_available_gtid(NULL); @@ -1557,6 +1593,11 @@ rpl_gno Certifier::generate_view_change_group_gno() int Certifier::set_certification_info(std::map *cert_info) { DBUG_ENTER("Certifier::set_certification_info"); + + if (!is_initialized()) { + return 1; + } + assert(cert_info != NULL); if (cert_info->size() == 1) { @@ -1674,6 +1715,10 @@ ulonglong Certifier::get_certification_info_size() void Certifier::get_last_conflict_free_transaction(std::string* value) { + if (!is_initialized()) { + return; + } + int length= 0; char buffer[Gtid::MAX_TEXT_LENGTH + 1]; @@ -1710,6 +1755,10 @@ void Certifier::enable_conflict_detection() DBUG_ENTER("Certifier::enable_conflict_detection"); assert(local_member_info->in_primary_mode()); + if (!is_initialized()) { + return; + } + mysql_mutex_lock(&LOCK_certification_info); conflict_detection_enable= true; local_member_info->enable_conflict_detection(); @@ -1720,6 +1769,11 @@ void Certifier::enable_conflict_detection() void Certifier::disable_conflict_detection() { DBUG_ENTER("Certifier::disable_conflict_detection"); + + if (!is_initialized()) { + return; + } + assert(local_member_info->in_primary_mode()); mysql_mutex_lock(&LOCK_certification_info); @@ -1738,6 +1792,10 @@ bool Certifier::is_conflict_detection_enable() { DBUG_ENTER("Certifier::is_conflict_detection_enable"); + if (!is_initialized()) { + return false; + } + mysql_mutex_lock(&LOCK_certification_info); bool result= conflict_detection_enable; mysql_mutex_unlock(&LOCK_certification_info); From 0bf866e57ce59510bb88a186c9a5385f3fd45ab0 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 16 May 2024 23:49:29 +0530 Subject: [PATCH 07/13] =?UTF-8?q?PS-9174=20InnoDB=EF=BC=9Atrx=20hangs=20du?= =?UTF-8?q?e=20to=20wrong=20trx->in=5Finnodb=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://perconadev.atlassian.net/browse/PS-9174 Bug#35277407 mysql/mysql-server@88b0ebafdf6 This patch will solve the following duplicates of this bug: Bug #112425: trx_t might be Use-After-Free in innobase_commit_by_xid Bug #99643: innobase_commit_by_xid/innobase_rollback_by_xid is not thread safe Bug #105036: trx would be used after free in `innobase_commit_by_xid` and rollback Background: TrxInInnoDB is a RAII wrapper for trx_t object used to track if the transaction's thread is currently executing within InnoDB code. It is acquired on all entry points, and as Innodb can be entered "recursively", the trx->in_depth is used to track the balance of enters and exits. On the outermost enter, the thread additionally checks if trx->in_innodb has the TRX_FORCE_ROLLBACK (0x8000 0000) flag set, which means a high priority transaction is attempting an asynchronous rollback of this transaction, so to avoid races, this thread should wait for the rollback to complete. Issue: TrxInInnoDB's destructor calls exit which resets in_depth and in_innodb increased by enter. However innobase_commit_by_xid and innobase_rollback_by_xid calls trx_free_for_background which returns the trx back to the pool, before the destructor is called. If this trx is being reused by another thread, it can lead to data-race and corrupted value of in_depth and in_innodb. If in_depth gets the value of -1, subsequent calls to enter and exit will bump in_innodb by one. This can lead to indefinite wait if in_innodb reaches TRX_FORCE_ROLLBACK. Fix: Ensure that TrxInInnoDB calls exit before returning the trx object to the pool. Further add checks to catch corrupt values of in_depth when freeing trx. Trx state validation before free was missed in trx_free_prepared_or_active_recovered Thanks to Shaohua Wang (Alibaba, Ex-Innodb) for the contribution Change-Id: Ibf79bec85ffa0eaf65f565c169db61536bff10a2 --- storage/innobase/handler/ha_innodb.cc | 15 +++++++++------ storage/innobase/include/trx0types.h | 2 +- storage/innobase/trx/trx0trx.cc | 6 ++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index d913bae0647d..48c0edddd7ab 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -17285,9 +17285,10 @@ innobase_commit_by_xid( trx_t* trx = trx_get_trx_by_xid(xid); if (trx != NULL) { - TrxInInnoDB trx_in_innodb(trx); - - innobase_commit_low(trx); + { + TrxInInnoDB trx_in_innodb(trx); + innobase_commit_low(trx); + } ut_ad(trx->mysql_thd == NULL); /* use cases are: disconnected xa, slave xa, recovery */ trx_deregister_from_2pc(trx); @@ -17317,9 +17318,11 @@ innobase_rollback_by_xid( trx_t* trx = trx_get_trx_by_xid(xid); if (trx != NULL) { - TrxInInnoDB trx_in_innodb(trx); - - int ret = innobase_rollback_trx(trx); + int ret; + { + TrxInInnoDB trx_in_innodb(trx); + ret = innobase_rollback_trx(trx); + } trx_deregister_from_2pc(trx); ut_ad(!trx->will_lock); diff --git a/storage/innobase/include/trx0types.h b/storage/innobase/include/trx0types.h index 5b3b95e1cbfd..c8a78ea66c91 100644 --- a/storage/innobase/include/trx0types.h +++ b/storage/innobase/include/trx0types.h @@ -72,7 +72,7 @@ static const ib_uint32_t TRX_FORCE_ROLLBACK_ASYNC = 1 << 30; /** Mark the transaction for forced rollback */ static const ib_uint32_t TRX_FORCE_ROLLBACK = 1 << 31; -/** For masking out the above four flags */ +/** For masking out the above flags */ static const ib_uint32_t TRX_FORCE_ROLLBACK_MASK = 0x1FFFFFFF; /** Transaction execution states when trx->state == TRX_STATE_ACTIVE */ diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index aaab1ea8f0a2..3d72d2dd3078 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -250,6 +250,9 @@ struct TrxFactory { trx->dict_operation_lock_mode = 0; + ut_a(trx->in_innodb == 0); + ut_a(trx->in_depth == 0); + trx->xid = UT_NEW_NOKEY(xid_t()); trx->detailed_error = reinterpret_cast( @@ -511,10 +514,12 @@ trx_free(trx_t*& trx) ut_ad(trx->read_view == NULL); ut_ad(trx->is_dd_trx == false); + ut_ad(trx->in_depth == 0); /* trx locking state should have been reset before returning trx to pool */ ut_ad(trx->will_lock == 0); + trx->in_innodb &= TRX_FORCE_ROLLBACK_MASK; trx_pools->mem_free(trx); @@ -647,6 +652,7 @@ trx_free_prepared( was not called. */ trx->lock.table_locks.clear(); + trx_validate_state_before_free(trx); trx_free(trx); } From 6c5eef2bd925de92796f7f78c9bfedd403344b35 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 16 May 2024 23:41:44 +0530 Subject: [PATCH 08/13] PS-9174 Inconsistent FTS state in concurrent scenarios https://perconadev.atlassian.net/browse/PS-9174 Bug#34929814 mysql/mysql-server@c94f9d873b1 mysql/mysql-server@a0367984115 Bug#36347647 Contribution by Tencent: Resiliency issue in fts_sync_commit Symptoms: During various operations on tables containing FTS indexes the state of FTS as comitted to database may become inconsistent, affecting the following scenarios: - the server terminates when synchronizing the FTS cache, - synchronization of FTS cache occurs concurrently with another FTS operation This inconsistency may lead to various negative effects including incorrect query results. An example operation which forces the synchronization of FTS cach is OPTIMIZE TABLE with innodb_optimize_fulltext_only set to ON. Root Cause: Function 'fts_cmp_set_sync_doc_id' and 'fts_sql_commit' use different trx_t objects in function 'fts_sync_commit'. This causes a scenario where 'synced_doc_id' in the config table is already committed, but remaining FTS data isn't yet, leading to issues in the scenarios described above - the server terminating between the commits, or concurrent access getting the intermediate state. Fix: When 'fts_cmp_set_sync_doc_id' is called from 'fts_sync_commit' it will use the transaction provided by the caller. Patch based on contribution by Tencent. Change-Id: I65fa5702db5e7b6b2004a7311a6b0aa97449034f --- .../r/fts_sync_commit_resiliency.result | 63 +++++++++++++++++++ .../t/fts_sync_commit_resiliency.test | 47 ++++++++++++++ storage/innobase/fts/fts0fts.cc | 41 +++++++++--- 3 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 mysql-test/suite/innodb_fts/r/fts_sync_commit_resiliency.result create mode 100644 mysql-test/suite/innodb_fts/t/fts_sync_commit_resiliency.test diff --git a/mysql-test/suite/innodb_fts/r/fts_sync_commit_resiliency.result b/mysql-test/suite/innodb_fts/r/fts_sync_commit_resiliency.result new file mode 100644 index 000000000000..8763566d3e56 --- /dev/null +++ b/mysql-test/suite/innodb_fts/r/fts_sync_commit_resiliency.result @@ -0,0 +1,63 @@ +CREATE TABLE opening_lines ( +id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, +opening_line TEXT(500), +author VARCHAR(200), +title VARCHAR(200) +) ENGINE=InnoDB; +CREATE FULLTEXT INDEX idx ON opening_lines(opening_line); +Warnings: +Warning 124 InnoDB rebuilding table to add column FTS_DOC_ID +CREATE FULLTEXT INDEX ft_idx1 ON opening_lines(title); +INSERT INTO opening_lines(opening_line,author,title) VALUES +('Call me Ishmael.','Herman Melville','Moby Dick'), +('A screaming comes across the sky.','Thomas Pynchon','Gravity\'s Rainbow'), + ('I am an invisible man.','Ralph Ellison','Invisible Man'), + ('Where now? Who now? When now?','Samuel Beckett','The Unnamable'), + ('It was love at first sight.','Joseph Heller','Catch-22'), + ('All this happened, more or less.','Kurt Vonnegut','Slaughterhouse-Five'), + ('Mrs. Dalloway said she would buy the flowers herself.','Virginia Woolf','Mrs. Dalloway'), + ('It was a pleasure to burn.','Ray Bradbury','Fahrenheit 451'); +SET GLOBAL innodb_ft_aux_table='test/opening_lines'; +SELECT * FROM information_schema.innodb_ft_config; +KEY VALUE +optimize_checkpoint_limit 180 +synced_doc_id 0 +stopword_table_name +use_stopword 1 +SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael'); +id opening_line author title +1 Call me Ishmael. Herman Melville Moby Dick +SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('invisible'); +id opening_line author title +3 I am an invisible man. Ralph Ellison Invisible Man +SELECT * FROM opening_lines; +id opening_line author title +1 Call me Ishmael. Herman Melville Moby Dick +2 A screaming comes across the sky. Thomas Pynchon Gravity's Rainbow +3 I am an invisible man. Ralph Ellison Invisible Man +4 Where now? Who now? When now? Samuel Beckett The Unnamable +5 It was love at first sight. Joseph Heller Catch-22 +6 All this happened, more or less. Kurt Vonnegut Slaughterhouse-Five +7 Mrs. Dalloway said she would buy the flowers herself. Virginia Woolf Mrs. Dalloway +8 It was a pleasure to burn. Ray Bradbury Fahrenheit 451 +SET GLOBAL innodb_optimize_fulltext_only=ON; +SET GLOBAL debug='+d,fts_crash_before_commit_sync'; +OPTIMIZE TABLE opening_lines; +ERROR HY000: Lost connection to MySQL server during query +SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael'); +id opening_line author title +1 Call me Ishmael. Herman Melville Moby Dick +SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('invisible'); +id opening_line author title +3 I am an invisible man. Ralph Ellison Invisible Man +SELECT * FROM opening_lines; +id opening_line author title +1 Call me Ishmael. Herman Melville Moby Dick +2 A screaming comes across the sky. Thomas Pynchon Gravity's Rainbow +3 I am an invisible man. Ralph Ellison Invisible Man +4 Where now? Who now? When now? Samuel Beckett The Unnamable +5 It was love at first sight. Joseph Heller Catch-22 +6 All this happened, more or less. Kurt Vonnegut Slaughterhouse-Five +7 Mrs. Dalloway said she would buy the flowers herself. Virginia Woolf Mrs. Dalloway +8 It was a pleasure to burn. Ray Bradbury Fahrenheit 451 +DROP TABLE opening_lines; diff --git a/mysql-test/suite/innodb_fts/t/fts_sync_commit_resiliency.test b/mysql-test/suite/innodb_fts/t/fts_sync_commit_resiliency.test new file mode 100644 index 000000000000..863124e6f632 --- /dev/null +++ b/mysql-test/suite/innodb_fts/t/fts_sync_commit_resiliency.test @@ -0,0 +1,47 @@ +# Test database resiliency against scenario where the server crashes +# right before fts_sync_commit commits its transaction + +source include/have_debug.inc; +source include/not_valgrind.inc; + +CREATE TABLE opening_lines ( + id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, + opening_line TEXT(500), + author VARCHAR(200), + title VARCHAR(200) + ) ENGINE=InnoDB; + +CREATE FULLTEXT INDEX idx ON opening_lines(opening_line); +CREATE FULLTEXT INDEX ft_idx1 ON opening_lines(title); + +INSERT INTO opening_lines(opening_line,author,title) VALUES + ('Call me Ishmael.','Herman Melville','Moby Dick'), + ('A screaming comes across the sky.','Thomas Pynchon','Gravity\'s Rainbow'), + ('I am an invisible man.','Ralph Ellison','Invisible Man'), + ('Where now? Who now? When now?','Samuel Beckett','The Unnamable'), + ('It was love at first sight.','Joseph Heller','Catch-22'), + ('All this happened, more or less.','Kurt Vonnegut','Slaughterhouse-Five'), + ('Mrs. Dalloway said she would buy the flowers herself.','Virginia Woolf','Mrs. Dalloway'), + ('It was a pleasure to burn.','Ray Bradbury','Fahrenheit 451'); + +SET GLOBAL innodb_ft_aux_table='test/opening_lines'; +SELECT * FROM information_schema.innodb_ft_config; + +SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael'); +SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('invisible'); +SELECT * FROM opening_lines; + +SET GLOBAL innodb_optimize_fulltext_only=ON; +SET GLOBAL debug='+d,fts_crash_before_commit_sync'; +--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--error 2013 # CR_SERVER_LOST +OPTIMIZE TABLE opening_lines; + +--enable_reconnect +--source include/wait_until_connected_again.inc + +SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael'); +SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('invisible'); +SELECT * FROM opening_lines; + +DROP TABLE opening_lines; diff --git a/storage/innobase/fts/fts0fts.cc b/storage/innobase/fts/fts0fts.cc index 08adc1cb1d36..b3df6d13dcdf 100644 --- a/storage/innobase/fts/fts0fts.cc +++ b/storage/innobase/fts/fts0fts.cc @@ -2777,7 +2777,7 @@ fts_get_next_doc_id( This function fetch the Doc ID from CONFIG table, and compare with the Doc ID supplied. And store the larger one to the CONFIG table. @return DB_SUCCESS if OK */ -static MY_ATTRIBUTE((nonnull)) +static dberr_t fts_cmp_set_sync_doc_id( /*====================*/ @@ -2785,18 +2785,22 @@ fts_cmp_set_sync_doc_id( doc_id_t doc_id_cmp, /*!< in: Doc ID to compare */ ibool read_only, /*!< in: TRUE if read the synced_doc_id only */ - doc_id_t* doc_id) /*!< out: larger document id + doc_id_t* doc_id, /*!< out: larger document id after comparing "doc_id_cmp" to the one stored in CONFIG table */ + trx_t* trx = NULL) /*!< in: transaction in which + the doc_id is retrieved and + stored */ { - trx_t* trx; pars_info_t* info; dberr_t error; fts_table_t fts_table; que_t* graph = NULL; fts_cache_t* cache = table->fts->cache; char table_name[MAX_FULL_NAME_LEN]; + bool trx_allocated; + trx_savept_t savept; retry: ut_a(table->fts->doc_col != ULINT_UNDEFINED); @@ -2807,7 +2811,13 @@ fts_cmp_set_sync_doc_id( fts_table.parent = table->name.m_name; - trx = trx_allocate_for_background(); + trx_allocated = false; + if (trx == NULL) { + trx = trx_allocate_for_background(); + trx_allocated = true; + } else { + savept = trx_savept_take(trx); + } trx->op_info = "update the next FTS document id"; @@ -2874,21 +2884,34 @@ fts_cmp_set_sync_doc_id( func_exit: if (error == DB_SUCCESS) { - fts_sql_commit(trx); + if (trx_allocated) { + fts_sql_commit(trx); + } } else { *doc_id = 0; ib::error() << "(" << ut_strerr(error) << ") while getting" " next doc id."; - fts_sql_rollback(trx); + if (trx_allocated) { + fts_sql_rollback(trx); + } else { + trx_rollback_to_savepoint(trx, &savept); + } if (error == DB_DEADLOCK) { os_thread_sleep(FTS_DEADLOCK_RETRY_WAIT); + if (trx_allocated) { + /* free trx before retry */ + trx_free_for_background(trx); + trx = NULL; + } goto retry; } } - trx_free_for_background(trx); + if (trx_allocated) { + trx_free_for_background(trx); + } return(error); } @@ -4617,7 +4640,7 @@ fts_sync_commit( /* After each Sync, update the CONFIG table about the max doc id we just sync-ed to index table */ error = fts_cmp_set_sync_doc_id(sync->table, sync->max_doc_id, FALSE, - &last_doc_id); + &last_doc_id, trx); /* Get the list of deleted documents that are either in the cache or were headed there but were deleted before the add @@ -4637,7 +4660,7 @@ fts_sync_commit( rw_lock_x_unlock(&cache->lock); if (error == DB_SUCCESS) { - + DBUG_EXECUTE_IF("fts_crash_before_commit_sync", { DBUG_SUICIDE(); }); fts_sql_commit(trx); } else if (error != DB_SUCCESS) { From b0b5672e74fd70ed4700c55c6ad0ae98cf8d365e Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 9 May 2024 19:53:10 +0530 Subject: [PATCH 09/13] PS-9174 Unified behaviour when calling plugin->deinit for all plugins https://perconadev.atlassian.net/browse/PS-9174 Bug#36317795 mysql/mysql-server@ce036717cb5 This patch unifies plugin's deinit function call to pass valid plugin pointer instead of the nullptr for all types of plugin. Change-Id: I482497bbaff28d5cd31d74d694056a4df6693152 --- plugin/audit_null/audit_null.c | 1 + sql/handler.cc | 4 ++-- sql/sql_audit.cc | 2 +- sql/sql_show.cc | 2 +- storage/example/ha_example.cc | 12 +++++++++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/plugin/audit_null/audit_null.c b/plugin/audit_null/audit_null.c index 3b1075ca31b9..81f6eb642e39 100644 --- a/plugin/audit_null/audit_null.c +++ b/plugin/audit_null/audit_null.c @@ -221,6 +221,7 @@ static int audit_null_plugin_init(void *arg MY_ATTRIBUTE((unused))) static int audit_null_plugin_deinit(void *arg MY_ATTRIBUTE((unused))) { + assert(arg); if (g_plugin_installed == TRUE) { my_free((void *)(g_record_buffer)); diff --git a/sql/handler.cc b/sql/handler.cc index f3aca4197d71..f27496e82fe1 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -797,7 +797,7 @@ int ha_finalize_handlerton(st_plugin_int *plugin) engine plugins. */ DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str)); - if (plugin->plugin->deinit(NULL)) + if (plugin->plugin->deinit(plugin)) { DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.", plugin->name.str)); @@ -957,7 +957,7 @@ int ha_initialize_handlerton(st_plugin_int *plugin) was successfully called before. */ if (plugin->plugin->deinit) - (void) plugin->plugin->deinit(NULL); + (void) plugin->plugin->deinit(plugin); err: my_free(hton); diff --git a/sql/sql_audit.cc b/sql/sql_audit.cc index 17543895d2b3..1da8ec4d548e 100644 --- a/sql/sql_audit.cc +++ b/sql/sql_audit.cc @@ -1346,7 +1346,7 @@ int finalize_audit_plugin(st_plugin_int *plugin) { unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE]; - if (plugin->plugin->deinit && plugin->plugin->deinit(NULL)) + if (plugin->plugin->deinit && plugin->plugin->deinit(plugin)) { DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.", plugin->name.str)); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 1f1f90cdf3ba..3221ab90743a 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -9208,7 +9208,7 @@ int finalize_schema_table(st_plugin_int *plugin) if (plugin->plugin->deinit) { DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str)); - if (plugin->plugin->deinit(NULL)) + if (plugin->plugin->deinit(plugin)) { DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.", plugin->name.str)); diff --git a/storage/example/ha_example.cc b/storage/example/ha_example.cc index 7ae66fd43879..bacd8ca6263a 100644 --- a/storage/example/ha_example.cc +++ b/storage/example/ha_example.cc @@ -132,6 +132,16 @@ static int example_init_func(void *p) } +static int example_deinit_func(void *p) +{ + DBUG_ENTER("example_deinit_func");; + + assert(p); + + DBUG_RETURN(0); +} + + /** @brief Example of simple lock controls. The "share" it creates is a @@ -1022,7 +1032,7 @@ mysql_declare_plugin(example) "Example storage engine", PLUGIN_LICENSE_GPL, example_init_func, /* Plugin Init */ - NULL, /* Plugin Deinit */ + example_deinit_func, /* Plugin Deinit */ 0x0001 /* 0.1 */, func_status, /* status variables */ example_system_variables, /* system variables */ From 38ac25956f6f502a41176fbc3649a4b9e75f8484 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 16 May 2024 12:10:11 +0530 Subject: [PATCH 10/13] PS-9092: Data inconsistencies when high rate of pages split/merge https://perconadev.atlassian.net/browse/PS-9092 Problem: Query over InnoDB table that uses backward scan over the index occasionally might return incorrect/incomplete results when changes to table (for example, DELETEs in other or even the same connection followed by asynchronous purge) cause concurrent B-tree page merges. Cause: The problem occurs when persistent cursor which is used to scan over index in backwards direction stops on infimum record of the page to which it points currently and releases all latches it has, before moving to the previous page. At this point merge from the previous page to cursor's current one can happen (because cursor doesn't hold latch on current or previous page). During this merge records from the previous page are moved over infimum record and placed before any old user records in the current page. When later our persistent cursor resumes its iteration it might use optimistic approach to cursor restoration which won't detect this kind of page update and resumes the iteration right from infimum record, effectively skipping the moved records. Solution: This patch solves the problem by forcing persisted cursor to use pessimistic approach to cursor restoration in such cases. With this approach cursor restoration is performed by looking up and continuing from user record which preceded infimum record when cursor stopped iteration and released the latches. Indeed, in this case records which were moved during the merge will be visited by cursor as they precede this old-post-infimum record in the page. This forcing of pessimistic restore is achieved by increasing page's modify_clock version counter for the page merged into, when merge happens from the previous page (normally this version counter is only incremented when we delete records from the page or the whole page). Theoretically, this might be also done when we are merging into page the page which follows it. But it is not clear if it is really required, as forward scan over the index is not affected by this problem. In forward scan case different approach to latching is used when we switch between B-tree leaf pages - we always acquire latch on the next page before releasing latch on the current one. As result concurrent merges from the next page to the current one are blocked. Note that the same approach to latching can't be used for backward iteration as it will mean that latching happens into opposite order which will lead to deadlocks. --- .../suite/innodb/r/percona_bug_ps9092.result | 89 +++++++++++++ .../suite/innodb/t/percona_bug_ps9092.test | 124 ++++++++++++++++++ storage/innobase/btr/btr0btr.cc | 19 +++ 3 files changed, 232 insertions(+) create mode 100644 mysql-test/suite/innodb/r/percona_bug_ps9092.result create mode 100644 mysql-test/suite/innodb/t/percona_bug_ps9092.test diff --git a/mysql-test/suite/innodb/r/percona_bug_ps9092.result b/mysql-test/suite/innodb/r/percona_bug_ps9092.result new file mode 100644 index 000000000000..6e3fa07a476f --- /dev/null +++ b/mysql-test/suite/innodb/r/percona_bug_ps9092.result @@ -0,0 +1,89 @@ +# +# PS-9092: Data inconsistencies when high rate of pages split/merge +# +CREATE TABLE testTable (`key` INT UNSIGNED, version BIGINT UNSIGNED, rowmarker CHAR(3) NOT NULL DEFAULT 'aaa', value MEDIUMBLOB, PRIMARY KEY (`key`, version)) ENGINE=InnoDB; +CREATE TABLE stopper (i INT); +INSERT INTO stopper VALUES (1); +CREATE PROCEDURE lft() BEGIN +DECLARE rnd VARBINARY(1024); +SET rnd = RANDOM_BYTES(1024); +INSERT INTO testTable (`key`, version, value) VALUES (0, 18446744073709551615, LEFT(rnd, 1)); +WHILE (SELECT COUNT(*) FROM stopper) DO +UPDATE testTable SET value = LEFT(rnd, RAND()*1023+1) WHERE `key` = 0; +END WHILE; +END | +CREATE PROCEDURE rght() BEGIN +DECLARE rnd VARBINARY(1024); +SET rnd = RANDOM_BYTES(1024); +INSERT INTO testTable (`key`, version, value) VALUES (2, 18446744073709551615, LEFT(rnd, 1)); +WHILE (SELECT COUNT(*) FROM stopper) DO +UPDATE testTable SET value = LEFT(rnd, RAND()*1023+1) WHERE `key` = 2; +END WHILE; +END | +CREATE PROCEDURE mdl() BEGIN +DECLARE rnd VARBINARY(1024); +DECLARE v BIGINT UNSIGNED DEFAULT 0; +SET rnd = RANDOM_BYTES(1024); +WHILE (SELECT COUNT(*) FROM stopper) DO +SET v = v + 1; +INSERT INTO testTable (`key`, version, value) VALUES (1, 18446744073709551615 - v, LEFT(rnd, RAND()*1023+1)); +IF RAND() <= 0.05 THEN +DELETE from testTable WHERE `key`=1 AND version > 18446744073709551615 - v; +END IF; +END WHILE; +END| +CREATE PROCEDURE ck() BEGIN +DECLARE global_max_seen BIGINT UNSIGNED DEFAULT 0; +foo: WHILE global_max_seen < 5000 DO +BEGIN +DECLARE done INT DEFAULT 0; +DECLARE local_max_seen BIGINT UNSIGNED DEFAULT 0; +DECLARE k INT; +DECLARE u BIGINT UNSIGNED; +DECLARE v BIGINT UNSIGNED; +DECLARE c1 CURSOR FOR select `key`, version FROM testTable WHERE `key` >= 1 AND `key` <= 1 ORDER BY `key` DESC; +DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; +OPEN c1; +REPEAT +FETCH c1 INTO k, v; +SET u = 18446744073709551615 - v; +IF u > local_max_seen THEN +SET local_max_seen = u; +END IF; +UNTIL done END REPEAT; +CLOSE c1; +IF local_max_seen < global_max_seen THEN +SELECT "ERROR! local_max_seen < global_max_seen!" AS msg, local_max_seen, global_max_seen; +LEAVE foo; +END IF; +SET global_max_seen = local_max_seen; +END; +END WHILE; +END| +connect con1,localhost,root,,; +CALL lft(); +connect con2,localhost,root,,; +CALL mdl(); +connect con3,localhost,root,,; +CALL rght(); +connect con4,localhost,root,,; +# +# Check procedure is not supposed to return ERROR. +CALL ck(); +# +# Stop activity in other connections +DELETE FROM stopper; +disconnect con4; +connection con1; +disconnect con1; +connection con2; +disconnect con2; +connection con3; +disconnect con3; +connection default; +DROP TABLE stopper; +DROP TABLE testTable; +DROP PROCEDURE lft; +DROP PROCEDURE mdl; +DROP PROCEDURE rght; +DROP PROCEDURE ck; diff --git a/mysql-test/suite/innodb/t/percona_bug_ps9092.test b/mysql-test/suite/innodb/t/percona_bug_ps9092.test new file mode 100644 index 000000000000..6b21f3859a33 --- /dev/null +++ b/mysql-test/suite/innodb/t/percona_bug_ps9092.test @@ -0,0 +1,124 @@ +--echo # +--echo # PS-9092: Data inconsistencies when high rate of pages split/merge +--echo # +CREATE TABLE testTable (`key` INT UNSIGNED, version BIGINT UNSIGNED, rowmarker CHAR(3) NOT NULL DEFAULT 'aaa', value MEDIUMBLOB, PRIMARY KEY (`key`, version)) ENGINE=InnoDB; +CREATE TABLE stopper (i INT); +INSERT INTO stopper VALUES (1); + +DELIMITER |; + +CREATE PROCEDURE lft() BEGIN + DECLARE rnd VARBINARY(1024); + SET rnd = RANDOM_BYTES(1024); + INSERT INTO testTable (`key`, version, value) VALUES (0, 18446744073709551615, LEFT(rnd, 1)); + WHILE (SELECT COUNT(*) FROM stopper) DO + UPDATE testTable SET value = LEFT(rnd, RAND()*1023+1) WHERE `key` = 0; + END WHILE; +END | + +CREATE PROCEDURE rght() BEGIN + DECLARE rnd VARBINARY(1024); + SET rnd = RANDOM_BYTES(1024); + INSERT INTO testTable (`key`, version, value) VALUES (2, 18446744073709551615, LEFT(rnd, 1)); + WHILE (SELECT COUNT(*) FROM stopper) DO + UPDATE testTable SET value = LEFT(rnd, RAND()*1023+1) WHERE `key` = 2; + END WHILE; +END | + +CREATE PROCEDURE mdl() BEGIN + DECLARE rnd VARBINARY(1024); + DECLARE v BIGINT UNSIGNED DEFAULT 0; + SET rnd = RANDOM_BYTES(1024); + + WHILE (SELECT COUNT(*) FROM stopper) DO + SET v = v + 1; + INSERT INTO testTable (`key`, version, value) VALUES (1, 18446744073709551615 - v, LEFT(rnd, RAND()*1023+1)); + + IF RAND() <= 0.05 THEN + DELETE from testTable WHERE `key`=1 AND version > 18446744073709551615 - v; + END IF; + END WHILE; +END| + +CREATE PROCEDURE ck() BEGIN + DECLARE global_max_seen BIGINT UNSIGNED DEFAULT 0; + + foo: WHILE global_max_seen < 5000 DO + BEGIN + DECLARE done INT DEFAULT 0; + DECLARE local_max_seen BIGINT UNSIGNED DEFAULT 0; + DECLARE k INT; + DECLARE u BIGINT UNSIGNED; + DECLARE v BIGINT UNSIGNED; + DECLARE c1 CURSOR FOR select `key`, version FROM testTable WHERE `key` >= 1 AND `key` <= 1 ORDER BY `key` DESC; + DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; + + OPEN c1; + + REPEAT + FETCH c1 INTO k, v; + SET u = 18446744073709551615 - v; + IF u > local_max_seen THEN + SET local_max_seen = u; + END IF; + UNTIL done END REPEAT; + CLOSE c1; + + IF local_max_seen < global_max_seen THEN + SELECT "ERROR! local_max_seen < global_max_seen!" AS msg, local_max_seen, global_max_seen; + LEAVE foo; + END IF; + SET global_max_seen = local_max_seen; + END; + END WHILE; +END| +DELIMITER ;| + +--enable_connect_log + +--connect (con1,localhost,root,,) +--send CALL lft() + +--connect (con2,localhost,root,,) +--send CALL mdl() + +--connect (con3,localhost,root,,) +--send CALL rght() + +--connect (con4,localhost,root,,) +--echo # +--echo # Check procedure is not supposed to return ERROR. +CALL ck(); + +--echo # +--echo # Stop activity in other connections +DELETE FROM stopper; + +--disconnect con4 +--source include/wait_until_disconnected.inc + +--connection con1 +--reap +--disconnect con1 +--source include/wait_until_disconnected.inc + +--connection con2 +--reap +--disconnect con2 +--source include/wait_until_disconnected.inc + +--connection con3 +--reap +--disconnect con3 +--source include/wait_until_disconnected.inc + +--connection default + +--disable_connect_log + +DROP TABLE stopper; +DROP TABLE testTable; +DROP PROCEDURE lft; +DROP PROCEDURE mdl; +DROP PROCEDURE rght; +DROP PROCEDURE ck; diff --git a/storage/innobase/btr/btr0btr.cc b/storage/innobase/btr/btr0btr.cc index 1adf766b989d..b60d8fab2734 100644 --- a/storage/innobase/btr/btr0btr.cc +++ b/storage/innobase/btr/btr0btr.cc @@ -3617,6 +3617,25 @@ btr_compress( goto err_exit; } + /* When persistent cursor is used to scan over index in backwards + direction it stops on infimum record of its current page and releases + all latches it has, before switching from the cursor's current page to + the previous one. At this point merge from the previous page to cursor's + current one might happen. During this merge records from the previous + page will be moved over cursor position/infimum record which is used + used to continue iteration in optimistic case, making moved records + invisible to the scan. + We force such cursor to use pessimistic approach of restoring its + position/continuing iteration, which is not affected by this problem + (as it relies on looking up user record which was visited by cursor + right before the infimum) by incrementing modification clock for page + being merged into. + The forward iteration seems to be unaffected by this problem as it + doesn't release latch on the current page before it acquires latch on + the next one when cursor switches pages. So merge from the next page + to the current one stays blocked. */ + buf_block_modify_clock_inc(merge_block); + btr_search_drop_page_hash_index(block); #ifdef UNIV_BTR_DEBUG From abff7be3e0e76f4258d923555e50db6572aaf3f8 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Mon, 20 May 2024 14:41:34 +0530 Subject: [PATCH 11/13] PS-9132 mysql.gtid_executed persistent GTID info lost when MySQL crash in Gtid_state::save https://perconadev.atlassian.net/browse/PS-9132 When the server is killed before persisting the GTIDs into mysql.gtid_executed, the subsequent crash recovery process fails to recover gtids from the binary logs because it does not find the "Prev_gtid_log_event" in the last binary log. This happens because, the last binlog was created during the previous restart but no information about Prev_gtid_log_event was written into the file since the server was killed before persisting to the table. The root cause here, is that the recovery process does not parse the previous binary logs if "Prev_gtid_log_event" was not found in the last binary log created by the server. This issue is fixed by parsing all previous binary logs until a valid "Prev_gtid_log_event" is seen. --- .../binlog/r/binlog_gtid_recovery.result | 22 ++++++++++ .../binlog/t/binlog_gtid_recovery-master.opt | 1 + .../suite/binlog/t/binlog_gtid_recovery.test | 40 +++++++++++++++++++ sql/binlog.cc | 2 +- sql/rpl_gtid_state.cc | 1 + 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/binlog/r/binlog_gtid_recovery.result create mode 100644 mysql-test/suite/binlog/t/binlog_gtid_recovery-master.opt create mode 100644 mysql-test/suite/binlog/t/binlog_gtid_recovery.test diff --git a/mysql-test/suite/binlog/r/binlog_gtid_recovery.result b/mysql-test/suite/binlog/r/binlog_gtid_recovery.result new file mode 100644 index 000000000000..ed17e0ebafba --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_gtid_recovery.result @@ -0,0 +1,22 @@ +RESET MASTER; +CREATE TABLE t1 (c1 INT NOT NULL PRIMARY KEY) ENGINE=InnoDB; +SHOW BINARY LOGS; +Log_name File_size +master-bin.000001 352 +SELECT * FROM mysql.gtid_executed; +source_uuid interval_start interval_end +Kill the server +# Kill the server +Restart the server only to kill it before GTID is saved during recovery process +Start the server again to see if server recovers GTIDs from binlogs and updates @@global.gtid_executed and mysql.gtid_executed table. +# restart +SELECT interval_start, interval_end from mysql.gtid_executed; +interval_start interval_end +1 1 +SHOW BINARY LOGS; +Log_name File_size +master-bin.000001 352 +master-bin.000002 123 +master-bin.000003 194 +include/assert.inc [GTID should be properly updated] +DROP TABLE t1; diff --git a/mysql-test/suite/binlog/t/binlog_gtid_recovery-master.opt b/mysql-test/suite/binlog/t/binlog_gtid_recovery-master.opt new file mode 100644 index 000000000000..676cad2851c4 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_gtid_recovery-master.opt @@ -0,0 +1 @@ +--enforce-gtid-consistency --gtid-mode=ON diff --git a/mysql-test/suite/binlog/t/binlog_gtid_recovery.test b/mysql-test/suite/binlog/t/binlog_gtid_recovery.test new file mode 100644 index 000000000000..bee8e9006184 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_gtid_recovery.test @@ -0,0 +1,40 @@ +# PS-9132 mysql.gtid_executed persistent GTID info lost when MySQL crash in Gtid_state::save +# +--source include/have_debug.inc +--source include/have_log_bin.inc +--source include/have_gtid.inc + +RESET MASTER; +CREATE TABLE t1 (c1 INT NOT NULL PRIMARY KEY) ENGINE=InnoDB; + +SHOW BINARY LOGS; +SELECT * FROM mysql.gtid_executed; + +--echo Kill the server +--source include/kill_mysqld.inc + +--echo Restart the server only to kill it before GTID is saved during recovery process +--error 137 +--exec $MYSQLD_CMD --debug="d,crash_before_gtid_persist" + +--echo Start the server again to see if server recovers GTIDs from binlogs and updates @@global.gtid_executed and mysql.gtid_executed table. +--source include/start_mysqld.inc + +SELECT interval_start, interval_end from mysql.gtid_executed; +SHOW BINARY LOGS; + +# Verify if GTID_EXECUTED is updated with the Previous_gtids properly +--let $prev_gtid = query_get_value(SHOW BINLOG EVENTS in 'master-bin.000003', Info, 2) +--let $gtid_executed = `SELECT @@GLOBAL.GTID_EXECUTED` +if($prev_gtid == '') { + die "Could not find Previous_gtid_log_event in the last binary log parsed by the server"; +} +if($gtid_executed == '') { + die "Server could not recover GTIDs from binary logs during recovery"; +} +--let $assert_text = GTID should be properly updated +--let $assert_cond = "$prev_gtid" = "$gtid_executed" +--source include/assert.inc + +#Cleanup +DROP TABLE t1; diff --git a/sql/binlog.cc b/sql/binlog.cc index 18c31a1d4949..a3a50218e691 100644 --- a/sql/binlog.cc +++ b/sql/binlog.cc @@ -4746,7 +4746,7 @@ bool MYSQL_BIN_LOG::init_gtid_sets(Gtid_set *all_gtids, Gtid_set *lost_gtids, GLOBAL.GTID_PURGED should be empty in the case. */ if (binlog_gtid_simple_recovery && is_server_starting && - !is_relay_log) + !is_relay_log && reached_first_file) { assert(all_gtids->is_empty()); assert(lost_gtids->is_empty()); diff --git a/sql/rpl_gtid_state.cc b/sql/rpl_gtid_state.cc index f4c19c391d09..ece9337e914a 100644 --- a/sql/rpl_gtid_state.cc +++ b/sql/rpl_gtid_state.cc @@ -738,6 +738,7 @@ int Gtid_state::save(THD *thd) int Gtid_state::save(const Gtid_set *gtid_set) { DBUG_ENTER("Gtid_state::save(Gtid_set *gtid_set)"); + DBUG_EXECUTE_IF("crash_before_gtid_persist", DBUG_SUICIDE();); int ret= gtid_table_persistor->save(gtid_set); DBUG_RETURN(ret); } From 35414934152163e9c60621878a0839acbde0f961 Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 9 May 2024 21:55:14 +0530 Subject: [PATCH 12/13] PS-9174 [ERROR] [MY-013183] [InnoDB] Assertion failure:ibuf0ibuf.cc:3833:ib::fatal https://perconadev.atlassian.net/browse/PS-9174 Bug#36343647 mysql/mysql-server@5d06230ae2a This is a cherry-pick of the following bug fix which is already fixed in 8.4 through commit#79d67. Bug#35676106 Assertion failure: ibuf0ibuf.cc:3825:ib::fatal triggered thread Description: ------------ When the pages of secondary index are brought to the buffer pool either through the ibuf merge background thread or read through usual io thread, first cached entries from the change buffer are applied to the pages. Once the entries are applied to the page, they are removed from the change buffer. It may possible that the table is deleted or being deleted during change buffer related operations described in the earlier. In the current code we handled the situation of tablespace is deleted but not being deleted. Latter situation must also be handled similarly. Fix: ==== - Replaced the call fil_space_get_flags() with fil_space_acquire_silent(). Later method refuses to acquire the tablespace that is being deleted. - Improved the doxygen of the method ibuf_restore_pos() Change-Id: Ibc5a07c705988282b8b7906d645e2a108f4ada76 --- storage/innobase/ibuf/ibuf0ibuf.cc | 37 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/storage/innobase/ibuf/ibuf0ibuf.cc b/storage/innobase/ibuf/ibuf0ibuf.cc index f56ec9ac7fab..d0e10b0a1e28 100644 --- a/storage/innobase/ibuf/ibuf0ibuf.cc +++ b/storage/innobase/ibuf/ibuf0ibuf.cc @@ -4233,22 +4233,23 @@ ibuf_delete( } } -/*********************************************************************//** -Restores insert buffer tree cursor position -@return TRUE if the position was restored; FALSE if not */ +/** Restores insert buffer tree cursor position +@param[in] space_id Tablespace id +@param[in] page_no index page number where the record should belong +@param [in] search_tuple search tuple for entries of page_no +@param[in] mode BTR_MODIFY_LEAF or BTR_MODIFY_TREE +@param[in,out] pcur persistent cursor whose position is to be restored +@param[in, out] mtr mini-transaction +@return true if the position was restored; false if not */ static MY_ATTRIBUTE((nonnull)) ibool ibuf_restore_pos( -/*=============*/ - ulint space, /*!< in: space id */ - ulint page_no,/*!< in: index page number where the record - should belong */ + ulint space_id, + ulint page_no, const dtuple_t* search_tuple, - /*!< in: search tuple for entries of page_no */ - ulint mode, /*!< in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */ - btr_pcur_t* pcur, /*!< in/out: persistent cursor whose - position is to be restored */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + ulint mode, + btr_pcur_t* pcur, + mtr_t* mtr) { ut_ad(mode == BTR_MODIFY_LEAF || BTR_LATCH_MODE_WITHOUT_INTENTION(mode) == BTR_MODIFY_TREE); @@ -4257,17 +4258,15 @@ ibuf_restore_pos( return(TRUE); } - - if (fil_space_get_flags(space) == ULINT_UNDEFINED || - fil_space_is_being_truncated(space)) { - /* The tablespace has been dropped. Or the tablespace is being - truncated. It is possible that another thread has deleted - the insert buffer entry. Do not complain. */ + fil_space_t* space = fil_space_acquire_silent(space_id); + if (space == NULL) { + /* The tablespace has been(or being) deleted. Do not complain. */ ibuf_btr_pcur_commit_specify_mtr(pcur, mtr); } else { + fil_space_release(space); ib::error() << "ibuf cursor restoration fails!." " ibuf record inserted to page " - << space << ":" << page_no; + << space_id << ":" << page_no; ib::error() << BUG_REPORT_MSG; From 963d3f45bdaa97cf85ae17cecdb4baf1f00e0b5e Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Thu, 23 May 2024 17:45:02 +0530 Subject: [PATCH 13/13] Update extra version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 749facb4e961..2f35eb544230 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=5 MYSQL_VERSION_MINOR=7 MYSQL_VERSION_PATCH=44 -MYSQL_VERSION_EXTRA=-post-eol-1 +MYSQL_VERSION_EXTRA=-post-eol-2