Commit 53af3d8c authored by Monty's avatar Monty Committed by Sergei Golubchik

Fixed memory leaks in embedded server and mysqltest

This commit fixes the following issues:
- memory leak checking enabled for mysqltest. This cover all cases except
  calls to 'die()' that only happens in case of internal failures in
  mysqltest. die() is not called anymore in the result files differs.
- One can now run mtr --embedded without failures (this crashed or hang
  before)
- cleanup_and_exit() has a new parameter that indicates that it is called
  from die(), in which case we should not do memory leak checks. We now
  always call cleanup_and_exit() instead of exit() to be able to free up
  memory and discover memory leaks.
- Lots of new assert to catch error conditions
- More DBUG statements.
- Fixed that all results are freed in mysqltest (Fixed a memory leak in
  mysqltest when using prepared statements).
- Fixed race condition in do_stmt_close() that caused embedded server
  to not free memory. (Memory leak in mysqltest with embedded server).
- Fixed two memory leaks in embedded server when using prepared statements.
  These memory leaks caused timeout hangs in mtr when server was compiled
  with safemalloc. This issue was not noticed (except as timeouts) as
  memory report checking was done but output of it was disabled.
parent fc6711c6
...@@ -616,7 +616,7 @@ void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, ...@@ -616,7 +616,7 @@ void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds,
const char *from); const char *from);
ATTRIBUTE_NORETURN ATTRIBUTE_NORETURN
static void cleanup_and_exit(int exit_code); static void cleanup_and_exit(int exit_code, bool called_from_die);
ATTRIBUTE_NORETURN ATTRIBUTE_NORETURN
static void really_die(const char *msg); static void really_die(const char *msg);
...@@ -933,6 +933,7 @@ pthread_attr_t cn_thd_attrib; ...@@ -933,6 +933,7 @@ pthread_attr_t cn_thd_attrib;
pthread_handler_t connection_thread(void *arg) pthread_handler_t connection_thread(void *arg)
{ {
struct st_connection *cn= (struct st_connection*)arg; struct st_connection *cn= (struct st_connection*)arg;
DBUG_ENTER("connection_thread");
mysql_thread_init(); mysql_thread_init();
while (cn->command != EMB_END_CONNECTION) while (cn->command != EMB_END_CONNECTION)
...@@ -944,6 +945,7 @@ pthread_handler_t connection_thread(void *arg) ...@@ -944,6 +945,7 @@ pthread_handler_t connection_thread(void *arg)
pthread_cond_wait(&cn->query_cond, &cn->query_mutex); pthread_cond_wait(&cn->query_cond, &cn->query_mutex);
pthread_mutex_unlock(&cn->query_mutex); pthread_mutex_unlock(&cn->query_mutex);
} }
DBUG_PRINT("info", ("executing command: %d", cn->command));
switch (cn->command) switch (cn->command)
{ {
case EMB_END_CONNECTION: case EMB_END_CONNECTION:
...@@ -964,24 +966,26 @@ pthread_handler_t connection_thread(void *arg) ...@@ -964,24 +966,26 @@ pthread_handler_t connection_thread(void *arg)
break; break;
case EMB_CLOSE_STMT: case EMB_CLOSE_STMT:
cn->result= mysql_stmt_close(cn->stmt); cn->result= mysql_stmt_close(cn->stmt);
cn->stmt= 0;
break; break;
default: default:
DBUG_ASSERT(0); DBUG_ASSERT(0);
} }
cn->command= 0;
pthread_mutex_lock(&cn->result_mutex); pthread_mutex_lock(&cn->result_mutex);
cn->query_done= 1; cn->query_done= 1;
cn->command= 0;
pthread_cond_signal(&cn->result_cond); pthread_cond_signal(&cn->result_cond);
pthread_mutex_unlock(&cn->result_mutex); pthread_mutex_unlock(&cn->result_mutex);
} }
end_thread: end_thread:
cn->query_done= 1; DBUG_ASSERT(cn->stmt == 0);
mysql_close(cn->mysql); mysql_close(cn->mysql);
cn->mysql= 0; cn->mysql= 0;
cn->query_done= 1;
mysql_thread_end(); mysql_thread_end();
pthread_exit(0); pthread_exit(0);
return 0; DBUG_RETURN(0);
} }
static void wait_query_thread_done(struct st_connection *con) static void wait_query_thread_done(struct st_connection *con)
...@@ -999,12 +1003,16 @@ static void wait_query_thread_done(struct st_connection *con) ...@@ -999,12 +1003,16 @@ static void wait_query_thread_done(struct st_connection *con)
static void signal_connection_thd(struct st_connection *cn, int command) static void signal_connection_thd(struct st_connection *cn, int command)
{ {
DBUG_ENTER("signal_connection_thd");
DBUG_PRINT("enter", ("command: %d", command));
DBUG_ASSERT(cn->has_thread); DBUG_ASSERT(cn->has_thread);
cn->query_done= 0; cn->query_done= 0;
cn->command= command;
pthread_mutex_lock(&cn->query_mutex); pthread_mutex_lock(&cn->query_mutex);
cn->command= command;
pthread_cond_signal(&cn->query_cond); pthread_cond_signal(&cn->query_cond);
pthread_mutex_unlock(&cn->query_mutex); pthread_mutex_unlock(&cn->query_mutex);
DBUG_VOID_RETURN;
} }
...@@ -1066,27 +1074,38 @@ static int do_stmt_execute(struct st_connection *cn) ...@@ -1066,27 +1074,38 @@ static int do_stmt_execute(struct st_connection *cn)
static int do_stmt_close(struct st_connection *cn) static int do_stmt_close(struct st_connection *cn)
{ {
/* The cn->stmt is already set. */ DBUG_ENTER("do_stmt_close");
if (!cn->has_thread) if (!cn->has_thread)
return mysql_stmt_close(cn->stmt); {
/* The cn->stmt is already set. */
int res= mysql_stmt_close(cn->stmt);
cn->stmt= 0;
DBUG_RETURN(res);
}
wait_query_thread_done(cn);
signal_connection_thd(cn, EMB_CLOSE_STMT); signal_connection_thd(cn, EMB_CLOSE_STMT);
wait_query_thread_done(cn); wait_query_thread_done(cn);
return cn->result; DBUG_ASSERT(cn->stmt == 0);
DBUG_RETURN(cn->result);
} }
static void emb_close_connection(struct st_connection *cn) static void emb_close_connection(struct st_connection *cn)
{ {
DBUG_ENTER("emb_close_connection");
if (!cn->has_thread) if (!cn->has_thread)
return; DBUG_VOID_RETURN;
wait_query_thread_done(cn); wait_query_thread_done(cn);
signal_connection_thd(cn, EMB_END_CONNECTION); signal_connection_thd(cn, EMB_END_CONNECTION);
pthread_join(cn->tid, NULL); pthread_join(cn->tid, NULL);
cn->has_thread= FALSE; cn->has_thread= FALSE;
DBUG_ASSERT(cn->mysql == 0);
DBUG_ASSERT(cn->stmt == 0);
pthread_mutex_destroy(&cn->query_mutex); pthread_mutex_destroy(&cn->query_mutex);
pthread_cond_destroy(&cn->query_cond); pthread_cond_destroy(&cn->query_cond);
pthread_mutex_destroy(&cn->result_mutex); pthread_mutex_destroy(&cn->result_mutex);
pthread_cond_destroy(&cn->result_cond); pthread_cond_destroy(&cn->result_cond);
DBUG_VOID_RETURN;
} }
...@@ -1110,7 +1129,13 @@ static void init_connection_thd(struct st_connection *cn) ...@@ -1110,7 +1129,13 @@ static void init_connection_thd(struct st_connection *cn)
#define do_read_query_result(cn) mysql_read_query_result(cn->mysql) #define do_read_query_result(cn) mysql_read_query_result(cn->mysql)
#define do_stmt_prepare(cn, q, q_len) mysql_stmt_prepare(cn->stmt, q, (ulong)q_len) #define do_stmt_prepare(cn, q, q_len) mysql_stmt_prepare(cn->stmt, q, (ulong)q_len)
#define do_stmt_execute(cn) mysql_stmt_execute(cn->stmt) #define do_stmt_execute(cn) mysql_stmt_execute(cn->stmt)
#define do_stmt_close(cn) mysql_stmt_close(cn->stmt)
static int do_stmt_close(struct st_connection *cn)
{
int res= mysql_stmt_close(cn->stmt);
cn->stmt= 0;
return res;
}
#endif /*EMBEDDED_LIBRARY*/ #endif /*EMBEDDED_LIBRARY*/
...@@ -1438,7 +1463,6 @@ void close_statements() ...@@ -1438,7 +1463,6 @@ void close_statements()
{ {
if (con->stmt) if (con->stmt)
do_stmt_close(con); do_stmt_close(con);
con->stmt= 0;
} }
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
...@@ -1505,7 +1529,8 @@ void free_used_memory() ...@@ -1505,7 +1529,8 @@ void free_used_memory()
} }
ATTRIBUTE_NORETURN static void cleanup_and_exit(int exit_code) ATTRIBUTE_NORETURN static void cleanup_and_exit(int exit_code,
bool called_from_die)
{ {
free_used_memory(); free_used_memory();
...@@ -1513,16 +1538,6 @@ ATTRIBUTE_NORETURN static void cleanup_and_exit(int exit_code) ...@@ -1513,16 +1538,6 @@ ATTRIBUTE_NORETURN static void cleanup_and_exit(int exit_code)
if (server_initialized) if (server_initialized)
mysql_server_end(); mysql_server_end();
/*
mysqltest is fundamentally written in a way that makes impossible
to free all memory before exit (consider memory allocated
for frame local DYNAMIC_STRING's and die() invoked down the stack.
We close stderr here to stop unavoidable safemalloc reports
from polluting the output.
*/
fclose(stderr);
my_end(my_end_arg); my_end(my_end_arg);
if (!silent) { if (!silent) {
...@@ -1542,6 +1557,11 @@ ATTRIBUTE_NORETURN static void cleanup_and_exit(int exit_code) ...@@ -1542,6 +1557,11 @@ ATTRIBUTE_NORETURN static void cleanup_and_exit(int exit_code)
} }
} }
/*
Report memory leaks, if not called from 'die()', as die() will not release
all memory.
*/
sf_leaking_memory= called_from_die;
exit(exit_code); exit(exit_code);
} }
...@@ -1608,7 +1628,7 @@ static void really_die(const char *msg) ...@@ -1608,7 +1628,7 @@ static void really_die(const char *msg)
second time, just exit second time, just exit
*/ */
if (dying) if (dying)
cleanup_and_exit(1); cleanup_and_exit(1, 1);
dying= 1; dying= 1;
log_file.show_tail(opt_tail_lines); log_file.show_tail(opt_tail_lines);
...@@ -1620,7 +1640,7 @@ static void really_die(const char *msg) ...@@ -1620,7 +1640,7 @@ static void really_die(const char *msg)
if (cur_con && !cur_con->pending) if (cur_con && !cur_con->pending)
show_warnings_before_error(cur_con->mysql); show_warnings_before_error(cur_con->mysql);
cleanup_and_exit(1); cleanup_and_exit(1, 1);
} }
void report_or_die(const char *fmt, ...) void report_or_die(const char *fmt, ...)
...@@ -1674,7 +1694,7 @@ void abort_not_supported_test(const char *fmt, ...) ...@@ -1674,7 +1694,7 @@ void abort_not_supported_test(const char *fmt, ...)
} }
va_end(args); va_end(args);
cleanup_and_exit(62); cleanup_and_exit(62, 0);
} }
...@@ -2220,14 +2240,14 @@ int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) ...@@ -2220,14 +2240,14 @@ int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname)
check_result check_result
RETURN VALUES RETURN VALUES
error - the function will not return 0 ok
1 error
*/ */
void check_result() int check_result()
{ {
const char *mess= 0; const char *mess= 0;
int error= 1;
DBUG_ENTER("check_result"); DBUG_ENTER("check_result");
DBUG_ASSERT(result_file_name); DBUG_ASSERT(result_file_name);
DBUG_PRINT("enter", ("result_file_name: %s", result_file_name)); DBUG_PRINT("enter", ("result_file_name: %s", result_file_name));
...@@ -2235,7 +2255,10 @@ void check_result() ...@@ -2235,7 +2255,10 @@ void check_result()
switch (compare_files(log_file.file_name(), result_file_name)) { switch (compare_files(log_file.file_name(), result_file_name)) {
case RESULT_OK: case RESULT_OK:
if (!error_count) if (!error_count)
{
error= 0;
break; /* ok */ break; /* ok */
}
mess= "Got errors while running test"; mess= "Got errors while running test";
/* Fallthrough */ /* Fallthrough */
case RESULT_LENGTH_MISMATCH: case RESULT_LENGTH_MISMATCH:
...@@ -2274,14 +2297,13 @@ void check_result() ...@@ -2274,14 +2297,13 @@ void check_result()
log_file.file_name(), reject_file, errno); log_file.file_name(), reject_file, errno);
show_diff(NULL, result_file_name, reject_file); show_diff(NULL, result_file_name, reject_file);
die("%s", mess); fprintf(stderr, "%s", mess);
break; break;
} }
default: /* impossible */ default: /* impossible */
die("Unknown error code from dyn_string_cmp()"); die("Unknown error code from dyn_string_cmp()");
} }
DBUG_RETURN(error);
DBUG_VOID_RETURN;
} }
...@@ -5631,7 +5653,6 @@ void do_close_connection(struct st_command *command) ...@@ -5631,7 +5653,6 @@ void do_close_connection(struct st_command *command)
#endif /*!EMBEDDED_LIBRARY*/ #endif /*!EMBEDDED_LIBRARY*/
if (con->stmt) if (con->stmt)
do_stmt_close(con); do_stmt_close(con);
con->stmt= 0;
#ifdef EMBEDDED_LIBRARY #ifdef EMBEDDED_LIBRARY
/* /*
As query could be still executed in a separate thread As query could be still executed in a separate thread
...@@ -7308,17 +7329,17 @@ get_one_option(const struct my_option *opt, const char *argument, const char *) ...@@ -7308,17 +7329,17 @@ get_one_option(const struct my_option *opt, const char *argument, const char *)
break; break;
case 'V': case 'V':
print_version(); print_version();
exit(0); cleanup_and_exit(0,0);
case OPT_MYSQL_PROTOCOL: case OPT_MYSQL_PROTOCOL:
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib,
opt->name)) <= 0) opt->name)) <= 0)
exit(1); cleanup_and_exit(1,0);
#endif #endif
break; break;
case '?': case '?':
usage(); usage();
exit(0); cleanup_and_exit(0,0);
} }
return 0; return 0;
} }
...@@ -7330,12 +7351,12 @@ int parse_args(int argc, char **argv) ...@@ -7330,12 +7351,12 @@ int parse_args(int argc, char **argv)
default_argv= argv; default_argv= argv;
if ((handle_options(&argc, &argv, my_long_options, get_one_option))) if ((handle_options(&argc, &argv, my_long_options, get_one_option)))
exit(1); cleanup_and_exit(1, 0);
if (argc > 1) if (argc > 1)
{ {
usage(); usage();
exit(1); cleanup_and_exit(1, 0);
} }
if (argc == 1) if (argc == 1)
opt_db= *argv; opt_db= *argv;
...@@ -8445,7 +8466,7 @@ void run_query_stmt(struct st_connection *cn, struct st_command *command, ...@@ -8445,7 +8466,7 @@ void run_query_stmt(struct st_connection *cn, struct st_command *command,
my_bool ds_res_1st_execution_init = FALSE; my_bool ds_res_1st_execution_init = FALSE;
my_bool compare_2nd_execution = TRUE; my_bool compare_2nd_execution = TRUE;
int query_match_ps2_re; int query_match_ps2_re;
MYSQL_RES *res;
DBUG_ENTER("run_query_stmt"); DBUG_ENTER("run_query_stmt");
DBUG_PRINT("query", ("'%-.60s'", query)); DBUG_PRINT("query", ("'%-.60s'", query));
...@@ -8631,10 +8652,13 @@ void run_query_stmt(struct st_connection *cn, struct st_command *command, ...@@ -8631,10 +8652,13 @@ void run_query_stmt(struct st_connection *cn, struct st_command *command,
The --enable_prepare_warnings command can be used to change this so The --enable_prepare_warnings command can be used to change this so
that warnings from both the prepare and execute phase are shown. that warnings from both the prepare and execute phase are shown.
*/ */
if ((mysql_stmt_result_metadata(stmt) != NULL) && if ((res= mysql_stmt_result_metadata(stmt)))
!disable_warnings && {
if (!disable_warnings &&
!prepare_warnings_enabled) !prepare_warnings_enabled)
dynstr_set(&ds_prepare_warnings, NULL); dynstr_set(&ds_prepare_warnings, NULL);
mysql_free_result(res);
}
/* /*
Fetch info before fetching warnings, since it will be reset Fetch info before fetching warnings, since it will be reset
...@@ -9729,6 +9753,7 @@ static sig_handler signal_handler(int sig) ...@@ -9729,6 +9753,7 @@ static sig_handler signal_handler(int sig)
fflush(stderr); fflush(stderr);
my_write_core(sig); my_write_core(sig);
#ifndef _WIN32 #ifndef _WIN32
sf_leaking_memory= 1;
exit(1); // Shouldn't get here but just in case exit(1); // Shouldn't get here but just in case
#endif #endif
} }
...@@ -9802,12 +9827,10 @@ int main(int argc, char **argv) ...@@ -9802,12 +9827,10 @@ int main(int argc, char **argv)
uint command_executed= 0, last_command_executed= 0; uint command_executed= 0, last_command_executed= 0;
char save_file[FN_REFLEN]; char save_file[FN_REFLEN];
bool empty_result= FALSE; bool empty_result= FALSE;
int error= 0;
MY_INIT(argv[0]); MY_INIT(argv[0]);
DBUG_ENTER("main"); DBUG_ENTER("main");
/* mysqltest has no way to free all its memory correctly */
sf_leaking_memory= 1;
save_file[0]= 0; save_file[0]= 0;
TMPDIR[0]= 0; TMPDIR[0]= 0;
...@@ -10486,7 +10509,7 @@ int main(int argc, char **argv) ...@@ -10486,7 +10509,7 @@ int main(int argc, char **argv)
die("Test ended with parsing disabled"); die("Test ended with parsing disabled");
/* /*
The whole test has been executed _successfully_. The whole test has been executed successfully.
Time to compare result or save it to record file. Time to compare result or save it to record file.
The entire output from test is in the log file The entire output from test is in the log file
*/ */
...@@ -10509,7 +10532,7 @@ int main(int argc, char **argv) ...@@ -10509,7 +10532,7 @@ int main(int argc, char **argv)
else else
{ {
/* Check that the output from test is equal to result file */ /* Check that the output from test is equal to result file */
check_result(); error= check_result();
} }
} }
} }
...@@ -10519,7 +10542,8 @@ int main(int argc, char **argv) ...@@ -10519,7 +10542,8 @@ int main(int argc, char **argv)
if (! result_file_name || record || if (! result_file_name || record ||
compare_files (log_file.file_name(), result_file_name)) compare_files (log_file.file_name(), result_file_name))
{ {
die("The test didn't produce any output"); fprintf(stderr, "mysqltest: The test didn't produce any output\n");
error= 1;
} }
else else
{ {
...@@ -10528,12 +10552,15 @@ int main(int argc, char **argv) ...@@ -10528,12 +10552,15 @@ int main(int argc, char **argv)
} }
if (!command_executed && result_file_name && !empty_result) if (!command_executed && result_file_name && !empty_result)
die("No queries executed but non-empty result file found!"); {
fprintf(stderr, "mysqltest: No queries executed but non-empty result file found!\n");
error= 1;
}
if (!error)
verbose_msg("Test has succeeded!"); verbose_msg("Test has succeeded!");
timer_output(); timer_output();
/* Yes, if we got this far the test has succeeded! Sakila smiles */ cleanup_and_exit(error, 0);
cleanup_and_exit(0);
return 0; /* Keep compiler happy too */ return 0; /* Keep compiler happy too */
} }
......
...@@ -266,6 +266,7 @@ static my_bool emb_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt) ...@@ -266,6 +266,7 @@ static my_bool emb_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt)
mysql->server_status|= SERVER_STATUS_IN_TRANS; mysql->server_status|= SERVER_STATUS_IN_TRANS;
stmt->fields= mysql->fields; stmt->fields= mysql->fields;
free_root(&stmt->mem_root, MYF(0));
stmt->mem_root= res->alloc; stmt->mem_root= res->alloc;
mysql->fields= NULL; mysql->fields= NULL;
my_free(res); my_free(res);
...@@ -375,6 +376,7 @@ int emb_read_binary_rows(MYSQL_STMT *stmt) ...@@ -375,6 +376,7 @@ int emb_read_binary_rows(MYSQL_STMT *stmt)
set_stmt_errmsg(stmt, &stmt->mysql->net); set_stmt_errmsg(stmt, &stmt->mysql->net);
return 1; return 1;
} }
free_root(&stmt->result.alloc, MYF(0));
stmt->result= *data; stmt->result= *data;
my_free(data); my_free(data);
set_stmt_errmsg(stmt, &stmt->mysql->net); set_stmt_errmsg(stmt, &stmt->mysql->net);
......
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