1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-41293: FederatedX: connection state after a net_read_timeout

Problem:
When reading the reply of the remote server times out, the client
library compiled into the server returns without closing the connection
and without setting a client error. FederatedX took the raw
ER_NET_READ_INTERRUPTED for an error of the remote server and went on
using a connection that still had the reply of the failed statement to
arrive. As a result:
* the next statement on that connection failed in various ways, either
  with ER_QUERY_ON_FOREIGN_DATA_SOURCE ("Server has gone away") or with
  an error packet that mysqltest took for a malformed packet;
* inside a transaction, the following statements continued on a new
  remote session in autocommit mode, so they were committed on the
  remote server one by one even though the local transaction was later
  rolled back. The savepoints of the lost session were also still
  remembered by FederatedX.
The same happened when the connection was lost while reading the reply.

Fix:
* After a timeout FederatedX closes the connection, so that the next
  statement opens a new one. The client still gets the error of the
  timeout, ER_NET_READ_INTERRUPTED.
* Whenever the connection is gone after a failed statement, timeout or
  not, the state of the lost session (autocommit mode, savepoints) is
  forgotten and the local transaction is marked to be rolled back, so a
  new connection starts the transaction again. The closed connection is
  also freed, which was leaked before.
* Errors of the client library are reported as
  ER_QUERY_ON_FOREIGN_DATA_SOURCE by ha_federatedx::info() as well, the
  same way as by the pushed down multi-table UPDATE/DELETE.
* The error state of ha_federatedx, read by info(), is initialized.

Test: federatedx_pushdown_upd_del covers the timeout of a pushed down
multi-table UPDATE and DELETE and of reading a result, the statements
after it, the same inside a transaction, a connection killed in the
middle of a statement, client library errors in info(), and that an
error of the remote server which merely has the code of
ER_NET_READ_INTERRUPTED is not taken for a timeout.
This commit is contained in:
bsrikanth-mariadb
2026-10-06 21:56:08 +05:30
parent 2db22df805
commit 6cfe62b05a
7 changed files with 672 additions and 19 deletions

View File

@@ -0,0 +1,27 @@
# Kill the statement matching the LIKE pattern $nrt_pattern (an SQL
# expression) on the remote server, and wait until it is gone. The remote
# server may have ended it already, e.g. when it noticed that the client
# closed the connection. If the statement is just being sent and may not be
# there yet, set $nrt_wait_start to wait for it first.
connection slave;
if ($nrt_wait_start)
{
let $wait_condition=
SELECT COUNT(*) >= 1 FROM information_schema.processlist
WHERE info LIKE $nrt_pattern AND id != CONNECTION_ID();
--source include/wait_condition.inc
}
let $nrt_id= `SELECT IFNULL(MAX(id), 0) FROM information_schema.processlist
WHERE info LIKE $nrt_pattern AND id != CONNECTION_ID()`;
if ($nrt_id)
{
--disable_query_log
eval KILL $nrt_id;
--enable_query_log
}
let $wait_condition=
SELECT COUNT(*) = 0 FROM information_schema.processlist
WHERE info LIKE $nrt_pattern AND id != CONNECTION_ID();
--source include/wait_condition.inc
let $nrt_wait_start= 0;

View File

@@ -0,0 +1,58 @@
# net_read_timeout of the pushed down multi-table statement $nrt_stmt, which
# the remote server executes as a statement matching $nrt_pattern.
# $nrt_after is run after the timeout, without and with pushdown.
--echo # Hold the table lock on the remote server, so the pushed down statement times out
connection slave;
TRUNCATE TABLE mysql.general_log;
LOCK TABLES federated_nrt.s1 WRITE;
connection master;
--enable_info
set global federated_pushdown=ON;
--error ER_NET_READ_INTERRUPTED
eval $nrt_stmt;
--disable_info
--echo # Kill the abandoned statement on the remote server and release the lock
--source federatedx_nrt_kill.inc
UNLOCK TABLES;
--echo # Run the same kind of statement without and with pushdown after the timeout
connection master;
--enable_info
set global federated_pushdown=OFF;
eval $nrt_after;
set global federated_pushdown=ON;
eval $nrt_after;
--disable_info
set global federated_pushdown= @old_federated_pushdown;
--echo # Repeat the timeout inside a transaction
connection slave;
TRUNCATE TABLE mysql.general_log;
LOCK TABLES federated_nrt.s1 WRITE;
connection master;
set global federated_pushdown=ON;
SET autocommit=0;
--error ER_NET_READ_INTERRUPTED
eval $nrt_stmt;
SELECT @@in_transaction;
--echo # Kill the abandoned statement and release the lock
--source federatedx_nrt_kill.inc
UNLOCK TABLES;
--echo # Statements after the timeout, then end the transaction
connection master;
INSERT INTO federated_nrt.s1 VALUES (0);
ROLLBACK;
SET autocommit=1;
set global federated_pushdown= @old_federated_pushdown;
--echo # Statements the remote server received for the new connection
connection slave;
SELECT argument FROM mysql.general_log
WHERE argument IN ('SET AUTOCOMMIT=0', 'ROLLBACK') OR argument LIKE 'INSERT %'
ORDER BY event_time;
DELETE FROM federated_nrt.s1 WHERE a=0;
SELECT COUNT(*) FROM federated_nrt.s1;

