#At file:///home/alik/MySQL/bzr/00/bug12374486/mysql-5.5-bug12374486/ based on revid:jon.hauglid@stripped
3458 Alexander Nozdrin 2011-04-25
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.
modified:
include/mysql.h
include/mysql.h.pp
libmysql/libmysql.c
=== modified file 'include/mysql.h'
--- a/include/mysql.h 2010-11-20 22:56:09 +0000
+++ b/include/mysql.h 2011-04-25 14:12:29 +0000
@@ -573,6 +573,8 @@ typedef struct st_mysql_bind
} MYSQL_BIND;
+struct st_mysql_stmt_extension;
+
/* statement handler */
typedef struct st_mysql_stmt
{
@@ -618,7 +620,7 @@ typedef struct st_mysql_stmt
metadata fields when doing mysql_stmt_store_result.
*/
my_bool update_max_length;
- void *extension;
+ struct st_mysql_stmt_extension *extension;
} MYSQL_STMT;
enum enum_stmt_attr_type
=== modified file 'include/mysql.h.pp'
--- a/include/mysql.h.pp 2011-02-11 14:00:09 +0000
+++ b/include/mysql.h.pp 2011-04-25 14:12:29 +0000
@@ -512,6 +512,7 @@ typedef struct st_mysql_bind
my_bool is_null_value;
void *extension;
} MYSQL_BIND;
+struct st_mysql_stmt_extension;
typedef struct st_mysql_stmt
{
MEM_ROOT mem_root;
@@ -541,7 +542,7 @@ typedef struct st_mysql_stmt
unsigned char bind_result_done;
my_bool unbuffered_fetch_cancelled;
my_bool update_max_length;
- void *extension;
+ struct st_mysql_stmt_extension *extension;
} MYSQL_STMT;
enum enum_stmt_attr_type
{
=== modified file 'libmysql/libmysql.c'
--- a/libmysql/libmysql.c 2011-03-08 17:39:25 +0000
+++ b/libmysql/libmysql.c 2011-04-25 14:12:29 +0000
@@ -94,6 +94,18 @@ sig_handler my_pipe_sig_handler(int sig)
static my_bool mysql_client_init= 0;
static my_bool org_my_init_done= 0;
+typedef struct st_mysql_stmt_extension
+{
+ my_bool fields_mem_root_initialized;
+ MEM_ROOT fields_mem_root;
+} MYSQL_STMT_EXT;
+
+
+static inline MYSQL_STMT_EXT *get_stmt_exension(MYSQL_STMT *stmt)
+{
+ return (MYSQL_STMT_EXT *) stmt->extension;
+}
+
/*
Initialize the MySQL client library
@@ -1478,6 +1490,7 @@ MYSQL_STMT * STDCALL
mysql_stmt_init(MYSQL *mysql)
{
MYSQL_STMT *stmt;
+ MYSQL_STMT_EXT *stmt_ext;
DBUG_ENTER("mysql_stmt_init");
if (!(stmt= (MYSQL_STMT *) my_malloc(sizeof(MYSQL_STMT),
@@ -1497,6 +1510,11 @@ mysql_stmt_init(MYSQL *mysql)
stmt->read_row_func= stmt_read_row_no_result_set;
stmt->prefetch_rows= DEFAULT_PREFETCH_ROWS;
strmov(stmt->sqlstate, not_error_sqlstate);
+
+ stmt_ext= alloc_root(&stmt->mem_root, sizeof (MYSQL_STMT_EXT));
+ stmt_ext->fields_mem_root_initialized= FALSE;
+
+ stmt->extension= stmt_ext;
/* The rest of statement members was bzeroed inside malloc */
DBUG_RETURN(stmt);
@@ -1541,6 +1559,8 @@ int STDCALL
mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length)
{
MYSQL *mysql= stmt->mysql;
+ MYSQL_STMT_EXT *stmt_ext= get_stmt_exension(stmt);
+
DBUG_ENTER("mysql_stmt_prepare");
if (!mysql)
@@ -1571,6 +1591,11 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, con
stmt->bind_param_done= stmt->bind_result_done= FALSE;
stmt->param_count= stmt->field_count= 0;
free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC));
+ if (stmt_ext->fields_mem_root_initialized)
+ {
+ free_root(&stmt_ext->fields_mem_root, MYF(MY_KEEP_PREALLOC));
+ stmt_ext->fields_mem_root_initialized= FALSE;
+ }
int4store(buff, stmt->stmt_id);
@@ -1631,12 +1656,17 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, con
static void alloc_stmt_fields(MYSQL_STMT *stmt)
{
MYSQL_FIELD *fields, *field, *end;
- MEM_ROOT *alloc= &stmt->mem_root;
+ MYSQL_STMT_EXT *stmt_ext= get_stmt_exension(stmt);
+ MEM_ROOT *alloc= &stmt_ext->fields_mem_root;
MYSQL *mysql= stmt->mysql;
- DBUG_ASSERT(mysql->field_count);
+ DBUG_ASSERT(stmt->field_count);
- stmt->field_count= mysql->field_count;
+ if (stmt_ext->fields_mem_root_initialized)
+ free_root(&stmt_ext->fields_mem_root, MYF(0));
+
+ init_alloc_root(&stmt_ext->fields_mem_root, 2048, 2048);
+ stmt_ext->fields_mem_root_initialized= TRUE;
/*
Get the field information for non-select statements
@@ -2387,6 +2417,9 @@ static void reinit_result_set_metadata(M
prepared statements can't send result set metadata for these queries
on prepare stage. Read it now.
*/
+
+ stmt->field_count= stmt->mysql->field_count;
+
alloc_stmt_fields(stmt);
}
else
@@ -2404,7 +2437,7 @@ static void reinit_result_set_metadata(M
previous branch always works.
TODO: send metadata only when it's really necessary and add a warning
'Metadata changed' when it's sent twice.
- */
+ */
update_stmt_fields(stmt);
}
}
@@ -4600,12 +4633,19 @@ my_bool STDCALL mysql_stmt_free_result(M
my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt)
{
MYSQL *mysql= stmt->mysql;
+ MYSQL_STMT_EXT *stmt_ext= get_stmt_exension(stmt);
int rc= 0;
DBUG_ENTER("mysql_stmt_close");
free_root(&stmt->result.alloc, MYF(0));
free_root(&stmt->mem_root, MYF(0));
+ if (stmt_ext->fields_mem_root_initialized)
+ {
+ free_root(&stmt_ext->fields_mem_root, MYF(0));
+ stmt_ext->fields_mem_root_initialized= FALSE;
+ }
+
if (mysql)
{
mysql->stmts= list_delete(mysql->stmts, &stmt->list);
@@ -4805,16 +4845,13 @@ int STDCALL mysql_stmt_next_result(MYSQL
stmt->state= MYSQL_STMT_EXECUTE_DONE;
stmt->bind_result_done= FALSE;
+ stmt->field_count= mysql->field_count;
if (mysql->field_count)
{
alloc_stmt_fields(stmt);
prepare_to_fetch_result(stmt);
}
- else
- {
- stmt->field_count= mysql->field_count;
- }
DBUG_RETURN(0);
}
Attachment: [text/bzr-bundle] bzr/alexander.nozdrin@oracle.com-20110425141229-z5txne0w12a6jwk4.bundle