Commit 03cd7571 authored by Tom Lane's avatar Tom Lane

Fix the plpgsql memory leak exhibited in bug #4677. That leak was introduced

by my patch of 2007-01-28 to use per-subtransaction ExprContexts/EStates:
since we re-prepared any expression tree when the current subtransaction ID
changed, we'd accumulate more and more leaked expression state trees in the
outermost subtransaction if the same function was executed at multiple levels
of subtransaction nesting.  To fix, go back to the previous scheme where
there was only one EState per transaction for simple plpgsql expressions.
We really only need an ExprContext per subtransaction, not a whole EState,
so it's possible to keep prepared expression state trees in the one EState
throughout the transaction.  This should be more efficient as well as not
leaking memory for cases involving lots of subtransactions.

The added regression test is the case that inspired the 2007-01-28 patch in
the first place, just to make sure we didn't go backwards.  The current
memory leak complaint is unfortunately hard to test for in the regression
test framework, though manual testing shows it's fixed.

Although this is a pre-existing bug, I'm not back-patching because I'd like to
see this method get some field testing first.  Consider back-patching if it
gets through 8.4beta unscathed.
parent 4703250a
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.239 2009/04/02 20:16:30 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.240 2009/04/09 02:57:53 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "funcapi.h" #include "funcapi.h"
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
#include "parser/scansup.h" #include "parser/scansup.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/builtins.h" #include "utils/builtins.h"
...@@ -51,27 +52,26 @@ typedef struct ...@@ -51,27 +52,26 @@ typedef struct
* creates its own "eval_econtext" ExprContext within this estate for * creates its own "eval_econtext" ExprContext within this estate for
* per-evaluation workspace. eval_econtext is freed at normal function exit, * per-evaluation workspace. eval_econtext is freed at normal function exit,
* and the EState is freed at transaction end (in case of error, we assume * and the EState is freed at transaction end (in case of error, we assume
* that the abort mechanisms clean it all up). In order to be sure * that the abort mechanisms clean it all up). Furthermore, any exception
* ExprContext callbacks are handled properly, each subtransaction has to have * block within a function has to have its own eval_econtext separate from
* its own such EState; hence we need a stack. We use a simple counter to * the containing function's, so that we can clean up ExprContext callbacks
* distinguish different instantiations of the EState, so that we can tell * properly at subtransaction exit. We maintain a stack that tracks the
* whether we have a current copy of a prepared expression. * individual econtexts so that we can clean up correctly at subxact exit.
* *
* This arrangement is a bit tedious to maintain, but it's worth the trouble * This arrangement is a bit tedious to maintain, but it's worth the trouble
* so that we don't have to re-prepare simple expressions on each trip through * so that we don't have to re-prepare simple expressions on each trip through
* a function. (We assume the case to optimize is many repetitions of a * a function. (We assume the case to optimize is many repetitions of a
* function within a transaction.) * function within a transaction.)
*/ */
typedef struct SimpleEstateStackEntry typedef struct SimpleEcontextStackEntry
{ {
EState *xact_eval_estate; /* EState for current xact level */ ExprContext *stack_econtext; /* a stacked econtext */
long int xact_estate_simple_id; /* ID for xact_eval_estate */ SubTransactionId xact_subxid; /* ID for current subxact */
SubTransactionId xact_subxid; /* ID for current subxact */ struct SimpleEcontextStackEntry *next; /* next stack entry up */
struct SimpleEstateStackEntry *next; /* next stack entry up */ } SimpleEcontextStackEntry;
} SimpleEstateStackEntry;
static SimpleEstateStackEntry *simple_estate_stack = NULL; static EState *simple_eval_estate = NULL;
static long int simple_estate_id_counter = 0; static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
/************************************************************ /************************************************************
* Local function forward declarations * Local function forward declarations
...@@ -193,6 +193,7 @@ static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned, ...@@ -193,6 +193,7 @@ static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned,
const char *msg); const char *msg);
static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void exec_set_found(PLpgSQL_execstate *estate, bool state);
static void plpgsql_create_econtext(PLpgSQL_execstate *estate); static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
static void free_var(PLpgSQL_var *var); static void free_var(PLpgSQL_var *var);
static void assign_text_var(PLpgSQL_var *var, const char *str); static void assign_text_var(PLpgSQL_var *var, const char *str);
static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate, static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
...@@ -452,8 +453,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo) ...@@ -452,8 +453,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
((*plugin_ptr)->func_end) (&estate, func); ((*plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */ /* Clean up any leftover temporary memory */
FreeExprContext(estate.eval_econtext); plpgsql_destroy_econtext(&estate);
estate.eval_econtext = NULL;
exec_eval_cleanup(&estate); exec_eval_cleanup(&estate);
/* /*
...@@ -718,8 +718,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func, ...@@ -718,8 +718,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
((*plugin_ptr)->func_end) (&estate, func); ((*plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */ /* Clean up any leftover temporary memory */
FreeExprContext(estate.eval_econtext); plpgsql_destroy_econtext(&estate);
estate.eval_econtext = NULL;
exec_eval_cleanup(&estate); exec_eval_cleanup(&estate);
/* /*
...@@ -984,8 +983,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) ...@@ -984,8 +983,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
MemoryContext oldcontext = CurrentMemoryContext; MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner; ResourceOwner oldowner = CurrentResourceOwner;
ExprContext *old_eval_econtext = estate->eval_econtext; ExprContext *old_eval_econtext = estate->eval_econtext;
EState *old_eval_estate = estate->eval_estate;
long int old_eval_estate_simple_id = estate->eval_estate_simple_id;
estate->err_text = gettext_noop("during statement block entry"); estate->err_text = gettext_noop("during statement block entry");
...@@ -1034,10 +1031,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) ...@@ -1034,10 +1031,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner; CurrentResourceOwner = oldowner;
/* Revert to outer eval_econtext */ /*
* Revert to outer eval_econtext. (The inner one was automatically
* cleaned up during subxact exit.)
*/
estate->eval_econtext = old_eval_econtext; estate->eval_econtext = old_eval_econtext;
estate->eval_estate = old_eval_estate;
estate->eval_estate_simple_id = old_eval_estate_simple_id;
/* /*
* AtEOSubXact_SPI() should not have popped any SPI context, but * AtEOSubXact_SPI() should not have popped any SPI context, but
...@@ -1064,8 +1062,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) ...@@ -1064,8 +1062,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
/* Revert to outer eval_econtext */ /* Revert to outer eval_econtext */
estate->eval_econtext = old_eval_econtext; estate->eval_econtext = old_eval_econtext;
estate->eval_estate = old_eval_estate;
estate->eval_estate_simple_id = old_eval_estate_simple_id;
/* /*
* If AtEOSubXact_SPI() popped any SPI context of the subxact, it * If AtEOSubXact_SPI() popped any SPI context of the subxact, it
...@@ -4333,6 +4329,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -4333,6 +4329,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
Oid *rettype) Oid *rettype)
{ {
ExprContext *econtext = estate->eval_econtext; ExprContext *econtext = estate->eval_econtext;
LocalTransactionId curlxid = MyProc->lxid;
CachedPlanSource *plansource; CachedPlanSource *plansource;
CachedPlan *cplan; CachedPlan *cplan;
ParamListInfo paramLI; ParamListInfo paramLI;
...@@ -4373,14 +4370,14 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -4373,14 +4370,14 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/* /*
* Prepare the expression for execution, if it's not been done already in * Prepare the expression for execution, if it's not been done already in
* the current eval_estate. (This will be forced to happen if we called * the current transaction. (This will be forced to happen if we called
* exec_simple_check_plan above.) * exec_simple_check_plan above.)
*/ */
if (expr->expr_simple_id != estate->eval_estate_simple_id) if (expr->expr_simple_lxid != curlxid)
{ {
expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr, expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
estate->eval_estate); simple_eval_estate);
expr->expr_simple_id = estate->eval_estate_simple_id; expr->expr_simple_lxid = curlxid;
} }
/* /*
...@@ -5103,7 +5100,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr) ...@@ -5103,7 +5100,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
*/ */
expr->expr_simple_expr = tle->expr; expr->expr_simple_expr = tle->expr;
expr->expr_simple_state = NULL; expr->expr_simple_state = NULL;
expr->expr_simple_id = -1; expr->expr_simple_lxid = InvalidLocalTransactionId;
/* Also stash away the expression result type */ /* Also stash away the expression result type */
expr->expr_simple_type = exprType((Node *) tle->expr); expr->expr_simple_type = exprType((Node *) tle->expr);
} }
...@@ -5165,46 +5162,69 @@ exec_set_found(PLpgSQL_execstate *estate, bool state) ...@@ -5165,46 +5162,69 @@ exec_set_found(PLpgSQL_execstate *estate, bool state)
/* /*
* plpgsql_create_econtext --- create an eval_econtext for the current function * plpgsql_create_econtext --- create an eval_econtext for the current function
* *
* We may need to create a new eval_estate too, if there's not one already * We may need to create a new simple_eval_estate too, if there's not one
* for the current (sub) transaction. The EState will be cleaned up at * already for the current transaction. The EState will be cleaned up at
* (sub) transaction end. * transaction end.
*/ */
static void static void
plpgsql_create_econtext(PLpgSQL_execstate *estate) plpgsql_create_econtext(PLpgSQL_execstate *estate)
{ {
SubTransactionId my_subxid = GetCurrentSubTransactionId(); SimpleEcontextStackEntry *entry;
SimpleEstateStackEntry *entry = simple_estate_stack;
/* Create new EState if not one for current subxact */ /*
if (entry == NULL || * Create an EState for evaluation of simple expressions, if there's not
entry->xact_subxid != my_subxid) * one already in the current transaction. The EState is made a child of
* TopTransactionContext so it will have the right lifespan.
*/
if (simple_eval_estate == NULL)
{ {
MemoryContext oldcontext; MemoryContext oldcontext;
/* Stack entries are kept in TopTransactionContext for simplicity */ oldcontext = MemoryContextSwitchTo(TopTransactionContext);
entry = (SimpleEstateStackEntry *) simple_eval_estate = CreateExecutorState();
MemoryContextAlloc(TopTransactionContext,
sizeof(SimpleEstateStackEntry));
/* But each EState should be a child of its CurTransactionContext */
oldcontext = MemoryContextSwitchTo(CurTransactionContext);
entry->xact_eval_estate = CreateExecutorState();
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
}
/* Assign a reasonably-unique ID to this EState */ /*
entry->xact_estate_simple_id = simple_estate_id_counter++; * Create a child econtext for the current function.
entry->xact_subxid = my_subxid; */
estate->eval_econtext = CreateExprContext(simple_eval_estate);
entry->next = simple_estate_stack; /*
simple_estate_stack = entry; * Make a stack entry so we can clean up the econtext at subxact end.
} * Stack entries are kept in TopTransactionContext for simplicity.
*/
entry = (SimpleEcontextStackEntry *)
MemoryContextAlloc(TopTransactionContext,
sizeof(SimpleEcontextStackEntry));
/* Link plpgsql estate to it */ entry->stack_econtext = estate->eval_econtext;
estate->eval_estate = entry->xact_eval_estate; entry->xact_subxid = GetCurrentSubTransactionId();
estate->eval_estate_simple_id = entry->xact_estate_simple_id;
/* And create a child econtext for the current function */ entry->next = simple_econtext_stack;
estate->eval_econtext = CreateExprContext(estate->eval_estate); simple_econtext_stack = entry;
}
/*
* plpgsql_destroy_econtext --- destroy function's econtext
*
* We check that it matches the top stack entry, and destroy the stack
* entry along with the context.
*/
static void
plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
{
SimpleEcontextStackEntry *next;
Assert(simple_econtext_stack != NULL);
Assert(simple_econtext_stack->stack_econtext == estate->eval_econtext);
next = simple_econtext_stack->next;
pfree(simple_econtext_stack);
simple_econtext_stack = next;
FreeExprContext(estate->eval_econtext);
estate->eval_econtext = NULL;
} }
/* /*
...@@ -5220,29 +5240,31 @@ plpgsql_xact_cb(XactEvent event, void *arg) ...@@ -5220,29 +5240,31 @@ plpgsql_xact_cb(XactEvent event, void *arg)
* If we are doing a clean transaction shutdown, free the EState (so that * If we are doing a clean transaction shutdown, free the EState (so that
* any remaining resources will be released correctly). In an abort, we * any remaining resources will be released correctly). In an abort, we
* expect the regular abort recovery procedures to release everything of * expect the regular abort recovery procedures to release everything of
* interest. We don't need to free the individual stack entries since * interest.
* TopTransactionContext is about to go away anyway.
*
* Note: if plpgsql_subxact_cb is doing its job, there should be at most
* one stack entry, but we may as well code this as a loop.
*/ */
if (event != XACT_EVENT_ABORT) if (event != XACT_EVENT_ABORT)
{ {
while (simple_estate_stack != NULL) /* Shouldn't be any econtext stack entries left at commit */
{ Assert(simple_econtext_stack == NULL);
FreeExecutorState(simple_estate_stack->xact_eval_estate);
simple_estate_stack = simple_estate_stack->next; if (simple_eval_estate)
} FreeExecutorState(simple_eval_estate);
simple_eval_estate = NULL;
} }
else else
simple_estate_stack = NULL; {
simple_econtext_stack = NULL;
simple_eval_estate = NULL;
}
} }
/* /*
* plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup * plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup
* *
* If a simple-expression EState was created in the current subtransaction, * Make sure any simple-expression econtexts created in the current
* it has to be cleaned up. * subtransaction get cleaned up. We have to do this explicitly because
* no other code knows which child econtexts of simple_eval_estate belong
* to which level of subxact.
*/ */
void void
plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid, plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
...@@ -5251,16 +5273,15 @@ plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid, ...@@ -5251,16 +5273,15 @@ plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
if (event == SUBXACT_EVENT_START_SUB) if (event == SUBXACT_EVENT_START_SUB)
return; return;
if (simple_estate_stack != NULL && while (simple_econtext_stack != NULL &&
simple_estate_stack->xact_subxid == mySubid) simple_econtext_stack->xact_subxid == mySubid)
{ {
SimpleEstateStackEntry *next; SimpleEcontextStackEntry *next;
if (event == SUBXACT_EVENT_COMMIT_SUB) FreeExprContext(simple_econtext_stack->stack_econtext);
FreeExecutorState(simple_estate_stack->xact_eval_estate); next = simple_econtext_stack->next;
next = simple_estate_stack->next; pfree(simple_econtext_stack);
pfree(simple_estate_stack); simple_econtext_stack = next;
simple_estate_stack = next;
} }
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.109 2009/02/17 11:34:34 petere Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.110 2009/04/09 02:57:53 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -205,12 +205,12 @@ typedef struct PLpgSQL_expr ...@@ -205,12 +205,12 @@ typedef struct PLpgSQL_expr
Oid expr_simple_type; /* result type Oid, if simple */ Oid expr_simple_type; /* result type Oid, if simple */
/* /*
* if expr is simple AND prepared in current eval_estate, * if expr is simple AND prepared in current transaction,
* expr_simple_state is valid. Test validity by seeing if expr_simple_id * expr_simple_state is valid. Test validity by seeing if expr_simple_lxid
* matches eval_estate_simple_id. * matches current LXID.
*/ */
ExprState *expr_simple_state; ExprState *expr_simple_state;
long int expr_simple_id; LocalTransactionId expr_simple_lxid;
/* params to pass to expr */ /* params to pass to expr */
int nparams; int nparams;
...@@ -719,8 +719,6 @@ typedef struct ...@@ -719,8 +719,6 @@ typedef struct
uint32 eval_processed; uint32 eval_processed;
Oid eval_lastoid; Oid eval_lastoid;
ExprContext *eval_econtext; /* for executing simple expressions */ ExprContext *eval_econtext; /* for executing simple expressions */
EState *eval_estate; /* EState containing eval_econtext */
long int eval_estate_simple_id; /* ID for eval_estate */
/* status information for error context reporting */ /* status information for error context reporting */
PLpgSQL_function *err_func; /* current func */ PLpgSQL_function *err_func; /* current func */
......
...@@ -3698,3 +3698,42 @@ NOTICE: f 0 ...@@ -3698,3 +3698,42 @@ NOTICE: f 0
(4 rows) (4 rows)
drop function rttest(); drop function rttest();
-- Test for proper cleanup at subtransaction exit. This example
-- exposed a bug in PG 8.2.
CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGER AS $$
DECLARE
v_var INTEGER;
BEGIN
BEGIN
v_var := (leaker_2(fail)).error_code;
EXCEPTION
WHEN others THEN RETURN 0;
END;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER)
RETURNS RECORD AS $$
BEGIN
IF fail THEN
RAISE EXCEPTION 'fail ...';
END IF;
error_code := 1;
new_id := 1;
RETURN;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM leaker_1(false);
leaker_1
----------
1
(1 row)
SELECT * FROM leaker_1(true);
leaker_1
----------
0
(1 row)
DROP FUNCTION leaker_1(bool);
DROP FUNCTION leaker_2(bool);
...@@ -2972,3 +2972,36 @@ select * from rttest(); ...@@ -2972,3 +2972,36 @@ select * from rttest();
drop function rttest(); drop function rttest();
-- Test for proper cleanup at subtransaction exit. This example
-- exposed a bug in PG 8.2.
CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGER AS $$
DECLARE
v_var INTEGER;
BEGIN
BEGIN
v_var := (leaker_2(fail)).error_code;
EXCEPTION
WHEN others THEN RETURN 0;
END;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER)
RETURNS RECORD AS $$
BEGIN
IF fail THEN
RAISE EXCEPTION 'fail ...';
END IF;
error_code := 1;
new_id := 1;
RETURN;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM leaker_1(false);
SELECT * FROM leaker_1(true);
DROP FUNCTION leaker_1(bool);
DROP FUNCTION leaker_2(bool);
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