View File

@@ -1050,6 +1050,294 @@ a c
DROP VIEW v4, v3, v2, v1;
connection master;
set global federated_pushdown=0;
#
# MDEV-41293: FederatedX: connection state after a net_read_timeout
#
# Separate database on both servers, so that a new connection is opened
connection master;
CREATE DATABASE federated_nrt;
connection slave;
CREATE DATABASE federated_nrt;
create table federated_nrt.s1 (a int);
connection master;
DROP TABLE IF EXISTS federated_nrt.s1;
Warnings:
Note 1051 Unknown table 'federated_nrt.s1'
# Short read timeout for the new connection to the remote server
set global net_read_timeout=2;
CREATE TABLE federated_nrt.s1 (a int)
ENGINE="FEDERATED" DEFAULT CHARSET=latin1
CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated_nrt/s1';
# Fill the table
insert into federated_nrt.s1 select seq from seq_1_to_100;
# Log the statements received by the remote server
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
# Pushed down multi-table UPDATE
connection master;
set @old_federated_pushdown= @@global.federated_pushdown;
# Hold the table lock on the remote server, so the pushed down statement times out
connection slave;
TRUNCATE TABLE mysql.general_log;
LOCK TABLES federated_nrt.s1 WRITE;
connection master;
set global federated_pushdown=ON;
affected rows: 0
UPDATE federated_nrt.s1 JOIN federated_nrt.s1 AS OUTR2 SET OUTR2.a = 1 where s1.a > 0;
ERROR 08S01: Got timeout reading communication packets
# Kill the abandoned statement on the remote server and release the lock
connection slave;
UNLOCK TABLES;
# Run the same kind of statement without and with pushdown after the timeout
connection master;
set global federated_pushdown=OFF;
affected rows: 0
UPDATE federated_nrt.s1 JOIN federated_nrt.s1 AS OUTR2 SET OUTR2.a = 1 where s1.a < 2 AND OUTR2.a < 2;
affected rows: 0
info: Rows matched: 1 Changed: 0 Warnings: 0
set global federated_pushdown=ON;
affected rows: 0
UPDATE federated_nrt.s1 JOIN federated_nrt.s1 AS OUTR2 SET OUTR2.a = 1 where s1.a < 2 AND OUTR2.a < 2;
affected rows: 0
info: Rows matched: 1 Changed: 0 Warnings: 0
set global federated_pushdown= @old_federated_pushdown;
# Repeat the timeout inside a transaction
connection slave;
TRUNCATE TABLE mysql.general_log;
LOCK TABLES federated_nrt.s1 WRITE;
connection master;
set global federated_pushdown=ON;
SET autocommit=0;
UPDATE federated_nrt.s1 JOIN federated_nrt.s1 AS OUTR2 SET OUTR2.a = 1 where s1.a > 0;
ERROR 08S01: Got timeout reading communication packets
SELECT @@in_transaction;
@@in_transaction
0
# Kill the abandoned statement and release the lock
connection slave;
UNLOCK TABLES;
# Statements after the timeout, then end the transaction
connection master;
INSERT INTO federated_nrt.s1 VALUES (0);
ROLLBACK;
SET autocommit=1;
set global federated_pushdown= @old_federated_pushdown;
# Statements the remote server received for the new connection
connection slave;
SELECT argument FROM mysql.general_log
WHERE argument IN ('SET AUTOCOMMIT=0', 'ROLLBACK') OR argument LIKE 'INSERT %'
ORDER BY event_time;
argument
SET AUTOCOMMIT=0
SET AUTOCOMMIT=0
INSERT INTO `s1` (`a`) VALUES (0)
ROLLBACK
DELETE FROM federated_nrt.s1 WHERE a=0;
SELECT COUNT(*) FROM federated_nrt.s1;
COUNT(*)
100
# Pushed down multi-table DELETE
# Hold the table lock on the remote server, so the pushed down statement times out
connection slave;
TRUNCATE TABLE mysql.general_log;
LOCK TABLES federated_nrt.s1 WRITE;
connection master;
set global federated_pushdown=ON;
affected rows: 0
DELETE t FROM federated_nrt.s1 AS t JOIN federated_nrt.s1 AS OUTR2 where t.a > 0;
ERROR 08S01: Got timeout reading communication packets
# Kill the abandoned statement on the remote server and release the lock
connection slave;
UNLOCK TABLES;
# Run the same kind of statement without and with pushdown after the timeout
connection master;
set global federated_pushdown=OFF;
affected rows: 0
DELETE t FROM federated_nrt.s1 AS t JOIN federated_nrt.s1 AS OUTR2 where t.a = 100 AND OUTR2.a = 100;
affected rows: 1
set global federated_pushdown=ON;
affected rows: 0
DELETE t FROM federated_nrt.s1 AS t JOIN federated_nrt.s1 AS OUTR2 where t.a = 100 AND OUTR2.a = 100;
affected rows: 0
set global federated_pushdown= @old_federated_pushdown;
# Repeat the timeout inside a transaction
connection slave;
TRUNCATE TABLE mysql.general_log;
LOCK TABLES federated_nrt.s1 WRITE;
connection master;
set global federated_pushdown=ON;
SET autocommit=0;
DELETE t FROM federated_nrt.s1 AS t JOIN federated_nrt.s1 AS OUTR2 where t.a > 0;
ERROR 08S01: Got timeout reading communication packets
SELECT @@in_transaction;
@@in_transaction
0
# Kill the abandoned statement and release the lock
connection slave;
UNLOCK TABLES;
# Statements after the timeout, then end the transaction
connection master;
INSERT INTO federated_nrt.s1 VALUES (0);
ROLLBACK;
SET autocommit=1;
set global federated_pushdown= @old_federated_pushdown;
# Statements the remote server received for the new connection
connection slave;
SELECT argument FROM mysql.general_log
WHERE argument IN ('SET AUTOCOMMIT=0', 'ROLLBACK') OR argument LIKE 'INSERT %'
ORDER BY event_time;
argument
SET AUTOCOMMIT=0
SET AUTOCOMMIT=0
INSERT INTO `s1` (`a`) VALUES (0)
ROLLBACK
DELETE FROM federated_nrt.s1 WHERE a=0;
SELECT COUNT(*) FROM federated_nrt.s1;
COUNT(*)
99
# The remote connection is killed while a transaction is open
connection slave;
TRUNCATE TABLE mysql.general_log;
connection master;
set global federated_pushdown=ON;
SET autocommit=0;
INSERT INTO federated_nrt.s1 VALUES (-1);
connection slave;
LOCK TABLES federated_nrt.s1 WRITE;
connection master;
UPDATE federated_nrt.s1 JOIN federated_nrt.s1 AS OUTR2 SET OUTR2.a = 1 where s1.a > 0;
connection slave;
UNLOCK TABLES;
connection master;
ERROR 08S01: Got an error reading communication packets
SELECT @@in_transaction;
@@in_transaction
0
INSERT INTO federated_nrt.s1 VALUES (0);
ROLLBACK;
SET autocommit=1;
set global federated_pushdown= @old_federated_pushdown;
connection slave;
SELECT argument FROM mysql.general_log
WHERE argument IN ('SET AUTOCOMMIT=0', 'ROLLBACK') OR argument LIKE 'INSERT %'
ORDER BY event_time;
argument
INSERT INTO `s1` (`a`) VALUES (-1)
SET AUTOCOMMIT=0
INSERT INTO `s1` (`a`) VALUES (0)
ROLLBACK
DELETE FROM federated_nrt.s1 WHERE a=0;
# The timeout while the rows of a result are read (store_result())
# The remote server flushes the first rows and then stalls in a row,
# so that the result is not complete when the query returns
connection slave;
create table federated_nrt.t_slow (a int);
insert into federated_nrt.t_slow select seq from seq_1_to_100;
create view federated_nrt.v_slow as
select a, repeat('x', 1000) as c, sleep(if(a=100, 5, 0)) as s
from federated_nrt.t_slow;
connection master;
CREATE TABLE federated_nrt.f_slow (a int, c varchar(1000), s int)
ENGINE="FEDERATED" DEFAULT CHARSET=latin1
CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated_nrt/v_slow';
select * from federated_nrt.f_slow;
ERROR 08S01: Got timeout reading communication packets
# Kill the abandoned SELECT on the remote server
connection slave;
connection master;
# The connection is usable after it
select * from federated_nrt.s1 where a=0;
a
DROP TABLE federated_nrt.f_slow;
connection slave;
DROP VIEW federated_nrt.v_slow;
DROP TABLE federated_nrt.t_slow;
# The timeout of the query that sets the time zone of a new connection
# The remote server runs init_connect after it accepted the connection,
# for the users that do not have the SUPER privilege
connection slave;
CREATE USER nrt_u@127.0.0.1;
GRANT SELECT ON federated_nrt.* TO nrt_u@127.0.0.1;
connection master;
CREATE TABLE federated_nrt.f_init (a int)
ENGINE="FEDERATED" DEFAULT CHARSET=latin1
CONNECTION='mysql://nrt_u@127.0.0.1:SLAVE_PORT/federated_nrt/s1';
connection slave;
set @old_init_connect= @@global.init_connect;
set global init_connect= 'do sleep(5)';
connection master;
select * from federated_nrt.f_init;
ERROR 08S01: Got timeout reading communication packets
connection slave;
set global init_connect= @old_init_connect;
# Wait for the abandoned connection to go
connection master;
# A new connection works once the remote server accepts it quietly
select count(*) > 0 from federated_nrt.f_init;
count(*) > 0
1
DROP TABLE federated_nrt.f_init;
connection slave;
DROP USER nrt_u@127.0.0.1;
# Errors of the client library in ha_federatedx::info()
connection master;
CREATE SERVER srv_nrt FOREIGN DATA WRAPPER mysql
OPTIONS (HOST '127.0.0.1', DATABASE 'federated_nrt', USER 'root', PORT SLAVE_PORT);
CREATE TABLE federated_nrt.f_gone (a int)
ENGINE="FEDERATED" DEFAULT CHARSET=latin1 CONNECTION='srv_nrt/s1';
ALTER SERVER srv_nrt OPTIONS (PORT 1);
FLUSH TABLES;
select * from federated_nrt.f_gone;
ERROR HY000: There was a problem processing the query on the foreign data source. Data source error: ...
DROP SERVER srv_nrt;
DROP TABLE federated_nrt.f_gone;
# An error packet of the remote server with the code of
# ER_NET_READ_INTERRUPTED is not a net_read_timeout: the connection and
# the transaction on the remote server must stay
connection slave;
CREATE DATABASE f2db;
CREATE TABLE f2db.s1 (a INT) ENGINE=InnoDB;
# The remote server rejects a=2 with an error packet whose code is 1159
CREATE TRIGGER f2db.tr BEFORE INSERT ON f2db.s1 FOR EACH ROW
BEGIN
IF NEW.a = 2 THEN
SIGNAL SQLSTATE 'HY000' SET MYSQL_ERRNO = 1159,
MESSAGE_TEXT = 'error raised by remote trigger';
END IF;
END|
connection master;
CREATE DATABASE f2db;
CREATE TABLE f2db.s1 (a INT)
ENGINE=FEDERATED CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/f2db/s1';
BEGIN;
INSERT INTO f2db.s1 VALUES (1);
INSERT INTO f2db.s1 VALUES (2);
ERROR HY000: Got error 10000 'Error on remote system: 1159: error raised by remote trigger' from FEDERATED
SHOW WARNINGS;
Level Code Message
Error 1296 Got error 10000 'Error on remote system: 1159: error raised by remote trigger' from FEDERATED
SELECT @@in_transaction;
@@in_transaction
1
INSERT INTO f2db.s1 VALUES (3);
COMMIT;
connection slave;
# Rows on the remote server
SELECT a FROM f2db.s1 ORDER BY a;
a
1
3
connection master;
DROP DATABASE f2db;
connection slave;
DROP DATABASE f2db;
# Cleanup
connection master;
connection slave;
DROP DATABASE federated_nrt;
connection master;
DROP DATABASE federated_nrt;
DROP PROCEDURE fill_tables;
DROP TABLE federated.t1, federated.t2, federated.t3;
connection slave;

