Commit 59ff5274 authored by unknown's avatar unknown

Fix for bug #17260 "Multiple invocations of triggers or stored functions

hog memory".

During each invocation of stored function or trigger some objects which
lifetime is one function call (e.g. sp_rcontext) were allocated on
arena/memroot of calling statement. This led to consumption of fixed amount
of memory for each function/trigger invocation and so statements which
involve lot of them were hogging memory. This in its return led to OOM
crashes or freezes.

This fix introduces new memroot and arena for objects which lifetime is
whole duration of function call. So all memory consumed by such objects
is freed at the end of function call.


sql/sp_head.cc:
  sp_head::execute_function():
    Introduced new memroot and arena for objects which lifetime is whole
    duration of function call (e.g. sp_rcontext, sp_cursor). We can't
    use caller's arena/memroot for those objects because in this case 
    some fixed amount of memory will be consumed for each function/trigger
    invocation and so statements which involve lot of them will hog memory.
    Got rid of param_values array to avoid excessive juggling with arenas.
parent 9f606932
...@@ -1209,128 +1209,142 @@ bool ...@@ -1209,128 +1209,142 @@ bool
sp_head::execute_function(THD *thd, Item **argp, uint argcount, sp_head::execute_function(THD *thd, Item **argp, uint argcount,
Field *return_value_fld) Field *return_value_fld)
{ {
Item_cache **param_values;
ulonglong binlog_save_options; ulonglong binlog_save_options;
bool need_binlog_call; bool need_binlog_call;
uint params; uint arg_no;
sp_rcontext *octx = thd->spcont; sp_rcontext *octx = thd->spcont;
sp_rcontext *nctx = NULL; sp_rcontext *nctx = NULL;
char buf[STRING_BUFFER_USUAL_SIZE];
String binlog_buf(buf, sizeof(buf), &my_charset_bin);
bool err_status= FALSE; bool err_status= FALSE;
MEM_ROOT call_mem_root;
Query_arena call_arena(&call_mem_root, Query_arena::INITIALIZED_FOR_SP);
Query_arena backup_arena;
DBUG_ENTER("sp_head::execute_function"); DBUG_ENTER("sp_head::execute_function");
DBUG_PRINT("info", ("function %s", m_name.str)); DBUG_PRINT("info", ("function %s", m_name.str));
params = m_pcont->context_var_count();
/* /*
Check that the function is called with all specified arguments. Check that the function is called with all specified arguments.
If it is not, use my_error() to report an error, or it will not terminate If it is not, use my_error() to report an error, or it will not terminate
the invoking query properly. the invoking query properly.
*/ */
if (argcount != m_pcont->context_var_count())
if (argcount != params)
{ {
/* /*
Need to use my_error here, or it will not terminate the Need to use my_error here, or it will not terminate the
invoking query properly. invoking query properly.
*/ */
my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0),
"FUNCTION", m_qname.str, params, argcount); "FUNCTION", m_qname.str, m_pcont->context_var_count(), argcount);
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
} }
/*
/* Allocate param_values to be used for dumping the call into binlog. */ Prepare arena and memroot for objects which lifetime is whole
duration of function call (sp_rcontext, it's tables and items,
if (!(param_values= (Item_cache**)thd->alloc(sizeof(Item_cache*)*argcount))) sp_cursor and Item_cache holders for case expressions).
DBUG_RETURN(TRUE); We can't use caller's arena/memroot for those objects because
in this case some fixed amount of memory will be consumed for
// QQ Should have some error checking here? (types, etc...) each function/trigger invocation and so statements which involve
lot of them will hog memory.
TODO: we should create sp_rcontext once per command and reuse
it on subsequent executions of a function/trigger.
*/
init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0);
thd->set_n_backup_active_arena(&call_arena, &backup_arena);
if (!(nctx= new sp_rcontext(m_pcont, return_value_fld, octx)) || if (!(nctx= new sp_rcontext(m_pcont, return_value_fld, octx)) ||
nctx->init(thd)) nctx->init(thd))
{ {
delete nctx; /* Delete nctx if it was init() that failed. */ thd->restore_active_arena(&call_arena, &backup_arena);
DBUG_RETURN(TRUE); err_status= TRUE;
goto err_with_cleanup;
} }
/*
We have to switch temporarily back to callers arena/memroot.
Function arguments belong to the caller and so the may reference
memory which they will allocate during calculation long after
this function call will be finished (e.g. in Item::cleanup()).
*/
thd->restore_active_arena(&call_arena, &backup_arena);
#ifndef DBUG_OFF #ifndef DBUG_OFF
nctx->sp= this; nctx->sp= this;
#endif #endif
/* Pass arguments. */ /* Pass arguments. */
for (arg_no= 0; arg_no < argcount; arg_no++)
{ {
uint i; /* Arguments must be fixed in Item_func_sp::fix_fields */
DBUG_ASSERT(argp[arg_no]->fixed);
for (i= 0 ; i < argcount ; i++) if ((err_status= nctx->set_variable(thd, arg_no, argp[arg_no])))
{ goto err_with_cleanup;
if (!argp[i]->fixed && argp[i]->fix_fields(thd, &argp[i]))
{
err_status= TRUE;
break;
} }
param_values[i]= Item_cache::get_cache(argp[i]->result_type()); need_binlog_call= mysql_bin_log.is_open() && (thd->options & OPTION_BIN_LOG);
param_values[i]->store(argp[i]);
if (nctx->set_variable(thd, i, param_values[i])) /*
Remember the original arguments for unrolled replication of functions
before they are changed by execution.
*/
if (need_binlog_call)
{ {
err_status= TRUE; binlog_buf.length(0);
break; binlog_buf.append(STRING_WITH_LEN("SELECT "));
} append_identifier(thd, &binlog_buf, m_name.str, m_name.length);
} binlog_buf.append('(');
} for (arg_no= 0; arg_no < argcount; arg_no++)
if (err_status)
{ {
delete nctx; String str_value_holder;
DBUG_RETURN(TRUE); String *str_value;
}
if (arg_no)
binlog_buf.append(',');
str_value= sp_get_item_value(nctx->get_item(arg_no),
&str_value_holder);
if (str_value)
binlog_buf.append(*str_value);
else
binlog_buf.append(STRING_WITH_LEN("NULL"));
}
binlog_buf.append(')');
}
thd->spcont= nctx; thd->spcont= nctx;
binlog_save_options= thd->options; binlog_save_options= thd->options;
need_binlog_call= mysql_bin_log.is_open() && (thd->options & OPTION_BIN_LOG);
if (need_binlog_call) if (need_binlog_call)
{ {
reset_dynamic(&thd->user_var_events); reset_dynamic(&thd->user_var_events);
mysql_bin_log.start_union_events(thd); mysql_bin_log.start_union_events(thd);
} }
/*
Switch to call arena/mem_root so objects like sp_cursor or
Item_cache holders for case expressions can be allocated on it.
TODO: In future we should associate call arena/mem_root with
sp_rcontext and allocate all these objects (and sp_rcontext
itself) on it directly rather than juggle with arenas.
*/
thd->set_n_backup_active_arena(&call_arena, &backup_arena);
thd->options&= ~OPTION_BIN_LOG; thd->options&= ~OPTION_BIN_LOG;
err_status= execute(thd); err_status= execute(thd);
thd->options= binlog_save_options; thd->options= binlog_save_options;
thd->restore_active_arena(&call_arena, &backup_arena);
if (need_binlog_call) if (need_binlog_call)
mysql_bin_log.stop_union_events(thd); mysql_bin_log.stop_union_events(thd);
if (need_binlog_call && thd->binlog_evt_union.unioned_events) if (need_binlog_call && thd->binlog_evt_union.unioned_events)
{ {
char buf[256]; Query_log_event qinfo(thd, binlog_buf.ptr(), binlog_buf.length(),
String bufstr(buf, sizeof(buf), &my_charset_bin);
bufstr.length(0);
bufstr.append(STRING_WITH_LEN("SELECT "));
append_identifier(thd, &bufstr, m_name.str, m_name.length);
bufstr.append('(');
for (uint i=0; i < argcount; i++)
{
String str_value_holder;
String *str_value;
if (i)
bufstr.append(',');
str_value= sp_get_item_value(param_values[i], &str_value_holder);
if (str_value)
bufstr.append(*str_value);
else
bufstr.append(STRING_WITH_LEN("NULL"));
}
bufstr.append(')');
Query_log_event qinfo(thd, bufstr.ptr(), bufstr.length(),
thd->binlog_evt_union.unioned_events_trans, FALSE); thd->binlog_evt_union.unioned_events_trans, FALSE);
if (mysql_bin_log.write(&qinfo) && if (mysql_bin_log.write(&qinfo) &&
thd->binlog_evt_union.unioned_events_trans) thd->binlog_evt_union.unioned_events_trans)
...@@ -1353,8 +1367,13 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, ...@@ -1353,8 +1367,13 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
} }
} }
nctx->pop_all_cursors(); // To avoid memory leaks after an error nctx->pop_all_cursors(); // To avoid memory leaks after an error
err_with_cleanup:
delete nctx; delete nctx;
call_arena.free_items();
free_root(&call_mem_root, MYF(0));
thd->spcont= octx; thd->spcont= octx;
DBUG_RETURN(err_status); DBUG_RETURN(err_status);
......
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