Commit d480b64f authored by Kristofer Pettersson's avatar Kristofer Pettersson

Bug#44521 Executing a stored procedure as a prepared statement can sometimes cause

          an assertion in a debug build.

The reason is that the C API doesn't support multiple result sets for prepared
statements and attempting to execute a stored routine which returns multiple result
sets sometimes lead to a network error. The network error sets the diagnostic area
prematurely which later leads to the assert when an attempt is made to set a second
server state.

This patch fixes the issue by changing the scope of the error code returned by
sp_instr_stmt::execute() to include any error which happened during the execution.
To assure that Diagnostic_area::is_sent really mean that the message was sent all
network related functions are checked for return status.
parent b8a69f4f
...@@ -803,11 +803,11 @@ MYSQL_DATA *THD::alloc_new_dataset() ...@@ -803,11 +803,11 @@ MYSQL_DATA *THD::alloc_new_dataset()
*/ */
static static
void bool
write_eof_packet(THD *thd, uint server_status, uint total_warn_count) write_eof_packet(THD *thd, uint server_status, uint total_warn_count)
{ {
if (!thd->mysql) // bootstrap file handling if (!thd->mysql) // bootstrap file handling
return; return FALSE;
/* /*
The following test should never be true, but it's better to do it The following test should never be true, but it's better to do it
because if 'is_fatal_error' is set the server is not going to execute because if 'is_fatal_error' is set the server is not going to execute
...@@ -822,6 +822,7 @@ write_eof_packet(THD *thd, uint server_status, uint total_warn_count) ...@@ -822,6 +822,7 @@ write_eof_packet(THD *thd, uint server_status, uint total_warn_count)
*/ */
thd->cur_data->embedded_info->warning_count= thd->cur_data->embedded_info->warning_count=
(thd->spcont ? 0 : min(total_warn_count, 65535)); (thd->spcont ? 0 : min(total_warn_count, 65535));
return FALSE;
} }
...@@ -1032,31 +1033,34 @@ bool Protocol_binary::write() ...@@ -1032,31 +1033,34 @@ bool Protocol_binary::write()
@sa Server implementation of net_send_ok in protocol.cc for @sa Server implementation of net_send_ok in protocol.cc for
description of the arguments. description of the arguments.
@return The function does not return errors. @return
@retval TRUE An error occurred
@retval FALSE Success
*/ */
void bool
net_send_ok(THD *thd, net_send_ok(THD *thd,
uint server_status, uint total_warn_count, uint server_status, uint total_warn_count,
ha_rows affected_rows, ulonglong id, const char *message) ha_rows affected_rows, ulonglong id, const char *message)
{ {
DBUG_ENTER("emb_net_send_ok"); DBUG_ENTER("emb_net_send_ok");
MYSQL_DATA *data; MYSQL_DATA *data;
bool error;
MYSQL *mysql= thd->mysql; MYSQL *mysql= thd->mysql;
if (!mysql) // bootstrap file handling if (!mysql) // bootstrap file handling
DBUG_VOID_RETURN; DBUG_RETURN(FALSE);
if (!(data= thd->alloc_new_dataset())) if (!(data= thd->alloc_new_dataset()))
return; return TRUE;
data->embedded_info->affected_rows= affected_rows; data->embedded_info->affected_rows= affected_rows;
data->embedded_info->insert_id= id; data->embedded_info->insert_id= id;
if (message) if (message)
strmake(data->embedded_info->info, message, strmake(data->embedded_info->info, message,
sizeof(data->embedded_info->info)-1); sizeof(data->embedded_info->info)-1);
write_eof_packet(thd, server_status, total_warn_count); error= write_eof_packet(thd, server_status, total_warn_count);
thd->cur_data= 0; thd->cur_data= 0;
DBUG_VOID_RETURN; DBUG_RETURN(error);
} }
...@@ -1065,18 +1069,21 @@ net_send_ok(THD *thd, ...@@ -1065,18 +1069,21 @@ net_send_ok(THD *thd,
@sa net_send_ok @sa net_send_ok
@return This function does not return errors. @return
@retval TRUE An error occurred
@retval FALSE Success
*/ */
void bool
net_send_eof(THD *thd, uint server_status, uint total_warn_count) net_send_eof(THD *thd, uint server_status, uint total_warn_count)
{ {
write_eof_packet(thd, server_status, total_warn_count); bool error= write_eof_packet(thd, server_status, total_warn_count);
thd->cur_data= 0; thd->cur_data= 0;
return error;
} }
void net_send_error_packet(THD *thd, uint sql_errno, const char *err) bool net_send_error_packet(THD *thd, uint sql_errno, const char *err)
{ {
MYSQL_DATA *data= thd->cur_data; MYSQL_DATA *data= thd->cur_data;
struct embedded_query_result *ei; struct embedded_query_result *ei;
...@@ -1084,7 +1091,7 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err) ...@@ -1084,7 +1091,7 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err)
if (!thd->mysql) // bootstrap file handling if (!thd->mysql) // bootstrap file handling
{ {
fprintf(stderr, "ERROR: %d %s\n", sql_errno, err); fprintf(stderr, "ERROR: %d %s\n", sql_errno, err);
return; return TRUE;
} }
if (!data) if (!data)
...@@ -1096,6 +1103,7 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err) ...@@ -1096,6 +1103,7 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err)
strmov(ei->sqlstate, mysql_errno_to_sqlstate(sql_errno)); strmov(ei->sqlstate, mysql_errno_to_sqlstate(sql_errno));
ei->server_status= thd->server_status; ei->server_status= thd->server_status;
thd->cur_data= 0; thd->cur_data= 0;
return FALSE;
} }
......
...@@ -249,3 +249,25 @@ DROP PROCEDURE p1; ...@@ -249,3 +249,25 @@ DROP PROCEDURE p1;
DELETE FROM mysql.user WHERE User='mysqltest_1'; DELETE FROM mysql.user WHERE User='mysqltest_1';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
set @@global.concurrent_insert= @old_concurrent_insert; set @@global.concurrent_insert= @old_concurrent_insert;
#
# Bug#44521 Prepared Statement: CALL p() - crashes: `! thd->main_da.is_sent' failed et.al.
#
SELECT GET_LOCK('Bug44521', 0);
GET_LOCK('Bug44521', 0)
1
** Connection con1
CREATE PROCEDURE p()
BEGIN
SELECT 1;
SELECT GET_LOCK('Bug44521', 100);
SELECT 2;
END$
CALL p();;
** Default connection
SELECT RELEASE_LOCK('Bug44521');
RELEASE_LOCK('Bug44521')
1
DROP PROCEDURE p;
# ------------------------------------------------------------------
# -- End of 5.1 tests
# ------------------------------------------------------------------
...@@ -380,3 +380,39 @@ set @@global.concurrent_insert= @old_concurrent_insert; ...@@ -380,3 +380,39 @@ set @@global.concurrent_insert= @old_concurrent_insert;
# Wait till all disconnects are completed # Wait till all disconnects are completed
--source include/wait_until_count_sessions.inc --source include/wait_until_count_sessions.inc
--echo #
--echo # Bug#44521 Prepared Statement: CALL p() - crashes: `! thd->main_da.is_sent' failed et.al.
--echo #
SELECT GET_LOCK('Bug44521', 0);
--connect (con1,localhost,root,,)
--echo ** Connection con1
delimiter $;
CREATE PROCEDURE p()
BEGIN
SELECT 1;
SELECT GET_LOCK('Bug44521', 100);
SELECT 2;
END$
delimiter ;$
--send CALL p();
--connection default
--echo ** Default connection
let $wait_condition=
SELECT count(*) = 1 FROM information_schema.processlist
WHERE state = "User lock" AND info = "SELECT GET_LOCK('Bug44521', 100)";
--source include/wait_condition.inc
let $conid =
`SELECT id FROM information_schema.processlist
WHERE state = "User lock" AND info = "SELECT GET_LOCK('Bug44521', 100)"`;
dirty_close con1;
SELECT RELEASE_LOCK('Bug44521');
let $wait_condition=
SELECT count(*) = 0 FROM information_schema.processlist
WHERE id = $conid;
--source include/wait_condition.inc
DROP PROCEDURE p;
--echo # ------------------------------------------------------------------
--echo # -- End of 5.1 tests
--echo # ------------------------------------------------------------------
...@@ -29,11 +29,11 @@ ...@@ -29,11 +29,11 @@
static const unsigned int PACKET_BUFFER_EXTRA_ALLOC= 1024; static const unsigned int PACKET_BUFFER_EXTRA_ALLOC= 1024;
/* Declared non-static only because of the embedded library. */ /* Declared non-static only because of the embedded library. */
void net_send_error_packet(THD *thd, uint sql_errno, const char *err); bool net_send_error_packet(THD *thd, uint sql_errno, const char *err);
void net_send_ok(THD *, uint, uint, ha_rows, ulonglong, const char *); bool net_send_ok(THD *, uint, uint, ha_rows, ulonglong, const char *);
void net_send_eof(THD *thd, uint server_status, uint total_warn_count); bool net_send_eof(THD *thd, uint server_status, uint total_warn_count);
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
static void write_eof_packet(THD *thd, NET *net, static bool write_eof_packet(THD *thd, NET *net,
uint server_status, uint total_warn_count); uint server_status, uint total_warn_count);
#endif #endif
...@@ -70,8 +70,17 @@ bool Protocol_binary::net_store_data(const uchar *from, size_t length) ...@@ -70,8 +70,17 @@ bool Protocol_binary::net_store_data(const uchar *from, size_t length)
For SIGNAL/RESIGNAL and GET DIAGNOSTICS functionality it's For SIGNAL/RESIGNAL and GET DIAGNOSTICS functionality it's
critical that every error that can be intercepted is issued in one critical that every error that can be intercepted is issued in one
place only, my_message_sql. place only, my_message_sql.
@param thd Thread handler
@param sql_errno The error code to send
@param err A pointer to the error message
@return
@retval FALSE The message was sent to the client
@retval TRUE An error occurred and the message wasn't sent properly
*/ */
void net_send_error(THD *thd, uint sql_errno, const char *err)
bool net_send_error(THD *thd, uint sql_errno, const char *err)
{ {
DBUG_ENTER("net_send_error"); DBUG_ENTER("net_send_error");
...@@ -80,6 +89,7 @@ void net_send_error(THD *thd, uint sql_errno, const char *err) ...@@ -80,6 +89,7 @@ void net_send_error(THD *thd, uint sql_errno, const char *err)
DBUG_ASSERT(err && err[0]); DBUG_ASSERT(err && err[0]);
DBUG_PRINT("enter",("sql_errno: %d err: %s", sql_errno, err)); DBUG_PRINT("enter",("sql_errno: %d err: %s", sql_errno, err));
bool error;
/* /*
It's one case when we can push an error even though there It's one case when we can push an error even though there
...@@ -90,11 +100,11 @@ void net_send_error(THD *thd, uint sql_errno, const char *err) ...@@ -90,11 +100,11 @@ void net_send_error(THD *thd, uint sql_errno, const char *err)
/* Abort multi-result sets */ /* Abort multi-result sets */
thd->server_status&= ~SERVER_MORE_RESULTS_EXISTS; thd->server_status&= ~SERVER_MORE_RESULTS_EXISTS;
net_send_error_packet(thd, sql_errno, err); error= net_send_error_packet(thd, sql_errno, err);
thd->main_da.can_overwrite_status= FALSE; thd->main_da.can_overwrite_status= FALSE;
DBUG_VOID_RETURN; DBUG_RETURN(error);
} }
/** /**
...@@ -113,25 +123,33 @@ void net_send_error(THD *thd, uint sql_errno, const char *err) ...@@ -113,25 +123,33 @@ void net_send_error(THD *thd, uint sql_errno, const char *err)
Is not stored if no message. Is not stored if no message.
@param thd Thread handler @param thd Thread handler
@param server_status The server status
@param total_warn_count Total number of warnings
@param affected_rows Number of rows changed by statement @param affected_rows Number of rows changed by statement
@param id Auto_increment id for first row (if used) @param id Auto_increment id for first row (if used)
@param message Message to send to the client (Used by mysql_status) @param message Message to send to the client (Used by mysql_status)
@return
@retval FALSE The message was successfully sent
@retval TRUE An error occurred and the messages wasn't sent properly
*/ */
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
void bool
net_send_ok(THD *thd, net_send_ok(THD *thd,
uint server_status, uint total_warn_count, uint server_status, uint total_warn_count,
ha_rows affected_rows, ulonglong id, const char *message) ha_rows affected_rows, ulonglong id, const char *message)
{ {
NET *net= &thd->net; NET *net= &thd->net;
uchar buff[MYSQL_ERRMSG_SIZE+10],*pos; uchar buff[MYSQL_ERRMSG_SIZE+10],*pos;
bool error= FALSE;
DBUG_ENTER("my_ok"); DBUG_ENTER("my_ok");
if (! net->vio) // hack for re-parsing queries if (! net->vio) // hack for re-parsing queries
{ {
DBUG_PRINT("info", ("vio present: NO")); DBUG_PRINT("info", ("vio present: NO"));
DBUG_VOID_RETURN; DBUG_RETURN(FALSE);
} }
buff[0]=0; // No fields buff[0]=0; // No fields
...@@ -162,13 +180,14 @@ net_send_ok(THD *thd, ...@@ -162,13 +180,14 @@ net_send_ok(THD *thd,
if (message && message[0]) if (message && message[0])
pos= net_store_data(pos, (uchar*) message, strlen(message)); pos= net_store_data(pos, (uchar*) message, strlen(message));
VOID(my_net_write(net, buff, (size_t) (pos-buff))); error= my_net_write(net, buff, (size_t) (pos-buff));
VOID(net_flush(net)); if (!error)
error= net_flush(net);
thd->main_da.can_overwrite_status= FALSE; thd->main_da.can_overwrite_status= FALSE;
DBUG_PRINT("info", ("OK sent, so no more error sending allowed")); DBUG_PRINT("info", ("OK sent, so no more error sending allowed"));
DBUG_VOID_RETURN; DBUG_RETURN(error);
} }
static uchar eof_buff[1]= { (uchar) 254 }; /* Marker for end of fields */ static uchar eof_buff[1]= { (uchar) 254 }; /* Marker for end of fields */
...@@ -188,37 +207,54 @@ static uchar eof_buff[1]= { (uchar) 254 }; /* Marker for end of fields */ ...@@ -188,37 +207,54 @@ static uchar eof_buff[1]= { (uchar) 254 }; /* Marker for end of fields */
client. client.
@param thd Thread handler @param thd Thread handler
@param no_flush Set to 1 if there will be more data to the client, @param server_status The server status
like in send_fields(). @param total_warn_count Total number of warnings
@return
@retval FALSE The message was successfully sent
@retval TRUE An error occurred and the message wasn't sent properly
*/ */
void bool
net_send_eof(THD *thd, uint server_status, uint total_warn_count) net_send_eof(THD *thd, uint server_status, uint total_warn_count)
{ {
NET *net= &thd->net; NET *net= &thd->net;
bool error= FALSE;
DBUG_ENTER("net_send_eof"); DBUG_ENTER("net_send_eof");
/* Set to TRUE if no active vio, to work well in case of --init-file */ /* Set to TRUE if no active vio, to work well in case of --init-file */
if (net->vio != 0) if (net->vio != 0)
{ {
thd->main_da.can_overwrite_status= TRUE; thd->main_da.can_overwrite_status= TRUE;
write_eof_packet(thd, net, server_status, total_warn_count); error= write_eof_packet(thd, net, server_status, total_warn_count);
VOID(net_flush(net)); if (!error)
error= net_flush(net);
thd->main_da.can_overwrite_status= FALSE; thd->main_da.can_overwrite_status= FALSE;
DBUG_PRINT("info", ("EOF sent, so no more error sending allowed")); DBUG_PRINT("info", ("EOF sent, so no more error sending allowed"));
} }
DBUG_VOID_RETURN; DBUG_RETURN(error);
} }
/** /**
Format EOF packet according to the current protocol and Format EOF packet according to the current protocol and
write it to the network output buffer. write it to the network output buffer.
@param thd The thread handler
@param net The network handler
@param server_status The server status
@param total_warn_count The number of warnings
@return
@retval FALSE The message was sent successfully
@retval TRUE An error occurred and the messages wasn't sent properly
*/ */
static void write_eof_packet(THD *thd, NET *net, static bool write_eof_packet(THD *thd, NET *net,
uint server_status, uint server_status,
uint total_warn_count) uint total_warn_count)
{ {
bool error;
if (thd->client_capabilities & CLIENT_PROTOCOL_41) if (thd->client_capabilities & CLIENT_PROTOCOL_41)
{ {
uchar buff[5]; uchar buff[5];
...@@ -237,10 +273,12 @@ static void write_eof_packet(THD *thd, NET *net, ...@@ -237,10 +273,12 @@ static void write_eof_packet(THD *thd, NET *net,
if (thd->is_fatal_error) if (thd->is_fatal_error)
server_status&= ~SERVER_MORE_RESULTS_EXISTS; server_status&= ~SERVER_MORE_RESULTS_EXISTS;
int2store(buff + 3, server_status); int2store(buff + 3, server_status);
VOID(my_net_write(net, buff, 5)); error= my_net_write(net, buff, 5);
} }
else else
VOID(my_net_write(net, eof_buff, 1)); error= my_net_write(net, eof_buff, 1);
return error;
} }
/** /**
...@@ -261,7 +299,17 @@ bool send_old_password_request(THD *thd) ...@@ -261,7 +299,17 @@ bool send_old_password_request(THD *thd)
} }
void net_send_error_packet(THD *thd, uint sql_errno, const char *err) /**
@param thd Thread handler
@param sql_errno The error code to send
@param err A pointer to the error message
@return
@retval FALSE The message was successfully sent
@retval TRUE An error occurred and the messages wasn't sent properly
*/
bool net_send_error_packet(THD *thd, uint sql_errno, const char *err)
{ {
NET *net= &thd->net; NET *net= &thd->net;
uint length; uint length;
...@@ -279,7 +327,7 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err) ...@@ -279,7 +327,7 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err)
/* In bootstrap it's ok to print on stderr */ /* In bootstrap it's ok to print on stderr */
fprintf(stderr,"ERROR: %d %s\n",sql_errno,err); fprintf(stderr,"ERROR: %d %s\n",sql_errno,err);
} }
DBUG_VOID_RETURN; DBUG_RETURN(FALSE);
} }
if (net->return_errno) if (net->return_errno)
...@@ -301,9 +349,8 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err) ...@@ -301,9 +349,8 @@ void net_send_error_packet(THD *thd, uint sql_errno, const char *err)
length=(uint) strlen(err); length=(uint) strlen(err);
set_if_smaller(length,MYSQL_ERRMSG_SIZE-1); set_if_smaller(length,MYSQL_ERRMSG_SIZE-1);
} }
VOID(net_write_command(net,(uchar) 255, (uchar*) "", 0, (uchar*) err, DBUG_RETURN(net_write_command(net,(uchar) 255, (uchar*) "", 0, (uchar*) err,
length)); length));
DBUG_VOID_RETURN;
} }
#endif /* EMBEDDED_LIBRARY */ #endif /* EMBEDDED_LIBRARY */
...@@ -389,20 +436,22 @@ void net_end_statement(THD *thd) ...@@ -389,20 +436,22 @@ void net_end_statement(THD *thd)
if (thd->main_da.is_sent) if (thd->main_da.is_sent)
return; return;
bool error= FALSE;
switch (thd->main_da.status()) { switch (thd->main_da.status()) {
case Diagnostics_area::DA_ERROR: case Diagnostics_area::DA_ERROR:
/* The query failed, send error to log and abort bootstrap. */ /* The query failed, send error to log and abort bootstrap. */
net_send_error(thd, error= net_send_error(thd,
thd->main_da.sql_errno(), thd->main_da.sql_errno(),
thd->main_da.message()); thd->main_da.message());
break; break;
case Diagnostics_area::DA_EOF: case Diagnostics_area::DA_EOF:
net_send_eof(thd, error= net_send_eof(thd,
thd->main_da.server_status(), thd->main_da.server_status(),
thd->main_da.total_warn_count()); thd->main_da.total_warn_count());
break; break;
case Diagnostics_area::DA_OK: case Diagnostics_area::DA_OK:
net_send_ok(thd, error= net_send_ok(thd,
thd->main_da.server_status(), thd->main_da.server_status(),
thd->main_da.total_warn_count(), thd->main_da.total_warn_count(),
thd->main_da.affected_rows(), thd->main_da.affected_rows(),
...@@ -414,10 +463,11 @@ void net_end_statement(THD *thd) ...@@ -414,10 +463,11 @@ void net_end_statement(THD *thd)
case Diagnostics_area::DA_EMPTY: case Diagnostics_area::DA_EMPTY:
default: default:
DBUG_ASSERT(0); DBUG_ASSERT(0);
net_send_ok(thd, thd->server_status, thd->total_warn_count, error= net_send_ok(thd, thd->server_status, thd->total_warn_count,
0, 0, NULL); 0, 0, NULL);
break; break;
} }
if (!error)
thd->main_da.is_sent= TRUE; thd->main_da.is_sent= TRUE;
} }
......
...@@ -173,7 +173,7 @@ public: ...@@ -173,7 +173,7 @@ public:
}; };
void send_warning(THD *thd, uint sql_errno, const char *err=0); void send_warning(THD *thd, uint sql_errno, const char *err=0);
void net_send_error(THD *thd, uint sql_errno=0, const char *err=0); bool net_send_error(THD *thd, uint sql_errno=0, const char *err=0);
void net_end_statement(THD *thd); void net_end_statement(THD *thd);
bool send_old_password_request(THD *thd); bool send_old_password_request(THD *thd);
uchar *net_store_data(uchar *to,const uchar *from, size_t length); uchar *net_store_data(uchar *to,const uchar *from, size_t length);
......
...@@ -2863,7 +2863,7 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) ...@@ -2863,7 +2863,7 @@ sp_instr_stmt::execute(THD *thd, uint *nextp)
if (!thd->is_error()) if (!thd->is_error())
thd->main_da.reset_diagnostics_area(); thd->main_da.reset_diagnostics_area();
} }
DBUG_RETURN(res); DBUG_RETURN(res || thd->is_error());
} }
......
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