View File

@@ -17,6 +17,8 @@
--source have_federatedx.inc
--source include/federated.inc
--source include/no_valgrind_without_big.inc
--source include/have_sequence.inc
--source include/have_innodb.inc
connection default;
ALTER DATABASE federated CHARACTER SET latin1;
@@ -777,6 +779,205 @@ DROP VIEW v4, v3, v2, v1;
connection master;
set global federated_pushdown=0;
--echo #
--echo # MDEV-41293: FederatedX: connection state after a net_read_timeout
--echo #
--echo # Separate database on both servers, so that a new connection is opened
connection master;
CREATE DATABASE federated_nrt;
connection slave;
CREATE DATABASE federated_nrt;
create table federated_nrt.s1 (a int);
connection master;
DROP TABLE IF EXISTS federated_nrt.s1;
--echo # Short read timeout for the new connection to the remote server
let $old_net_read_timeout= `SELECT @@global.net_read_timeout`;
set global net_read_timeout=2;
--replace_result $SLAVE_MYPORT SLAVE_PORT
eval
CREATE TABLE federated_nrt.s1 (a int)
ENGINE="FEDERATED" DEFAULT CHARSET=latin1
CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated_nrt/s1';
--echo # Fill the table
insert into federated_nrt.s1 select seq from seq_1_to_100;
--echo # Log the statements received by the remote server
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
--echo # Pushed down multi-table UPDATE
let $nrt_stmt= UPDATE federated_nrt.s1 JOIN federated_nrt.s1 AS OUTR2 SET OUTR2.a = 1 where s1.a > 0;
let $nrt_pattern= 'update %';
let $nrt_after= UPDATE federated_nrt.s1 JOIN federated_nrt.s1 AS OUTR2 SET OUTR2.a = 1 where s1.a < 2 AND OUTR2.a < 2;
connection master;
set @old_federated_pushdown= @@global.federated_pushdown;
--source federatedx_nrt_timeout.inc
--echo # Pushed down multi-table DELETE
let $nrt_stmt= DELETE t FROM federated_nrt.s1 AS t JOIN federated_nrt.s1 AS OUTR2 where t.a > 0;
let $nrt_pattern= 'delete %';
let $nrt_after= DELETE t FROM federated_nrt.s1 AS t JOIN federated_nrt.s1 AS OUTR2 where t.a = 100 AND OUTR2.a = 100;
--source federatedx_nrt_timeout.inc
--echo # The remote connection is killed while a transaction is open
connection slave;
TRUNCATE TABLE mysql.general_log;
connection master;
set global federated_pushdown=ON;
SET autocommit=0;
INSERT INTO federated_nrt.s1 VALUES (-1);
connection slave;
LOCK TABLES federated_nrt.s1 WRITE;
connection master;
send UPDATE federated_nrt.s1 JOIN federated_nrt.s1 AS OUTR2 SET OUTR2.a = 1 where s1.a > 0;
let $nrt_pattern= 'update %';
let $nrt_wait_start= 1;
--source federatedx_nrt_kill.inc
UNLOCK TABLES;
connection master;
--disable_abort_on_error
reap;
--enable_abort_on_error
SELECT @@in_transaction;
INSERT INTO federated_nrt.s1 VALUES (0);
ROLLBACK;
SET autocommit=1;
set global federated_pushdown= @old_federated_pushdown;
connection slave;
SELECT argument FROM mysql.general_log
WHERE argument IN ('SET AUTOCOMMIT=0', 'ROLLBACK') OR argument LIKE 'INSERT %'
ORDER BY event_time;
DELETE FROM federated_nrt.s1 WHERE a=0;
--echo # The timeout while the rows of a result are read (store_result())
--echo # The remote server flushes the first rows and then stalls in a row,
--echo # so that the result is not complete when the query returns
connection slave;
create table federated_nrt.t_slow (a int);
insert into federated_nrt.t_slow select seq from seq_1_to_100;
create view federated_nrt.v_slow as
select a, repeat('x', 1000) as c, sleep(if(a=100, 5, 0)) as s
from federated_nrt.t_slow;
connection master;
--replace_result $SLAVE_MYPORT SLAVE_PORT
eval CREATE TABLE federated_nrt.f_slow (a int, c varchar(1000), s int)
ENGINE="FEDERATED" DEFAULT CHARSET=latin1
CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated_nrt/v_slow';
--error ER_NET_READ_INTERRUPTED
select * from federated_nrt.f_slow;
--echo # Kill the abandoned SELECT on the remote server
let $nrt_pattern= CONCAT('SELECT ', CHAR(96), '%');
--source federatedx_nrt_kill.inc
connection master;
--echo # The connection is usable after it
select * from federated_nrt.s1 where a=0;
DROP TABLE federated_nrt.f_slow;
connection slave;
DROP VIEW federated_nrt.v_slow;
DROP TABLE federated_nrt.t_slow;
--echo # The timeout of the query that sets the time zone of a new connection
--echo # The remote server runs init_connect after it accepted the connection,
--echo # for the users that do not have the SUPER privilege
connection slave;
CREATE USER nrt_u@127.0.0.1;
GRANT SELECT ON federated_nrt.* TO nrt_u@127.0.0.1;
connection master;
--replace_result $SLAVE_MYPORT SLAVE_PORT
eval CREATE TABLE federated_nrt.f_init (a int)
ENGINE="FEDERATED" DEFAULT CHARSET=latin1
CONNECTION='mysql://nrt_u@127.0.0.1:$SLAVE_MYPORT/federated_nrt/s1';
connection slave;
set @old_init_connect= @@global.init_connect;
set global init_connect= 'do sleep(5)';
connection master;
--error ER_NET_READ_INTERRUPTED
select * from federated_nrt.f_init;
connection slave;
set global init_connect= @old_init_connect;
--echo # Wait for the abandoned connection to go
let $wait_condition=
SELECT COUNT(*) = 0 FROM information_schema.processlist
WHERE user = 'nrt_u';
--source include/wait_condition.inc
connection master;
--echo # A new connection works once the remote server accepts it quietly
select count(*) > 0 from federated_nrt.f_init;
DROP TABLE federated_nrt.f_init;
connection slave;
--disable_warnings
DROP USER nrt_u@127.0.0.1;
--enable_warnings
--echo # Errors of the client library in ha_federatedx::info()
connection master;
--replace_result $SLAVE_MYPORT SLAVE_PORT
eval CREATE SERVER srv_nrt FOREIGN DATA WRAPPER mysql
OPTIONS (HOST '127.0.0.1', DATABASE 'federated_nrt', USER 'root', PORT $SLAVE_MYPORT);
CREATE TABLE federated_nrt.f_gone (a int)
ENGINE="FEDERATED" DEFAULT CHARSET=latin1 CONNECTION='srv_nrt/s1';
ALTER SERVER srv_nrt OPTIONS (PORT 1);
FLUSH TABLES;
--replace_regex /Data source error: .*/Data source error: .../
--error ER_QUERY_ON_FOREIGN_DATA_SOURCE
select * from federated_nrt.f_gone;
DROP SERVER srv_nrt;
DROP TABLE federated_nrt.f_gone;
--echo # An error packet of the remote server with the code of
--echo # ER_NET_READ_INTERRUPTED is not a net_read_timeout: the connection and
--echo # the transaction on the remote server must stay
connection slave;
CREATE DATABASE f2db;
CREATE TABLE f2db.s1 (a INT) ENGINE=InnoDB;
--echo # The remote server rejects a=2 with an error packet whose code is 1159
delimiter |;
CREATE TRIGGER f2db.tr BEFORE INSERT ON f2db.s1 FOR EACH ROW
BEGIN
IF NEW.a = 2 THEN
SIGNAL SQLSTATE 'HY000' SET MYSQL_ERRNO = 1159,
MESSAGE_TEXT = 'error raised by remote trigger';
END IF;
END|
delimiter ;|
connection master;
CREATE DATABASE f2db;
--replace_result $SLAVE_MYPORT SLAVE_PORT
eval CREATE TABLE f2db.s1 (a INT)
ENGINE=FEDERATED CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/f2db/s1';
BEGIN;
INSERT INTO f2db.s1 VALUES (1);
--error ER_GET_ERRMSG
INSERT INTO f2db.s1 VALUES (2);
SHOW WARNINGS;
SELECT @@in_transaction;
INSERT INTO f2db.s1 VALUES (3);
COMMIT;
connection slave;
--echo # Rows on the remote server
SELECT a FROM f2db.s1 ORDER BY a;
connection master;
DROP DATABASE f2db;
connection slave;
DROP DATABASE f2db;
--echo # Cleanup
connection master;
--disable_query_log
eval set global net_read_timeout= $old_net_read_timeout;
--enable_query_log
connection slave;
DROP DATABASE federated_nrt;
connection master;
DROP DATABASE federated_nrt;
DROP PROCEDURE fill_tables;
DROP TABLE federated.t1, federated.t2, federated.t3;

View File

@@ -38,6 +38,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "m_string.h"
#include "mysqld_error.h"
#include "sql_servers.h"
#include <errmsg.h>
#define SAVEPOINT_REALIZED 1
#define SAVEPOINT_RESTRICT 2
@@ -64,8 +65,10 @@ class federatedx_io_mysql :public federatedx_io
bool requested_autocommit;
bool actual_autocommit;
int actual_query(const char *buffer, size_t length);
int actual_query(const char *buffer, size_t length,
bool ending_transaction= false);
bool test_all_restrict() const;
void connection_lost(bool ending_transaction);
public:
federatedx_io_mysql(FEDERATEDX_SERVER *);
~federatedx_io_mysql() override;
@@ -170,7 +173,7 @@ int federatedx_io_mysql::commit()
int error= 0;
DBUG_ENTER("federatedx_io_mysql::commit");
if (!actual_autocommit && (error= actual_query("COMMIT", 6)))
if (!actual_autocommit && (error= actual_query("COMMIT", 6, true)))
rollback();
reset();
@@ -184,7 +187,7 @@ int federatedx_io_mysql::rollback()
DBUG_ENTER("federatedx_io_mysql::rollback");
if (!actual_autocommit)
error= actual_query("ROLLBACK", 8);
error= actual_query("ROLLBACK", 8, true);
else
error= ER_WARNING_NOT_COMPLETE_ROLLBACK;
@@ -418,7 +421,64 @@ int federatedx_io_mysql::query(const char *buffer, size_t length)
}
int federatedx_io_mysql::actual_query(const char *buffer, size_t length)
/*
Called after a failed query or a failed read of its result. If the
connection is gone, forget the state of its session. The next query opens
a new session in autocommit mode, so query() has to send SET AUTOCOMMIT=0
to it again, or the rest of the transaction would be committed on the
remote server statement by statement.
The client library compiled into the server returns from a timeout of
reading the remote reply (net_read_timeout) without closing the connection,
so the reply to the query is still to arrive. Close it, so that the next
query does not read a stale reply. mysql_close() rather than end_server()
is needed, because the next query initializes the MYSQL structure from
scratch (see actual_query()), which would lose everything the closed
connection has allocated. The timeout is already reported to the user by
the network layer (ER_NET_READ_INTERRUPTED); CR_SERVER_LOST is only what
the error of this io object says.
@param ending_transaction the failed query is the COMMIT or ROLLBACK of the
transaction. Its error is returned to the
caller, so the transaction is not marked for
rollback here.
*/
void federatedx_io_mysql::connection_lost(bool ending_transaction)
{
void *thd= mysql.net.thd;
const bool in_transaction= !actual_autocommit;
/* net.error == 2: reading failed; an error packet of the server with the
same code (via SIGNAL) leaves it alone */
const bool timeout= mysql.net.vio && mysql.net.error == 2 &&
mysql.net.last_errno == ER_NET_READ_INTERRUPTED;
if (mysql.net.vio && !timeout)
return;
/* The library closed the connection by end_server(), which the next
mysql_init() of the structure would leak. Keep its error. */
uint last_errno= timeout ? CR_SERVER_LOST : mysql.net.last_errno;
char sqlstate[SQLSTATE_LENGTH + 1];
char last_error[MYSQL_ERRMSG_SIZE];
strmake_buf(sqlstate, timeout ? "HY000" : mysql.net.sqlstate);
strmake_buf(last_error, timeout ? "Lost connection to server during query"
: mysql.net.last_error);
mysql_close(&mysql);
/* set_mysql_error() is not exported to plugins on Windows */
mysql.net.last_errno= last_errno;
strmov(mysql.net.sqlstate, sqlstate);
strmov(mysql.net.last_error, last_error);
reset();
actual_autocommit= TRUE;
if (in_transaction && !ending_transaction && thd)
static_cast<THD *>(thd)->mark_transaction_to_rollback(true);
}
int federatedx_io_mysql::actual_query(const char *buffer, size_t length,
bool ending_transaction)
{
int error;
DBUG_ENTER("federatedx_io_mysql::actual_query");
@@ -447,13 +507,19 @@ int federatedx_io_mysql::actual_query(const char *buffer, size_t length)
DBUG_RETURN(ER_CONNECT_TO_FOREIGN_DATA_SOURCE);
if ((error= mysql_real_query(&mysql, STRING_WITH_LEN("set time_zone='+00:00'"))))
{
connection_lost(false);
DBUG_RETURN(error);
}
mysql.reconnect= 1;
}
error= mysql_real_query(&mysql, buffer, (ulong)length);
if (error)
connection_lost(ending_transaction);
DBUG_RETURN(error);
}
@@ -517,7 +583,8 @@ FEDERATEDX_IO_RESULT *federatedx_io_mysql::store_result()
FEDERATEDX_IO_RESULT *result;
DBUG_ENTER("federatedx_io_mysql::store_result");
result= (FEDERATEDX_IO_RESULT *) mysql_store_result(&mysql);
if (!(result= (FEDERATEDX_IO_RESULT *) mysql_store_result(&mysql)))
connection_lost(false);
DBUG_RETURN(result);
}

