
The test performs the following: CREATE tbl (x INT PRIMARY KEY) t1 t2 BEGIN BEGIN INSERT INTO tbl VALUES (1) SELECT * FROM tbl FOR UPDATE That will cause t2 to wait. INSERT INTO tbl VALUES (0) That will cause t2 to be rolled back due to a deadlock. Without transaction replay, the SELECT will return with an error. With transaction replay, the deadlock error will be caught, the transaction replayed and SELECT will return successfully.
181 lines
4.5 KiB
C++
181 lines
4.5 KiB
C++
/*
|
|
* Copyright (c) 2019 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
|
*
|
|
* Change Date: 2022-01-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
|
|
#include <maxbase/assert.h>
|
|
#include <maxbase/semaphore.hh>
|
|
#include "mariadb_func.h"
|
|
#include "testconnections.h"
|
|
#include <iostream>
|
|
|
|
using namespace std;
|
|
|
|
namespace
|
|
{
|
|
|
|
namespace Query
|
|
{
|
|
|
|
enum Status
|
|
{
|
|
SUCCESS, // Execution succeeded.
|
|
FAILURE, // Execution failed (e.g. broken SQL).
|
|
ERROR // Execution succeeded but ended with an error (e.g. deadlock).
|
|
};
|
|
|
|
struct Result : std::pair<Query::Status, std::string>
|
|
{
|
|
Result(Status status = Query::FAILURE)
|
|
{
|
|
first = status;
|
|
}
|
|
|
|
Result(const std::string& message)
|
|
{
|
|
mxb_assert(!message.empty());
|
|
first = Query::ERROR;
|
|
second = message;
|
|
}
|
|
};
|
|
|
|
Result execute(MYSQL* pConn, const char* zStmt)
|
|
{
|
|
Result rv;
|
|
|
|
if (mysql_query(pConn, zStmt) == 0)
|
|
{
|
|
MYSQL_RES* pRes = mysql_store_result(pConn);
|
|
|
|
rv.second = mysql_error(pConn);
|
|
|
|
if (rv.second.empty())
|
|
{
|
|
rv.first = Query::SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
rv.first = Query::ERROR;
|
|
}
|
|
|
|
mysql_free_result(pRes);
|
|
}
|
|
else
|
|
{
|
|
rv.second = mysql_error(pConn);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void execute(MYSQL* pConn, const char* zStmt, Query::Status expectation)
|
|
{
|
|
Result rv = execute(pConn, zStmt);
|
|
|
|
mxb_assert(rv.first == expectation);
|
|
}
|
|
|
|
}
|
|
|
|
enum class Expectation
|
|
{
|
|
SUCCESS,
|
|
FAILURE
|
|
};
|
|
|
|
void run_test(TestConnections& test, Expectation expectation)
|
|
{
|
|
maxbase::Semaphore sem1;
|
|
maxbase::Semaphore sem2;
|
|
|
|
std::thread t1([&test, &sem1, &sem2]() {
|
|
MYSQL* pConn = test.maxscales->open_rwsplit_connection();
|
|
mxb_assert(pConn);
|
|
mysql_autocommit(pConn, false);
|
|
|
|
Query::execute(pConn, "BEGIN", Query::SUCCESS);
|
|
Query::execute(pConn, "INSERT INTO mxs2512 VALUES(1)", Query::SUCCESS);
|
|
sem1.post();
|
|
sem2.wait();
|
|
|
|
// First we sleep to be sure that t2 has time to issue its SELECT (that will block).
|
|
sleep(5);
|
|
Query::execute(pConn, "INSERT INTO mxs2512 VALUES(0)", Query::SUCCESS);
|
|
|
|
mysql_close(pConn);
|
|
});
|
|
|
|
Query::Result rv;
|
|
std::thread t2([&test, &sem1, &sem2, &rv]() {
|
|
MYSQL* pConn = test.maxscales->open_rwsplit_connection();
|
|
mxb_assert(pConn);
|
|
mysql_autocommit(pConn, false);
|
|
|
|
Query::execute(pConn, "BEGIN", Query::SUCCESS);
|
|
sem1.wait();
|
|
sem2.post();
|
|
|
|
rv = Query::execute(pConn, "SELECT * from mxs2512 FOR UPDATE");
|
|
|
|
mysql_close(pConn);
|
|
});
|
|
|
|
t1.join();
|
|
t2.join();
|
|
|
|
if (expectation == Expectation::FAILURE)
|
|
{
|
|
test.expect(rv.first == Query::ERROR, "SELECT did NOT fail.");
|
|
}
|
|
else
|
|
{
|
|
test.expect(rv.first == Query::SUCCESS, "SELECT DID fail.");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
TestConnections test(argc, argv);
|
|
|
|
MYSQL* pConn = test.maxscales->open_rwsplit_connection();
|
|
test.expect(pConn, "Could not connect to rwsplit.");
|
|
|
|
// Preparations
|
|
test.try_query(pConn, "DROP TABLE IF EXISTS mxs2512");
|
|
test.try_query(pConn, "CREATE TABLE mxs2512 (x INT PRIMARY KEY)");
|
|
|
|
// Test with 'transaction_replay=false' => should fail.
|
|
cout << "Testing with 'transaction_replay=false', SELECT should fail." << endl;
|
|
run_test(test, Expectation::FAILURE);
|
|
|
|
// Intermediate cleanup; delete contents from table, turn on transaction replay, restart MaxScale.
|
|
test.try_query(pConn, "DELETE FROM mxs2512");
|
|
mysql_close(pConn);
|
|
test.stop_maxscale();
|
|
const char* zSed = "sed -i -e 's/transaction_replay=false/transaction_replay=true/' /etc/maxscale.cnf";
|
|
test.add_result(test.maxscales->ssh_node(0, zSed, true), "Could not tweak /etc/maxscale.cnf");
|
|
test.start_maxscale();
|
|
|
|
// Test with 'transaction_replay=true' => should succeed.
|
|
cout << "Testing with 'transaction_replay=true', SELECT should succeed." << endl;
|
|
run_test(test, Expectation::SUCCESS);
|
|
|
|
// Final cleanup
|
|
pConn = test.maxscales->open_rwsplit_connection();
|
|
test.expect(pConn, "Could not connect to rwsplit.");
|
|
test.try_query(pConn, "DROP TABLE mxs2512");
|
|
mysql_close(pConn);
|
|
|
|
return test.global_result;
|
|
}
|