Skip to content

Commit 007b330

Browse files
author
Nisha Gopalakrishnan
committed
BUG#16857395 - EXCESSIVE MEMORY USAGE AFTER REPEATED TRIGGER
EXCEPTION HANDLERS Analysis -------- Excessive memory consumption is observed when executing a stored procedure multiple times when: a) The stored procedure has an SQL statement which fails during validation. b) The stored procedure has an an SQL statement which requires to be re-prepared. Under the above scenarios, the query within the stored procedure is invalidated during the execution. The invalidation calls for the re-parse of the query. During the re-parse, the lex tree is allocated on the persistent mem_root of the stored procedure. When the stored procedure is executed multiple times, the repeated allocation of the new lex tree on the persistent mem_root is observed. The previous allocations for the lex tree in the mem_root are never freed. In addition to the lex tree, the Ref_ptr_array(array of pointers to the field items) was also allocated on the persistent mem_root and never freed during re-parse. Hence the memory growth was observed. Also the patch solves the memory growth due to the cases listed below: a) Allocation of Sql_condition and its memory on persistent mem-root every time a condition is raised. b) Allocation of Handler-structure on the persistent mem-root every time an SQL-handler is pushed. Observed when the execution flow goes to DECLARE HANDLER statement. c) Allocation of Cursor-structure on the persistent mem-root every time a cursor is pushed. Observed when the DECLARE CURSOR statement is executed. Fix: ---- 1) Patch for the allocation of the lex tree and 'Ref_ptr_array' on the persistent mem_root: A new mem_root 'm_lex_mem_root' is introduced which holds the lex tree and the Ref_ptr_array. When a query is invalidated, before the reparse we free this mem_root, init the root and use it to allocate the newly parsed lex tree. The 'ref_pointer_array' part of the lex tree object points the Ref_ptr_array. Since 'Ref_ptr_array' should not be allocated on the persistent mem_root, we switch the stmt_arena's mem_root to that of the new 'm_lex_mem_root'. This mem_root is persistent until reparse or the stored routine is dropped, hence the 'Ref_ptr_array' is allocated on the new mem_root as well. Since this mem_root is freed and re-initialised during re-parse we will not observe a memory growth. Also ensures that the prepared statement execution is unaffected. 2) Patch for the next set of issues listed above: a) The Sql_condition is a temporary instance hence initialised in the execution mem_root which is freed at the end of the execution. b) The handler is now stored on the heap instead of the caller's mem_root thus avoiding the memory growth. c) The cursor is now stored on the heap instead of the caller's mem_root thus avoiding the memory growth. Note: I have not added an mtr test case: A test case which executes for a longer period is needed to observe the increasing memory consumption and is not suitable for mtr. Also the existing test suite has tests which uses the new mem_root.
1 parent 30e2c99 commit 007b330

File tree

4 files changed

+146
-65
lines changed

4 files changed

+146
-65
lines changed

sql/sp_instr.cc

+20-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License as published by
@@ -458,6 +458,25 @@ LEX *sp_lex_instr::parse_expr(THD *thd, sp_head *sp)
458458
return NULL;
459459
}
460460

461+
// Cleanup current THD from previously held objects before new parsing.
462+
cleanup_before_parsing(thd);
463+
464+
// Cleanup and re-init the lex mem_root for re-parse.
465+
free_root(&m_lex_mem_root, MYF(0));
466+
init_sql_alloc(&m_lex_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
467+
468+
/*
469+
Switch mem-roots. We store the new LEX and its Items in the
470+
m_lex_mem_root since it is freed before reparse triggered due to
471+
invalidation. This avoids the memory leak in case re-parse is
472+
initiated. Also set the statement query arena to the lex mem_root.
473+
*/
474+
MEM_ROOT *execution_mem_root= thd->mem_root;
475+
Query_arena parse_arena(&m_lex_mem_root, thd->stmt_arena->state);
476+
477+
thd->mem_root= &m_lex_mem_root;
478+
thd->stmt_arena->set_query_arena(&parse_arena);
479+
461480
// Prepare parser state. It can be done just before parse_sql(), do it here
462481
// only to simplify exit in case of failure (out-of-memory error).
463482

@@ -466,17 +485,6 @@ LEX *sp_lex_instr::parse_expr(THD *thd, sp_head *sp)
466485
if (parser_state.init(thd, sql_query.c_ptr(), sql_query.length()))
467486
return NULL;
468487

469-
// Cleanup current THD from previously held objects before new parsing.
470-
471-
cleanup_before_parsing(thd);
472-
473-
// Switch mem-roots. We need to store new LEX and its Items in the persistent
474-
// SP-memory (memory which is not freed between executions).
475-
476-
MEM_ROOT *execution_mem_root= thd->mem_root;
477-
478-
thd->mem_root= thd->sp_runtime_ctx->sp->get_persistent_mem_root();
479-
480488
// Switch THD::free_list. It's used to remember the newly created set of Items
481489
// during parsing. We should clean those items after each execution.
482490

sql/sp_instr.h

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License as published by
@@ -177,10 +177,21 @@ class sp_lex_instr : public sp_instr
177177
m_lex_query_tables_own_last(NULL)
178178
{
179179
set_lex(lex, is_lex_owner);
180+
memset(&m_lex_mem_root, 0, sizeof (MEM_ROOT));
180181
}
181182

182183
virtual ~sp_lex_instr()
183-
{ free_lex(); }
184+
{
185+
free_lex();
186+
/*
187+
If the instruction is reparsed, m_lex_mem_root was used to allocate the
188+
items, then freeing the memroot, frees the items. Hence set the free_list
189+
pointer to NULL.
190+
*/
191+
if (alloc_root_inited(&m_lex_mem_root))
192+
free_list= NULL;
193+
free_root(&m_lex_mem_root, MYF(0));
194+
}
184195

185196
/**
186197
Make a few attempts to execute the instruction.
@@ -334,6 +345,13 @@ class sp_lex_instr : public sp_instr
334345
virtual void cleanup_before_parsing(THD *thd);
335346

336347
private:
348+
/**
349+
Mem-root for storing the LEX-tree during reparse. This
350+
mem-root is freed when a reparse is triggered or the stored
351+
routine is dropped.
352+
*/
353+
MEM_ROOT m_lex_mem_root;
354+
337355
/// LEX-object.
338356
LEX *m_lex;
339357

sql/sp_rcontext.cc

+77-34
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@
2323
#include "sql_tmp_table.h" // create_virtual_tmp_table
2424
#include "sp_instr.h"
2525

26+
extern "C" void sql_alloc_error_handler(void);
2627

2728
///////////////////////////////////////////////////////////////////////////
2829
// sp_rcontext implementation.
@@ -51,8 +52,12 @@ sp_rcontext::~sp_rcontext()
5152
while (m_activated_handlers.elements())
5253
delete m_activated_handlers.pop();
5354

54-
// Leave m_visible_handlers, m_var_items, m_cstack
55-
// and m_case_expr_holders untouched.
55+
while (m_visible_handlers.elements())
56+
delete m_visible_handlers.pop();
57+
58+
pop_all_cursors();
59+
60+
// Leave m_var_items and m_case_expr_holders untouched.
5661
// They are allocated in mem roots and will be freed accordingly.
5762
}
5863

@@ -158,13 +163,19 @@ bool sp_rcontext::set_return_value(THD *thd, Item **return_value_item)
158163
bool sp_rcontext::push_cursor(sp_instr_cpush *i)
159164
{
160165
/*
161-
We should create cursors in the callers arena, as
162-
it could be (and usually is) used in several instructions.
166+
We should create cursors on the system heap because:
167+
- they could be (and usually are) used in several instructions,
168+
thus they can not be stored on an execution mem-root;
169+
- a cursor can be pushed/popped many times in a loop, having these objects
170+
on callers' mem-root would lead to a memory leak in every iteration.
163171
*/
164-
sp_cursor *c= new (callers_arena->mem_root) sp_cursor(i);
172+
sp_cursor *c= new (std::nothrow) sp_cursor(i);
165173

166-
if (c == NULL)
174+
if (!c)
175+
{
176+
sql_alloc_error_handler();
167177
return true;
178+
}
168179

169180
m_cstack[m_ccount++]= c;
170181
return false;
@@ -183,14 +194,22 @@ void sp_rcontext::pop_cursors(uint count)
183194
bool sp_rcontext::push_handler(sp_handler *handler, uint first_ip)
184195
{
185196
/*
186-
We should create handler entries in the callers arena, as
187-
they could be (and usually are) used in several instructions.
197+
We should create handler entries on the system heap because:
198+
- they could be (and usually are) used in several instructions,
199+
thus they can not be stored on an execution mem-root;
200+
- a handler can be pushed/popped many times in a loop, having these
201+
objects on callers' mem-root would lead to a memory leak in every
202+
iteration.
188203
*/
204+
189205
sp_handler_entry *he=
190-
new (callers_arena->mem_root) sp_handler_entry(handler, first_ip);
206+
new (std::nothrow) sp_handler_entry(handler, first_ip);
191207

192-
if (he == NULL)
208+
if (!he)
209+
{
210+
sql_alloc_error_handler();
193211
return true;
212+
}
194213

195214
return m_visible_handlers.append(he);
196215
}
@@ -203,7 +222,7 @@ void sp_rcontext::pop_handlers(sp_pcontext *current_scope)
203222
int handler_level= m_visible_handlers.at(i)->handler->scope->get_level();
204223

205224
if (handler_level >= current_scope->get_level())
206-
m_visible_handlers.pop();
225+
delete m_visible_handlers.pop();
207226
}
208227
}
209228

@@ -249,7 +268,11 @@ bool sp_rcontext::handle_sql_condition(THD *thd,
249268

250269
Diagnostics_area *da= thd->get_stmt_da();
251270
const sp_handler *found_handler= NULL;
252-
const Sql_condition *found_condition= NULL;
271+
272+
uint condition_sql_errno= 0;
273+
Sql_condition::enum_warning_level condition_level;
274+
const char *condition_sqlstate;
275+
const char *condition_message;
253276

254277
if (thd->is_error())
255278
{
@@ -259,24 +282,29 @@ bool sp_rcontext::handle_sql_condition(THD *thd,
259282
da->sql_errno(),
260283
Sql_condition::WARN_LEVEL_ERROR);
261284

262-
if (found_handler)
263-
found_condition= da->get_error_condition();
285+
if (!found_handler)
286+
DBUG_RETURN(false);
264287

265-
/*
266-
Found condition can be NULL if the diagnostics area was full
267-
when the error was raised. It can also be NULL if
268-
Diagnostics_area::set_error_status(uint sql_error) was used.
269-
In these cases, make a temporary Sql_condition here so the
270-
error can be handled.
271-
*/
272-
if (!found_condition)
288+
if (da->get_error_condition())
273289
{
274-
Sql_condition *condition=
275-
new (callers_arena->mem_root) Sql_condition(callers_arena->mem_root);
276-
condition->set(da->sql_errno(), da->get_sqlstate(),
277-
Sql_condition::WARN_LEVEL_ERROR,
278-
da->message());
279-
found_condition= condition;
290+
const Sql_condition *c= da->get_error_condition();
291+
condition_sql_errno= c->get_sql_errno();
292+
condition_level= c->get_level();
293+
condition_sqlstate= c->get_sqlstate();
294+
condition_message= c->get_message_text();
295+
}
296+
else
297+
{
298+
/*
299+
SQL condition can be NULL if the diagnostics area was full
300+
when the error was raised. It can also be NULL if
301+
Diagnostics_area::set_error_status(uint sql_error) was used.
302+
*/
303+
304+
condition_sql_errno= da->sql_errno();
305+
condition_level= Sql_condition::WARN_LEVEL_ERROR;
306+
condition_sqlstate= da->get_sqlstate();
307+
condition_message= da->message();
280308
}
281309
}
282310
else if (da->current_statement_warn_count())
@@ -301,7 +329,11 @@ bool sp_rcontext::handle_sql_condition(THD *thd,
301329
if (handler)
302330
{
303331
found_handler= handler;
304-
found_condition= c;
332+
333+
condition_sql_errno= c->get_sql_errno();
334+
condition_level= c->get_level();
335+
condition_sqlstate= c->get_sqlstate();
336+
condition_message= c->get_message_text();
305337
}
306338
}
307339
}
@@ -314,7 +346,7 @@ bool sp_rcontext::handle_sql_condition(THD *thd,
314346
// - there is a pending SQL-condition (error or warning);
315347
// - there is an SQL-handler for it.
316348

317-
DBUG_ASSERT(found_condition);
349+
DBUG_ASSERT(condition_sql_errno != 0);
318350

319351
sp_handler_entry *handler_entry= NULL;
320352
for (int i= 0; i < m_visible_handlers.elements(); ++i)
@@ -363,9 +395,20 @@ bool sp_rcontext::handle_sql_condition(THD *thd,
363395
// (e.g. "bad data").
364396

365397
/* Add a frame to handler-call-stack. */
366-
Handler_call_frame *frame= new Handler_call_frame(found_handler,
367-
found_condition,
368-
continue_ip);
398+
Handler_call_frame *frame=
399+
new (std::nothrow) Handler_call_frame(found_handler,
400+
condition_sql_errno,
401+
condition_sqlstate,
402+
condition_level,
403+
condition_message,
404+
continue_ip);
405+
406+
if (!frame)
407+
{
408+
sql_alloc_error_handler();
409+
DBUG_RETURN(false);
410+
}
411+
369412
m_activated_handlers.append(frame);
370413

371414
*ip= handler_entry->first_ip;

sql/sp_rcontext.h

+29-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License as published by
@@ -79,7 +79,7 @@ class sp_rcontext : public Sql_alloc
7979
private:
8080
/// This is an auxillary class to store entering instruction pointer for an
8181
/// SQL-handler.
82-
class sp_handler_entry : public Sql_alloc
82+
class sp_handler_entry
8383
{
8484
public:
8585
/// Handler definition (from parsing context).
@@ -130,16 +130,21 @@ class sp_rcontext : public Sql_alloc
130130

131131
/// The constructor.
132132
///
133-
/// @param _sql_condition The SQL condition.
134-
/// @param arena Query arena for SP
135-
Sql_condition_info(const Sql_condition *_sql_condition)
136-
:sql_errno(_sql_condition->get_sql_errno()),
137-
level(_sql_condition->get_level())
133+
/// @param _sql_errno SQL error number.
134+
/// @param _sql_state Diagnostic status.
135+
/// @param _level Error level of the condition.
136+
/// @param _message Error message.
137+
Sql_condition_info(uint _sql_errno,
138+
const char *_sql_state,
139+
Sql_condition::enum_warning_level _level,
140+
const char *_message)
141+
:sql_errno(_sql_errno),
142+
level(_level)
138143
{
139-
memcpy(sql_state, _sql_condition->get_sqlstate(), SQLSTATE_LENGTH);
144+
memcpy(sql_state, _sql_state, SQLSTATE_LENGTH);
140145
sql_state[SQLSTATE_LENGTH]= '\0';
141146

142-
strncpy(message, _sql_condition->get_message_text(), MYSQL_ERRMSG_SIZE);
147+
strncpy(message, _message, MYSQL_ERRMSG_SIZE);
143148
}
144149
};
145150

@@ -154,21 +159,28 @@ class sp_rcontext : public Sql_alloc
154159
const sp_handler *handler;
155160

156161
/// SQL-condition, triggered handler activation.
157-
const Sql_condition_info sql_condition;
162+
Sql_condition_info sql_condition_info;
158163

159164
/// Continue-instruction-pointer for CONTINUE-handlers.
160165
/// The attribute contains 0 for EXIT-handlers.
161166
uint continue_ip;
162167

163168
/// The constructor.
164169
///
165-
/// @param _sql_condition SQL-condition, triggered handler activation.
166-
/// @param _continue_ip Continue instruction pointer.
170+
/// @param _handler handler triggered due to SQL-condition.
171+
/// @param _sql_errno SQL error number.
172+
/// @param _sql_state Diagnostic status.
173+
/// @param _level Error level of the condition.
174+
/// @param _message Error message.
175+
/// @param _continue_ip Continue instruction pointer.
167176
Handler_call_frame(const sp_handler *_handler,
168-
const Sql_condition *_sql_condition,
177+
uint _sql_errno,
178+
const char *_sql_state,
179+
Sql_condition::enum_warning_level _level,
180+
const char *_message,
169181
uint _continue_ip)
170182
:handler(_handler),
171-
sql_condition(_sql_condition),
183+
sql_condition_info(_sql_errno, _sql_state, _level, _message),
172184
continue_ip(_continue_ip)
173185
{ }
174186
};
@@ -229,7 +241,7 @@ class sp_rcontext : public Sql_alloc
229241
const Sql_condition_info *raised_condition() const
230242
{
231243
return m_activated_handlers.elements() ?
232-
&(*m_activated_handlers.back())->sql_condition : NULL;
244+
&(*m_activated_handlers.back())->sql_condition_info : NULL;
233245
}
234246

235247
/// Handle current SQL condition (if any).
@@ -420,7 +432,7 @@ typedef class st_select_lex_unit SELECT_LEX_UNIT;
420432

421433
/* A mediator between stored procedures and server side cursors */
422434

423-
class sp_cursor : public Sql_alloc
435+
class sp_cursor
424436
{
425437
private:
426438
/// An interceptor of cursor result set used to implement
@@ -468,6 +480,6 @@ class sp_cursor : public Sql_alloc
468480

469481
private:
470482
void destroy();
471-
}; // class sp_cursor : public Sql_alloc
483+
}; // class sp_cursor
472484

473485
#endif /* _SP_RCONTEXT_H_ */

0 commit comments

Comments
 (0)