Commit 88a4be75 authored by sjaakola's avatar sjaakola Committed by Jan Lindström

MDEV-25114 Crash: WSREP: invalid state ROLLED_BACK (FATAL)

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: default avatarJan Lindström <jan.lindstrom@mariadb.com>
parent 9d97f92f
...@@ -68,6 +68,9 @@ f1 f2 f3 ...@@ -68,6 +68,9 @@ f1 f2 f3
10 10 0 10 10 0
INSERT INTO t1 VALUES (7,7,7); INSERT INTO t1 VALUES (7,7,7);
INSERT INTO t1 VALUES (8,8,8); INSERT INTO t1 VALUES (8,8,8);
SELECT COUNT(*) FROM t1;
COUNT(*)
7
SELECT * FROM t1; SELECT * FROM t1;
f1 f2 f3 f1 f2 f3
1 1 0 1 1 0
...@@ -78,6 +81,9 @@ f1 f2 f3 ...@@ -78,6 +81,9 @@ f1 f2 f3
8 8 8 8 8 8
10 10 0 10 10 0
connection node_1; connection node_1;
SELECT COUNT(*) FROM t1;
COUNT(*)
7
SELECT * FROM t1; SELECT * FROM t1;
f1 f2 f3 f1 f2 f3
1 1 0 1 1 0
...@@ -85,5 +91,6 @@ f1 f2 f3 ...@@ -85,5 +91,6 @@ f1 f2 f3
4 4 2 4 4 2
5 5 2 5 5 2
7 7 7 7 7 7
8 8 8
10 10 0 10 10 0
DROP TABLE t1; DROP TABLE t1;
#
# 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;
...@@ -21,22 +21,6 @@ connection node_1a; ...@@ -21,22 +21,6 @@ connection node_1a;
connection node_1b; connection node_1b;
connection node_2; connection node_2;
connection node_2a; 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 child;
DROP TABLE parent; DROP TABLE parent;
DROP TABLE ten; DROP TABLE ten;
...@@ -140,9 +140,13 @@ SELECT * FROM t1; ...@@ -140,9 +140,13 @@ SELECT * FROM t1;
# original state in node 1 # original state in node 1
INSERT INTO t1 VALUES (7,7,7); INSERT INTO t1 VALUES (7,7,7);
INSERT INTO t1 VALUES (8,8,8); INSERT INTO t1 VALUES (8,8,8);
SELECT COUNT(*) FROM t1;
SELECT * FROM t1; SELECT * FROM t1;
--connection node_1 --connection node_1
--let $wait_condition = SELECT COUNT(*) = 7 FROM t1
--source include/wait_condition.inc
SELECT COUNT(*) FROM t1;
SELECT * FROM t1; SELECT * FROM t1;
DROP TABLE t1; DROP TABLE t1;
!include ../galera_2nodes.cnf
[mysqld.1]
wsrep_log_conflicts=ON
wsrep_debug=1
[mysqld.2]
wsrep_log_conflicts=ON
wsrep_debug=1
--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
...@@ -54,15 +54,11 @@ INSERT INTO parent VALUES (1, 0); ...@@ -54,15 +54,11 @@ INSERT INTO parent VALUES (1, 0);
--connection node_2a --connection node_2a
--reap --reap
--connection node_1 #
SET SESSION wsrep_sync_wait=15; # ALTER TABLE could bf kill one or more of INSERTs to parent, so
SELECT COUNT(*) FROM parent; # the actual number of rows in PARENT depends on whether
SELECT COUNT(*) FROM child; # the INSERT is committed before ALTER TABLE is executed
#
--connection node_2
SET SESSION wsrep_sync_wait=15;
SELECT COUNT(*) FROM parent;
SELECT COUNT(*) FROM child;
DROP TABLE child; DROP TABLE child;
DROP TABLE parent; DROP TABLE parent;
......
...@@ -66,7 +66,7 @@ call mtr.add_suppression("WSREP: Failed to get provider options"); ...@@ -66,7 +66,7 @@ call mtr.add_suppression("WSREP: Failed to get provider options");
#evalp SET GLOBAL wsrep_provider= '$WSREP_PROVIDER'; #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_provider;
SELECT @@global.wsrep_slave_threads; SELECT @@global.wsrep_slave_threads;
SELECT @@global.wsrep_cluster_address; SELECT @@global.wsrep_cluster_address;
...@@ -77,7 +77,7 @@ SHOW STATUS LIKE 'wsrep_thread_count'; ...@@ -77,7 +77,7 @@ SHOW STATUS LIKE 'wsrep_thread_count';
#evalp SET GLOBAL wsrep_provider= '$WSREP_PROVIDER'; #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_provider;
SELECT @@global.wsrep_cluster_address; SELECT @@global.wsrep_cluster_address;
SELECT @@global.wsrep_on; SELECT @@global.wsrep_on;
...@@ -101,7 +101,7 @@ SELECT VARIABLE_VALUE AS EXPECT_1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VA ...@@ -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_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'; 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_provider;
SELECT @@global.wsrep_cluster_address; SELECT @@global.wsrep_cluster_address;
SELECT @@global.wsrep_on; SELECT @@global.wsrep_on;
......
...@@ -2391,7 +2391,7 @@ static void clean_up_mutexes() ...@@ -2391,7 +2391,7 @@ static void clean_up_mutexes()
static void set_ports() static void set_ports()
{ {
} }
void close_connection(THD *thd, uint sql_errno) void close_connection(THD *thd, uint sql_errno, my_bool locked)
{ {
} }
#else #else
...@@ -2867,7 +2867,7 @@ static void network_init(void) ...@@ -2867,7 +2867,7 @@ static void network_init(void)
For the connection that is doing shutdown, this is called twice 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"); DBUG_ENTER("close_connection");
...@@ -2877,7 +2877,10 @@ void close_connection(THD *thd, uint sql_errno) ...@@ -2877,7 +2877,10 @@ void close_connection(THD *thd, uint sql_errno)
thd->print_aborted_warning(3, sql_errno ? ER_DEFAULT(sql_errno) thd->print_aborted_warning(3, sql_errno ? ER_DEFAULT(sql_errno)
: "CLOSE_CONNECTION"); : "CLOSE_CONNECTION");
thd->disconnect(); if (locked)
thd->disconnect_mutexed();
else
thd->disconnect();
MYSQL_CONNECTION_DONE((int) sql_errno, thd->thread_id); MYSQL_CONNECTION_DONE((int) sql_errno, thd->thread_id);
......
...@@ -83,7 +83,7 @@ enum enum_slave_parallel_mode { ...@@ -83,7 +83,7 @@ enum enum_slave_parallel_mode {
/* Function prototypes */ /* Function prototypes */
void kill_mysql(THD *thd= 0); 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 handle_connection_in_main_thread(CONNECT *thd);
void create_thread_to_handle_connection(CONNECT *connect); void create_thread_to_handle_connection(CONNECT *connect);
void signal_thd_deleted(); void signal_thd_deleted();
......
...@@ -1804,11 +1804,11 @@ void THD::awake(killed_state state_to_set) ...@@ -1804,11 +1804,11 @@ void THD::awake(killed_state state_to_set)
the Vio might be disassociated concurrently. the Vio might be disassociated concurrently.
*/ */
void THD::disconnect() void THD::disconnect_mutexed()
{ {
Vio *vio= NULL; Vio *vio= NULL;
mysql_mutex_lock(&LOCK_thd_data); mysql_mutex_assert_owner(&LOCK_thd_data);
set_killed(KILL_CONNECTION); set_killed(KILL_CONNECTION);
...@@ -1826,8 +1826,6 @@ void THD::disconnect() ...@@ -1826,8 +1826,6 @@ void THD::disconnect()
if (net.vio != vio) if (net.vio != vio)
vio_close(net.vio); vio_close(net.vio);
net.thd= 0; // Don't collect statistics 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, ...@@ -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). thread can see those instances (e.g. see partitioning code).
*/ */
if (!thd_table->needs_reopen()) if (!thd_table->needs_reopen())
{
signalled|= mysql_lock_abort_for_thread(this, thd_table); 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); mysql_mutex_unlock(&in_use->LOCK_thd_data);
} }
......
...@@ -3231,8 +3231,13 @@ class THD :public Statement, ...@@ -3231,8 +3231,13 @@ class THD :public Statement,
void awake(killed_state state_to_set); void awake(killed_state state_to_set);
/** Disconnect the associated communication endpoint. */ /** 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 Allows this thread to serve as a target for others to schedule Async
......
/* Copyright (c) 2000, 2017, Oracle and/or its affiliates. /* 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 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 it under the terms of the GNU General Public License as published by
...@@ -9069,6 +9069,18 @@ static ...@@ -9069,6 +9069,18 @@ static
void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) void sql_kill(THD *thd, longlong id, killed_state state, killed_type type)
{ {
uint error; 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 (!(error= kill_one_thread(thd, id, state, type)))
{ {
if (!thd->killed) if (!thd->killed)
...@@ -9078,6 +9090,11 @@ void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) ...@@ -9078,6 +9090,11 @@ void sql_kill(THD *thd, longlong id, killed_state state, killed_type type)
} }
else else
my_error(error, MYF(0), id); 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) ...@@ -9086,6 +9103,18 @@ void sql_kill_user(THD *thd, LEX_USER *user, killed_state state)
{ {
uint error; uint error;
ha_rows rows; 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))) if (!(error= kill_threads_for_user(thd, user, state, &rows)))
my_ok(thd, rows); my_ok(thd, rows);
else else
...@@ -9096,6 +9125,11 @@ void sql_kill_user(THD *thd, LEX_USER *user, killed_state state) ...@@ -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); 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 */
} }
......
/* Copyright 2008-2015 Codership Oy <http://www.codership.com> /* Copyright 2008-2021 Codership Oy <http://www.codership.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
...@@ -835,13 +835,25 @@ void wsrep_thr_init() ...@@ -835,13 +835,25 @@ void wsrep_thr_init()
DBUG_VOID_RETURN; 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) void wsrep_init_startup (bool first)
{ {
if (wsrep_init()) unireg_abort(1); if (wsrep_init()) unireg_abort(1);
wsrep_thr_lock_init( wsrep_thr_lock_init(
(wsrep_thd_is_brute_force_fun)wsrep_thd_is_BF, (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_debug, wsrep_convert_LOCK_to_trx,
(wsrep_on_fun)wsrep_on); (wsrep_on_fun)wsrep_on);
...@@ -1694,6 +1706,11 @@ static int wsrep_TOI_begin(THD *thd, char *db_, char *table_, ...@@ -1694,6 +1706,11 @@ static int wsrep_TOI_begin(THD *thd, char *db_, char *table_,
case SQLCOM_DROP_TABLE: case SQLCOM_DROP_TABLE:
buf_err= wsrep_drop_table_query(thd, &buf, &buf_len); buf_err= wsrep_drop_table_query(thd, &buf, &buf_len);
break; 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: case SQLCOM_CREATE_ROLE:
if (sp_process_definer(thd)) if (sp_process_definer(thd))
{ {
...@@ -2058,8 +2075,11 @@ bool wsrep_grant_mdl_exception(MDL_context *requestor_ctx, ...@@ -2058,8 +2075,11 @@ bool wsrep_grant_mdl_exception(MDL_context *requestor_ctx,
ticket->wsrep_report(true); 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); wsrep_abort_thd((void *) request_thd, (void *) granted_thd, 1);
mysql_mutex_unlock(&granted_thd->LOCK_thd_data);
ret= false; ret= false;
} }
} }
...@@ -2241,6 +2261,7 @@ pthread_handler_t start_wsrep_THD(void *arg) ...@@ -2241,6 +2261,7 @@ pthread_handler_t start_wsrep_THD(void *arg)
static bool abort_replicated(THD *thd) static bool abort_replicated(THD *thd)
{ {
bool ret_code= false; bool ret_code= false;
mysql_mutex_assert_owner(&thd->LOCK_thd_data);
if (thd->wsrep_query_state== QUERY_COMMITTING) if (thd->wsrep_query_state== QUERY_COMMITTING)
{ {
WSREP_DEBUG("aborting replicated trx: %llu", (ulonglong)(thd->real_id)); WSREP_DEBUG("aborting replicated trx: %llu", (ulonglong)(thd->real_id));
...@@ -2255,6 +2276,7 @@ static bool abort_replicated(THD *thd) ...@@ -2255,6 +2276,7 @@ static bool abort_replicated(THD *thd)
/**/ /**/
static inline bool is_client_connection(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); return (thd->wsrep_client_thread && thd->variables.wsrep_on);
} }
...@@ -2263,9 +2285,8 @@ static inline bool is_replaying_connection(THD *thd) ...@@ -2263,9 +2285,8 @@ static inline bool is_replaying_connection(THD *thd)
{ {
bool ret; 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; ret= (thd->wsrep_conflict_state == REPLAYING) ? true : false;
mysql_mutex_unlock(&thd->LOCK_thd_data);
return ret; return ret;
} }
...@@ -2275,9 +2296,8 @@ static inline bool is_committing_connection(THD *thd) ...@@ -2275,9 +2296,8 @@ static inline bool is_committing_connection(THD *thd)
{ {
bool ret; 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; ret= (thd->wsrep_query_state == QUERY_COMMITTING) ? true : false;
mysql_mutex_unlock(&thd->LOCK_thd_data);
return ret; return ret;
} }
...@@ -2290,13 +2310,17 @@ static bool have_client_connections() ...@@ -2290,13 +2310,17 @@ static bool have_client_connections()
I_List_iterator<THD> it(threads); I_List_iterator<THD> it(threads);
while ((tmp=it++)) 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", DBUG_PRINT("quit",("Informing thread %lld that it's time to die",
(longlong) tmp->thread_id)); (longlong) tmp->thread_id));
if (is_client_connection(tmp) && tmp->killed == KILL_CONNECTION) if (is_client_connection(tmp) && tmp->killed == KILL_CONNECTION)
{ {
(void)abort_replicated(tmp); (void)abort_replicated(tmp);
mysql_mutex_unlock(&tmp->LOCK_thd_data);
return true; return true;
} }
mysql_mutex_unlock(&tmp->LOCK_thd_data);
} }
return false; return false;
} }
...@@ -2328,14 +2352,21 @@ static my_bool have_committing_connections() ...@@ -2328,14 +2352,21 @@ static my_bool have_committing_connections()
I_List_iterator<THD> it(threads); I_List_iterator<THD> it(threads);
while ((tmp=it++)) while ((tmp=it++))
{ {
/* Protect from concurrent usage or disconnect or delete */
mysql_mutex_lock(&tmp->LOCK_thd_data);
if (!is_client_connection(tmp)) if (!is_client_connection(tmp))
{
mysql_mutex_unlock(&tmp->LOCK_thd_data);
continue; continue;
}
if (is_committing_connection(tmp)) if (is_committing_connection(tmp))
{ {
mysql_mutex_unlock(&LOCK_thread_count); mysql_mutex_unlock(&LOCK_thread_count);
mysql_mutex_unlock(&tmp->LOCK_thd_data);
return TRUE; return TRUE;
} }
mysql_mutex_unlock(&tmp->LOCK_thd_data);
} }
mysql_mutex_unlock(&LOCK_thread_count); mysql_mutex_unlock(&LOCK_thread_count);
return FALSE; return FALSE;
...@@ -2378,33 +2409,44 @@ void wsrep_close_client_connections(my_bool wait_to_end, THD *except_caller_thd) ...@@ -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", DBUG_PRINT("quit",("Informing thread %lld that it's time to die",
(longlong) tmp->thread_id)); (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. */ /* We skip slave threads & scheduler on this first loop through. */
if (!is_client_connection(tmp)) if (!is_client_connection(tmp))
{
mysql_mutex_unlock(&tmp->LOCK_thd_data);
continue; continue;
}
if (tmp == except_caller_thd) if (tmp == except_caller_thd)
{ {
DBUG_ASSERT(is_client_connection(tmp)); DBUG_ASSERT(is_client_connection(tmp));
mysql_mutex_unlock(&tmp->LOCK_thd_data);
continue; continue;
} }
if (is_replaying_connection(tmp)) if (is_replaying_connection(tmp))
{ {
tmp->set_killed(KILL_CONNECTION); tmp->set_killed(KILL_CONNECTION);
mysql_mutex_unlock(&tmp->LOCK_thd_data);
continue; continue;
} }
/* replicated transactions must be skipped */ /* replicated transactions must be skipped and aborted
with wsrep_abort_thd. */
if (abort_replicated(tmp)) if (abort_replicated(tmp))
{
mysql_mutex_unlock(&tmp->LOCK_thd_data);
continue; continue;
}
WSREP_DEBUG("closing connection %lld", (longlong) tmp->thread_id); WSREP_DEBUG("closing connection %lld", (longlong) tmp->thread_id);
/* /*
instead of wsrep_close_thread() we do now soft kill by THD::awake instead of wsrep_close_thread() we do now soft kill by
*/ THD::awake(). Here also victim needs to be protected from
mysql_mutex_lock(&tmp->LOCK_thd_data); concurrent usage or disconnect or delete.
*/
tmp->awake(KILL_CONNECTION); tmp->awake(KILL_CONNECTION);
mysql_mutex_unlock(&tmp->LOCK_thd_data); 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) ...@@ -2423,16 +2465,19 @@ void wsrep_close_client_connections(my_bool wait_to_end, THD *except_caller_thd)
I_List_iterator<THD> it2(threads); I_List_iterator<THD> it2(threads);
while ((tmp=it2++)) while ((tmp=it2++))
{ {
#ifndef __bsdi__ // Bug in BSDI kernel /* Protect from concurrent usage or disconnect or delete */
if (is_client_connection(tmp) && mysql_mutex_lock(&tmp->LOCK_thd_data);
!abort_replicated(tmp) && if (is_client_connection(tmp))
!is_replaying_connection(tmp) &&
tmp != except_caller_thd)
{ {
WSREP_INFO("killing local connection: %lld", (longlong) tmp->thread_id); if (!abort_replicated(tmp) &&
close_connection(tmp,0); !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)); DBUG_PRINT("quit",("Waiting for threads to die (count=%u)",thread_count));
...@@ -2621,7 +2666,9 @@ extern "C" void wsrep_thd_set_query_state( ...@@ -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) 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) ...@@ -2762,9 +2809,10 @@ extern "C" void wsrep_thd_awake(THD *thd, my_bool signal)
{ {
if (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); thd->awake(KILL_QUERY);
mysql_mutex_unlock(&thd->LOCK_thd_data);
} }
else else
{ {
......
/* Copyright (C) 2013 Codership Oy <info@codership.com> /* Copyright (C) 2013-2021 Codership Oy <info@codership.com>
This program is free software; you can redistribute it and/or modify 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 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) ...@@ -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) int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, my_bool signal)
{ {
THD *victim_thd = (THD *) victim_thd_ptr; THD *victim_thd= (THD *) victim_thd_ptr;
THD *bf_thd = (THD *) bf_thd_ptr; THD *bf_thd= (THD *) bf_thd_ptr;
DBUG_ENTER("wsrep_abort_thd"); DBUG_ENTER("wsrep_abort_thd");
mysql_mutex_assert_owner(&victim_thd->LOCK_thd_data);
if ( (WSREP(bf_thd) || if ( (WSREP(bf_thd) ||
( (WSREP_ON || bf_thd->variables.wsrep_OSU_method == WSREP_OSU_RSU) && ( (WSREP_ON || bf_thd->variables.wsrep_OSU_method == WSREP_OSU_RSU) &&
bf_thd->wsrep_exec_mode == TOTAL_ORDER) ) && bf_thd->wsrep_exec_mode == TOTAL_ORDER) ) &&
......
...@@ -32,7 +32,6 @@ void wsrep_create_rollbacker(); ...@@ -32,7 +32,6 @@ void wsrep_create_rollbacker();
int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr,
my_bool signal); my_bool signal);
/* /*
PA = Parallel Applying (on the slave side) PA = Parallel Applying (on the slave side)
*/ */
......
This diff is collapsed.
...@@ -233,12 +233,11 @@ innobase_casedn_str( ...@@ -233,12 +233,11 @@ innobase_casedn_str(
char* a); /*!< in/out: string to put in lower case */ char* a); /*!< in/out: string to put in lower case */
#ifdef WITH_WSREP #ifdef WITH_WSREP
UNIV_INTERN
void void
wsrep_innobase_kill_one_trx(MYSQL_THD const thd_ptr, wsrep_innobase_kill_one_trx(MYSQL_THD const thd_ptr,
const trx_t * const bf_trx, const trx_t * const bf_trx,
trx_t *victim_trx, trx_t *victim_trx,
ibool signal); my_bool signal);
int wsrep_innobase_mysql_sort(int mysql_type, uint charset_number, int wsrep_innobase_mysql_sort(int mysql_type, uint charset_number,
unsigned char* str, unsigned int str_length, unsigned char* str, unsigned int str_length,
unsigned int buf_length); unsigned int buf_length);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment