From 9d97f92febc89941784d17d59c60275e21140ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Mon, 13 Sep 2021 08:19:29 +0300 Subject: [PATCH 01/10] Revert "MDEV-23328 Server hang due to Galera lock conflict resolution" and Revert "MDEV-24873 galera.galera_as_slave_ctas MTR failed:..." This reverts commit 29bbcac0ee841faaa68eeb09c86ff825eabbe6b6 and later commit 5ecaf52d42a1e464c71515f35be97855072bcafe. --- sql/wsrep_mysqld.cc | 2 + storage/innobase/handler/ha_innodb.cc | 184 ++++++++++---------------- 2 files changed, 74 insertions(+), 112 deletions(-) diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index f22d8bf0f5a..e60100e2e90 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -2762,7 +2762,9 @@ extern "C" void wsrep_thd_awake(THD *thd, my_bool signal) { if (signal) { + mysql_mutex_lock(&thd->LOCK_thd_data); thd->awake(KILL_QUERY); + mysql_mutex_unlock(&thd->LOCK_thd_data); } else { diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index c17bb219a72..d4142481a62 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -60,7 +60,6 @@ this program; if not, write to the Free Software Foundation, Inc., #include #include -#include /* Include necessary InnoDB headers */ #include "btr0btr.h" @@ -19536,68 +19535,61 @@ wsrep_abort_slave_trx( (long long)bf_seqno, (long long)victim_seqno); abort(); } - -struct bg_wsrep_kill_trx_arg { - my_thread_id thd_id; - trx_id_t trx_id; - int64_t bf_seqno; - ibool signal; -}; - -static void bg_wsrep_kill_trx( - void *void_arg) +/*******************************************************************//** +This function is used to kill one transaction in BF. */ +UNIV_INTERN +void +wsrep_innobase_kill_one_trx( +/*========================*/ + MYSQL_THD const bf_thd, + const trx_t * const bf_trx, + trx_t *victim_trx, + ibool signal) { - bg_wsrep_kill_trx_arg *arg = (bg_wsrep_kill_trx_arg*)void_arg; - THD *thd = find_thread_by_id(arg->thd_id, false); - trx_t *victim_trx = NULL; - bool awake = false; - DBUG_ENTER("bg_wsrep_kill_trx"); + ut_ad(bf_thd); + ut_ad(victim_trx); + ut_ad(lock_mutex_own()); + ut_ad(trx_mutex_own(victim_trx)); - if (thd) { - victim_trx= thd_to_trx(thd); - /* Victim trx might not exist e.g. on MDL-conflict. */ - if (victim_trx) { - lock_mutex_enter(); - trx_mutex_enter(victim_trx); - if (victim_trx->id != arg->trx_id || - victim_trx->state == TRX_STATE_COMMITTED_IN_MEMORY) - { - /* Victim was meanwhile rolled back or - committed */ - trx_mutex_exit(victim_trx); - lock_mutex_exit(); - wsrep_thd_UNLOCK(thd); - victim_trx= NULL; - } - } else { - /* find_thread_by_id locked - THD::LOCK_thd_data */ - wsrep_thd_UNLOCK(thd); - } + DBUG_ENTER("wsrep_innobase_kill_one_trx"); + THD *thd = (THD *) victim_trx->mysql_thd; + int64_t bf_seqno = wsrep_thd_trx_seqno(bf_thd); + + if (!thd) { + DBUG_PRINT("wsrep", ("no thd for conflicting lock")); + WSREP_WARN("no THD for trx: " TRX_ID_FMT, victim_trx->id); + DBUG_VOID_RETURN; } - if (!victim_trx) { - /* Victim trx might not exist (MDL-conflict) or victim - was meanwhile rolled back or committed because of - a KILL statement or a disconnect. */ - goto ret; - } + WSREP_LOG_CONFLICT(bf_thd, thd, TRUE); WSREP_DEBUG("BF kill (" ULINTPF ", seqno: " INT64PF "), victim: (%lu) trx: " TRX_ID_FMT, - arg->signal, arg->bf_seqno, + signal, bf_seqno, thd_get_thread_id(thd), victim_trx->id); WSREP_DEBUG("Aborting query: %s conf %d trx: %" PRId64, - (wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void", + (thd && wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void", wsrep_thd_conflict_state(thd, FALSE), wsrep_thd_ws_handle(thd)->trx_id); + wsrep_thd_LOCK(thd); + DBUG_EXECUTE_IF("sync.wsrep_after_BF_victim_lock", + { + const char act[]= + "now " + "wait_for signal.wsrep_after_BF_victim_lock"; + DBUG_ASSERT(!debug_sync_set_action(bf_thd, + STRING_WITH_LEN(act))); + };); + + if (wsrep_thd_query_state(thd) == QUERY_EXITING) { WSREP_DEBUG("kill trx EXITING for " TRX_ID_FMT, victim_trx->id); - goto ret_unlock; + wsrep_thd_UNLOCK(thd); + DBUG_VOID_RETURN; } if (wsrep_thd_exec_mode(thd) != LOCAL_STATE) { @@ -19613,13 +19605,18 @@ static void bg_wsrep_kill_trx( case MUST_ABORT: WSREP_DEBUG("victim " TRX_ID_FMT " in MUST ABORT state", victim_trx->id); - goto ret_awake; + wsrep_thd_UNLOCK(thd); + wsrep_thd_awake(thd, signal); + DBUG_VOID_RETURN; + break; case ABORTED: case ABORTING: // fall through default: WSREP_DEBUG("victim " TRX_ID_FMT " in state %d", victim_trx->id, wsrep_thd_get_conflict_state(thd)); - goto ret_unlock; + wsrep_thd_UNLOCK(thd); + DBUG_VOID_RETURN; + break; } switch (wsrep_thd_query_state(thd)) { @@ -19632,12 +19629,12 @@ static void bg_wsrep_kill_trx( victim_trx->id); if (wsrep_thd_exec_mode(thd) == REPL_RECV) { - wsrep_abort_slave_trx(arg->bf_seqno, + wsrep_abort_slave_trx(bf_seqno, wsrep_thd_trx_seqno(thd)); } else { wsrep_t *wsrep= get_wsrep(); rcode = wsrep->abort_pre_commit( - wsrep, arg->bf_seqno, + wsrep, bf_seqno, (wsrep_trx_id_t)wsrep_thd_ws_handle(thd)->trx_id ); @@ -19646,7 +19643,10 @@ static void bg_wsrep_kill_trx( WSREP_DEBUG("cancel commit warning: " TRX_ID_FMT, victim_trx->id); - goto ret_awake; + wsrep_thd_UNLOCK(thd); + wsrep_thd_awake(thd, signal); + DBUG_VOID_RETURN; + break; case WSREP_OK: break; default: @@ -19659,9 +19659,12 @@ static void bg_wsrep_kill_trx( * kill the lock holder first. */ abort(); + break; } } - goto ret_awake; + wsrep_thd_UNLOCK(thd); + wsrep_thd_awake(thd, signal); + break; case QUERY_EXEC: /* it is possible that victim trx is itself waiting for some * other lock. We need to cancel this waiting @@ -19682,20 +19685,26 @@ static void bg_wsrep_kill_trx( lock_cancel_waiting_and_release(wait_lock); } + wsrep_thd_UNLOCK(thd); + wsrep_thd_awake(thd, signal); } else { /* abort currently executing query */ DBUG_PRINT("wsrep",("sending KILL_QUERY to: %lu", thd_get_thread_id(thd))); WSREP_DEBUG("kill query for: %ld", thd_get_thread_id(thd)); + /* Note that innobase_kill_query will take lock_mutex + and trx_mutex */ + wsrep_thd_UNLOCK(thd); + wsrep_thd_awake(thd, signal); /* for BF thd, we need to prevent him from committing */ if (wsrep_thd_exec_mode(thd) == REPL_RECV) { - wsrep_abort_slave_trx(arg->bf_seqno, + wsrep_abort_slave_trx(bf_seqno, wsrep_thd_trx_seqno(thd)); } } - goto ret_awake; + break; case QUERY_IDLE: { WSREP_DEBUG("kill IDLE for " TRX_ID_FMT, victim_trx->id); @@ -19703,9 +19712,10 @@ static void bg_wsrep_kill_trx( if (wsrep_thd_exec_mode(thd) == REPL_RECV) { WSREP_DEBUG("kill BF IDLE, seqno: %lld", (long long)wsrep_thd_trx_seqno(thd)); - wsrep_abort_slave_trx(arg->bf_seqno, + wsrep_thd_UNLOCK(thd); + wsrep_abort_slave_trx(bf_seqno, wsrep_thd_trx_seqno(thd)); - goto ret_unlock; + DBUG_VOID_RETURN; } /* This will lock thd from proceeding after net_read() */ wsrep_thd_set_conflict_state(thd, ABORTING); @@ -19726,67 +19736,17 @@ static void bg_wsrep_kill_trx( DBUG_PRINT("wsrep",("signalling wsrep rollbacker")); WSREP_DEBUG("signaling aborter"); wsrep_unlock_rollback(); - goto ret_unlock; + wsrep_thd_UNLOCK(thd); + + break; } default: WSREP_WARN("bad wsrep query state: %d", wsrep_thd_query_state(thd)); - goto ret_unlock; + wsrep_thd_UNLOCK(thd); + break; } -ret_awake: - awake= true; - -ret_unlock: - trx_mutex_exit(victim_trx); - lock_mutex_exit(); - if (awake) - wsrep_thd_awake(thd, arg->signal); - wsrep_thd_UNLOCK(thd); - -ret: - free(arg); - DBUG_VOID_RETURN; - -} - -/*******************************************************************//** -This function is used to kill one transaction in BF. */ -UNIV_INTERN -void -wsrep_innobase_kill_one_trx( -/*========================*/ - MYSQL_THD const bf_thd, - const trx_t * const bf_trx, - trx_t *victim_trx, - ibool signal) -{ - ut_ad(bf_thd); - ut_ad(victim_trx); - ut_ad(lock_mutex_own()); - ut_ad(trx_mutex_own(victim_trx)); - - bg_wsrep_kill_trx_arg *arg = (bg_wsrep_kill_trx_arg*)malloc(sizeof(*arg)); - arg->thd_id = thd_get_thread_id(victim_trx->mysql_thd); - arg->trx_id = victim_trx->id; - arg->bf_seqno = wsrep_thd_trx_seqno((THD*)bf_thd); - arg->signal = signal; - - DBUG_ENTER("wsrep_innobase_kill_one_trx"); - - WSREP_LOG_CONFLICT(bf_thd, victim_trx->mysql_thd, TRUE); - - DBUG_EXECUTE_IF("sync.wsrep_after_BF_victim_lock", - { - const char act[]= - "now " - "wait_for signal.wsrep_after_BF_victim_lock"; - DBUG_ASSERT(!debug_sync_set_action(bf_thd, - STRING_WITH_LEN(act))); - };); - - - mysql_manager_submit(bg_wsrep_kill_trx, arg); DBUG_VOID_RETURN; } @@ -19821,8 +19781,8 @@ wsrep_abort_transaction( WSREP_DEBUG("victim does not have transaction"); wsrep_thd_LOCK(victim_thd); wsrep_thd_set_conflict_state(victim_thd, MUST_ABORT); - wsrep_thd_awake(victim_thd, signal); wsrep_thd_UNLOCK(victim_thd); + wsrep_thd_awake(victim_thd, signal); } DBUG_VOID_RETURN; From 88a4be75a5f3b8d59ac8f6347ff2c197813c05dc Mon Sep 17 00:00:00 2001 From: sjaakola Date: Wed, 15 Sep 2021 09:16:44 +0300 Subject: [PATCH 02/10] MDEV-25114 Crash: WSREP: invalid state ROLLED_BACK (FATAL) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch is the plan D variant for fixing potetial mutex locking order exercised by BF aborting and KILL command execution. In this approach, KILL command is replicated as TOI operation. This guarantees total isolation for the KILL command execution in the first node: there is no concurrent replication applying and no concurrent DDL executing. Therefore there is no risk of BF aborting to happen in parallel with KILL command execution either. Potential mutex deadlocks between the different mutex access paths with KILL command execution and BF aborting cannot therefore happen. TOI replication is used, in this approach, purely as means to provide isolated KILL command execution in the first node. KILL command should not (and must not) be applied in secondary nodes. In this patch, we make this sure by skipping KILL execution in secondary nodes, in applying phase, where we bail out if applier thread is trying to execute KILL command. This is effective, but skipping the applying of KILL command could happen much earlier as well. This patch also fixes mutex locking order and unprotected THD member accesses on bf aborting case. We try to hold THD::LOCK_thd_data during bf aborting. Only case where it is not possible is at wsrep_abort_transaction before call wsrep_innobase_kill_one_trx where we take InnoDB mutexes first and then THD::LOCK_thd_data. This will also fix possible race condition during close_connection and while wsrep is disconnecting connections. Added wsrep_bf_kill_debug test case Reviewed-by: Jan Lindström --- .../suite/galera/r/galera_UK_conflict.result | 7 + .../galera/r/galera_bf_kill_debug.result | 163 ++++++++++ .../galera/r/galera_toi_ddl_fk_insert.result | 16 - .../suite/galera/t/galera_UK_conflict.test | 4 + .../suite/galera/t/galera_bf_kill_debug.cnf | 9 + .../suite/galera/t/galera_bf_kill_debug.test | 283 ++++++++++++++++++ .../galera/t/galera_toi_ddl_fk_insert.test | 14 +- mysql-test/suite/wsrep/t/variables.test | 6 +- sql/mysqld.cc | 9 +- sql/mysqld.h | 2 +- sql/sql_class.cc | 24 +- sql/sql_class.h | 9 +- sql/sql_parse.cc | 36 ++- sql/wsrep_mysqld.cc | 94 ++++-- sql/wsrep_thd.cc | 8 +- sql/wsrep_thd.h | 1 - storage/innobase/handler/ha_innodb.cc | 186 +++++++----- storage/innobase/include/ha_prototypes.h | 3 +- 18 files changed, 724 insertions(+), 150 deletions(-) create mode 100644 mysql-test/suite/galera/r/galera_bf_kill_debug.result create mode 100644 mysql-test/suite/galera/t/galera_bf_kill_debug.cnf create mode 100644 mysql-test/suite/galera/t/galera_bf_kill_debug.test diff --git a/mysql-test/suite/galera/r/galera_UK_conflict.result b/mysql-test/suite/galera/r/galera_UK_conflict.result index 76649f1b268..2795a86d6a6 100644 --- a/mysql-test/suite/galera/r/galera_UK_conflict.result +++ b/mysql-test/suite/galera/r/galera_UK_conflict.result @@ -68,6 +68,9 @@ f1 f2 f3 10 10 0 INSERT INTO t1 VALUES (7,7,7); INSERT INTO t1 VALUES (8,8,8); +SELECT COUNT(*) FROM t1; +COUNT(*) +7 SELECT * FROM t1; f1 f2 f3 1 1 0 @@ -78,6 +81,9 @@ f1 f2 f3 8 8 8 10 10 0 connection node_1; +SELECT COUNT(*) FROM t1; +COUNT(*) +7 SELECT * FROM t1; f1 f2 f3 1 1 0 @@ -85,5 +91,6 @@ f1 f2 f3 4 4 2 5 5 2 7 7 7 +8 8 8 10 10 0 DROP TABLE t1; diff --git a/mysql-test/suite/galera/r/galera_bf_kill_debug.result b/mysql-test/suite/galera/r/galera_bf_kill_debug.result new file mode 100644 index 00000000000..c20bb80131a --- /dev/null +++ b/mysql-test/suite/galera/r/galera_bf_kill_debug.result @@ -0,0 +1,163 @@ +# +# Case 1: We execute bf kill to wsrep_innobase_kill_one_trx +# function just before wsrep_thd_LOCK(thd) call. Then we +# try to kill victim transaction by KILL QUERY +# +CREATE TABLE t1(id int not null primary key, b int) engine=innodb; +INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); +connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; +begin; +update t1 set b = b * 10 where id between 2 and 4; +connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1; +SET DEBUG_SYNC='wsrep_before_BF_victim_lock SIGNAL bf_kill WAIT_FOR bf_continue'; +ALTER TABLE t1 ADD UNIQUE KEY b1(b);; +connection node_1; +SET DEBUG_SYNC='now WAIT_FOR bf_kill'; +connection node_1b; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL, + `b` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `b1` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +id b +1 1 +2 2 +3 3 +4 4 +5 5 +connection node_1; +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; +disconnect node_1a; +disconnect node_1b; +disconnect node_1c; +# +# Case 2: We execute bf kill to wsrep_innobase_kill_one_trx +# function just after wsrep_thd_LOCK(thd) call. Then we +# try to kill victim transaction by KILL QUERY +# +CREATE TABLE t1(id int not null primary key, b int) engine=innodb; +INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); +connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; +begin; +update t1 set b = b * 10 where id between 2 and 4; +connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1; +SET DEBUG_SYNC='wsrep_after_BF_victim_lock SIGNAL bf_kill WAIT_FOR bf_continue'; +ALTER TABLE t1 ADD UNIQUE KEY b1(b);; +connection node_1; +SET DEBUG_SYNC='now WAIT_FOR bf_kill'; +connection node_1b; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL, + `b` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `b1` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +id b +1 1 +2 2 +3 3 +4 4 +5 5 +connection node_1; +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; +disconnect node_1a; +disconnect node_1b; +disconnect node_1c; +# +# Case 3: Create victim transaction and try to send user KILL +# from several threads +# +CREATE TABLE t1(id int not null primary key, b int) engine=innodb; +INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); +connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; +begin; +update t1 set b = b * 10 where id between 2 and 4; +connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1d, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connection node_1b; +connection node_1c; +connection node_1d; +connection node_1; +disconnect node_1a; +disconnect node_1b; +disconnect node_1c; +disconnect node_1d; +DROP TABLE t1; +# +# Case 4: MDL-conflict, we execute ALTER until we hit gap in +# wsrep_abort_transaction, while we are there we try to +# manually KILL conflicting transaction (UPDATE) and +# send conflicting transaction from other node to be executed +# in this node by applier. As ALTER and KILL are TOI they +# are not executed concurrently. Similarly UPDATE from other +# node will wait for certification. +# +CREATE TABLE t1(id int not null primary key, b int) engine=innodb; +INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); +connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; +begin; +update t1 set b = b * 10 where id between 2 and 4; +connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1; +SET DEBUG_SYNC='wsrep_abort_victim_unlocked SIGNAL bf_kill_unlocked WAIT_FOR bf_continue'; +ALTER TABLE t1 ADD UNIQUE KEY b1(b);; +connection node_1; +SET DEBUG_SYNC='now WAIT_FOR bf_kill_unlocked'; +connection node_1b; +connection node_2; +update t1 set b = b + 1000 where id between 2 and 4;; +connection node_1; +SET DEBUG_SYNC='now SIGNAL bf_continue'; +connection node_1c; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL, + `b` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `b1` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SELECT * FROM t1; +id b +1 1 +5 5 +2 1002 +3 1003 +4 1004 +connection node_1b; +connection node_1; +SET DEBUG_SYNC= 'RESET'; +SELECT * FROM t1; +id b +1 1 +5 5 +2 1002 +3 1003 +4 1004 +connection node_2; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL, + `b` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `b1` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +SELECT * FROM t1; +id b +1 1 +5 5 +2 1002 +3 1003 +4 1004 +DROP TABLE t1; +disconnect node_1a; +disconnect node_1c; diff --git a/mysql-test/suite/galera/r/galera_toi_ddl_fk_insert.result b/mysql-test/suite/galera/r/galera_toi_ddl_fk_insert.result index 94752ed7c76..a972394f87c 100644 --- a/mysql-test/suite/galera/r/galera_toi_ddl_fk_insert.result +++ b/mysql-test/suite/galera/r/galera_toi_ddl_fk_insert.result @@ -21,22 +21,6 @@ connection node_1a; connection node_1b; connection node_2; connection node_2a; -connection node_1; -SET SESSION wsrep_sync_wait=15; -SELECT COUNT(*) FROM parent; -COUNT(*) -20001 -SELECT COUNT(*) FROM child; -COUNT(*) -10000 -connection node_2; -SET SESSION wsrep_sync_wait=15; -SELECT COUNT(*) FROM parent; -COUNT(*) -20001 -SELECT COUNT(*) FROM child; -COUNT(*) -10000 DROP TABLE child; DROP TABLE parent; DROP TABLE ten; diff --git a/mysql-test/suite/galera/t/galera_UK_conflict.test b/mysql-test/suite/galera/t/galera_UK_conflict.test index 57bafbf8ae0..83d0e47dc3d 100644 --- a/mysql-test/suite/galera/t/galera_UK_conflict.test +++ b/mysql-test/suite/galera/t/galera_UK_conflict.test @@ -140,9 +140,13 @@ SELECT * FROM t1; # original state in node 1 INSERT INTO t1 VALUES (7,7,7); INSERT INTO t1 VALUES (8,8,8); +SELECT COUNT(*) FROM t1; SELECT * FROM t1; --connection node_1 +--let $wait_condition = SELECT COUNT(*) = 7 FROM t1 +--source include/wait_condition.inc +SELECT COUNT(*) FROM t1; SELECT * FROM t1; DROP TABLE t1; diff --git a/mysql-test/suite/galera/t/galera_bf_kill_debug.cnf b/mysql-test/suite/galera/t/galera_bf_kill_debug.cnf new file mode 100644 index 00000000000..77bb6af9f35 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_bf_kill_debug.cnf @@ -0,0 +1,9 @@ +!include ../galera_2nodes.cnf + +[mysqld.1] +wsrep_log_conflicts=ON +wsrep_debug=1 + +[mysqld.2] +wsrep_log_conflicts=ON +wsrep_debug=1 diff --git a/mysql-test/suite/galera/t/galera_bf_kill_debug.test b/mysql-test/suite/galera/t/galera_bf_kill_debug.test new file mode 100644 index 00000000000..f83d4a28ce9 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_bf_kill_debug.test @@ -0,0 +1,283 @@ +--source include/galera_cluster.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc + +--echo # +--echo # Case 1: We execute bf kill to wsrep_innobase_kill_one_trx +--echo # function just before wsrep_thd_LOCK(thd) call. Then we +--echo # try to kill victim transaction by KILL QUERY +--echo # + +CREATE TABLE t1(id int not null primary key, b int) engine=innodb; +INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); + +# +# This will be victim transaction for both bf kill and +# user KILL +# +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 +begin; +update t1 set b = b * 10 where id between 2 and 4; + +# +# Take thread id for above query +# +--connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` + +# +# Set DEBUG_SYNC and send conflicting DDL that will be TOI (bf) and +# cause bf_kill +# +--connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1 +SET DEBUG_SYNC='wsrep_before_BF_victim_lock SIGNAL bf_kill WAIT_FOR bf_continue'; +--send ALTER TABLE t1 ADD UNIQUE KEY b1(b); + +# +# Wait until we have reached the sync point +# +--connection node_1 +SET DEBUG_SYNC='now WAIT_FOR bf_kill'; + +# +# Try to kill update query +# +--connection node_1b +--disable_query_log +--send_eval KILL QUERY $k_thread; + + +# +# Let bf_kill continue +# +--connection node_1 +SET DEBUG_SYNC='now SIGNAL bf_continue'; +--connection node_1c +--reap +SHOW CREATE TABLE t1; +SELECT * FROM t1; + +--connection node_1b +--reap +--enable_query_log + +--connection node_1 +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; + +--disconnect node_1a +--disconnect node_1b +--disconnect node_1c + +--echo # +--echo # Case 2: We execute bf kill to wsrep_innobase_kill_one_trx +--echo # function just after wsrep_thd_LOCK(thd) call. Then we +--echo # try to kill victim transaction by KILL QUERY +--echo # + +CREATE TABLE t1(id int not null primary key, b int) engine=innodb; +INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); + +# +# This will be victim transaction for both bf kill and +# user KILL +# +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 +begin; +update t1 set b = b * 10 where id between 2 and 4; + +# +# Take thread id for above query +# +--connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` + +# +# Set DEBUG_SYNC and send conflicting DDL that will be TOI (bf) and +# cause bf_kill +# +--connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1 +SET DEBUG_SYNC='wsrep_after_BF_victim_lock SIGNAL bf_kill WAIT_FOR bf_continue'; +--send ALTER TABLE t1 ADD UNIQUE KEY b1(b); + +# +# Wait until we have reached the sync point +# +--connection node_1 +SET DEBUG_SYNC='now WAIT_FOR bf_kill'; + +# +# Try to kill update query +# +--connection node_1b +--disable_query_log +--send_eval KILL QUERY $k_thread; + +# +# Let bf_kill continue +# +--connection node_1 +SET DEBUG_SYNC='now SIGNAL bf_continue'; +--connection node_1c +--reap +SHOW CREATE TABLE t1; +SELECT * FROM t1; + +--connection node_1b +--reap +--enable_query_log + +--connection node_1 +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; + +--disconnect node_1a +--disconnect node_1b +--disconnect node_1c + +--echo # +--echo # Case 3: Create victim transaction and try to send user KILL +--echo # from several threads +--echo # + +CREATE TABLE t1(id int not null primary key, b int) engine=innodb; +INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); + +# +# This will be victim transaction for user KILL +# +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 +begin; +update t1 set b = b * 10 where id between 2 and 4; + +# +# Take thread id for above query +# +--connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connect node_1d, 127.0.0.1, root, , test, $NODE_MYPORT_1 + +--connection node_1b +--let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` + +# +# Try to kill update query from several connections concurrently +# +--disable_query_log +--send_eval KILL QUERY $k_thread; + +--connection node_1c +--disable_query_log +--send_eval KILL QUERY $k_thread; + +--connection node_1d +--disable_query_log +--send_eval KILL QUERY $k_thread; + +# +# We do not know execution order so any of these could fail as KILL +# has been already done +# +--connection node_1b +--enable_query_log +--error 0,ER_KILL_DENIED_ERROR +--reap +--connection node_1c +--enable_query_log +--error 0,ER_KILL_DENIED_ERROR +--reap +--connection node_1d +--enable_query_log +--error 0,ER_KILL_DENIED_ERROR +--reap + +--connection node_1 +--disconnect node_1a +--disconnect node_1b +--disconnect node_1c +--disconnect node_1d +DROP TABLE t1; + +--echo # +--echo # Case 4: MDL-conflict, we execute ALTER until we hit gap in +--echo # wsrep_abort_transaction, while we are there we try to +--echo # manually KILL conflicting transaction (UPDATE) and +--echo # send conflicting transaction from other node to be executed +--echo # in this node by applier. As ALTER and KILL are TOI they +--echo # are not executed concurrently. Similarly UPDATE from other +--echo # node will wait for certification. +--echo # + +CREATE TABLE t1(id int not null primary key, b int) engine=innodb; +INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); + +# +# This will be victim transaction for both bf kill and +# user KILL, and should not have any effect on result +# +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 +begin; +update t1 set b = b * 10 where id between 2 and 4; + +# +# Take thread id for above query +# +--connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` + +# +# Set DEBUG_SYNC and send conflicting DDL that will be TOI (bf) and +# cause bf_kill but let's execute it only to gap in wsrep_abort_transaction +# +--connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1 +SET DEBUG_SYNC='wsrep_abort_victim_unlocked SIGNAL bf_kill_unlocked WAIT_FOR bf_continue'; +--send ALTER TABLE t1 ADD UNIQUE KEY b1(b); + +# +# Wait until we have reached the sync point +# +--connection node_1 +SET DEBUG_SYNC='now WAIT_FOR bf_kill_unlocked'; + +# +# Try to kill update query +# +--connection node_1b +--disable_query_log +--send_eval KILL QUERY $k_thread; + +# +# Send conflicting update from other node, this should be applied on both nodes +# but should not kill ALTER +# +--enable_query_log +--connection node_2 +--send update t1 set b = b + 1000 where id between 2 and 4; + +# +# Let bf_kill continue +# +--connection node_1 +SET DEBUG_SYNC='now SIGNAL bf_continue'; +--connection node_1c +--reap +SHOW CREATE TABLE t1; +SELECT * FROM t1; + +--connection node_1b +--reap +--enable_query_log + +--connection node_1 +SET DEBUG_SYNC= 'RESET'; +SELECT * FROM t1; + +--connection node_2 +--reap +SHOW CREATE TABLE t1; +SELECT * FROM t1; +DROP TABLE t1; + +--disconnect node_1a +--disconnect node_1c + diff --git a/mysql-test/suite/galera/t/galera_toi_ddl_fk_insert.test b/mysql-test/suite/galera/t/galera_toi_ddl_fk_insert.test index fadc94d78ff..3b4b427f551 100644 --- a/mysql-test/suite/galera/t/galera_toi_ddl_fk_insert.test +++ b/mysql-test/suite/galera/t/galera_toi_ddl_fk_insert.test @@ -54,15 +54,11 @@ INSERT INTO parent VALUES (1, 0); --connection node_2a --reap ---connection node_1 -SET SESSION wsrep_sync_wait=15; -SELECT COUNT(*) FROM parent; -SELECT COUNT(*) FROM child; - ---connection node_2 -SET SESSION wsrep_sync_wait=15; -SELECT COUNT(*) FROM parent; -SELECT COUNT(*) FROM child; +# +# ALTER TABLE could bf kill one or more of INSERTs to parent, so +# the actual number of rows in PARENT depends on whether +# the INSERT is committed before ALTER TABLE is executed +# DROP TABLE child; DROP TABLE parent; diff --git a/mysql-test/suite/wsrep/t/variables.test b/mysql-test/suite/wsrep/t/variables.test index 0cf13650ce0..875315c0e7c 100644 --- a/mysql-test/suite/wsrep/t/variables.test +++ b/mysql-test/suite/wsrep/t/variables.test @@ -66,7 +66,7 @@ call mtr.add_suppression("WSREP: Failed to get provider options"); #evalp SET GLOBAL wsrep_provider= '$WSREP_PROVIDER'; ---replace_regex /.*libgalera_smm.*/libgalera_smm.so/ +--replace_regex /.*libgalera.*/libgalera_smm.so/ SELECT @@global.wsrep_provider; SELECT @@global.wsrep_slave_threads; SELECT @@global.wsrep_cluster_address; @@ -77,7 +77,7 @@ SHOW STATUS LIKE 'wsrep_thread_count'; #evalp SET GLOBAL wsrep_provider= '$WSREP_PROVIDER'; ---replace_regex /.*libgalera_smm.*/libgalera_smm.so/ +--replace_regex /.*libgalera.*/libgalera_smm.so/ SELECT @@global.wsrep_provider; SELECT @@global.wsrep_cluster_address; SELECT @@global.wsrep_on; @@ -101,7 +101,7 @@ SELECT VARIABLE_VALUE AS EXPECT_1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VA SELECT VARIABLE_VALUE AS EXPECT_1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_rollbacker_thread_count'; SELECT VARIABLE_VALUE AS EXPECT_2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_thread_count'; ---replace_regex /.*libgalera_smm.*/libgalera_smm.so/ +--replace_regex /.*libgalera.*/libgalera_smm.so/ SELECT @@global.wsrep_provider; SELECT @@global.wsrep_cluster_address; SELECT @@global.wsrep_on; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 9afc701c6ba..269ad67165a 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -2391,7 +2391,7 @@ static void clean_up_mutexes() static void set_ports() { } -void close_connection(THD *thd, uint sql_errno) +void close_connection(THD *thd, uint sql_errno, my_bool locked) { } #else @@ -2867,7 +2867,7 @@ static void network_init(void) For the connection that is doing shutdown, this is called twice */ -void close_connection(THD *thd, uint sql_errno) +void close_connection(THD *thd, uint sql_errno, my_bool locked) { DBUG_ENTER("close_connection"); @@ -2877,7 +2877,10 @@ void close_connection(THD *thd, uint sql_errno) thd->print_aborted_warning(3, sql_errno ? ER_DEFAULT(sql_errno) : "CLOSE_CONNECTION"); - thd->disconnect(); + if (locked) + thd->disconnect_mutexed(); + else + thd->disconnect(); MYSQL_CONNECTION_DONE((int) sql_errno, thd->thread_id); diff --git a/sql/mysqld.h b/sql/mysqld.h index 44b0491f138..1084173ae5f 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -83,7 +83,7 @@ enum enum_slave_parallel_mode { /* Function prototypes */ void kill_mysql(THD *thd= 0); -void close_connection(THD *thd, uint sql_errno= 0); +void close_connection(THD *thd, uint sql_errno= 0, my_bool locked=false); void handle_connection_in_main_thread(CONNECT *thd); void create_thread_to_handle_connection(CONNECT *connect); void signal_thd_deleted(); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 5ada018e540..05ec9a1c369 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1804,11 +1804,11 @@ void THD::awake(killed_state state_to_set) the Vio might be disassociated concurrently. */ -void THD::disconnect() +void THD::disconnect_mutexed() { Vio *vio= NULL; - mysql_mutex_lock(&LOCK_thd_data); + mysql_mutex_assert_owner(&LOCK_thd_data); set_killed(KILL_CONNECTION); @@ -1826,8 +1826,6 @@ void THD::disconnect() if (net.vio != vio) vio_close(net.vio); net.thd= 0; // Don't collect statistics - - mysql_mutex_unlock(&LOCK_thd_data); } @@ -1879,16 +1877,18 @@ bool THD::notify_shared_lock(MDL_context_owner *ctx_in_use, thread can see those instances (e.g. see partitioning code). */ if (!thd_table->needs_reopen()) - { signalled|= mysql_lock_abort_for_thread(this, thd_table); - if (WSREP(this) && wsrep_thd_is_BF(this, FALSE)) - { - WSREP_DEBUG("remove_table_from_cache: %llu", - (unsigned long long) this->real_id); - wsrep_abort_thd((void *)this, (void *)in_use, FALSE); - } - } } +#ifdef WITH_WSREP + if (WSREP(this) && wsrep_thd_is_BF(this, false)) + { + WSREP_DEBUG("notify_shared_lock: BF thread %llu query %s" + " victim %llu query %s", + this->real_id, wsrep_thd_query(this), + in_use->real_id, wsrep_thd_query(in_use)); + wsrep_abort_thd((void *)this, (void *)in_use, false); + } +#endif /* WITH_WSREP */ } mysql_mutex_unlock(&in_use->LOCK_thd_data); } diff --git a/sql/sql_class.h b/sql/sql_class.h index 5f871f9caf6..7dbc81be770 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3231,8 +3231,13 @@ public: void awake(killed_state state_to_set); /** Disconnect the associated communication endpoint. */ - void disconnect(); - + inline void disconnect() + { + mysql_mutex_lock(&LOCK_thd_data); + disconnect_mutexed(); + mysql_mutex_unlock(&LOCK_thd_data); + } + void disconnect_mutexed(); /* Allows this thread to serve as a target for others to schedule Async diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3e1f248b082..2bec9c6b6cd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2017, Oracle and/or its affiliates. - Copyright (c) 2008, 2020, MariaDB + Copyright (c) 2008, 2021, MariaDB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -9069,6 +9069,18 @@ static void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) { uint error; +#ifdef WITH_WSREP + if (WSREP(thd)) + { + WSREP_DEBUG("sql_kill called"); + if (thd->wsrep_applier) + { + WSREP_DEBUG("KILL in applying, bailing out here"); + return; + } + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + } +#endif /* WITH_WSREP */ if (!(error= kill_one_thread(thd, id, state, type))) { if (!thd->killed) @@ -9078,6 +9090,11 @@ void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) } else my_error(error, MYF(0), id); +#ifdef WITH_WSREP + return; + wsrep_error_label: + my_error(ER_CANNOT_USER, MYF(0), wsrep_thd_query(thd)); +#endif /* WITH_WSREP */ } @@ -9086,6 +9103,18 @@ void sql_kill_user(THD *thd, LEX_USER *user, killed_state state) { uint error; ha_rows rows; +#ifdef WITH_WSREP + if (WSREP(thd)) + { + WSREP_DEBUG("sql_kill_user called"); + if (thd->wsrep_applier) + { + WSREP_DEBUG("KILL in applying, bailing out here"); + return; + } + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + } +#endif /* WITH_WSREP */ if (!(error= kill_threads_for_user(thd, user, state, &rows))) my_ok(thd, rows); else @@ -9096,6 +9125,11 @@ void sql_kill_user(THD *thd, LEX_USER *user, killed_state state) */ my_error(error, MYF(0), user->host.str, user->user.str); } +#ifdef WITH_WSREP + return; + wsrep_error_label: + my_error(ER_CANNOT_USER, MYF(0), user->user.str); +#endif /* WITH_WSREP */ } diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index e60100e2e90..00a6dfe2f8a 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1,4 +1,4 @@ -/* Copyright 2008-2015 Codership Oy +/* Copyright 2008-2021 Codership Oy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -835,13 +835,25 @@ void wsrep_thr_init() DBUG_VOID_RETURN; } +/* This is wrapper for wsrep_break_lock in thr_lock.c */ +static int wsrep_thr_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, my_bool signal) +{ + THD* victim_thd= (THD *) victim_thd_ptr; + /* We need to lock THD::LOCK_thd_data to protect victim + from concurrent usage or disconnect or delete. */ + mysql_mutex_lock(&victim_thd->LOCK_thd_data); + int res= wsrep_abort_thd(bf_thd_ptr, victim_thd_ptr, signal); + mysql_mutex_unlock(&victim_thd->LOCK_thd_data); + return res; +} + void wsrep_init_startup (bool first) { if (wsrep_init()) unireg_abort(1); wsrep_thr_lock_init( (wsrep_thd_is_brute_force_fun)wsrep_thd_is_BF, - (wsrep_abort_thd_fun)wsrep_abort_thd, + (wsrep_abort_thd_fun)wsrep_thr_abort_thd, wsrep_debug, wsrep_convert_LOCK_to_trx, (wsrep_on_fun)wsrep_on); @@ -1694,6 +1706,11 @@ static int wsrep_TOI_begin(THD *thd, char *db_, char *table_, case SQLCOM_DROP_TABLE: buf_err= wsrep_drop_table_query(thd, &buf, &buf_len); break; + case SQLCOM_KILL: + WSREP_DEBUG("KILL as TOI: %s", thd->query()); + buf_err= wsrep_to_buf_helper(thd, thd->query(), thd->query_length(), + &buf, &buf_len); + break; case SQLCOM_CREATE_ROLE: if (sp_process_definer(thd)) { @@ -2058,8 +2075,11 @@ bool wsrep_grant_mdl_exception(MDL_context *requestor_ctx, ticket->wsrep_report(true); } - mysql_mutex_unlock(&granted_thd->LOCK_thd_data); + /* This will call wsrep_abort_transaction so we should hold + THD::LOCK_thd_data to protect victim from concurrent usage + or disconnect or delete. */ wsrep_abort_thd((void *) request_thd, (void *) granted_thd, 1); + mysql_mutex_unlock(&granted_thd->LOCK_thd_data); ret= false; } } @@ -2241,6 +2261,7 @@ error: static bool abort_replicated(THD *thd) { bool ret_code= false; + mysql_mutex_assert_owner(&thd->LOCK_thd_data); if (thd->wsrep_query_state== QUERY_COMMITTING) { WSREP_DEBUG("aborting replicated trx: %llu", (ulonglong)(thd->real_id)); @@ -2255,6 +2276,7 @@ static bool abort_replicated(THD *thd) /**/ static inline bool is_client_connection(THD *thd) { + mysql_mutex_assert_owner(&thd->LOCK_thd_data); return (thd->wsrep_client_thread && thd->variables.wsrep_on); } @@ -2263,9 +2285,8 @@ static inline bool is_replaying_connection(THD *thd) { bool ret; - mysql_mutex_lock(&thd->LOCK_thd_data); + mysql_mutex_assert_owner(&thd->LOCK_thd_data); ret= (thd->wsrep_conflict_state == REPLAYING) ? true : false; - mysql_mutex_unlock(&thd->LOCK_thd_data); return ret; } @@ -2275,9 +2296,8 @@ static inline bool is_committing_connection(THD *thd) { bool ret; - mysql_mutex_lock(&thd->LOCK_thd_data); + mysql_mutex_assert_owner(&thd->LOCK_thd_data); ret= (thd->wsrep_query_state == QUERY_COMMITTING) ? true : false; - mysql_mutex_unlock(&thd->LOCK_thd_data); return ret; } @@ -2290,13 +2310,17 @@ static bool have_client_connections() I_List_iterator it(threads); while ((tmp=it++)) { + /* Protect thread from concurrent usage or disconnect or delete. */ + mysql_mutex_lock(&tmp->LOCK_thd_data); DBUG_PRINT("quit",("Informing thread %lld that it's time to die", (longlong) tmp->thread_id)); if (is_client_connection(tmp) && tmp->killed == KILL_CONNECTION) { (void)abort_replicated(tmp); + mysql_mutex_unlock(&tmp->LOCK_thd_data); return true; } + mysql_mutex_unlock(&tmp->LOCK_thd_data); } return false; } @@ -2328,14 +2352,21 @@ static my_bool have_committing_connections() I_List_iterator it(threads); while ((tmp=it++)) { + /* Protect from concurrent usage or disconnect or delete */ + mysql_mutex_lock(&tmp->LOCK_thd_data); if (!is_client_connection(tmp)) + { + mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; + } if (is_committing_connection(tmp)) { mysql_mutex_unlock(&LOCK_thread_count); + mysql_mutex_unlock(&tmp->LOCK_thd_data); return TRUE; } + mysql_mutex_unlock(&tmp->LOCK_thd_data); } mysql_mutex_unlock(&LOCK_thread_count); return FALSE; @@ -2378,33 +2409,44 @@ void wsrep_close_client_connections(my_bool wait_to_end, THD *except_caller_thd) { DBUG_PRINT("quit",("Informing thread %lld that it's time to die", (longlong) tmp->thread_id)); + /* Protect from concurrent usage or disconnect or delete */ + mysql_mutex_lock(&tmp->LOCK_thd_data); /* We skip slave threads & scheduler on this first loop through. */ if (!is_client_connection(tmp)) + { + mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; + } if (tmp == except_caller_thd) { DBUG_ASSERT(is_client_connection(tmp)); + mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; } if (is_replaying_connection(tmp)) { tmp->set_killed(KILL_CONNECTION); + mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; } - /* replicated transactions must be skipped */ + /* replicated transactions must be skipped and aborted + with wsrep_abort_thd. */ if (abort_replicated(tmp)) + { + mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; + } WSREP_DEBUG("closing connection %lld", (longlong) tmp->thread_id); /* - instead of wsrep_close_thread() we do now soft kill by THD::awake - */ - mysql_mutex_lock(&tmp->LOCK_thd_data); - + instead of wsrep_close_thread() we do now soft kill by + THD::awake(). Here also victim needs to be protected from + concurrent usage or disconnect or delete. + */ tmp->awake(KILL_CONNECTION); mysql_mutex_unlock(&tmp->LOCK_thd_data); @@ -2423,16 +2465,19 @@ void wsrep_close_client_connections(my_bool wait_to_end, THD *except_caller_thd) I_List_iterator it2(threads); while ((tmp=it2++)) { -#ifndef __bsdi__ // Bug in BSDI kernel - if (is_client_connection(tmp) && - !abort_replicated(tmp) && - !is_replaying_connection(tmp) && - tmp != except_caller_thd) + /* Protect from concurrent usage or disconnect or delete */ + mysql_mutex_lock(&tmp->LOCK_thd_data); + if (is_client_connection(tmp)) { - WSREP_INFO("killing local connection: %lld", (longlong) tmp->thread_id); - close_connection(tmp,0); + if (!abort_replicated(tmp) && + !is_replaying_connection(tmp) && + tmp != except_caller_thd) + { + WSREP_INFO("killing local connection: %lld", (longlong) tmp->thread_id); + close_connection(tmp,0, true); + } } -#endif + mysql_mutex_unlock(&tmp->LOCK_thd_data); } DBUG_PRINT("quit",("Waiting for threads to die (count=%u)",thread_count)); @@ -2621,7 +2666,9 @@ extern "C" void wsrep_thd_set_query_state( void wsrep_thd_set_conflict_state(THD *thd, enum wsrep_conflict_state state) { - if (WSREP(thd)) thd->wsrep_conflict_state= state; + DBUG_ASSERT(thd); + mysql_mutex_assert_owner(&thd->LOCK_thd_data); + thd->wsrep_conflict_state= state; } @@ -2762,9 +2809,10 @@ extern "C" void wsrep_thd_awake(THD *thd, my_bool signal) { if (signal) { - mysql_mutex_lock(&thd->LOCK_thd_data); + /* Here we should hold THD::LOCK_thd_data to + protect from concurrent usage. */ + mysql_mutex_assert_owner(&thd->LOCK_thd_data); thd->awake(KILL_QUERY); - mysql_mutex_unlock(&thd->LOCK_thd_data); } else { diff --git a/sql/wsrep_thd.cc b/sql/wsrep_thd.cc index ef8c0e132f7..d849cd2cd78 100644 --- a/sql/wsrep_thd.cc +++ b/sql/wsrep_thd.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Codership Oy +/* Copyright (C) 2013-2021 Codership Oy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -804,10 +804,12 @@ my_bool wsrep_thd_is_local(void *thd_ptr, my_bool sync) int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, my_bool signal) { - THD *victim_thd = (THD *) victim_thd_ptr; - THD *bf_thd = (THD *) bf_thd_ptr; + THD *victim_thd= (THD *) victim_thd_ptr; + THD *bf_thd= (THD *) bf_thd_ptr; DBUG_ENTER("wsrep_abort_thd"); + mysql_mutex_assert_owner(&victim_thd->LOCK_thd_data); + if ( (WSREP(bf_thd) || ( (WSREP_ON || bf_thd->variables.wsrep_OSU_method == WSREP_OSU_RSU) && bf_thd->wsrep_exec_mode == TOTAL_ORDER) ) && diff --git a/sql/wsrep_thd.h b/sql/wsrep_thd.h index 46bc08a466a..1284cad1acf 100644 --- a/sql/wsrep_thd.h +++ b/sql/wsrep_thd.h @@ -32,7 +32,6 @@ void wsrep_create_rollbacker(); int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, my_bool signal); - /* PA = Parallel Applying (on the slave side) */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index d4142481a62..efcdd201fa6 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -5233,17 +5233,21 @@ UNIV_INTERN void lock_cancel_waiting_and_release(lock_t* lock); @sa THD::awake() @sa ha_kill_query() */ static void innobase_kill_query(handlerton*, THD* thd, enum thd_kill_levels) { - DBUG_ENTER("innobase_kill_query"); + DBUG_ENTER("innobase_kill_query"); #ifdef WITH_WSREP - if (wsrep_thd_get_conflict_state(thd) != NO_CONFLICT) { - /* if victim has been signaled by BF thread and/or aborting - is already progressing, following query aborting is not necessary - any more. - Also, BF thread should own trx mutex for the victim, which would - conflict with trx_mutex_enter() below - */ - DBUG_VOID_RETURN; - } + if (wsrep_thd_get_conflict_state(thd) != NO_CONFLICT) + { + /* if victim has been signaled by BF thread and/or aborting + is already progressing, following query aborting is not necessary + any more. E.g. wsrep_innobase_kill_one_trx(). + Also, BF thread should own trx mutex for the victim, which would + conflict with trx_mutex_enter() below + */ + WSREP_DEBUG("Victim thread %ld bail out conflict_state %s query %s", + thd_get_thread_id(thd), + wsrep_thd_conflict_state_str(thd), wsrep_thd_query(thd)); + DBUG_VOID_RETURN; + } #endif /* WITH_WSREP */ if (trx_t* trx= thd_to_trx(thd)) @@ -19520,9 +19524,9 @@ static struct st_mysql_storage_engine innobase_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; #ifdef WITH_WSREP +static void wsrep_abort_slave_trx( -/*==================*/ wsrep_seqno_t bf_seqno, wsrep_seqno_t victim_seqno) { @@ -19532,19 +19536,17 @@ wsrep_abort_slave_trx( "2) a bug in the code.\n\t" "3) a database corruption.\n Node consistency compromized, " "need to abort. Restart the node to resync with cluster.", - (long long)bf_seqno, (long long)victim_seqno); + bf_seqno, victim_seqno); abort(); } /*******************************************************************//** This function is used to kill one transaction in BF. */ -UNIV_INTERN void wsrep_innobase_kill_one_trx( -/*========================*/ MYSQL_THD const bf_thd, const trx_t * const bf_trx, trx_t *victim_trx, - ibool signal) + my_bool signal) { ut_ad(bf_thd); ut_ad(victim_trx); @@ -19552,38 +19554,41 @@ wsrep_innobase_kill_one_trx( ut_ad(trx_mutex_own(victim_trx)); DBUG_ENTER("wsrep_innobase_kill_one_trx"); - THD *thd = (THD *) victim_trx->mysql_thd; - int64_t bf_seqno = wsrep_thd_trx_seqno(bf_thd); + THD *thd= (THD *) victim_trx->mysql_thd; + int64_t bf_seqno= wsrep_thd_trx_seqno(bf_thd); if (!thd) { - DBUG_PRINT("wsrep", ("no thd for conflicting lock")); WSREP_WARN("no THD for trx: " TRX_ID_FMT, victim_trx->id); DBUG_VOID_RETURN; } - WSREP_LOG_CONFLICT(bf_thd, thd, TRUE); - - WSREP_DEBUG("BF kill (" ULINTPF ", seqno: " INT64PF - "), victim: (%lu) trx: " TRX_ID_FMT, - signal, bf_seqno, - thd_get_thread_id(thd), - victim_trx->id); - - WSREP_DEBUG("Aborting query: %s conf %d trx: %" PRId64, - (thd && wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void", - wsrep_thd_conflict_state(thd, FALSE), - wsrep_thd_ws_handle(thd)->trx_id); - + /* Here we need to lock THD::LOCK_thd_data to protect from + concurrent usage or disconnect or delete. */ + DEBUG_SYNC(bf_thd, "wsrep_before_BF_victim_lock"); wsrep_thd_LOCK(thd); - DBUG_EXECUTE_IF("sync.wsrep_after_BF_victim_lock", - { - const char act[]= - "now " - "wait_for signal.wsrep_after_BF_victim_lock"; - DBUG_ASSERT(!debug_sync_set_action(bf_thd, - STRING_WITH_LEN(act))); - };); + DEBUG_SYNC(bf_thd, "wsrep_after_BF_victim_lock"); + WSREP_DEBUG("Aborter %s trx_id: " TRX_ID_FMT " thread: %ld " + "seqno: %lld query_state: %s conflict_state: %s query: %s", + wsrep_thd_is_BF(bf_thd, false) ? "BF" : "normal", + bf_trx ? bf_trx->id : TRX_ID_MAX, + thd_get_thread_id(bf_thd), + bf_seqno, + wsrep_thd_query_state_str(bf_thd), + wsrep_thd_conflict_state_str(bf_thd), + wsrep_thd_query(bf_thd)); + + WSREP_DEBUG("Victim %s trx_id: " TRX_ID_FMT " thread: %ld " + "seqno: %lld query_state: %s conflict_state: %s query: %s", + wsrep_thd_is_BF(thd, false) ? "BF" : "normal", + victim_trx->id, + thd_get_thread_id(thd), + wsrep_thd_trx_seqno(thd), + wsrep_thd_query_state_str(thd), + wsrep_thd_conflict_state_str(thd), + wsrep_thd_query(thd)); + + WSREP_LOG_CONFLICT(bf_thd, thd, TRUE); if (wsrep_thd_query_state(thd) == QUERY_EXITING) { WSREP_DEBUG("kill trx EXITING for " TRX_ID_FMT, @@ -19593,27 +19598,32 @@ wsrep_innobase_kill_one_trx( } if (wsrep_thd_exec_mode(thd) != LOCAL_STATE) { - WSREP_DEBUG("withdraw for BF trx: " TRX_ID_FMT ", state: %d", + WSREP_DEBUG("withdraw for BF trx: " TRX_ID_FMT + ", state: %s exec %s", victim_trx->id, - wsrep_thd_get_conflict_state(thd)); + wsrep_thd_conflict_state_str(thd), + wsrep_thd_exec_mode_str(thd)); } switch (wsrep_thd_get_conflict_state(thd)) { case NO_CONFLICT: + /* This will cause any call to innobase_kill_query() + for this thd to bail out. */ wsrep_thd_set_conflict_state(thd, MUST_ABORT); break; case MUST_ABORT: WSREP_DEBUG("victim " TRX_ID_FMT " in MUST ABORT state", victim_trx->id); - wsrep_thd_UNLOCK(thd); wsrep_thd_awake(thd, signal); + wsrep_thd_UNLOCK(thd); DBUG_VOID_RETURN; break; case ABORTED: case ABORTING: // fall through default: - WSREP_DEBUG("victim " TRX_ID_FMT " in state %d", - victim_trx->id, wsrep_thd_get_conflict_state(thd)); + WSREP_DEBUG("victim " TRX_ID_FMT " in state %s", + victim_trx->id, + wsrep_thd_conflict_state_str(thd)); wsrep_thd_UNLOCK(thd); DBUG_VOID_RETURN; break; @@ -19643,8 +19653,8 @@ wsrep_innobase_kill_one_trx( WSREP_DEBUG("cancel commit warning: " TRX_ID_FMT, victim_trx->id); - wsrep_thd_UNLOCK(thd); wsrep_thd_awake(thd, signal); + wsrep_thd_UNLOCK(thd); DBUG_VOID_RETURN; break; case WSREP_OK: @@ -19662,8 +19672,8 @@ wsrep_innobase_kill_one_trx( break; } } - wsrep_thd_UNLOCK(thd); wsrep_thd_awake(thd, signal); + wsrep_thd_UNLOCK(thd); break; case QUERY_EXEC: /* it is possible that victim trx is itself waiting for some @@ -19685,23 +19695,19 @@ wsrep_innobase_kill_one_trx( lock_cancel_waiting_and_release(wait_lock); } - wsrep_thd_UNLOCK(thd); wsrep_thd_awake(thd, signal); + wsrep_thd_UNLOCK(thd); } else { /* abort currently executing query */ - DBUG_PRINT("wsrep",("sending KILL_QUERY to: %lu", - thd_get_thread_id(thd))); WSREP_DEBUG("kill query for: %ld", thd_get_thread_id(thd)); - /* Note that innobase_kill_query will take lock_mutex - and trx_mutex */ - wsrep_thd_UNLOCK(thd); wsrep_thd_awake(thd, signal); + wsrep_thd_UNLOCK(thd); /* for BF thd, we need to prevent him from committing */ if (wsrep_thd_exec_mode(thd) == REPL_RECV) { wsrep_abort_slave_trx(bf_seqno, - wsrep_thd_trx_seqno(thd)); + wsrep_thd_trx_seqno(thd)); } } break; @@ -19711,29 +19717,27 @@ wsrep_innobase_kill_one_trx( if (wsrep_thd_exec_mode(thd) == REPL_RECV) { WSREP_DEBUG("kill BF IDLE, seqno: %lld", - (long long)wsrep_thd_trx_seqno(thd)); + wsrep_thd_trx_seqno(thd)); wsrep_thd_UNLOCK(thd); wsrep_abort_slave_trx(bf_seqno, wsrep_thd_trx_seqno(thd)); DBUG_VOID_RETURN; } - /* This will lock thd from proceeding after net_read() */ + /* This will lock thd from proceeding after net_read() + and innobase_kill_query to bail out for this thd. */ wsrep_thd_set_conflict_state(thd, ABORTING); wsrep_lock_rollback(); if (wsrep_aborting_thd_contains(thd)) { WSREP_WARN("duplicate thd aborter %lu", - (ulong) thd_get_thread_id(thd)); + thd_get_thread_id(thd)); } else { wsrep_aborting_thd_enqueue(thd); - DBUG_PRINT("wsrep",("enqueuing trx abort for %lu", - thd_get_thread_id(thd))); WSREP_DEBUG("enqueuing trx abort for (%lu)", - thd_get_thread_id(thd)); + thd_get_thread_id(thd)); } - DBUG_PRINT("wsrep",("signalling wsrep rollbacker")); WSREP_DEBUG("signaling aborter"); wsrep_unlock_rollback(); wsrep_thd_UNLOCK(thd); @@ -19741,10 +19745,7 @@ wsrep_innobase_kill_one_trx( break; } default: - WSREP_WARN("bad wsrep query state: %d", - wsrep_thd_query_state(thd)); - wsrep_thd_UNLOCK(thd); - break; + ut_error; } DBUG_VOID_RETURN; @@ -19753,7 +19754,6 @@ wsrep_innobase_kill_one_trx( static void wsrep_abort_transaction( -/*====================*/ handlerton* hton, THD *bf_thd, THD *victim_thd, @@ -19761,27 +19761,65 @@ wsrep_abort_transaction( { DBUG_ENTER("wsrep_abort_transaction"); - trx_t* victim_trx = thd_to_trx(victim_thd); - trx_t* bf_trx = (bf_thd) ? thd_to_trx(bf_thd) : NULL; + ut_ad(bf_thd); + ut_ad(victim_thd); + trx_t* victim_trx= thd_to_trx(victim_thd); + trx_t* bf_trx= thd_to_trx(bf_thd); - WSREP_DEBUG("abort transaction: BF: %s victim: %s victim conf: %d", - wsrep_thd_query(bf_thd), - wsrep_thd_query(victim_thd), - wsrep_thd_conflict_state(victim_thd, FALSE)); + /* Here we should hold THD::LOCK_thd_data to protect + victim from concurrent usage or disconnect or delete. */ + WSREP_DEBUG("wsrep_abort_transaction: BF:" + " thread %ld query_state %s conflict_state %s" + " exec %s query %s trx " TRX_ID_FMT, + thd_get_thread_id(bf_thd), + wsrep_thd_query_state_str(bf_thd), + wsrep_thd_conflict_state_str(bf_thd), + wsrep_thd_exec_mode_str(bf_thd), + wsrep_thd_query(bf_thd), + bf_trx ? bf_trx->id : 0); + + WSREP_DEBUG("wsrep_abort_transaction: victim:" + " thread %ld query_state %s conflict_state %s" + " exec %s query %s trx " TRX_ID_FMT, + thd_get_thread_id(victim_thd), + wsrep_thd_query_state_str(victim_thd), + wsrep_thd_conflict_state_str(victim_thd), + wsrep_thd_exec_mode_str(victim_thd), + wsrep_thd_query(victim_thd), + victim_trx ? victim_trx->id : 0); if (victim_trx) { + WSREP_DEBUG("wsrep_abort_transaction: Victim thread %ld " + "transaction " TRX_ID_FMT " trx_state %d", + thd_get_thread_id(victim_thd), + victim_trx->id, + victim_trx->state); + /* This is necessary as correct mutexing order is + lock_sys -> trx -> THD::LOCK_thd_data and below + function assumes we have lock_sys and trx locked + and takes THD::LOCK_thd_data for THD state check. */ + wsrep_thd_UNLOCK(victim_thd); + DEBUG_SYNC(bf_thd, "wsrep_abort_victim_unlocked"); + DBUG_EXECUTE_IF("wsrep_abort_replicated_sleep", + WSREP_DEBUG("wsrep_abort_transaction: sleeping " + "for thread %ld ", + thd_get_thread_id(victim_thd)); + my_sleep(100000);); lock_mutex_enter(); trx_mutex_enter(victim_trx); wsrep_innobase_kill_one_trx(bf_thd, bf_trx, victim_trx, signal); lock_mutex_exit(); trx_mutex_exit(victim_trx); wsrep_srv_conc_cancel_wait(victim_trx); + wsrep_thd_LOCK(victim_thd); DBUG_VOID_RETURN; } else { - WSREP_DEBUG("victim does not have transaction"); - wsrep_thd_LOCK(victim_thd); + WSREP_DEBUG("wsrep_abort_transaction: Victim thread %ld " + "no transaction", + thd_get_thread_id(victim_thd)); + /* This will cause any call to innobase_kill_query() + for this thd to bail out. */ wsrep_thd_set_conflict_state(victim_thd, MUST_ABORT); - wsrep_thd_UNLOCK(victim_thd); wsrep_thd_awake(victim_thd, signal); } diff --git a/storage/innobase/include/ha_prototypes.h b/storage/innobase/include/ha_prototypes.h index 3eab2135969..427e57f09d2 100644 --- a/storage/innobase/include/ha_prototypes.h +++ b/storage/innobase/include/ha_prototypes.h @@ -233,12 +233,11 @@ innobase_casedn_str( char* a); /*!< in/out: string to put in lower case */ #ifdef WITH_WSREP -UNIV_INTERN void wsrep_innobase_kill_one_trx(MYSQL_THD const thd_ptr, const trx_t * const bf_trx, trx_t *victim_trx, - ibool signal); + my_bool signal); int wsrep_innobase_mysql_sort(int mysql_type, uint charset_number, unsigned char* str, unsigned int str_length, unsigned int buf_length); From 47ba5523046094db33e68c92a182491a629bbd56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Fri, 24 Sep 2021 07:33:27 +0300 Subject: [PATCH 03/10] Revert "MDEV-24978 : SIGABRT in __libc_message" This reverts commit 30dea4599e44e3008fb9bc5fe79ab5747841f21f. --- .../suite/galera/r/galera_fulltext.result | 28 -------- .../suite/galera/t/galera_fulltext.test | 23 ------- storage/innobase/handler/ha_innodb.cc | 64 ++++++------------- 3 files changed, 21 insertions(+), 94 deletions(-) diff --git a/mysql-test/suite/galera/r/galera_fulltext.result b/mysql-test/suite/galera/r/galera_fulltext.result index bb482b7f4f7..f52f5c996a3 100644 --- a/mysql-test/suite/galera/r/galera_fulltext.result +++ b/mysql-test/suite/galera/r/galera_fulltext.result @@ -36,34 +36,6 @@ DROP TABLE t1; DROP TABLE ten; connection node_1; SET @value=REPEAT (1,5001); -CREATE TABLE t (a VARCHAR(5000),FULLTEXT (a)) engine=innodb; -INSERT IGNORE INTO t VALUES(@value); -Warnings: -Warning 1265 Data truncated for column 'a' at row 1 -SELECT COUNT(*) FROM t; -COUNT(*) -1 -connection node_2; -SELECT COUNT(*) FROM t; -COUNT(*) -1 -connection node_1; -DROP TABLE t; -CREATE TABLE t (a VARCHAR(5000)) engine=innodb; -INSERT IGNORE INTO t VALUES(@value); -Warnings: -Warning 1265 Data truncated for column 'a' at row 1 -SELECT COUNT(*) FROM t; -COUNT(*) -1 -connection node_2; -SELECT COUNT(*) FROM t; -COUNT(*) -1 -connection node_1; -DROP TABLE t; -connection node_1; -SET @value=REPEAT (1,5001); CREATE TABLE t (a VARCHAR(5000),FULLTEXT (a)) engine=innodb DEFAULT CHARSET=utf8; INSERT IGNORE INTO t VALUES(@value); Warnings: diff --git a/mysql-test/suite/galera/t/galera_fulltext.test b/mysql-test/suite/galera/t/galera_fulltext.test index 25f4f83b7b7..76c29da4123 100644 --- a/mysql-test/suite/galera/t/galera_fulltext.test +++ b/mysql-test/suite/galera/t/galera_fulltext.test @@ -60,29 +60,6 @@ SELECT COUNT(f1) = 1000 FROM t1 WHERE MATCH(f1) AGAINST ('abcdefjhk'); DROP TABLE t1; DROP TABLE ten; -# -# MDEV-24978 : SIGABRT in __libc_message -# ---connection node_1 -SET @value=REPEAT (1,5001); -CREATE TABLE t (a VARCHAR(5000),FULLTEXT (a)) engine=innodb; -INSERT IGNORE INTO t VALUES(@value); -SELECT COUNT(*) FROM t; - ---connection node_2 -SELECT COUNT(*) FROM t; - ---connection node_1 -DROP TABLE t; -CREATE TABLE t (a VARCHAR(5000)) engine=innodb; -INSERT IGNORE INTO t VALUES(@value); -SELECT COUNT(*) FROM t; - ---connection node_2 -SELECT COUNT(*) FROM t; - ---connection node_1 -DROP TABLE t; # # Case 2: UTF-8 diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index efcdd201fa6..921b8282e45 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -6727,8 +6727,8 @@ wsrep_innobase_mysql_sort( case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_VARCHAR: { - uchar *tmp_str; - uint tmp_length; + uchar tmp_str[REC_VERSION_56_MAX_INDEX_COL_LEN] = {'\0'}; + uint tmp_length = REC_VERSION_56_MAX_INDEX_COL_LEN; /* Use the charset number to pick the right charset struct for the comparison. Since the MySQL function get_charset may be @@ -6751,12 +6751,7 @@ wsrep_innobase_mysql_sort( } } - // Note that strnxfrm may change length of string - tmp_length= charset->coll->strnxfrmlen(charset, str_length); - tmp_length= tmp_length * charset->mbmaxlen; - tmp_length= ut_max(str_length, tmp_length) + charset->mbmaxlen; - tmp_str= static_cast(ut_malloc_nokey(tmp_length)); - ut_ad(str_length <= tmp_length); + ut_a(str_length <= tmp_length); memcpy(tmp_str, str, str_length); tmp_length = charset->coll->strnxfrm(charset, str, str_length, @@ -6780,7 +6775,6 @@ wsrep_innobase_mysql_sort( ret_length = tmp_length; } - ut_free(tmp_str); break; } case MYSQL_TYPE_DECIMAL : @@ -7132,7 +7126,7 @@ wsrep_store_key_val_for_row( THD* thd, TABLE* table, uint keynr, /*!< in: key number */ - uchar* buff, /*!< in/out: buffer for the key value (in MySQL + char* buff, /*!< in/out: buffer for the key value (in MySQL format) */ uint buff_len,/*!< in: buffer length */ const uchar* record, @@ -7141,7 +7135,7 @@ wsrep_store_key_val_for_row( KEY* key_info = table->key_info + keynr; KEY_PART_INFO* key_part = key_info->key_part; KEY_PART_INFO* end = key_part + key_info->user_defined_key_parts; - uchar* buff_start = buff; + char* buff_start = buff; enum_field_types mysql_type; Field* field; uint buff_space = buff_len; @@ -7153,8 +7147,7 @@ wsrep_store_key_val_for_row( for (; key_part != end; key_part++) { - uchar *sorted=NULL; - uint max_len=0; + uchar sorted[REC_VERSION_56_MAX_INDEX_COL_LEN] = {'\0'}; ibool part_is_null = FALSE; if (key_part->null_bit) { @@ -7233,14 +7226,10 @@ wsrep_store_key_val_for_row( true_len = key_len; } - max_len= true_len; - sorted= static_cast(ut_malloc_nokey(max_len+1)); memcpy(sorted, data, true_len); true_len = wsrep_innobase_mysql_sort( mysql_type, cs->number, sorted, true_len, - max_len); - ut_ad(true_len <= max_len); - + REC_VERSION_56_MAX_INDEX_COL_LEN); if (wsrep_protocol_version > 1) { /* Note that we always reserve the maximum possible length of the true VARCHAR in the key value, though @@ -7325,13 +7314,11 @@ wsrep_store_key_val_for_row( true_len = key_len; } - max_len= true_len; - sorted= static_cast(ut_malloc_nokey(max_len+1)); memcpy(sorted, blob_data, true_len); true_len = wsrep_innobase_mysql_sort( mysql_type, cs->number, sorted, true_len, - max_len); - ut_ad(true_len <= max_len); + REC_VERSION_56_MAX_INDEX_COL_LEN); + /* Note that we always reserve the maximum possible length of the BLOB prefix in the key value. */ @@ -7407,14 +7394,10 @@ wsrep_store_key_val_for_row( cs->mbmaxlen), &error); } - - max_len= true_len; - sorted= static_cast(ut_malloc_nokey(max_len+1)); memcpy(sorted, src_start, true_len); true_len = wsrep_innobase_mysql_sort( mysql_type, cs->number, sorted, true_len, - max_len); - ut_ad(true_len <= max_len); + REC_VERSION_56_MAX_INDEX_COL_LEN); if (true_len > buff_space) { fprintf (stderr, @@ -7429,11 +7412,6 @@ wsrep_store_key_val_for_row( buff += true_len; buff_space -= true_len; } - - if (sorted) { - ut_free(sorted); - sorted= NULL; - } } ut_a(buff <= buff_start + buff_len); @@ -10510,7 +10488,7 @@ wsrep_append_key( trx_t *trx, TABLE_SHARE *table_share, TABLE *table, - const uchar* key, + const char* key, uint16_t key_len, wsrep_key_type key_type /*!< in: access type of this key (shared, exclusive, semi...) */ @@ -10614,8 +10592,8 @@ ha_innobase::wsrep_append_keys( if (wsrep_protocol_version == 0) { uint len; - uchar keyval[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; - uchar *key = &keyval[0]; + char keyval[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; + char *key = &keyval[0]; ibool is_null; len = wsrep_store_key_val_for_row( @@ -10647,18 +10625,18 @@ ha_innobase::wsrep_append_keys( for (i=0; is->keys; ++i) { uint len; - uchar keyval0[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; - uchar keyval1[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; - uchar* key0 = &keyval0[1]; - uchar* key1 = &keyval1[1]; + char keyval0[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; + char keyval1[WSREP_MAX_SUPPORTED_KEY_LENGTH+1] = {'\0'}; + char* key0 = &keyval0[1]; + char* key1 = &keyval1[1]; KEY* key_info = table->key_info + i; ibool is_null; dict_index_t* idx = innobase_get_index(i); dict_table_t* tab = (idx) ? idx->table : NULL; - keyval0[0] = (uchar)i; - keyval1[0] = (uchar)i; + keyval0[0] = (char)i; + keyval1[0] = (char)i; if (!tab) { WSREP_WARN("MariaDB-InnoDB key mismatch %s %s", @@ -10716,7 +10694,7 @@ ha_innobase::wsrep_append_keys( wsrep_calc_row_hash(digest, record0, table, m_prebuilt, thd); if ((rcode = wsrep_append_key(thd, trx, table_share, table, - (const uchar*) digest, 16, + (const char*) digest, 16, key_type))) { DBUG_RETURN(rcode); } @@ -10726,7 +10704,7 @@ ha_innobase::wsrep_append_keys( digest, record1, table, m_prebuilt, thd); if ((rcode = wsrep_append_key(thd, trx, table_share, table, - (const uchar*) digest, + (const char*) digest, 16, key_type))) { DBUG_RETURN(rcode); } From 467011bcac3b3f42ae6f21dde8d88e78708b21d1 Mon Sep 17 00:00:00 2001 From: Alexey Bychko Date: Wed, 22 Sep 2021 18:08:50 +0700 Subject: [PATCH 04/10] MDEV-26612 Two different ways to start MariaDB service can cause data corruption RedHat systems have both files for lsb and init functions. Old code was written as if/else, so second file (RedHat-specific) was not processed. So, systemd redirect didn't work, because its logic is described in RedHat-specific functions file --- support-files/mysql.server.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/support-files/mysql.server.sh b/support-files/mysql.server.sh index 6eb2d0bc257..7f034601539 100644 --- a/support-files/mysql.server.sh +++ b/support-files/mysql.server.sh @@ -91,16 +91,15 @@ datadir_set= # # Use LSB init script functions for printing messages, if possible -# +# Include non-LSB RedHat init functions to make systemctl redirect work +init_functions="/etc/init.d/functions" lsb_functions="/lib/lsb/init-functions" -if test -f $lsb_functions ; then +if test -f $lsb_functions; then . $lsb_functions -else - # Include non-LSB RedHat init functions to make systemctl redirect work - init_functions="/etc/init.d/functions" - if test -f $init_functions; then - . $init_functions - fi +fi + +if test -f $init_functions; then + . $init_functions log_success_msg() { echo " SUCCESS! $@" From 77b11965220e249b8fe1dc178e0aff4a8a58db2a Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Thu, 23 Sep 2021 16:14:54 +0200 Subject: [PATCH 05/10] MDEV-26360: Using hostnames breaks certificate validation Fixed flaws with overly strict or, conversely, overly soft verification of certificates in some scenarios: 1. Removed the check that the 'commonname' (CN) in the certificate matches the 'localhost' value on the side of the joiner node, which was performed earlier, even if the address was received by the script only as an argument (out of the exchange via the Galera protocol) - since for the joining node this argument always contains its own local address, not the address of the remote host, so it is always treated as 'localhost', which is not necessarily true (outside of mtr testing); 2. Removed checking the domain name or IP-address of the peer node in the encrypt=2 mode; 3. Fixed checking of compliance of certificates when rsync SST is used; 4. Added the ability to specify CA not only as a file, but also as a path to the directory where the certificates are stored. To do this, the user just needs to specify the path to this directory as the value ssl-ca or tca parameter, ending with the '/' character. --- scripts/wsrep_sst_common.sh | 16 ++++++-- scripts/wsrep_sst_mariabackup.sh | 55 +++++++++++++++++++------- scripts/wsrep_sst_rsync.sh | 63 +++++++++++++++++++----------- scripts/wsrep_sst_xtrabackup-v2.sh | 61 +++++++++++++++++++++-------- 4 files changed, 136 insertions(+), 59 deletions(-) diff --git a/scripts/wsrep_sst_common.sh b/scripts/wsrep_sst_common.sh index 67244a7c622..a1293fcb749 100644 --- a/scripts/wsrep_sst_common.sh +++ b/scripts/wsrep_sst_common.sh @@ -1065,8 +1065,9 @@ check_for_dhparams() # verify_ca_matches_cert() { - local ca_path="$1" - local cert_path="$2" + local ca="$1" + local cert="$2" + local path=${3:-0} # If the openssl utility is not installed, then # we will not do this certificate check: @@ -1075,8 +1076,15 @@ verify_ca_matches_cert() return fi - if ! "$OPENSSL_BINARY" verify -verbose -CAfile "$ca_path" "$cert_path" >/dev/null 2>&1 - then + local not_match=0 + + if [ $path -eq 0 ]; then + "$OPENSSL_BINARY" verify -verbose -CAfile "$ca" "$cert" >/dev/null 2>&1 || not_match=1 + else + "$OPENSSL_BINARY" verify -verbose -CApath "$ca" "$cert" >/dev/null 2>&1 || not_match=1 + fi + + if [ $not_match -eq 1 ]; then wsrep_log_error "******** FATAL ERROR ********************************************" wsrep_log_error "* The certifcate and CA (certificate authority) do not match. *" wsrep_log_error "* It does not appear that the certificate was issued by the CA. *" diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 54632e5f79b..3fe3bf5c206 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -34,6 +34,7 @@ ssyslog="" ssystag="" BACKUP_PID="" tcert="" +tpath=0 tpem="" tkey="" tmode="DISABLED" @@ -85,7 +86,6 @@ readonly SECRET_TAG="secret" # Required for backup locks # For backup locks it is 1 sent by joiner -# 5.6.21 PXC and later can't donate to an older joiner sst_ver=1 if [ -n "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then @@ -339,64 +339,83 @@ get_transfer() fi fi + CN_option=",commonname=''" + if [ $encrypt -eq 2 ]; then wsrep_log_info "Using openssl based encryption with socat: with crt and pem" if [ -z "$tpem" -o -z "$tcert" ]; then - wsrep_log_error "Both PEM and CRT files required" + wsrep_log_error \ + "Both PEM file and CRT file (or path) are required" exit 22 fi if [ ! -r "$tpem" -o ! -r "$tcert" ]; then - wsrep_log_error "Both PEM and CRT files must be readable" + wsrep_log_error \ + "Both PEM file and CRT file (or path) must be readable" exit 22 fi - verify_ca_matches_cert "$tcert" "$tpem" - tcmd="$tcmd,cert='$tpem',cafile='$tcert'$sockopt" + verify_ca_matches_cert "$tcert" "$tpem" $tpath + if [ $tpath -eq 0 ]; then + tcmd="$tcmd,cert='$tpem',cafile='$tcert'" + else + tcmd="$tcmd,cert='$tpem',capath='$tcert'" + fi stagemsg="$stagemsg-OpenSSL-Encrypted-2" - wsrep_log_info "$action with cert=$tpem, cafile=$tcert" + wsrep_log_info "$action with cert=$tpem, ca=$tcert" elif [ $encrypt -eq 3 -o $encrypt -eq 4 ]; then wsrep_log_info "Using openssl based encryption with socat: with key and crt" if [ -z "$tpem" -o -z "$tkey" ]; then - wsrep_log_error "Both certificate and key files required" + wsrep_log_error "Both certificate file (or path) " \ + "and key file are required" exit 22 fi if [ ! -r "$tpem" -o ! -r "$tkey" ]; then - wsrep_log_error "Both certificate and key files must be readable" + wsrep_log_error "Both certificate file (or path) " \ + "and key file must be readable" exit 22 fi verify_cert_matches_key "$tpem" "$tkey" stagemsg="$stagemsg-OpenSSL-Encrypted-3" if [ -z "$tcert" ]; then if [ $encrypt -eq 4 ]; then - wsrep_log_error "Peer certificate required if encrypt=4" + wsrep_log_error \ + "Peer certificate file (or path) required if encrypt=4" exit 22 fi # no verification - tcmd="$tcmd,cert='$tpem',key='$tkey',verify=0$sockopt" + CN_option="" + tcmd="$tcmd,cert='$tpem',key='$tkey',verify=0" wsrep_log_info "$action with cert=$tpem, key=$tkey, verify=0" else # CA verification if [ ! -r "$tcert" ]; then - wsrep_log_error "Certificate file must be readable" + wsrep_log_error "Certificate file or path must be readable" exit 22 fi - verify_ca_matches_cert "$tcert" "$tpem" + verify_ca_matches_cert "$tcert" "$tpem" $tpath if [ -n "$WSREP_SST_OPT_REMOTE_USER" ]; then CN_option=",commonname='$WSREP_SST_OPT_REMOTE_USER'" - elif [ $encrypt -eq 4 ]; then + elif [ "$WSREP_SST_OPT_ROLE" = 'joiner' -o $encrypt -eq 4 ] + then CN_option=",commonname=''" elif is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then CN_option=',commonname=localhost' else CN_option=",commonname='$WSREP_SST_OPT_HOST_UNESCAPED'" fi - tcmd="$tcmd,cert='$tpem',key='$tkey',cafile='$tcert'$CN_option$sockopt" - wsrep_log_info "$action with cert=$tpem, key=$tkey, cafile=$tcert" + if [ $tpath -eq 0 ]; then + tcmd="$tcmd,cert='$tpem',key='$tkey',cafile='$tcert'" + else + tcmd="$tcmd,cert='$tpem',key='$tkey',capath='$tcert'" + fi + wsrep_log_info "$action with cert=$tpem, key=$tkey, ca=$tcert" fi else wsrep_log_info "Unknown encryption mode: encrypt=$encrypt" exit 22 fi + tcmd="$tcmd$CN_option$sockopt" + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then tcmd="$tcmd stdio" fi @@ -473,6 +492,12 @@ check_server_ssl_config() "of the tca, tcert and/or tkey in the [sst] section" fi fi + if [ -n "$tcert" ]; then + tcert=$(trim_string "$tcert") + if [ "${tcert%/}" != "$tcert" ]; then + tpath=1 + fi + fi } read_cnf() diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index e16ed75cb16..ad9688011e1 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -236,11 +236,18 @@ check_server_ssl_config() SSLMODE=$(parse_cnf "$SST_SECTIONS" 'ssl-mode' | tr [:lower:] [:upper:]) # no old-style SSL config in [sst], check for new one: -if [ -z "$SSTKEY" -a -z "$SSTCERT" -a -z "$SSTCA" ] -then +if [ -z "$SSTKEY" -a -z "$SSTCERT" -a -z "$SSTCA" ]; then check_server_ssl_config fi +SSTPATH=0 +if [ -n "$SSTCA" ]; then + SSTCA=$(trim_string "$SSTCA") + if [ "${SSTCA%/}" != "$SSTCA" ]; then + SSTPATH=1 + fi +fi + if [ -z "$SSLMODE" ]; then # Implicit verification if CA is set and the SSL mode # is not specified by user: @@ -254,9 +261,19 @@ if [ -z "$SSLMODE" ]; then fi fi -if [ -n "$SSTCA" ] -then - CAFILE_OPT="CAfile = $SSTCA" +if [ -n "$SSTCERT" -a -n "$SSTKEY" ]; then + verify_cert_matches_key "$SSTCERT" "$SSTKEY" +fi + +if [ -n "$SSTCA" ]; then + if [ $SSTPATH -eq 0 ]; then + CAFILE_OPT="CAfile = $SSTCA" + else + CAFILE_OPT="CApath = $SSTCA" + fi + if [ -n "$SSTCERT" ]; then + verify_ca_matches_cert "$SSTCA" "$SSTCERT" $SSTPATH + fi else CAFILE_OPT="" fi @@ -272,38 +289,38 @@ then ;; 'VERIFY_CA') VERIFY_OPT='verifyChain = yes' - if [ -n "$WSREP_SST_OPT_REMOTE_USER" ]; then - CHECK_OPT="checkHost = $WSREP_SST_OPT_REMOTE_USER" - else - # check if the address is an ip-address (v4 or v6): - if echo "$WSREP_SST_OPT_HOST_UNESCAPED" | \ - grep -q -E '^([0-9]+(\.[0-9]+){3}|[0-9a-fA-F]*(\:[0-9a-fA-F]*)+)$' - then - CHECK_OPT="checkIP = $WSREP_SST_OPT_HOST_UNESCAPED" - else - CHECK_OPT="checkHost = $WSREP_SST_OPT_HOST" - fi - if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then - CHECK_OPT_LOCAL="checkHost = localhost" - fi - fi ;; *) wsrep_log_error "Unrecognized ssl-mode option: '$SSLMODE'" exit 22 # EINVAL ;; esac - if [ -z "$CAFILE_OPT" ]; then - wsrep_log_error "Can't have ssl-mode='$SSLMODE' without CA file" + if [ -z "$SSTCA" ]; then + wsrep_log_error "Can't have ssl-mode='$SSLMODE' without CA file or path" exit 22 # EINVAL fi + if [ -n "$WSREP_SST_OPT_REMOTE_USER" ]; then + CHECK_OPT="checkHost = $WSREP_SST_OPT_REMOTE_USER" + elif [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + # check if the address is an ip-address (v4 or v6): + if echo "$WSREP_SST_OPT_HOST_UNESCAPED" | \ + grep -q -E '^([0-9]+(\.[0-9]+){3}|[0-9a-fA-F]*(\:[0-9a-fA-F]*)+)$' + then + CHECK_OPT="checkIP = $WSREP_SST_OPT_HOST_UNESCAPED" + else + CHECK_OPT="checkHost = $WSREP_SST_OPT_HOST" + fi + if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then + CHECK_OPT_LOCAL="checkHost = localhost" + fi + fi fi STUNNEL="" if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ]; then STUNNEL_BIN="$(command -v stunnel)" if [ -n "$STUNNEL_BIN" ]; then - wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', ssl-mode='$SSLMODE'" + wsrep_log_info "Using stunnel for SSL encryption: CA: '$SSTCA', ssl-mode='$SSLMODE'" STUNNEL="$STUNNEL_BIN $STUNNEL_CONF" fi fi diff --git a/scripts/wsrep_sst_xtrabackup-v2.sh b/scripts/wsrep_sst_xtrabackup-v2.sh index 999c6c649ba..7cc05185ce3 100644 --- a/scripts/wsrep_sst_xtrabackup-v2.sh +++ b/scripts/wsrep_sst_xtrabackup-v2.sh @@ -34,6 +34,7 @@ ssyslog="" ssystag="" BACKUP_PID="" tcert="" +tpath=0 tpem="" tkey="" tmode="DISABLED" @@ -201,9 +202,10 @@ get_keys() if [ -z "$ekey" ]; then ecmd="xbcrypt --encrypt-algo='$ealgo' --encrypt-key-file='$ekeyfile'" else - wsrep_log_warning "Using the 'encrypt-key' option causes the encryption key" - wsrep_log_warning "to be set via the command-line and is considered insecure." - wsrep_log_warning "It is recommended to use the 'encrypt-key-file' option instead." + wsrep_log_warning \ + "Using the 'encrypt-key' option causes the encryption key " \ + "to be set via the command-line and is considered insecure. " \ + "It is recommended to use the 'encrypt-key-file' option instead." ecmd="xbcrypt --encrypt-algo='$ealgo' --encrypt-key='$ekey'" fi if [ -n "$encrypt_threads" ]; then @@ -341,64 +343,83 @@ get_transfer() fi fi + CN_option=",commonname=''" + if [ $encrypt -eq 2 ]; then wsrep_log_info "Using openssl based encryption with socat: with crt and pem" if [ -z "$tpem" -o -z "$tcert" ]; then - wsrep_log_error "Both PEM and CRT files required" + wsrep_log_error \ + "Both PEM file and CRT file (or path) are required" exit 22 fi if [ ! -r "$tpem" -o ! -r "$tcert" ]; then - wsrep_log_error "Both PEM and CRT files must be readable" + wsrep_log_error \ + "Both PEM file and CRT file (or path) must be readable" exit 22 fi - verify_ca_matches_cert "$tcert" "$tpem" - tcmd="$tcmd,cert='$tpem',cafile='$tcert'$sockopt" + verify_ca_matches_cert "$tcert" "$tpem" $tpath + if [ $tpath -eq 0 ]; then + tcmd="$tcmd,cert='$tpem',cafile='$tcert'" + else + tcmd="$tcmd,cert='$tpem',capath='$tcert'" + fi stagemsg="$stagemsg-OpenSSL-Encrypted-2" - wsrep_log_info "$action with cert=$tpem, cafile=$tcert" + wsrep_log_info "$action with cert=$tpem, ca=$tcert" elif [ $encrypt -eq 3 -o $encrypt -eq 4 ]; then wsrep_log_info "Using openssl based encryption with socat: with key and crt" if [ -z "$tpem" -o -z "$tkey" ]; then - wsrep_log_error "Both certificate and key files required" + wsrep_log_error "Both certificate file (or path) " \ + "and key file are required" exit 22 fi if [ ! -r "$tpem" -o ! -r "$tkey" ]; then - wsrep_log_error "Both certificate and key files must be readable" + wsrep_log_error "Both certificate file (or path) " \ + "and key file must be readable" exit 22 fi verify_cert_matches_key "$tpem" "$tkey" stagemsg="$stagemsg-OpenSSL-Encrypted-3" if [ -z "$tcert" ]; then if [ $encrypt -eq 4 ]; then - wsrep_log_error "Peer certificate required if encrypt=4" + wsrep_log_error \ + "Peer certificate file (or path) required if encrypt=4" exit 22 fi # no verification - tcmd="$tcmd,cert='$tpem',key='$tkey',verify=0$sockopt" + CN_option="" + tcmd="$tcmd,cert='$tpem',key='$tkey',verify=0" wsrep_log_info "$action with cert=$tpem, key=$tkey, verify=0" else # CA verification if [ ! -r "$tcert" ]; then - wsrep_log_error "Certificate file must be readable" + wsrep_log_error "Certificate file or path must be readable" exit 22 fi - verify_ca_matches_cert "$tcert" "$tpem" + verify_ca_matches_cert "$tcert" "$tpem" $tpath if [ -n "$WSREP_SST_OPT_REMOTE_USER" ]; then CN_option=",commonname='$WSREP_SST_OPT_REMOTE_USER'" - elif [ $encrypt -eq 4 ]; then + elif [ "$WSREP_SST_OPT_ROLE" = 'joiner' -o $encrypt -eq 4 ] + then CN_option=",commonname=''" elif is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then CN_option=',commonname=localhost' else CN_option=",commonname='$WSREP_SST_OPT_HOST_UNESCAPED'" fi - tcmd="$tcmd,cert='$tpem',key='$tkey',cafile='$tcert'$CN_option$sockopt" - wsrep_log_info "$action with cert=$tpem, key=$tkey, cafile=$tcert" + if [ $tpath -eq 0 ]; then + tcmd="$tcmd,cert='$tpem',key='$tkey',cafile='$tcert'" + else + tcmd="$tcmd,cert='$tpem',key='$tkey',capath='$tcert'" + fi + wsrep_log_info "$action with cert=$tpem, key=$tkey, ca=$tcert" fi else wsrep_log_info "Unknown encryption mode: encrypt=$encrypt" exit 22 fi + tcmd="$tcmd$CN_option$sockopt" + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then tcmd="$tcmd stdio" fi @@ -475,6 +496,12 @@ check_server_ssl_config() "of the tca, tcert and/or tkey in the [sst] section" fi fi + if [ -n "$tcert" ]; then + tcert=$(trim_string "$tcert") + if [ "${tcert%/}" != "$tcert" ]; then + tpath=1 + fi + fi } read_cnf() From ca7046dc19d8bbb5916c89d9e98e9d4632294fa8 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Fri, 24 Sep 2021 01:33:05 +0200 Subject: [PATCH 06/10] MDEV-11499 mysqltest, Windows : improve diagnostics if server fails to shutdown Create minidump when server fails to shutdown. If process is being debugged, cause a debug break. Moves some code which is part of safe_kill into mysys, as both safe_kill, and mysqltest produce minidumps on different timeouts. Small cleanup in wait_until_dead() - replace inefficient loop with a single wait. --- client/mysqltest.cc | 54 ++++++-- include/my_minidump.h | 25 ++++ mysql-test/lib/My/SafeProcess/CMakeLists.txt | 3 +- .../lib/My/SafeProcess/safe_kill_win.cc | 109 +---------------- mysys/CMakeLists.txt | 4 +- mysys/my_minidump.cc | 115 ++++++++++++++++++ 6 files changed, 193 insertions(+), 117 deletions(-) create mode 100644 include/my_minidump.h create mode 100644 mysys/my_minidump.cc diff --git a/client/mysqltest.cc b/client/mysqltest.cc index ad1eb29ab70..82bd1abf1a3 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -5026,13 +5026,34 @@ int query_get_string(MYSQL* mysql, const char* query, } +#ifdef _WIN32 +#define SIGKILL 9 +#include static int my_kill(int pid, int sig) { - DBUG_PRINT("info", ("Killing server, pid: %d", pid)); -#ifdef _WIN32 -#define SIGKILL 9 /* ignored anyway, see below */ HANDLE proc; - if ((proc= OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE, pid)) == NULL) + if (sig == SIGABRT) + { + /* + Create a minidump. If process is being debugged, debug break + Otherwise, terminate. + */ + verbose_msg("Aborting %d",pid); + my_create_minidump(pid,TRUE); + proc= OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + if(!proc) + return -1; + BOOL debugger_present; + if (CheckRemoteDebuggerPresent(proc,&debugger_present) && debugger_present) + { + if (DebugBreakProcess(proc)) + { + CloseHandle(proc); + return 0; + } + } + } + else if ((proc= OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE, pid)) == NULL) return -1; if (sig == 0) { @@ -5043,12 +5064,30 @@ static int my_kill(int pid, int sig) (void)TerminateProcess(proc, 201); CloseHandle(proc); return 1; -#else - return kill(pid, sig); -#endif } +/* Wait until process is gone, with timeout */ +static int wait_until_dead(int pid, int timeout) +{ + HANDLE proc= OpenProcess(SYNCHRONIZE, FALSE, pid); + if (!proc) + return 0; /* already dead */ + DBUG_ASSERT(timeout >= 0); + DBUG_ASSERT(timeout <= UINT_MAX/1000); + DWORD wait_result= WaitForSingleObject(proc, (DWORD)timeout*1000); + CloseHandle(proc); + return (int)wait_result; +} + +#else /* !_WIN32 */ + + +static int my_kill(int pid, int sig) +{ + DBUG_PRINT("info", ("Killing server, pid: %d", pid)); + return kill(pid, sig); +} /* Shutdown the server of current connection and @@ -5083,6 +5122,7 @@ static int wait_until_dead(int pid, int timeout) } DBUG_RETURN(1); // Did not die } +#endif /* _WIN32 */ void do_shutdown_server(struct st_command *command) diff --git a/include/my_minidump.h b/include/my_minidump.h new file mode 100644 index 00000000000..0c0e843be8c --- /dev/null +++ b/include/my_minidump.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2021, MariaDB Corporation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include +#ifdef __cplusplus +extern "C" { +#endif + +BOOL my_create_minidump(DWORD pid, BOOL verbose); + +#ifdef __cplusplus +} +#endif diff --git a/mysql-test/lib/My/SafeProcess/CMakeLists.txt b/mysql-test/lib/My/SafeProcess/CMakeLists.txt index 33ff99da89b..4946af7d899 100644 --- a/mysql-test/lib/My/SafeProcess/CMakeLists.txt +++ b/mysql-test/lib/My/SafeProcess/CMakeLists.txt @@ -17,7 +17,8 @@ IF (WIN32) ADD_EXECUTABLE(my_safe_process safe_process_win.cc) ADD_EXECUTABLE(my_safe_kill safe_kill_win.cc) - TARGET_LINK_LIBRARIES(my_safe_kill dbghelp psapi) + TARGET_INCLUDE_DIRECTORIES(my_safe_kill PRIVATE ${CMAKE_SOURCE_DIR}/include) + TARGET_LINK_LIBRARIES(my_safe_kill mysys psapi) ELSE() ADD_EXECUTABLE(my_safe_process safe_process.cc) ENDIF() diff --git a/mysql-test/lib/My/SafeProcess/safe_kill_win.cc b/mysql-test/lib/My/SafeProcess/safe_kill_win.cc index 4a9d5f2b8cc..375ed80b292 100644 --- a/mysql-test/lib/My/SafeProcess/safe_kill_win.cc +++ b/mysql-test/lib/My/SafeProcess/safe_kill_win.cc @@ -26,19 +26,7 @@ #include #include #include - -#ifdef _MSC_VER -/* Silence warning in OS header dbghelp.h */ -#pragma warning(push) -#pragma warning(disable : 4091) -#endif - -#include - -#ifdef _MSC_VER -/* Silence warning in OS header dbghelp.h */ -#pragma warning(pop) -#endif +#include #include #include @@ -64,106 +52,13 @@ static std::vector find_children(DWORD pid) return children; } -void dump_single_process(DWORD pid) -{ - HANDLE file = 0; - HANDLE process= 0; - DWORD size= MAX_PATH; - char path[MAX_PATH]; - char working_dir[MAX_PATH]; - char tmpname[MAX_PATH]; - char *filename= 0; - - process= OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); - if (!process) - { - fprintf(stderr, "safe_kill : cannot open process pid=%lu to create dump, last error %lu\n", - pid, GetLastError()); - goto exit; - } - - if (QueryFullProcessImageName(process, 0, path, &size) == 0) - { - fprintf(stderr, "safe_kill : cannot read process path for pid %lu, last error %lu\n", - pid, GetLastError()); - goto exit; - } - - filename= strrchr(path, '\\'); - if (filename) - { - filename++; - // We are not interested in dump of some proceses (my_safe_process.exe,cmd.exe) - // since they are only used to start up other programs. - // We're interested however in their children; - const char *exclude_programs[] = {"my_safe_process.exe","cmd.exe", 0}; - for(size_t i=0; exclude_programs[i]; i++) - if (_stricmp(filename, exclude_programs[i]) == 0) - goto exit; - } - else - filename= path; - - // Add .dmp extension - char *p; - if ((p= strrchr(filename, '.')) == 0) - p= filename + strlen(filename); - - strncpy(p, ".dmp", path + MAX_PATH - p); - - // Íf file with this name exist, generate unique name with .dmp extension - if (GetFileAttributes(filename) != INVALID_FILE_ATTRIBUTES) - { - if (!GetTempFileName(".", filename, 0, tmpname)) - { - fprintf(stderr, "GetTempFileName failed, last error %lu", GetLastError()); - goto exit; - } - strncat_s(tmpname, ".dmp", sizeof(tmpname)); - filename= tmpname; - } - - - if (!GetCurrentDirectory(MAX_PATH, working_dir)) - { - fprintf(stderr, "GetCurrentDirectory failed, last error %lu", GetLastError()); - goto exit; - } - - file= CreateFile(filename, GENERIC_READ | GENERIC_WRITE, - 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - - if (file == INVALID_HANDLE_VALUE) - { - fprintf(stderr, "safe_kill : CreateFile() failed for file %s, working dir %s, last error = %lu\n", - filename, working_dir, GetLastError()); - goto exit; - } - - if (!MiniDumpWriteDump(process, pid, file, MiniDumpNormal, 0, 0, 0)) - { - fprintf(stderr, "Failed to write minidump to %s, working dir %s, last error %lu\n", - filename, working_dir, GetLastError()); - goto exit; - } - - fprintf(stderr, "Minidump written to %s, directory %s\n", filename, working_dir); - -exit: - if (process != 0 && process != INVALID_HANDLE_VALUE) - CloseHandle(process); - - if (file != 0 && file != INVALID_HANDLE_VALUE) - CloseHandle(file); -} - static int create_dump(DWORD pid, int recursion_depth= 5) { if (recursion_depth < 0) return 0; - dump_single_process(pid); + my_create_minidump(pid, TRUE); std::vector children= find_children(pid); for(size_t i=0; i < children.size(); i++) create_dump(children[i], recursion_depth -1); diff --git a/mysys/CMakeLists.txt b/mysys/CMakeLists.txt index 3505f417762..f97e3b4d390 100644 --- a/mysys/CMakeLists.txt +++ b/mysys/CMakeLists.txt @@ -48,7 +48,7 @@ SET(MYSYS_SOURCES array.c charset-def.c charset.c checksum.c my_default.c file_logger.c my_dlerror.c) IF (WIN32) - SET (MYSYS_SOURCES ${MYSYS_SOURCES} my_winthread.c my_wincond.c my_winerr.c my_winfile.c my_windac.c my_conio.c) + SET (MYSYS_SOURCES ${MYSYS_SOURCES} my_winthread.c my_wincond.c my_winerr.c my_winfile.c my_windac.c my_conio.c my_minidump.cc) ENDIF() IF(UNIX) @@ -83,7 +83,7 @@ IF(HAVE_BFD_H) ENDIF(HAVE_BFD_H) IF (WIN32) - TARGET_LINK_LIBRARIES(mysys IPHLPAPI) + TARGET_LINK_LIBRARIES(mysys iphlpapi dbghelp) ENDIF(WIN32) # Need explicit pthread for gcc -fsanitize=address diff --git a/mysys/my_minidump.cc b/mysys/my_minidump.cc new file mode 100644 index 00000000000..d81aab2f764 --- /dev/null +++ b/mysys/my_minidump.cc @@ -0,0 +1,115 @@ +/* Copyright (c) 2021, MariaDB Corporation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include +#include +#include +#include + +#define VERBOSE(fmt,...) \ + if (verbose) { fprintf(stderr, "my_create_minidump : " fmt,__VA_ARGS__); } + +extern "C" BOOL my_create_minidump(DWORD pid, BOOL verbose) +{ + HANDLE file = 0; + HANDLE process= 0; + DWORD size= MAX_PATH; + char path[MAX_PATH]; + char working_dir[MAX_PATH]; + char tmpname[MAX_PATH]; + char *filename= 0; + bool ret= FALSE; + process= OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (!process) + { + VERBOSE("cannot open process pid=%lu to create dump, last error %lu\n", + pid, GetLastError()); + goto exit; + } + + if (QueryFullProcessImageName(process, 0, path, &size) == 0) + { + VERBOSE("cannot read process path for pid %lu, last error %lu\n", + pid, GetLastError()); + goto exit; + } + + filename= strrchr(path, '\\'); + if (filename) + { + filename++; + // We are not interested in dump of some proceses (my_safe_process.exe,cmd.exe) + // since they are only used to start up other programs. + // We're interested however in their children; + const char *exclude_programs[] = {"my_safe_process.exe","cmd.exe", 0}; + for(size_t i=0; exclude_programs[i]; i++) + if (_stricmp(filename, exclude_programs[i]) == 0) + goto exit; + } + else + filename= path; + + // Add .dmp extension + char *p; + if ((p= strrchr(filename, '.')) == 0) + p= filename + strlen(filename); + + strncpy(p, ".dmp", path + MAX_PATH - p); + + // Ãf file with this name exist, generate unique name with .dmp extension + if (GetFileAttributes(filename) != INVALID_FILE_ATTRIBUTES) + { + if (!GetTempFileName(".", filename, 0, tmpname)) + { + fprintf(stderr, "GetTempFileName failed, last error %lu", GetLastError()); + goto exit; + } + strncat_s(tmpname, ".dmp", sizeof(tmpname)); + filename= tmpname; + } + + if (!GetCurrentDirectory(MAX_PATH, working_dir)) + { + VERBOSE("GetCurrentDirectory failed, last error %lu", GetLastError()); + goto exit; + } + + file= CreateFile(filename, GENERIC_READ | GENERIC_WRITE, + 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if (file == INVALID_HANDLE_VALUE) + { + VERBOSE("CreateFile() failed for file %s, working dir %s, last error = %lu\n", + filename, working_dir, GetLastError()); + goto exit; + } + + if (!MiniDumpWriteDump(process, pid, file, MiniDumpNormal, 0, 0, 0)) + { + VERBOSE("Failed to write minidump to %s, working dir %s, last error %lu\n", + filename, working_dir, GetLastError()); + goto exit; + } + + VERBOSE("Minidump written to %s, directory %s\n", filename, working_dir); + ret= TRUE; +exit: + if (process != 0 && process != INVALID_HANDLE_VALUE) + CloseHandle(process); + + if (file != 0 && file != INVALID_HANDLE_VALUE) + CloseHandle(file); + return ret; +} From 8221708e389728aef799046eef3c49b1eec2e400 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Fri, 24 Sep 2021 10:07:01 +0200 Subject: [PATCH 07/10] Windows, mysqltest : cleanup, remove dead code USE_CYGWIN --- client/mysqltest.cc | 54 --------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 82bd1abf1a3..736e708e0f4 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -71,10 +71,6 @@ static my_bool non_blocking_api_enabled= 0; #include "../tests/nonblock-wrappers.h" #endif -/* Use cygwin for --exec and --system before 5.0 */ -#if MYSQL_VERSION_ID < 50000 -#define USE_CYGWIN -#endif #define MAX_VAR_NAME_LENGTH 256 #define MAX_COLUMNS 256 @@ -618,11 +614,6 @@ void fix_win_paths(char *val, size_t len); const char *get_errname_from_code (uint error_code); int multi_reg_replace(struct st_replace_regex* r,char* val); -#ifdef _WIN32 -void free_tmp_sh_file(); -void free_win_path_patterns(); -#endif - /* For replace_column */ static char *replace_column[MAX_COLUMNS]; @@ -1471,10 +1462,6 @@ void free_used_memory() free_root(&require_file_root, MYF(0)); free_re(); my_free(read_command_buf); -#ifdef _WIN32 - free_tmp_sh_file(); - free_win_path_patterns(); -#endif DBUG_VOID_RETURN; } @@ -3204,33 +3191,6 @@ void do_source(struct st_command *command) } -#if defined _WIN32 - -#ifdef USE_CYGWIN -/* Variables used for temporary sh files used for emulating Unix on Windows */ -char tmp_sh_name[64], tmp_sh_cmd[70]; -#endif - -void init_tmp_sh_file() -{ -#ifdef USE_CYGWIN - /* Format a name for the tmp sh file that is unique for this process */ - my_snprintf(tmp_sh_name, sizeof(tmp_sh_name), "tmp_%d.sh", getpid()); - /* Format the command to execute in order to run the script */ - my_snprintf(tmp_sh_cmd, sizeof(tmp_sh_cmd), "sh %s", tmp_sh_name); -#endif -} - - -void free_tmp_sh_file() -{ -#ifdef USE_CYGWIN - my_delete(tmp_sh_name, MYF(0)); -#endif -} -#endif - - static void init_builtin_echo(void) { #ifdef _WIN32 @@ -3346,14 +3306,12 @@ void do_exec(struct st_command *command) } #ifdef _WIN32 -#ifndef USE_CYGWIN /* Replace /dev/null with NUL */ while(replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0) ; /* Replace "closed stdout" with non existing output fd */ while(replace(&ds_cmd, ">&-", 3, ">&4", 3) == 0) ; -#endif #endif if (disable_result_log) @@ -3512,13 +3470,7 @@ int do_modify_var(struct st_command *command, int my_system(DYNAMIC_STRING* ds_cmd) { -#if defined _WIN32 && defined USE_CYGWIN - /* Dump the command into a sh script file and execute with system */ - str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); - return system(tmp_sh_cmd); -#else return system(ds_cmd->str); -#endif } @@ -3552,12 +3504,10 @@ void do_system(struct st_command *command) do_eval(&ds_cmd, command->first_argument, command->end, !is_windows); #ifdef _WIN32 -#ifndef USE_CYGWIN /* Replace /dev/null with NUL */ while(replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0) ; #endif -#endif DBUG_PRINT("info", ("running system command '%s' as '%s'", @@ -9221,12 +9171,8 @@ int main(int argc, char **argv) init_builtin_echo(); #ifdef _WIN32 -#ifndef USE_CYGWIN is_windows= 1; #endif - init_tmp_sh_file(); - init_win_path_patterns(); -#endif read_command_buf= (char*)my_malloc(read_command_buflen= 65536, MYF(MY_FAE)); From cfe1a258e884a4e21df9c752c2c3351da96bfb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 24 Sep 2021 15:33:53 +0300 Subject: [PATCH 08/10] Update libmariadb --- libmariadb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmariadb b/libmariadb index 42cb1e442c4..b99172386a7 160000 --- a/libmariadb +++ b/libmariadb @@ -1 +1 @@ -Subproject commit 42cb1e442c43902e2866bea38d15f2ed1f5d38b3 +Subproject commit b99172386a740ef0c8136e9a6cd7d9ad9a77b31f From a5df5aec06d429cfaf0c7a8994e67eaeeebf080b Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Fri, 24 Sep 2021 15:17:52 +0200 Subject: [PATCH 09/10] Fixup "Windows, mysqltest : cleanup, remove dead code USE_CYGWIN" last commit 8221708e389728aef799046eef3c49b1eec2e400 removed too much, mtr is failing --- client/mysqltest.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 736e708e0f4..bca35b74a64 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -614,6 +614,10 @@ void fix_win_paths(char *val, size_t len); const char *get_errname_from_code (uint error_code); int multi_reg_replace(struct st_replace_regex* r,char* val); +#ifdef _WIN32 +void free_win_path_patterns(); +#endif + /* For replace_column */ static char *replace_column[MAX_COLUMNS]; @@ -1462,6 +1466,9 @@ void free_used_memory() free_root(&require_file_root, MYF(0)); free_re(); my_free(read_command_buf); +#ifdef _WIN32 + free_win_path_patterns(); +#endif DBUG_VOID_RETURN; } @@ -9172,6 +9179,7 @@ int main(int argc, char **argv) init_builtin_echo(); #ifdef _WIN32 is_windows= 1; + init_win_path_patterns(); #endif read_command_buf= (char*)my_malloc(read_command_buflen= 65536, MYF(MY_FAE)); From f59f5c4a10151b18d1407065ca48746384b6a25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 24 Sep 2021 16:21:20 +0300 Subject: [PATCH 10/10] Revert MDEV-25114 Revert 88a4be75a5f3b8d59ac8f6347ff2c197813c05dc and 9d97f92febc89941784d17d59c60275e21140ce0, which had been prematurely pushed by accident. --- .../suite/galera/r/galera_UK_conflict.result | 7 - .../galera/r/galera_bf_kill_debug.result | 163 --------- .../galera/r/galera_toi_ddl_fk_insert.result | 16 + .../suite/galera/t/galera_UK_conflict.test | 4 - .../suite/galera/t/galera_bf_kill_debug.cnf | 9 - .../suite/galera/t/galera_bf_kill_debug.test | 283 ---------------- .../galera/t/galera_toi_ddl_fk_insert.test | 14 +- mysql-test/suite/wsrep/t/variables.test | 6 +- sql/mysqld.cc | 9 +- sql/mysqld.h | 2 +- sql/sql_class.cc | 24 +- sql/sql_class.h | 9 +- sql/sql_parse.cc | 36 +- sql/wsrep_mysqld.cc | 92 ++---- sql/wsrep_thd.cc | 8 +- sql/wsrep_thd.h | 1 + storage/innobase/handler/ha_innodb.cc | 308 +++++++++--------- storage/innobase/include/ha_prototypes.h | 3 +- 18 files changed, 229 insertions(+), 765 deletions(-) delete mode 100644 mysql-test/suite/galera/r/galera_bf_kill_debug.result delete mode 100644 mysql-test/suite/galera/t/galera_bf_kill_debug.cnf delete mode 100644 mysql-test/suite/galera/t/galera_bf_kill_debug.test diff --git a/mysql-test/suite/galera/r/galera_UK_conflict.result b/mysql-test/suite/galera/r/galera_UK_conflict.result index 2795a86d6a6..76649f1b268 100644 --- a/mysql-test/suite/galera/r/galera_UK_conflict.result +++ b/mysql-test/suite/galera/r/galera_UK_conflict.result @@ -68,9 +68,6 @@ f1 f2 f3 10 10 0 INSERT INTO t1 VALUES (7,7,7); INSERT INTO t1 VALUES (8,8,8); -SELECT COUNT(*) FROM t1; -COUNT(*) -7 SELECT * FROM t1; f1 f2 f3 1 1 0 @@ -81,9 +78,6 @@ f1 f2 f3 8 8 8 10 10 0 connection node_1; -SELECT COUNT(*) FROM t1; -COUNT(*) -7 SELECT * FROM t1; f1 f2 f3 1 1 0 @@ -91,6 +85,5 @@ f1 f2 f3 4 4 2 5 5 2 7 7 7 -8 8 8 10 10 0 DROP TABLE t1; diff --git a/mysql-test/suite/galera/r/galera_bf_kill_debug.result b/mysql-test/suite/galera/r/galera_bf_kill_debug.result deleted file mode 100644 index c20bb80131a..00000000000 --- a/mysql-test/suite/galera/r/galera_bf_kill_debug.result +++ /dev/null @@ -1,163 +0,0 @@ -# -# Case 1: We execute bf kill to wsrep_innobase_kill_one_trx -# function just before wsrep_thd_LOCK(thd) call. Then we -# try to kill victim transaction by KILL QUERY -# -CREATE TABLE t1(id int not null primary key, b int) engine=innodb; -INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); -connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; -begin; -update t1 set b = b * 10 where id between 2 and 4; -connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; -connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1; -SET DEBUG_SYNC='wsrep_before_BF_victim_lock SIGNAL bf_kill WAIT_FOR bf_continue'; -ALTER TABLE t1 ADD UNIQUE KEY b1(b);; -connection node_1; -SET DEBUG_SYNC='now WAIT_FOR bf_kill'; -connection node_1b; -Table Create Table -t1 CREATE TABLE `t1` ( - `id` int(11) NOT NULL, - `b` int(11) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `b1` (`b`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 -id b -1 1 -2 2 -3 3 -4 4 -5 5 -connection node_1; -SET DEBUG_SYNC= 'RESET'; -DROP TABLE t1; -disconnect node_1a; -disconnect node_1b; -disconnect node_1c; -# -# Case 2: We execute bf kill to wsrep_innobase_kill_one_trx -# function just after wsrep_thd_LOCK(thd) call. Then we -# try to kill victim transaction by KILL QUERY -# -CREATE TABLE t1(id int not null primary key, b int) engine=innodb; -INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); -connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; -begin; -update t1 set b = b * 10 where id between 2 and 4; -connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; -connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1; -SET DEBUG_SYNC='wsrep_after_BF_victim_lock SIGNAL bf_kill WAIT_FOR bf_continue'; -ALTER TABLE t1 ADD UNIQUE KEY b1(b);; -connection node_1; -SET DEBUG_SYNC='now WAIT_FOR bf_kill'; -connection node_1b; -Table Create Table -t1 CREATE TABLE `t1` ( - `id` int(11) NOT NULL, - `b` int(11) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `b1` (`b`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 -id b -1 1 -2 2 -3 3 -4 4 -5 5 -connection node_1; -SET DEBUG_SYNC= 'RESET'; -DROP TABLE t1; -disconnect node_1a; -disconnect node_1b; -disconnect node_1c; -# -# Case 3: Create victim transaction and try to send user KILL -# from several threads -# -CREATE TABLE t1(id int not null primary key, b int) engine=innodb; -INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); -connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; -begin; -update t1 set b = b * 10 where id between 2 and 4; -connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; -connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1; -connect node_1d, 127.0.0.1, root, , test, $NODE_MYPORT_1; -connection node_1b; -connection node_1c; -connection node_1d; -connection node_1; -disconnect node_1a; -disconnect node_1b; -disconnect node_1c; -disconnect node_1d; -DROP TABLE t1; -# -# Case 4: MDL-conflict, we execute ALTER until we hit gap in -# wsrep_abort_transaction, while we are there we try to -# manually KILL conflicting transaction (UPDATE) and -# send conflicting transaction from other node to be executed -# in this node by applier. As ALTER and KILL are TOI they -# are not executed concurrently. Similarly UPDATE from other -# node will wait for certification. -# -CREATE TABLE t1(id int not null primary key, b int) engine=innodb; -INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); -connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; -begin; -update t1 set b = b * 10 where id between 2 and 4; -connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; -connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1; -SET DEBUG_SYNC='wsrep_abort_victim_unlocked SIGNAL bf_kill_unlocked WAIT_FOR bf_continue'; -ALTER TABLE t1 ADD UNIQUE KEY b1(b);; -connection node_1; -SET DEBUG_SYNC='now WAIT_FOR bf_kill_unlocked'; -connection node_1b; -connection node_2; -update t1 set b = b + 1000 where id between 2 and 4;; -connection node_1; -SET DEBUG_SYNC='now SIGNAL bf_continue'; -connection node_1c; -SHOW CREATE TABLE t1; -Table Create Table -t1 CREATE TABLE `t1` ( - `id` int(11) NOT NULL, - `b` int(11) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `b1` (`b`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 -SELECT * FROM t1; -id b -1 1 -5 5 -2 1002 -3 1003 -4 1004 -connection node_1b; -connection node_1; -SET DEBUG_SYNC= 'RESET'; -SELECT * FROM t1; -id b -1 1 -5 5 -2 1002 -3 1003 -4 1004 -connection node_2; -SHOW CREATE TABLE t1; -Table Create Table -t1 CREATE TABLE `t1` ( - `id` int(11) NOT NULL, - `b` int(11) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `b1` (`b`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 -SELECT * FROM t1; -id b -1 1 -5 5 -2 1002 -3 1003 -4 1004 -DROP TABLE t1; -disconnect node_1a; -disconnect node_1c; diff --git a/mysql-test/suite/galera/r/galera_toi_ddl_fk_insert.result b/mysql-test/suite/galera/r/galera_toi_ddl_fk_insert.result index a972394f87c..94752ed7c76 100644 --- a/mysql-test/suite/galera/r/galera_toi_ddl_fk_insert.result +++ b/mysql-test/suite/galera/r/galera_toi_ddl_fk_insert.result @@ -21,6 +21,22 @@ connection node_1a; connection node_1b; connection node_2; connection node_2a; +connection node_1; +SET SESSION wsrep_sync_wait=15; +SELECT COUNT(*) FROM parent; +COUNT(*) +20001 +SELECT COUNT(*) FROM child; +COUNT(*) +10000 +connection node_2; +SET SESSION wsrep_sync_wait=15; +SELECT COUNT(*) FROM parent; +COUNT(*) +20001 +SELECT COUNT(*) FROM child; +COUNT(*) +10000 DROP TABLE child; DROP TABLE parent; DROP TABLE ten; diff --git a/mysql-test/suite/galera/t/galera_UK_conflict.test b/mysql-test/suite/galera/t/galera_UK_conflict.test index 83d0e47dc3d..57bafbf8ae0 100644 --- a/mysql-test/suite/galera/t/galera_UK_conflict.test +++ b/mysql-test/suite/galera/t/galera_UK_conflict.test @@ -140,13 +140,9 @@ SELECT * FROM t1; # original state in node 1 INSERT INTO t1 VALUES (7,7,7); INSERT INTO t1 VALUES (8,8,8); -SELECT COUNT(*) FROM t1; SELECT * FROM t1; --connection node_1 ---let $wait_condition = SELECT COUNT(*) = 7 FROM t1 ---source include/wait_condition.inc -SELECT COUNT(*) FROM t1; SELECT * FROM t1; DROP TABLE t1; diff --git a/mysql-test/suite/galera/t/galera_bf_kill_debug.cnf b/mysql-test/suite/galera/t/galera_bf_kill_debug.cnf deleted file mode 100644 index 77bb6af9f35..00000000000 --- a/mysql-test/suite/galera/t/galera_bf_kill_debug.cnf +++ /dev/null @@ -1,9 +0,0 @@ -!include ../galera_2nodes.cnf - -[mysqld.1] -wsrep_log_conflicts=ON -wsrep_debug=1 - -[mysqld.2] -wsrep_log_conflicts=ON -wsrep_debug=1 diff --git a/mysql-test/suite/galera/t/galera_bf_kill_debug.test b/mysql-test/suite/galera/t/galera_bf_kill_debug.test deleted file mode 100644 index f83d4a28ce9..00000000000 --- a/mysql-test/suite/galera/t/galera_bf_kill_debug.test +++ /dev/null @@ -1,283 +0,0 @@ ---source include/galera_cluster.inc ---source include/have_debug.inc ---source include/have_debug_sync.inc - ---echo # ---echo # Case 1: We execute bf kill to wsrep_innobase_kill_one_trx ---echo # function just before wsrep_thd_LOCK(thd) call. Then we ---echo # try to kill victim transaction by KILL QUERY ---echo # - -CREATE TABLE t1(id int not null primary key, b int) engine=innodb; -INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); - -# -# This will be victim transaction for both bf kill and -# user KILL -# ---connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 -begin; -update t1 set b = b * 10 where id between 2 and 4; - -# -# Take thread id for above query -# ---connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 ---let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` - -# -# Set DEBUG_SYNC and send conflicting DDL that will be TOI (bf) and -# cause bf_kill -# ---connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1 -SET DEBUG_SYNC='wsrep_before_BF_victim_lock SIGNAL bf_kill WAIT_FOR bf_continue'; ---send ALTER TABLE t1 ADD UNIQUE KEY b1(b); - -# -# Wait until we have reached the sync point -# ---connection node_1 -SET DEBUG_SYNC='now WAIT_FOR bf_kill'; - -# -# Try to kill update query -# ---connection node_1b ---disable_query_log ---send_eval KILL QUERY $k_thread; - - -# -# Let bf_kill continue -# ---connection node_1 -SET DEBUG_SYNC='now SIGNAL bf_continue'; ---connection node_1c ---reap -SHOW CREATE TABLE t1; -SELECT * FROM t1; - ---connection node_1b ---reap ---enable_query_log - ---connection node_1 -SET DEBUG_SYNC= 'RESET'; -DROP TABLE t1; - ---disconnect node_1a ---disconnect node_1b ---disconnect node_1c - ---echo # ---echo # Case 2: We execute bf kill to wsrep_innobase_kill_one_trx ---echo # function just after wsrep_thd_LOCK(thd) call. Then we ---echo # try to kill victim transaction by KILL QUERY ---echo # - -CREATE TABLE t1(id int not null primary key, b int) engine=innodb; -INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); - -# -# This will be victim transaction for both bf kill and -# user KILL -# ---connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 -begin; -update t1 set b = b * 10 where id between 2 and 4; - -# -# Take thread id for above query -# ---connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 ---let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` - -# -# Set DEBUG_SYNC and send conflicting DDL that will be TOI (bf) and -# cause bf_kill -# ---connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1 -SET DEBUG_SYNC='wsrep_after_BF_victim_lock SIGNAL bf_kill WAIT_FOR bf_continue'; ---send ALTER TABLE t1 ADD UNIQUE KEY b1(b); - -# -# Wait until we have reached the sync point -# ---connection node_1 -SET DEBUG_SYNC='now WAIT_FOR bf_kill'; - -# -# Try to kill update query -# ---connection node_1b ---disable_query_log ---send_eval KILL QUERY $k_thread; - -# -# Let bf_kill continue -# ---connection node_1 -SET DEBUG_SYNC='now SIGNAL bf_continue'; ---connection node_1c ---reap -SHOW CREATE TABLE t1; -SELECT * FROM t1; - ---connection node_1b ---reap ---enable_query_log - ---connection node_1 -SET DEBUG_SYNC= 'RESET'; -DROP TABLE t1; - ---disconnect node_1a ---disconnect node_1b ---disconnect node_1c - ---echo # ---echo # Case 3: Create victim transaction and try to send user KILL ---echo # from several threads ---echo # - -CREATE TABLE t1(id int not null primary key, b int) engine=innodb; -INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); - -# -# This will be victim transaction for user KILL -# ---connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 -begin; -update t1 set b = b * 10 where id between 2 and 4; - -# -# Take thread id for above query -# ---connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 ---connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1 ---connect node_1d, 127.0.0.1, root, , test, $NODE_MYPORT_1 - ---connection node_1b ---let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` - -# -# Try to kill update query from several connections concurrently -# ---disable_query_log ---send_eval KILL QUERY $k_thread; - ---connection node_1c ---disable_query_log ---send_eval KILL QUERY $k_thread; - ---connection node_1d ---disable_query_log ---send_eval KILL QUERY $k_thread; - -# -# We do not know execution order so any of these could fail as KILL -# has been already done -# ---connection node_1b ---enable_query_log ---error 0,ER_KILL_DENIED_ERROR ---reap ---connection node_1c ---enable_query_log ---error 0,ER_KILL_DENIED_ERROR ---reap ---connection node_1d ---enable_query_log ---error 0,ER_KILL_DENIED_ERROR ---reap - ---connection node_1 ---disconnect node_1a ---disconnect node_1b ---disconnect node_1c ---disconnect node_1d -DROP TABLE t1; - ---echo # ---echo # Case 4: MDL-conflict, we execute ALTER until we hit gap in ---echo # wsrep_abort_transaction, while we are there we try to ---echo # manually KILL conflicting transaction (UPDATE) and ---echo # send conflicting transaction from other node to be executed ---echo # in this node by applier. As ALTER and KILL are TOI they ---echo # are not executed concurrently. Similarly UPDATE from other ---echo # node will wait for certification. ---echo # - -CREATE TABLE t1(id int not null primary key, b int) engine=innodb; -INSERT INTO t1 values (1,1),(2,2),(3,3),(4,4),(5,5); - -# -# This will be victim transaction for both bf kill and -# user KILL, and should not have any effect on result -# ---connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 -begin; -update t1 set b = b * 10 where id between 2 and 4; - -# -# Take thread id for above query -# ---connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 ---let $k_thread = `SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'root' AND COMMAND = 'Sleep' LIMIT 1` - -# -# Set DEBUG_SYNC and send conflicting DDL that will be TOI (bf) and -# cause bf_kill but let's execute it only to gap in wsrep_abort_transaction -# ---connect node_1c, 127.0.0.1, root, , test, $NODE_MYPORT_1 -SET DEBUG_SYNC='wsrep_abort_victim_unlocked SIGNAL bf_kill_unlocked WAIT_FOR bf_continue'; ---send ALTER TABLE t1 ADD UNIQUE KEY b1(b); - -# -# Wait until we have reached the sync point -# ---connection node_1 -SET DEBUG_SYNC='now WAIT_FOR bf_kill_unlocked'; - -# -# Try to kill update query -# ---connection node_1b ---disable_query_log ---send_eval KILL QUERY $k_thread; - -# -# Send conflicting update from other node, this should be applied on both nodes -# but should not kill ALTER -# ---enable_query_log ---connection node_2 ---send update t1 set b = b + 1000 where id between 2 and 4; - -# -# Let bf_kill continue -# ---connection node_1 -SET DEBUG_SYNC='now SIGNAL bf_continue'; ---connection node_1c ---reap -SHOW CREATE TABLE t1; -SELECT * FROM t1; - ---connection node_1b ---reap ---enable_query_log - ---connection node_1 -SET DEBUG_SYNC= 'RESET'; -SELECT * FROM t1; - ---connection node_2 ---reap -SHOW CREATE TABLE t1; -SELECT * FROM t1; -DROP TABLE t1; - ---disconnect node_1a ---disconnect node_1c - diff --git a/mysql-test/suite/galera/t/galera_toi_ddl_fk_insert.test b/mysql-test/suite/galera/t/galera_toi_ddl_fk_insert.test index 3b4b427f551..fadc94d78ff 100644 --- a/mysql-test/suite/galera/t/galera_toi_ddl_fk_insert.test +++ b/mysql-test/suite/galera/t/galera_toi_ddl_fk_insert.test @@ -54,11 +54,15 @@ INSERT INTO parent VALUES (1, 0); --connection node_2a --reap -# -# ALTER TABLE could bf kill one or more of INSERTs to parent, so -# the actual number of rows in PARENT depends on whether -# the INSERT is committed before ALTER TABLE is executed -# +--connection node_1 +SET SESSION wsrep_sync_wait=15; +SELECT COUNT(*) FROM parent; +SELECT COUNT(*) FROM child; + +--connection node_2 +SET SESSION wsrep_sync_wait=15; +SELECT COUNT(*) FROM parent; +SELECT COUNT(*) FROM child; DROP TABLE child; DROP TABLE parent; diff --git a/mysql-test/suite/wsrep/t/variables.test b/mysql-test/suite/wsrep/t/variables.test index 875315c0e7c..0cf13650ce0 100644 --- a/mysql-test/suite/wsrep/t/variables.test +++ b/mysql-test/suite/wsrep/t/variables.test @@ -66,7 +66,7 @@ call mtr.add_suppression("WSREP: Failed to get provider options"); #evalp SET GLOBAL wsrep_provider= '$WSREP_PROVIDER'; ---replace_regex /.*libgalera.*/libgalera_smm.so/ +--replace_regex /.*libgalera_smm.*/libgalera_smm.so/ SELECT @@global.wsrep_provider; SELECT @@global.wsrep_slave_threads; SELECT @@global.wsrep_cluster_address; @@ -77,7 +77,7 @@ SHOW STATUS LIKE 'wsrep_thread_count'; #evalp SET GLOBAL wsrep_provider= '$WSREP_PROVIDER'; ---replace_regex /.*libgalera.*/libgalera_smm.so/ +--replace_regex /.*libgalera_smm.*/libgalera_smm.so/ SELECT @@global.wsrep_provider; SELECT @@global.wsrep_cluster_address; SELECT @@global.wsrep_on; @@ -101,7 +101,7 @@ SELECT VARIABLE_VALUE AS EXPECT_1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VA SELECT VARIABLE_VALUE AS EXPECT_1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_rollbacker_thread_count'; SELECT VARIABLE_VALUE AS EXPECT_2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_thread_count'; ---replace_regex /.*libgalera.*/libgalera_smm.so/ +--replace_regex /.*libgalera_smm.*/libgalera_smm.so/ SELECT @@global.wsrep_provider; SELECT @@global.wsrep_cluster_address; SELECT @@global.wsrep_on; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 269ad67165a..9afc701c6ba 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -2391,7 +2391,7 @@ static void clean_up_mutexes() static void set_ports() { } -void close_connection(THD *thd, uint sql_errno, my_bool locked) +void close_connection(THD *thd, uint sql_errno) { } #else @@ -2867,7 +2867,7 @@ static void network_init(void) For the connection that is doing shutdown, this is called twice */ -void close_connection(THD *thd, uint sql_errno, my_bool locked) +void close_connection(THD *thd, uint sql_errno) { DBUG_ENTER("close_connection"); @@ -2877,10 +2877,7 @@ void close_connection(THD *thd, uint sql_errno, my_bool locked) thd->print_aborted_warning(3, sql_errno ? ER_DEFAULT(sql_errno) : "CLOSE_CONNECTION"); - if (locked) - thd->disconnect_mutexed(); - else - thd->disconnect(); + thd->disconnect(); MYSQL_CONNECTION_DONE((int) sql_errno, thd->thread_id); diff --git a/sql/mysqld.h b/sql/mysqld.h index 1084173ae5f..44b0491f138 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -83,7 +83,7 @@ enum enum_slave_parallel_mode { /* Function prototypes */ void kill_mysql(THD *thd= 0); -void close_connection(THD *thd, uint sql_errno= 0, my_bool locked=false); +void close_connection(THD *thd, uint sql_errno= 0); void handle_connection_in_main_thread(CONNECT *thd); void create_thread_to_handle_connection(CONNECT *connect); void signal_thd_deleted(); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 05ec9a1c369..5ada018e540 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1804,11 +1804,11 @@ void THD::awake(killed_state state_to_set) the Vio might be disassociated concurrently. */ -void THD::disconnect_mutexed() +void THD::disconnect() { Vio *vio= NULL; - mysql_mutex_assert_owner(&LOCK_thd_data); + mysql_mutex_lock(&LOCK_thd_data); set_killed(KILL_CONNECTION); @@ -1826,6 +1826,8 @@ void THD::disconnect_mutexed() if (net.vio != vio) vio_close(net.vio); net.thd= 0; // Don't collect statistics + + mysql_mutex_unlock(&LOCK_thd_data); } @@ -1877,18 +1879,16 @@ bool THD::notify_shared_lock(MDL_context_owner *ctx_in_use, thread can see those instances (e.g. see partitioning code). */ if (!thd_table->needs_reopen()) + { signalled|= mysql_lock_abort_for_thread(this, thd_table); + if (WSREP(this) && wsrep_thd_is_BF(this, FALSE)) + { + WSREP_DEBUG("remove_table_from_cache: %llu", + (unsigned long long) this->real_id); + wsrep_abort_thd((void *)this, (void *)in_use, FALSE); + } + } } -#ifdef WITH_WSREP - if (WSREP(this) && wsrep_thd_is_BF(this, false)) - { - WSREP_DEBUG("notify_shared_lock: BF thread %llu query %s" - " victim %llu query %s", - this->real_id, wsrep_thd_query(this), - in_use->real_id, wsrep_thd_query(in_use)); - wsrep_abort_thd((void *)this, (void *)in_use, false); - } -#endif /* WITH_WSREP */ } mysql_mutex_unlock(&in_use->LOCK_thd_data); } diff --git a/sql/sql_class.h b/sql/sql_class.h index 7dbc81be770..5f871f9caf6 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3231,13 +3231,8 @@ public: void awake(killed_state state_to_set); /** Disconnect the associated communication endpoint. */ - inline void disconnect() - { - mysql_mutex_lock(&LOCK_thd_data); - disconnect_mutexed(); - mysql_mutex_unlock(&LOCK_thd_data); - } - void disconnect_mutexed(); + void disconnect(); + /* Allows this thread to serve as a target for others to schedule Async diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2bec9c6b6cd..3e1f248b082 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2017, Oracle and/or its affiliates. - Copyright (c) 2008, 2021, MariaDB + Copyright (c) 2008, 2020, MariaDB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -9069,18 +9069,6 @@ static void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) { uint error; -#ifdef WITH_WSREP - if (WSREP(thd)) - { - WSREP_DEBUG("sql_kill called"); - if (thd->wsrep_applier) - { - WSREP_DEBUG("KILL in applying, bailing out here"); - return; - } - WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) - } -#endif /* WITH_WSREP */ if (!(error= kill_one_thread(thd, id, state, type))) { if (!thd->killed) @@ -9090,11 +9078,6 @@ void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) } else my_error(error, MYF(0), id); -#ifdef WITH_WSREP - return; - wsrep_error_label: - my_error(ER_CANNOT_USER, MYF(0), wsrep_thd_query(thd)); -#endif /* WITH_WSREP */ } @@ -9103,18 +9086,6 @@ void sql_kill_user(THD *thd, LEX_USER *user, killed_state state) { uint error; ha_rows rows; -#ifdef WITH_WSREP - if (WSREP(thd)) - { - WSREP_DEBUG("sql_kill_user called"); - if (thd->wsrep_applier) - { - WSREP_DEBUG("KILL in applying, bailing out here"); - return; - } - WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) - } -#endif /* WITH_WSREP */ if (!(error= kill_threads_for_user(thd, user, state, &rows))) my_ok(thd, rows); else @@ -9125,11 +9096,6 @@ void sql_kill_user(THD *thd, LEX_USER *user, killed_state state) */ my_error(error, MYF(0), user->host.str, user->user.str); } -#ifdef WITH_WSREP - return; - wsrep_error_label: - my_error(ER_CANNOT_USER, MYF(0), user->user.str); -#endif /* WITH_WSREP */ } diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 00a6dfe2f8a..f22d8bf0f5a 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1,4 +1,4 @@ -/* Copyright 2008-2021 Codership Oy +/* Copyright 2008-2015 Codership Oy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -835,25 +835,13 @@ void wsrep_thr_init() DBUG_VOID_RETURN; } -/* This is wrapper for wsrep_break_lock in thr_lock.c */ -static int wsrep_thr_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, my_bool signal) -{ - THD* victim_thd= (THD *) victim_thd_ptr; - /* We need to lock THD::LOCK_thd_data to protect victim - from concurrent usage or disconnect or delete. */ - mysql_mutex_lock(&victim_thd->LOCK_thd_data); - int res= wsrep_abort_thd(bf_thd_ptr, victim_thd_ptr, signal); - mysql_mutex_unlock(&victim_thd->LOCK_thd_data); - return res; -} - void wsrep_init_startup (bool first) { if (wsrep_init()) unireg_abort(1); wsrep_thr_lock_init( (wsrep_thd_is_brute_force_fun)wsrep_thd_is_BF, - (wsrep_abort_thd_fun)wsrep_thr_abort_thd, + (wsrep_abort_thd_fun)wsrep_abort_thd, wsrep_debug, wsrep_convert_LOCK_to_trx, (wsrep_on_fun)wsrep_on); @@ -1706,11 +1694,6 @@ static int wsrep_TOI_begin(THD *thd, char *db_, char *table_, case SQLCOM_DROP_TABLE: buf_err= wsrep_drop_table_query(thd, &buf, &buf_len); break; - case SQLCOM_KILL: - WSREP_DEBUG("KILL as TOI: %s", thd->query()); - buf_err= wsrep_to_buf_helper(thd, thd->query(), thd->query_length(), - &buf, &buf_len); - break; case SQLCOM_CREATE_ROLE: if (sp_process_definer(thd)) { @@ -2075,11 +2058,8 @@ bool wsrep_grant_mdl_exception(MDL_context *requestor_ctx, ticket->wsrep_report(true); } - /* This will call wsrep_abort_transaction so we should hold - THD::LOCK_thd_data to protect victim from concurrent usage - or disconnect or delete. */ - wsrep_abort_thd((void *) request_thd, (void *) granted_thd, 1); mysql_mutex_unlock(&granted_thd->LOCK_thd_data); + wsrep_abort_thd((void *) request_thd, (void *) granted_thd, 1); ret= false; } } @@ -2261,7 +2241,6 @@ error: static bool abort_replicated(THD *thd) { bool ret_code= false; - mysql_mutex_assert_owner(&thd->LOCK_thd_data); if (thd->wsrep_query_state== QUERY_COMMITTING) { WSREP_DEBUG("aborting replicated trx: %llu", (ulonglong)(thd->real_id)); @@ -2276,7 +2255,6 @@ static bool abort_replicated(THD *thd) /**/ static inline bool is_client_connection(THD *thd) { - mysql_mutex_assert_owner(&thd->LOCK_thd_data); return (thd->wsrep_client_thread && thd->variables.wsrep_on); } @@ -2285,8 +2263,9 @@ static inline bool is_replaying_connection(THD *thd) { bool ret; - mysql_mutex_assert_owner(&thd->LOCK_thd_data); + mysql_mutex_lock(&thd->LOCK_thd_data); ret= (thd->wsrep_conflict_state == REPLAYING) ? true : false; + mysql_mutex_unlock(&thd->LOCK_thd_data); return ret; } @@ -2296,8 +2275,9 @@ static inline bool is_committing_connection(THD *thd) { bool ret; - mysql_mutex_assert_owner(&thd->LOCK_thd_data); + mysql_mutex_lock(&thd->LOCK_thd_data); ret= (thd->wsrep_query_state == QUERY_COMMITTING) ? true : false; + mysql_mutex_unlock(&thd->LOCK_thd_data); return ret; } @@ -2310,17 +2290,13 @@ static bool have_client_connections() I_List_iterator it(threads); while ((tmp=it++)) { - /* Protect thread from concurrent usage or disconnect or delete. */ - mysql_mutex_lock(&tmp->LOCK_thd_data); DBUG_PRINT("quit",("Informing thread %lld that it's time to die", (longlong) tmp->thread_id)); if (is_client_connection(tmp) && tmp->killed == KILL_CONNECTION) { (void)abort_replicated(tmp); - mysql_mutex_unlock(&tmp->LOCK_thd_data); return true; } - mysql_mutex_unlock(&tmp->LOCK_thd_data); } return false; } @@ -2352,21 +2328,14 @@ static my_bool have_committing_connections() I_List_iterator it(threads); while ((tmp=it++)) { - /* Protect from concurrent usage or disconnect or delete */ - mysql_mutex_lock(&tmp->LOCK_thd_data); if (!is_client_connection(tmp)) - { - mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; - } if (is_committing_connection(tmp)) { mysql_mutex_unlock(&LOCK_thread_count); - mysql_mutex_unlock(&tmp->LOCK_thd_data); return TRUE; } - mysql_mutex_unlock(&tmp->LOCK_thd_data); } mysql_mutex_unlock(&LOCK_thread_count); return FALSE; @@ -2409,44 +2378,33 @@ void wsrep_close_client_connections(my_bool wait_to_end, THD *except_caller_thd) { DBUG_PRINT("quit",("Informing thread %lld that it's time to die", (longlong) tmp->thread_id)); - /* Protect from concurrent usage or disconnect or delete */ - mysql_mutex_lock(&tmp->LOCK_thd_data); /* We skip slave threads & scheduler on this first loop through. */ if (!is_client_connection(tmp)) - { - mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; - } if (tmp == except_caller_thd) { DBUG_ASSERT(is_client_connection(tmp)); - mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; } if (is_replaying_connection(tmp)) { tmp->set_killed(KILL_CONNECTION); - mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; } - /* replicated transactions must be skipped and aborted - with wsrep_abort_thd. */ + /* replicated transactions must be skipped */ if (abort_replicated(tmp)) - { - mysql_mutex_unlock(&tmp->LOCK_thd_data); continue; - } WSREP_DEBUG("closing connection %lld", (longlong) tmp->thread_id); /* - instead of wsrep_close_thread() we do now soft kill by - THD::awake(). Here also victim needs to be protected from - concurrent usage or disconnect or delete. - */ + instead of wsrep_close_thread() we do now soft kill by THD::awake + */ + mysql_mutex_lock(&tmp->LOCK_thd_data); + tmp->awake(KILL_CONNECTION); mysql_mutex_unlock(&tmp->LOCK_thd_data); @@ -2465,19 +2423,16 @@ void wsrep_close_client_connections(my_bool wait_to_end, THD *except_caller_thd) I_List_iterator it2(threads); while ((tmp=it2++)) { - /* Protect from concurrent usage or disconnect or delete */ - mysql_mutex_lock(&tmp->LOCK_thd_data); - if (is_client_connection(tmp)) +#ifndef __bsdi__ // Bug in BSDI kernel + if (is_client_connection(tmp) && + !abort_replicated(tmp) && + !is_replaying_connection(tmp) && + tmp != except_caller_thd) { - if (!abort_replicated(tmp) && - !is_replaying_connection(tmp) && - tmp != except_caller_thd) - { - WSREP_INFO("killing local connection: %lld", (longlong) tmp->thread_id); - close_connection(tmp,0, true); - } + WSREP_INFO("killing local connection: %lld", (longlong) tmp->thread_id); + close_connection(tmp,0); } - mysql_mutex_unlock(&tmp->LOCK_thd_data); +#endif } DBUG_PRINT("quit",("Waiting for threads to die (count=%u)",thread_count)); @@ -2666,9 +2621,7 @@ extern "C" void wsrep_thd_set_query_state( void wsrep_thd_set_conflict_state(THD *thd, enum wsrep_conflict_state state) { - DBUG_ASSERT(thd); - mysql_mutex_assert_owner(&thd->LOCK_thd_data); - thd->wsrep_conflict_state= state; + if (WSREP(thd)) thd->wsrep_conflict_state= state; } @@ -2809,9 +2762,6 @@ extern "C" void wsrep_thd_awake(THD *thd, my_bool signal) { if (signal) { - /* Here we should hold THD::LOCK_thd_data to - protect from concurrent usage. */ - mysql_mutex_assert_owner(&thd->LOCK_thd_data); thd->awake(KILL_QUERY); } else diff --git a/sql/wsrep_thd.cc b/sql/wsrep_thd.cc index d849cd2cd78..ef8c0e132f7 100644 --- a/sql/wsrep_thd.cc +++ b/sql/wsrep_thd.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2013-2021 Codership Oy +/* Copyright (C) 2013 Codership Oy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -804,12 +804,10 @@ my_bool wsrep_thd_is_local(void *thd_ptr, my_bool sync) int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, my_bool signal) { - THD *victim_thd= (THD *) victim_thd_ptr; - THD *bf_thd= (THD *) bf_thd_ptr; + THD *victim_thd = (THD *) victim_thd_ptr; + THD *bf_thd = (THD *) bf_thd_ptr; DBUG_ENTER("wsrep_abort_thd"); - mysql_mutex_assert_owner(&victim_thd->LOCK_thd_data); - if ( (WSREP(bf_thd) || ( (WSREP_ON || bf_thd->variables.wsrep_OSU_method == WSREP_OSU_RSU) && bf_thd->wsrep_exec_mode == TOTAL_ORDER) ) && diff --git a/sql/wsrep_thd.h b/sql/wsrep_thd.h index 1284cad1acf..46bc08a466a 100644 --- a/sql/wsrep_thd.h +++ b/sql/wsrep_thd.h @@ -32,6 +32,7 @@ void wsrep_create_rollbacker(); int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, my_bool signal); + /* PA = Parallel Applying (on the slave side) */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 921b8282e45..a42e8e3699c 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -60,6 +60,7 @@ this program; if not, write to the Free Software Foundation, Inc., #include #include +#include /* Include necessary InnoDB headers */ #include "btr0btr.h" @@ -5233,21 +5234,17 @@ UNIV_INTERN void lock_cancel_waiting_and_release(lock_t* lock); @sa THD::awake() @sa ha_kill_query() */ static void innobase_kill_query(handlerton*, THD* thd, enum thd_kill_levels) { - DBUG_ENTER("innobase_kill_query"); + DBUG_ENTER("innobase_kill_query"); #ifdef WITH_WSREP - if (wsrep_thd_get_conflict_state(thd) != NO_CONFLICT) - { - /* if victim has been signaled by BF thread and/or aborting - is already progressing, following query aborting is not necessary - any more. E.g. wsrep_innobase_kill_one_trx(). - Also, BF thread should own trx mutex for the victim, which would - conflict with trx_mutex_enter() below - */ - WSREP_DEBUG("Victim thread %ld bail out conflict_state %s query %s", - thd_get_thread_id(thd), - wsrep_thd_conflict_state_str(thd), wsrep_thd_query(thd)); - DBUG_VOID_RETURN; - } + if (wsrep_thd_get_conflict_state(thd) != NO_CONFLICT) { + /* if victim has been signaled by BF thread and/or aborting + is already progressing, following query aborting is not necessary + any more. + Also, BF thread should own trx mutex for the victim, which would + conflict with trx_mutex_enter() below + */ + DBUG_VOID_RETURN; + } #endif /* WITH_WSREP */ if (trx_t* trx= thd_to_trx(thd)) @@ -19502,9 +19499,9 @@ static struct st_mysql_storage_engine innobase_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; #ifdef WITH_WSREP -static void wsrep_abort_slave_trx( +/*==================*/ wsrep_seqno_t bf_seqno, wsrep_seqno_t victim_seqno) { @@ -19514,97 +19511,93 @@ wsrep_abort_slave_trx( "2) a bug in the code.\n\t" "3) a database corruption.\n Node consistency compromized, " "need to abort. Restart the node to resync with cluster.", - bf_seqno, victim_seqno); + (long long)bf_seqno, (long long)victim_seqno); abort(); } -/*******************************************************************//** -This function is used to kill one transaction in BF. */ -void -wsrep_innobase_kill_one_trx( - MYSQL_THD const bf_thd, - const trx_t * const bf_trx, - trx_t *victim_trx, - my_bool signal) + +struct bg_wsrep_kill_trx_arg { + my_thread_id thd_id; + trx_id_t trx_id; + int64_t bf_seqno; + ibool signal; +}; + +static void bg_wsrep_kill_trx( + void *void_arg) { - ut_ad(bf_thd); - ut_ad(victim_trx); - ut_ad(lock_mutex_own()); - ut_ad(trx_mutex_own(victim_trx)); + bg_wsrep_kill_trx_arg *arg = (bg_wsrep_kill_trx_arg*)void_arg; + THD *thd = find_thread_by_id(arg->thd_id, false); + trx_t *victim_trx = NULL; + bool awake = false; + DBUG_ENTER("bg_wsrep_kill_trx"); - DBUG_ENTER("wsrep_innobase_kill_one_trx"); - THD *thd= (THD *) victim_trx->mysql_thd; - int64_t bf_seqno= wsrep_thd_trx_seqno(bf_thd); - - if (!thd) { - WSREP_WARN("no THD for trx: " TRX_ID_FMT, victim_trx->id); - DBUG_VOID_RETURN; + if (thd) { + victim_trx= thd_to_trx(thd); + /* Victim trx might not exist e.g. on MDL-conflict. */ + if (victim_trx) { + lock_mutex_enter(); + trx_mutex_enter(victim_trx); + if (victim_trx->id != arg->trx_id || + victim_trx->state == TRX_STATE_COMMITTED_IN_MEMORY) + { + /* Victim was meanwhile rolled back or + committed */ + trx_mutex_exit(victim_trx); + lock_mutex_exit(); + wsrep_thd_UNLOCK(thd); + victim_trx= NULL; + } + } else { + /* find_thread_by_id locked + THD::LOCK_thd_data */ + wsrep_thd_UNLOCK(thd); + } } - /* Here we need to lock THD::LOCK_thd_data to protect from - concurrent usage or disconnect or delete. */ - DEBUG_SYNC(bf_thd, "wsrep_before_BF_victim_lock"); - wsrep_thd_LOCK(thd); - DEBUG_SYNC(bf_thd, "wsrep_after_BF_victim_lock"); + if (!victim_trx) { + /* Victim trx might not exist (MDL-conflict) or victim + was meanwhile rolled back or committed because of + a KILL statement or a disconnect. */ + goto ret; + } - WSREP_DEBUG("Aborter %s trx_id: " TRX_ID_FMT " thread: %ld " - "seqno: %lld query_state: %s conflict_state: %s query: %s", - wsrep_thd_is_BF(bf_thd, false) ? "BF" : "normal", - bf_trx ? bf_trx->id : TRX_ID_MAX, - thd_get_thread_id(bf_thd), - bf_seqno, - wsrep_thd_query_state_str(bf_thd), - wsrep_thd_conflict_state_str(bf_thd), - wsrep_thd_query(bf_thd)); - - WSREP_DEBUG("Victim %s trx_id: " TRX_ID_FMT " thread: %ld " - "seqno: %lld query_state: %s conflict_state: %s query: %s", - wsrep_thd_is_BF(thd, false) ? "BF" : "normal", - victim_trx->id, + WSREP_DEBUG("BF kill (" ULINTPF ", seqno: " INT64PF + "), victim: (%lu) trx: " TRX_ID_FMT, + arg->signal, arg->bf_seqno, thd_get_thread_id(thd), - wsrep_thd_trx_seqno(thd), - wsrep_thd_query_state_str(thd), - wsrep_thd_conflict_state_str(thd), - wsrep_thd_query(thd)); + victim_trx->id); - WSREP_LOG_CONFLICT(bf_thd, thd, TRUE); + WSREP_DEBUG("Aborting query: %s conf %d trx: %" PRId64, + (wsrep_thd_query(thd)) ? wsrep_thd_query(thd) : "void", + wsrep_thd_conflict_state(thd, FALSE), + wsrep_thd_ws_handle(thd)->trx_id); if (wsrep_thd_query_state(thd) == QUERY_EXITING) { WSREP_DEBUG("kill trx EXITING for " TRX_ID_FMT, victim_trx->id); - wsrep_thd_UNLOCK(thd); - DBUG_VOID_RETURN; + goto ret_unlock; } if (wsrep_thd_exec_mode(thd) != LOCAL_STATE) { - WSREP_DEBUG("withdraw for BF trx: " TRX_ID_FMT - ", state: %s exec %s", + WSREP_DEBUG("withdraw for BF trx: " TRX_ID_FMT ", state: %d", victim_trx->id, - wsrep_thd_conflict_state_str(thd), - wsrep_thd_exec_mode_str(thd)); + wsrep_thd_get_conflict_state(thd)); } switch (wsrep_thd_get_conflict_state(thd)) { case NO_CONFLICT: - /* This will cause any call to innobase_kill_query() - for this thd to bail out. */ wsrep_thd_set_conflict_state(thd, MUST_ABORT); break; case MUST_ABORT: WSREP_DEBUG("victim " TRX_ID_FMT " in MUST ABORT state", victim_trx->id); - wsrep_thd_awake(thd, signal); - wsrep_thd_UNLOCK(thd); - DBUG_VOID_RETURN; - break; + goto ret_awake; case ABORTED: case ABORTING: // fall through default: - WSREP_DEBUG("victim " TRX_ID_FMT " in state %s", - victim_trx->id, - wsrep_thd_conflict_state_str(thd)); - wsrep_thd_UNLOCK(thd); - DBUG_VOID_RETURN; - break; + WSREP_DEBUG("victim " TRX_ID_FMT " in state %d", + victim_trx->id, wsrep_thd_get_conflict_state(thd)); + goto ret_unlock; } switch (wsrep_thd_query_state(thd)) { @@ -19617,12 +19610,12 @@ wsrep_innobase_kill_one_trx( victim_trx->id); if (wsrep_thd_exec_mode(thd) == REPL_RECV) { - wsrep_abort_slave_trx(bf_seqno, + wsrep_abort_slave_trx(arg->bf_seqno, wsrep_thd_trx_seqno(thd)); } else { wsrep_t *wsrep= get_wsrep(); rcode = wsrep->abort_pre_commit( - wsrep, bf_seqno, + wsrep, arg->bf_seqno, (wsrep_trx_id_t)wsrep_thd_ws_handle(thd)->trx_id ); @@ -19631,10 +19624,7 @@ wsrep_innobase_kill_one_trx( WSREP_DEBUG("cancel commit warning: " TRX_ID_FMT, victim_trx->id); - wsrep_thd_awake(thd, signal); - wsrep_thd_UNLOCK(thd); - DBUG_VOID_RETURN; - break; + goto ret_awake; case WSREP_OK: break; default: @@ -19647,12 +19637,9 @@ wsrep_innobase_kill_one_trx( * kill the lock holder first. */ abort(); - break; } } - wsrep_thd_awake(thd, signal); - wsrep_thd_UNLOCK(thd); - break; + goto ret_awake; case QUERY_EXEC: /* it is possible that victim trx is itself waiting for some * other lock. We need to cancel this waiting @@ -19673,65 +19660,118 @@ wsrep_innobase_kill_one_trx( lock_cancel_waiting_and_release(wait_lock); } - wsrep_thd_awake(thd, signal); - wsrep_thd_UNLOCK(thd); } else { /* abort currently executing query */ + DBUG_PRINT("wsrep",("sending KILL_QUERY to: %lu", + thd_get_thread_id(thd))); WSREP_DEBUG("kill query for: %ld", thd_get_thread_id(thd)); - wsrep_thd_awake(thd, signal); - wsrep_thd_UNLOCK(thd); /* for BF thd, we need to prevent him from committing */ if (wsrep_thd_exec_mode(thd) == REPL_RECV) { - wsrep_abort_slave_trx(bf_seqno, - wsrep_thd_trx_seqno(thd)); + wsrep_abort_slave_trx(arg->bf_seqno, + wsrep_thd_trx_seqno(thd)); } } - break; + goto ret_awake; case QUERY_IDLE: { WSREP_DEBUG("kill IDLE for " TRX_ID_FMT, victim_trx->id); if (wsrep_thd_exec_mode(thd) == REPL_RECV) { WSREP_DEBUG("kill BF IDLE, seqno: %lld", - wsrep_thd_trx_seqno(thd)); - wsrep_thd_UNLOCK(thd); - wsrep_abort_slave_trx(bf_seqno, + (long long)wsrep_thd_trx_seqno(thd)); + wsrep_abort_slave_trx(arg->bf_seqno, wsrep_thd_trx_seqno(thd)); - DBUG_VOID_RETURN; + goto ret_unlock; } - /* This will lock thd from proceeding after net_read() - and innobase_kill_query to bail out for this thd. */ + /* This will lock thd from proceeding after net_read() */ wsrep_thd_set_conflict_state(thd, ABORTING); wsrep_lock_rollback(); if (wsrep_aborting_thd_contains(thd)) { WSREP_WARN("duplicate thd aborter %lu", - thd_get_thread_id(thd)); + (ulong) thd_get_thread_id(thd)); } else { wsrep_aborting_thd_enqueue(thd); + DBUG_PRINT("wsrep",("enqueuing trx abort for %lu", + thd_get_thread_id(thd))); WSREP_DEBUG("enqueuing trx abort for (%lu)", - thd_get_thread_id(thd)); + thd_get_thread_id(thd)); } + DBUG_PRINT("wsrep",("signalling wsrep rollbacker")); WSREP_DEBUG("signaling aborter"); wsrep_unlock_rollback(); - wsrep_thd_UNLOCK(thd); - - break; + goto ret_unlock; } default: - ut_error; + WSREP_WARN("bad wsrep query state: %d", + wsrep_thd_query_state(thd)); + goto ret_unlock; } +ret_awake: + awake= true; + +ret_unlock: + trx_mutex_exit(victim_trx); + lock_mutex_exit(); + if (awake) + wsrep_thd_awake(thd, arg->signal); + wsrep_thd_UNLOCK(thd); + +ret: + free(arg); + DBUG_VOID_RETURN; + +} + +/*******************************************************************//** +This function is used to kill one transaction in BF. */ +UNIV_INTERN +void +wsrep_innobase_kill_one_trx( +/*========================*/ + MYSQL_THD const bf_thd, + const trx_t * const bf_trx, + trx_t *victim_trx, + ibool signal) +{ + ut_ad(bf_thd); + ut_ad(victim_trx); + ut_ad(lock_mutex_own()); + ut_ad(trx_mutex_own(victim_trx)); + + bg_wsrep_kill_trx_arg *arg = (bg_wsrep_kill_trx_arg*)malloc(sizeof(*arg)); + arg->thd_id = thd_get_thread_id(victim_trx->mysql_thd); + arg->trx_id = victim_trx->id; + arg->bf_seqno = wsrep_thd_trx_seqno((THD*)bf_thd); + arg->signal = signal; + + DBUG_ENTER("wsrep_innobase_kill_one_trx"); + + WSREP_LOG_CONFLICT(bf_thd, victim_trx->mysql_thd, TRUE); + + DBUG_EXECUTE_IF("sync.wsrep_after_BF_victim_lock", + { + const char act[]= + "now " + "wait_for signal.wsrep_after_BF_victim_lock"; + DBUG_ASSERT(!debug_sync_set_action(bf_thd, + STRING_WITH_LEN(act))); + };); + + + mysql_manager_submit(bg_wsrep_kill_trx, arg); DBUG_VOID_RETURN; } static void wsrep_abort_transaction( +/*====================*/ handlerton* hton, THD *bf_thd, THD *victim_thd, @@ -19739,66 +19779,28 @@ wsrep_abort_transaction( { DBUG_ENTER("wsrep_abort_transaction"); - ut_ad(bf_thd); - ut_ad(victim_thd); - trx_t* victim_trx= thd_to_trx(victim_thd); - trx_t* bf_trx= thd_to_trx(bf_thd); + trx_t* victim_trx = thd_to_trx(victim_thd); + trx_t* bf_trx = (bf_thd) ? thd_to_trx(bf_thd) : NULL; - /* Here we should hold THD::LOCK_thd_data to protect - victim from concurrent usage or disconnect or delete. */ - WSREP_DEBUG("wsrep_abort_transaction: BF:" - " thread %ld query_state %s conflict_state %s" - " exec %s query %s trx " TRX_ID_FMT, - thd_get_thread_id(bf_thd), - wsrep_thd_query_state_str(bf_thd), - wsrep_thd_conflict_state_str(bf_thd), - wsrep_thd_exec_mode_str(bf_thd), - wsrep_thd_query(bf_thd), - bf_trx ? bf_trx->id : 0); - - WSREP_DEBUG("wsrep_abort_transaction: victim:" - " thread %ld query_state %s conflict_state %s" - " exec %s query %s trx " TRX_ID_FMT, - thd_get_thread_id(victim_thd), - wsrep_thd_query_state_str(victim_thd), - wsrep_thd_conflict_state_str(victim_thd), - wsrep_thd_exec_mode_str(victim_thd), - wsrep_thd_query(victim_thd), - victim_trx ? victim_trx->id : 0); + WSREP_DEBUG("abort transaction: BF: %s victim: %s victim conf: %d", + wsrep_thd_query(bf_thd), + wsrep_thd_query(victim_thd), + wsrep_thd_conflict_state(victim_thd, FALSE)); if (victim_trx) { - WSREP_DEBUG("wsrep_abort_transaction: Victim thread %ld " - "transaction " TRX_ID_FMT " trx_state %d", - thd_get_thread_id(victim_thd), - victim_trx->id, - victim_trx->state); - /* This is necessary as correct mutexing order is - lock_sys -> trx -> THD::LOCK_thd_data and below - function assumes we have lock_sys and trx locked - and takes THD::LOCK_thd_data for THD state check. */ - wsrep_thd_UNLOCK(victim_thd); - DEBUG_SYNC(bf_thd, "wsrep_abort_victim_unlocked"); - DBUG_EXECUTE_IF("wsrep_abort_replicated_sleep", - WSREP_DEBUG("wsrep_abort_transaction: sleeping " - "for thread %ld ", - thd_get_thread_id(victim_thd)); - my_sleep(100000);); lock_mutex_enter(); trx_mutex_enter(victim_trx); wsrep_innobase_kill_one_trx(bf_thd, bf_trx, victim_trx, signal); lock_mutex_exit(); trx_mutex_exit(victim_trx); wsrep_srv_conc_cancel_wait(victim_trx); - wsrep_thd_LOCK(victim_thd); DBUG_VOID_RETURN; } else { - WSREP_DEBUG("wsrep_abort_transaction: Victim thread %ld " - "no transaction", - thd_get_thread_id(victim_thd)); - /* This will cause any call to innobase_kill_query() - for this thd to bail out. */ + WSREP_DEBUG("victim does not have transaction"); + wsrep_thd_LOCK(victim_thd); wsrep_thd_set_conflict_state(victim_thd, MUST_ABORT); wsrep_thd_awake(victim_thd, signal); + wsrep_thd_UNLOCK(victim_thd); } DBUG_VOID_RETURN; diff --git a/storage/innobase/include/ha_prototypes.h b/storage/innobase/include/ha_prototypes.h index 427e57f09d2..3eab2135969 100644 --- a/storage/innobase/include/ha_prototypes.h +++ b/storage/innobase/include/ha_prototypes.h @@ -233,11 +233,12 @@ innobase_casedn_str( char* a); /*!< in/out: string to put in lower case */ #ifdef WITH_WSREP +UNIV_INTERN void wsrep_innobase_kill_one_trx(MYSQL_THD const thd_ptr, const trx_t * const bf_trx, trx_t *victim_trx, - my_bool signal); + ibool signal); int wsrep_innobase_mysql_sort(int mysql_type, uint charset_number, unsigned char* str, unsigned int str_length, unsigned int buf_length);