Commit c4841133 authored by Alexander Nozdrin's avatar Alexander Nozdrin

Patch for Bug#12374486 - SEVERE MEMORY LEAK IN PREPARED STATEMENTS

THAT CALL STORED PROCEDURES.

The bug was introduced by WL#4435. The problem was that if a stored
procedure generated a few result sets with different set of columns,
a new memory would be allocated after every EXECUTE for every
result set.

The fix is to introduce a new memory root in scope of MYSQL_STMT,
and to store result-set metadata in that memory root.
parent f9a86592
...@@ -573,6 +573,8 @@ typedef struct st_mysql_bind ...@@ -573,6 +573,8 @@ typedef struct st_mysql_bind
} MYSQL_BIND; } MYSQL_BIND;
struct st_mysql_stmt_extension;
/* statement handler */ /* statement handler */
typedef struct st_mysql_stmt typedef struct st_mysql_stmt
{ {
...@@ -618,7 +620,7 @@ typedef struct st_mysql_stmt ...@@ -618,7 +620,7 @@ typedef struct st_mysql_stmt
metadata fields when doing mysql_stmt_store_result. metadata fields when doing mysql_stmt_store_result.
*/ */
my_bool update_max_length; my_bool update_max_length;
void *extension; struct st_mysql_stmt_extension *extension;
} MYSQL_STMT; } MYSQL_STMT;
enum enum_stmt_attr_type enum enum_stmt_attr_type
......
...@@ -512,6 +512,7 @@ typedef struct st_mysql_bind ...@@ -512,6 +512,7 @@ typedef struct st_mysql_bind
my_bool is_null_value; my_bool is_null_value;
void *extension; void *extension;
} MYSQL_BIND; } MYSQL_BIND;
struct st_mysql_stmt_extension;
typedef struct st_mysql_stmt typedef struct st_mysql_stmt
{ {
MEM_ROOT mem_root; MEM_ROOT mem_root;
...@@ -541,7 +542,7 @@ typedef struct st_mysql_stmt ...@@ -541,7 +542,7 @@ typedef struct st_mysql_stmt
unsigned char bind_result_done; unsigned char bind_result_done;
my_bool unbuffered_fetch_cancelled; my_bool unbuffered_fetch_cancelled;
my_bool update_max_length; my_bool update_max_length;
void *extension; struct st_mysql_stmt_extension *extension;
} MYSQL_STMT; } MYSQL_STMT;
enum enum_stmt_attr_type enum enum_stmt_attr_type
{ {
......
...@@ -94,6 +94,11 @@ sig_handler my_pipe_sig_handler(int sig); ...@@ -94,6 +94,11 @@ sig_handler my_pipe_sig_handler(int sig);
static my_bool mysql_client_init= 0; static my_bool mysql_client_init= 0;
static my_bool org_my_init_done= 0; static my_bool org_my_init_done= 0;
typedef struct st_mysql_stmt_extension
{
MEM_ROOT fields_mem_root;
} MYSQL_STMT_EXT;
/* /*
Initialize the MySQL client library Initialize the MySQL client library
...@@ -1480,11 +1485,16 @@ mysql_stmt_init(MYSQL *mysql) ...@@ -1480,11 +1485,16 @@ mysql_stmt_init(MYSQL *mysql)
MYSQL_STMT *stmt; MYSQL_STMT *stmt;
DBUG_ENTER("mysql_stmt_init"); DBUG_ENTER("mysql_stmt_init");
if (!(stmt= (MYSQL_STMT *) my_malloc(sizeof(MYSQL_STMT), if (!(stmt=
(MYSQL_STMT *) my_malloc(sizeof (MYSQL_STMT),
MYF(MY_WME | MY_ZEROFILL))) ||
!(stmt->extension=
(MYSQL_STMT_EXT *) my_malloc(sizeof (MYSQL_STMT_EXT),
MYF(MY_WME | MY_ZEROFILL)))) MYF(MY_WME | MY_ZEROFILL))))
{ {
set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate);
DBUG_RETURN(0); my_free(stmt);
DBUG_RETURN(NULL);
} }
init_alloc_root(&stmt->mem_root, 2048, 2048); init_alloc_root(&stmt->mem_root, 2048, 2048);
...@@ -1499,6 +1509,8 @@ mysql_stmt_init(MYSQL *mysql) ...@@ -1499,6 +1509,8 @@ mysql_stmt_init(MYSQL *mysql)
strmov(stmt->sqlstate, not_error_sqlstate); strmov(stmt->sqlstate, not_error_sqlstate);
/* The rest of statement members was bzeroed inside malloc */ /* The rest of statement members was bzeroed inside malloc */
init_alloc_root(&stmt->extension->fields_mem_root, 2048, 0);
DBUG_RETURN(stmt); DBUG_RETURN(stmt);
} }
...@@ -1571,6 +1583,7 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) ...@@ -1571,6 +1583,7 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length)
stmt->bind_param_done= stmt->bind_result_done= FALSE; stmt->bind_param_done= stmt->bind_result_done= FALSE;
stmt->param_count= stmt->field_count= 0; stmt->param_count= stmt->field_count= 0;
free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC)); free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC));
free_root(&stmt->extension->fields_mem_root, MYF(0));
int4store(buff, stmt->stmt_id); int4store(buff, stmt->stmt_id);
...@@ -1631,21 +1644,21 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) ...@@ -1631,21 +1644,21 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length)
static void alloc_stmt_fields(MYSQL_STMT *stmt) static void alloc_stmt_fields(MYSQL_STMT *stmt)
{ {
MYSQL_FIELD *fields, *field, *end; MYSQL_FIELD *fields, *field, *end;
MEM_ROOT *alloc= &stmt->mem_root; MEM_ROOT *fields_mem_root= &stmt->extension->fields_mem_root;
MYSQL *mysql= stmt->mysql; MYSQL *mysql= stmt->mysql;
DBUG_ASSERT(mysql->field_count); DBUG_ASSERT(stmt->field_count);
stmt->field_count= mysql->field_count; free_root(fields_mem_root, MYF(0));
/* /*
Get the field information for non-select statements Get the field information for non-select statements
like SHOW and DESCRIBE commands like SHOW and DESCRIBE commands
*/ */
if (!(stmt->fields= (MYSQL_FIELD *) alloc_root(alloc, if (!(stmt->fields= (MYSQL_FIELD *) alloc_root(fields_mem_root,
sizeof(MYSQL_FIELD) * sizeof(MYSQL_FIELD) *
stmt->field_count)) || stmt->field_count)) ||
!(stmt->bind= (MYSQL_BIND *) alloc_root(alloc, !(stmt->bind= (MYSQL_BIND *) alloc_root(fields_mem_root,
sizeof(MYSQL_BIND) * sizeof(MYSQL_BIND) *
stmt->field_count))) stmt->field_count)))
{ {
...@@ -1658,18 +1671,36 @@ static void alloc_stmt_fields(MYSQL_STMT *stmt) ...@@ -1658,18 +1671,36 @@ static void alloc_stmt_fields(MYSQL_STMT *stmt)
field && fields < end; fields++, field++) field && fields < end; fields++, field++)
{ {
*field= *fields; /* To copy all numeric parts. */ *field= *fields; /* To copy all numeric parts. */
field->catalog= strmake_root(alloc, fields->catalog, field->catalog= strmake_root(fields_mem_root,
fields->catalog,
fields->catalog_length); fields->catalog_length);
field->db= strmake_root(alloc, fields->db, fields->db_length); field->db= strmake_root(fields_mem_root,
field->table= strmake_root(alloc, fields->table, fields->table_length); fields->db,
field->org_table= strmake_root(alloc, fields->org_table, fields->db_length);
field->table= strmake_root(fields_mem_root,
fields->table,
fields->table_length);
field->org_table= strmake_root(fields_mem_root,
fields->org_table,
fields->org_table_length); fields->org_table_length);
field->name= strmake_root(alloc, fields->name, fields->name_length); field->name= strmake_root(fields_mem_root,
field->org_name= strmake_root(alloc, fields->org_name, fields->name,
fields->name_length);
field->org_name= strmake_root(fields_mem_root,
fields->org_name,
fields->org_name_length); fields->org_name_length);
field->def= fields->def ? strmake_root(alloc, fields->def, if (fields->def)
fields->def_length) : 0; {
field->def_length= field->def ? fields->def_length : 0; field->def= strmake_root(fields_mem_root,
fields->def,
fields->def_length);
field->def_length= fields->def_length;
}
else
{
field->def= NULL;
field->def_length= 0;
}
field->extension= 0; /* Avoid dangling links. */ field->extension= 0; /* Avoid dangling links. */
field->max_length= 0; /* max_length is set in mysql_stmt_store_result() */ field->max_length= 0; /* max_length is set in mysql_stmt_store_result() */
} }
...@@ -2387,6 +2418,9 @@ static void reinit_result_set_metadata(MYSQL_STMT *stmt) ...@@ -2387,6 +2418,9 @@ static void reinit_result_set_metadata(MYSQL_STMT *stmt)
prepared statements can't send result set metadata for these queries prepared statements can't send result set metadata for these queries
on prepare stage. Read it now. on prepare stage. Read it now.
*/ */
stmt->field_count= stmt->mysql->field_count;
alloc_stmt_fields(stmt); alloc_stmt_fields(stmt);
} }
else else
...@@ -2404,7 +2438,7 @@ static void reinit_result_set_metadata(MYSQL_STMT *stmt) ...@@ -2404,7 +2438,7 @@ static void reinit_result_set_metadata(MYSQL_STMT *stmt)
previous branch always works. previous branch always works.
TODO: send metadata only when it's really necessary and add a warning TODO: send metadata only when it's really necessary and add a warning
'Metadata changed' when it's sent twice. 'Metadata changed' when it's sent twice.
*/ */
update_stmt_fields(stmt); update_stmt_fields(stmt);
} }
} }
...@@ -4605,6 +4639,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) ...@@ -4605,6 +4639,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt)
free_root(&stmt->result.alloc, MYF(0)); free_root(&stmt->result.alloc, MYF(0));
free_root(&stmt->mem_root, MYF(0)); free_root(&stmt->mem_root, MYF(0));
free_root(&stmt->extension->fields_mem_root, MYF(0));
if (mysql) if (mysql)
{ {
...@@ -4639,6 +4674,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) ...@@ -4639,6 +4674,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt)
} }
} }
my_free(stmt->extension);
my_free(stmt); my_free(stmt);
DBUG_RETURN(test(rc)); DBUG_RETURN(test(rc));
...@@ -4805,16 +4841,13 @@ int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt) ...@@ -4805,16 +4841,13 @@ int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt)
stmt->state= MYSQL_STMT_EXECUTE_DONE; stmt->state= MYSQL_STMT_EXECUTE_DONE;
stmt->bind_result_done= FALSE; stmt->bind_result_done= FALSE;
stmt->field_count= mysql->field_count;
if (mysql->field_count) if (mysql->field_count)
{ {
alloc_stmt_fields(stmt); alloc_stmt_fields(stmt);
prepare_to_fetch_result(stmt); prepare_to_fetch_result(stmt);
} }
else
{
stmt->field_count= mysql->field_count;
}
DBUG_RETURN(0); DBUG_RETURN(0);
} }
......
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