
When a BEGIN statement is prepared using the binary protocol, it returns a single OK packet. Due to a bug in the code that deals with multi-statement results and EOF packets, the response was never sent to the client. Also added back the error messages of failed session commands to the INFO level. This way it's still possible to see why a session command fails but the log isn't flooded by them in normal usage.
155 lines
5.2 KiB
C++
155 lines
5.2 KiB
C++
/*
|
|
* Copyright (c) 2016 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: 2020-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 "readwritesplit.hh"
|
|
#include "rwsplit_internal.hh"
|
|
|
|
#include <stdio.h>
|
|
#include <strings.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
|
|
#include <maxscale/router.h>
|
|
|
|
/**
|
|
* Functions for session command handling
|
|
*/
|
|
|
|
|
|
static std::string extract_error(GWBUF* buffer)
|
|
{
|
|
std::string rval;
|
|
|
|
if (MYSQL_IS_ERROR_PACKET(((uint8_t *)GWBUF_DATA(buffer))))
|
|
{
|
|
size_t replylen = MYSQL_GET_PAYLOAD_LEN(GWBUF_DATA(buffer));
|
|
char replybuf[replylen];
|
|
gwbuf_copy_data(buffer, 0, gwbuf_length(buffer), (uint8_t*)replybuf);
|
|
std::string err;
|
|
std::string msg;
|
|
err.append(replybuf + 8, 5);
|
|
msg.append(replybuf + 13, replylen - 4 - 5);
|
|
rval = err + ": " + msg;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* Discards the slave connection if its response differs from the master's response
|
|
*
|
|
* @param backend The slave Backend
|
|
* @param master_cmd Master's reply
|
|
* @param slave_cmd Slave's reply
|
|
*
|
|
* @return True if the responses were different and connection was discarded
|
|
*/
|
|
static bool discard_if_response_differs(SRWBackend backend, uint8_t master_cmd, uint8_t slave_cmd)
|
|
{
|
|
bool rval = false;
|
|
|
|
if (master_cmd != slave_cmd)
|
|
{
|
|
MXS_WARNING("Slave server '%s': response (0x%02hhx) differs "
|
|
"from master's response(0x%02hhx). Closing slave "
|
|
"connection due to inconsistent session state.",
|
|
backend->name(), slave_cmd, master_cmd);
|
|
backend->close(mxs::Backend::CLOSE_FATAL);
|
|
rval = true;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
void process_sescmd_response(RWSplitSession* rses, SRWBackend& backend,
|
|
GWBUF** ppPacket, bool* pReconnect)
|
|
{
|
|
if (backend->session_command_count())
|
|
{
|
|
/** We are executing a session command */
|
|
if (GWBUF_IS_TYPE_SESCMD_RESPONSE((*ppPacket)))
|
|
{
|
|
uint8_t cmd;
|
|
gwbuf_copy_data(*ppPacket, MYSQL_HEADER_LEN, 1, &cmd);
|
|
uint8_t command = backend->next_session_command()->get_command();
|
|
uint64_t id = backend->complete_session_command();
|
|
MXS_PS_RESPONSE resp = {};
|
|
bool discard = true;
|
|
|
|
if (command == MXS_COM_STMT_PREPARE && cmd != MYSQL_REPLY_ERR)
|
|
{
|
|
// This should never fail or the backend protocol is broken
|
|
ss_debug(bool b = )mxs_mysql_extract_ps_response(*ppPacket, &resp);
|
|
ss_dassert(b);
|
|
backend->add_ps_handle(id, resp.id);
|
|
}
|
|
|
|
if (rses->recv_sescmd < rses->sent_sescmd && id == rses->recv_sescmd + 1)
|
|
{
|
|
if (!rses->current_master || !rses->current_master->in_use() || // Session doesn't have a master
|
|
rses->current_master == backend) // This is the master's response
|
|
{
|
|
/** First reply to this session command, route it to the client */
|
|
++rses->recv_sescmd;
|
|
discard = false;
|
|
|
|
/** Store the master's response so that the slave responses can
|
|
* be compared to it */
|
|
rses->sescmd_responses[id] = cmd;
|
|
|
|
if (cmd == MYSQL_REPLY_ERR)
|
|
{
|
|
MXS_INFO("Session command no. %lu failed: %s",
|
|
id, extract_error(*ppPacket).c_str());
|
|
}
|
|
else if (command == MXS_COM_STMT_PREPARE)
|
|
{
|
|
/** Map the returned response to the internal ID */
|
|
MXS_INFO("PS ID %u maps to internal ID %lu", resp.id, id);
|
|
rses->ps_handles[resp.id] = id;
|
|
}
|
|
|
|
// Discard any slave connections that did not return the same result
|
|
for (SlaveResponseList::iterator it = rses->slave_responses.begin();
|
|
it != rses->slave_responses.end(); it++)
|
|
{
|
|
if (discard_if_response_differs(it->first, cmd, it->second))
|
|
{
|
|
*pReconnect = true;
|
|
}
|
|
}
|
|
|
|
rses->slave_responses.clear();
|
|
}
|
|
else
|
|
{
|
|
/** Record slave command so that the response can be validated
|
|
* against the master's response when it arrives. */
|
|
rses->slave_responses.push_back(std::make_pair(backend, cmd));
|
|
}
|
|
}
|
|
else if (discard_if_response_differs(backend, rses->sescmd_responses[id], cmd))
|
|
{
|
|
*pReconnect = true;
|
|
}
|
|
|
|
if (discard)
|
|
{
|
|
gwbuf_free(*ppPacket);
|
|
*ppPacket = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|