View File

@@ -15,8 +15,6 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/* !!! For inclusion into ha_federatedx.cc */
/* For CR_MIN_ERROR/CR_MAX_ERROR, the error code range of the client library */
#include <errmsg.h>
/*
This is a quick a dirty implemention of the derived_handler and select_handler
@@ -491,10 +489,8 @@ int ha_federatedx_multi_upddel_handler::batch_update_delete(
server error codes and are reported as a foreign data source failure.
*/
const int remote_errno= (*iop)->error_code();
if (remote_errno < CR_MIN_ERROR || remote_errno > CR_MAX_ERROR)
if (!report_client_library_error(remote_errno, (*iop)->error_str()))
my_message(remote_errno, (*iop)->error_str(), MYF(0));
else
my_error(ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), (*iop)->error_str());
rc= HA_FEDERATEDX_ERROR_WITH_REMOTE_SYSTEM;
}
else

View File

@@ -318,6 +318,25 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "sql_show.h" // append_identifier()
#include "tztime.h" // my_tz_find()
#include "sql_select.h"
#include <errmsg.h> // CR_MIN_ERROR, CR_MAX_ERROR
/*
Only a server error code can be sent to the client as is. The errors of the
client library (e.g. CR_SERVER_GONE_ERROR) are in the range reserved for
the client side, and a libmariadb based client reports such a code in an
error packet as CR_MALFORMED_PACKET. They are to be reported as a failure
of the foreign data source instead.
@retval true err was an error of the client library and has been reported
@retval false err is a server error code, the caller reports it
*/
static bool report_client_library_error(int err, const char *errmsg)
{
if (err < CR_MIN_ERROR || err > CR_MAX_ERROR)
return false;
my_error(ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), errmsg);
return true;
}
#ifdef I_AM_PARANOID
#define MIN_PORT 1023
@@ -3144,15 +3163,12 @@ int ha_federatedx::info(uint flag)
DBUG_RETURN(0);
error:
if (iop && *iop)
/* iop is set: the only way here is a failed table_metadata() on it */
{
my_printf_error((*iop)->error_code(), "Received error: %d : %s", MYF(0),
(*iop)->error_code(), (*iop)->error_str());
}
else if (remote_error_number != -1 /* error already reported */)
{
error_code= remote_error_number;
my_error(error_code, MYF(0), ER_THD(thd, error_code));
const int remote_errno= (*iop)->error_code();
if (!report_client_library_error(remote_errno, (*iop)->error_str()))
my_printf_error(remote_errno, "Received error: %d : %s", MYF(0),
remote_errno, (*iop)->error_str());
}
fail:
tmp_txn->release(&tmp_io);