commit cac4c325f17b724825c0b3850cfc268df0a9710a Author: GAO Xiaoxin Date: Tue Mar 24 19:05:31 2020 +0800 Bug #99051 XA commit may do engine commit before MYSQL_BIN_LOG::ordered_commit -Description: In ubuntu 18.04 the xa commit operation may do a engine commit before binlog flush. If "xa commit" is done on the xid from "xa recover", it may invoke the function ha_commit_or_rollback_by_xid, which will invoke plugin_foreach_with_mask to loop all engine plugins to do xacommit_handlerton. In ubuntu 16.04, the binlog plugin will be invoked before innodb plugin, so the MYSQL_BIN_LOG::ordered_commit will be done before innobase_commit_by_xid. The binlog will be sync to disk first. But in ubuntu 18.04, the innodb plugin will be invoked before binlog plugin, if mysql crash after innobase_commit_by_xid and before MYSQL_BIN_LOG::ordered_commit, the innodb engine will in-consistence with the binlog file, which will lead replication broken. For detail, plugin_foreach_with_mask function may generate different sequence for plugins in ubuntu 16.04 and ubuntu 18.04. And the innodb plugin may be before binlog plugin. -How to repeat: Do the following test on Ubuntu 18.04 (can use docker image: "FROM ubuntu:18.04") In mysql5.7.25 (5.7.29 has the same problem), and use the following compile command: cmake -DDOWNLOAD_BOOST=1 -DWITH_BOOST=`pwd`/../boost -DCMAKE_INSTALL_PREFIX=`pwd`/../install_debug -DWITH_INNODB_MEMCACHED=ON .. compile and install mysql, then do the following test by using gdb. Add a break-point at the beginning of MYSQL_BIN_LOG::ordered_commit, then execute the following sql: mysql> xa start '1'; mysql> insert into t1 values (1); mysql> xa end '1'; mysql> xa prepare '1'; mysql> quit then login again execute: mysql> xa commit '1'; //-------> blocked by gdb quit the gdb to simulate the mysql crash, and start-up again. mysql> select * from t1; we will find the "1" has been inserted, but in binlog, there is no related "xa commit" event. -Suggested fix: Adjust the sequence in plugin_foreach_with_mask to ensue the generated plugins vector will always put the binlog plugin in the first. diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 6ed656f..744c4ff 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1642,6 +1642,10 @@ bool plugin_register_dynamic_and_init_all(int *argc, if (!initialized) DBUG_RETURN(true); + size_t total_plugin = plugin_hash[1].records; + size_t idx = 0; + struct st_plugin_int *plugin_tmp = NULL; + /* Allocate the temporary mem root, will be freed before returning */ MEM_ROOT tmp_root; init_alloc_root(key_memory_plugin_init_tmp, &tmp_root, 4096, 4096); @@ -1669,6 +1673,16 @@ bool plugin_register_dynamic_and_init_all(int *argc, end: free_root(&tmp_root, MYF(0)); + for (idx= 0; idx < total_plugin; idx++) + { + plugin_tmp = (st_plugin_int *) my_hash_element(&plugin_hash[1], idx); + if (!my_strcasecmp(&my_charset_latin1, plugin_tmp->name.str, "binlog")) + { + plugin_tmp->binlog_plugin = true; + } else + plugin_tmp->binlog_plugin = false; + } + DBUG_RETURN(0); err: @@ -2492,10 +2506,23 @@ bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func **funcs, else { HASH *hash= plugin_hash + type; + bool has_binlog_plugin = false; + size_t binlog_pos = 0; for (idx= 0; idx < total; idx++) { plugin= (st_plugin_int *) my_hash_element(hash, idx); plugins[idx]= !(plugin->state & state_mask) ? plugin : NULL; + if (plugins[idx] && !has_binlog_plugin && plugins[idx]->binlog_plugin) + { + has_binlog_plugin = true; + binlog_pos = idx; + } + } + if (has_binlog_plugin && binlog_pos != 0) + { + st_plugin_int *tmp = plugins[0]; + plugins[0] = plugins[binlog_pos]; + plugins[binlog_pos] = tmp; } } mysql_mutex_unlock(&LOCK_plugin); diff --git a/sql/sql_plugin_ref.h b/sql/sql_plugin_ref.h index 89709d9..51caa19 100644 --- a/sql/sql_plugin_ref.h +++ b/sql/sql_plugin_ref.h @@ -46,6 +46,7 @@ struct st_plugin_int MEM_ROOT mem_root; /* memory for dynamic plugin structures */ sys_var *system_vars; /* server variables for this plugin */ enum_plugin_load_option load_option; /* OFF, ON, FORCE, F+PERMANENT */ + bool binlog_plugin; }; /*