Skip to content

MDEV-41293: FederatedX: connection state after a net_read_timeout - #5825

Open
bsrikanth-mariadb wants to merge 1 commit into
13.2-MDEV-39226-direct-multi_table-update-deletefrom
13.2-MDEV-41293-multi-table-upd-del-savepoint-assertion
Open

bsrikanth-mariadb wants to merge 1 commit into
13.2-MDEV-39226-direct-multi_table-update-deletefrom
13.2-MDEV-41293-multi-table-upd-del-savepoint-assertion

Conversation

@bsrikanth-mariadb

@bsrikanth-mariadb bsrikanth-mariadb commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

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.

@bsrikanth-mariadb
bsrikanth-mariadb force-pushed the 13.2-MDEV-41293-multi-table-upd-del-savepoint-assertion branch 2 times, most recently from f5a3759 to 706f8ee Compare October 12, 2026 05:13

@DaveGosselin-MariaDB DaveGosselin-MariaDB left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following MTR test case shows a regression introduced by this patch but which works correctly on the parent commit. To summarize, the test inserts 1, 2 and 3 into a FederatedX table inside one local transaction and then commits. A trigger on the remote table rejects a=2 with an error whose code, 1159, is the same as a read timeout's error code. On the parent commit, the failed INSERT keeps the remote error and the transaction, and a=1 and a=3 are committed. With this patch, close_on_net_timeout() takes that error for a local read timeout, closes the healthy connection, and rolls back the transaction, so only a=3 makes it to the remote table. The parent commit does have a timeout bug, fixed by this patch. The trigger test never times out, though, so it apparently doesn't reach that path.

--source have_federatedx.inc
--source include/federated.inc
--source include/have_innodb.inc

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://[email protected]:$SLAVE_MYPORT/f2db/s1';

BEGIN;
INSERT INTO f2db.s1 VALUES (1);
--error 1159,2013,1430,1296
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;
connection default;
source include/federated_cleanup.inc;

Comment on lines +3162 to 3177
const int remote_errno= (*iop)->error_code();
if (is_client_library_errno(remote_errno))
my_error(ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0),
(*iop)->error_str());
else
my_printf_error(remote_errno, "Received error: %d : %s", MYF(0),
remote_errno, (*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));
if (is_client_library_errno(error_code))
my_error(ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), remote_error_buf);
else
my_error(error_code, MYF(0), ER_THD(thd, error_code));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new code is not exercised by the tests modified in this same commit.


result= (FEDERATEDX_IO_RESULT *) mysql_store_result(&mysql);
if (!(result= (FEDERATEDX_IO_RESULT *) mysql_store_result(&mysql)))
close_on_net_timeout();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably fine, because we expect to cleanup and close down connections on such a failure, but this particular path isn't exercised by the tests modified on this commit.

@bsrikanth-mariadb
bsrikanth-mariadb force-pushed the 13.2-MDEV-41293-multi-table-upd-del-savepoint-assertion branch from 706f8ee to 38c3ce2 Compare October 22, 2026 08:48
@bsrikanth-mariadb
bsrikanth-mariadb marked this pull request as draft October 22, 2026 08:48
@bsrikanth-mariadb
bsrikanth-mariadb force-pushed the 13.2-MDEV-41293-multi-table-upd-del-savepoint-assertion branch 2 times, most recently from fc1957b to ae3435e Compare October 22, 2026 12:48
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.
@bsrikanth-mariadb
bsrikanth-mariadb force-pushed the 13.2-MDEV-41293-multi-table-upd-del-savepoint-assertion branch from ae3435e to a1250cd Compare October 22, 2026 13:47
@bsrikanth-mariadb

Copy link
Copy Markdown
Contributor Author

The following MTR test case shows a regression introduced by this patch but which works correctly on the parent commit. To summarize, the test inserts 1, 2 and 3 into a FederatedX table inside one local transaction and then commits. A trigger on the remote table rejects a=2 with an error whose code, 1159, is the same as a read timeout's error code. On the parent commit, the failed INSERT keeps the remote error and the transaction, and a=1 and a=3 are committed. With this patch, close_on_net_timeout() takes that error for a local read timeout, closes the healthy connection, and rolls back the transaction, so only a=3 makes it to the remote table. The parent commit does have a timeout bug, fixed by this patch. The trigger test never times out, though, so it apparently doesn't reach that path.

--source have_federatedx.inc
--source include/federated.inc
--source include/have_innodb.inc

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://[email protected]:$SLAVE_MYPORT/f2db/s1';

BEGIN;
INSERT INTO f2db.s1 VALUES (1);
--error 1159,2013,1430,1296
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;
connection default;
source include/federated_cleanup.inc;

sure, included this test case as well. It now succeeds.

@bsrikanth-mariadb

Copy link
Copy Markdown
Contributor Author

Tried to get coverage as much as possible. But, there could be gaps.

@bsrikanth-mariadb
bsrikanth-mariadb marked this pull request as ready for review October 22, 2026 14:58
@DaveGosselin-MariaDB

Copy link
Copy Markdown
Member

Tried to get coverage as much as possible. But, there could be gaps.

Please use GCOV to measure what lines are tested by your changes. Obviously there are combinations across query types that increase complexity and GCOV won't necessarily indicate whether those are exercised, but it is useful for checking that the tests in the patch exercise the modified code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants