Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 03cd757

Browse files
committed
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.
1 parent 4703250 commit 03cd757

File tree

4 files changed

+176
-85
lines changed

4 files changed

+176
-85
lines changed

src/pl/plpgsql/src/pl_exec.c

+99-78
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.239 2009/04/02 20:16:30 tgl Exp $
11+
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.240 2009/04/09 02:57:53 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -24,6 +24,7 @@
2424
#include "funcapi.h"
2525
#include "nodes/nodeFuncs.h"
2626
#include "parser/scansup.h"
27+
#include "storage/proc.h"
2728
#include "tcop/tcopprot.h"
2829
#include "utils/array.h"
2930
#include "utils/builtins.h"
@@ -51,27 +52,26 @@ typedef struct
5152
* creates its own "eval_econtext" ExprContext within this estate for
5253
* per-evaluation workspace. eval_econtext is freed at normal function exit,
5354
* and the EState is freed at transaction end (in case of error, we assume
54-
* that the abort mechanisms clean it all up). In order to be sure
55-
* ExprContext callbacks are handled properly, each subtransaction has to have
56-
* its own such EState; hence we need a stack. We use a simple counter to
57-
* distinguish different instantiations of the EState, so that we can tell
58-
* whether we have a current copy of a prepared expression.
55+
* that the abort mechanisms clean it all up). Furthermore, any exception
56+
* block within a function has to have its own eval_econtext separate from
57+
* the containing function's, so that we can clean up ExprContext callbacks
58+
* properly at subtransaction exit. We maintain a stack that tracks the
59+
* individual econtexts so that we can clean up correctly at subxact exit.
5960
*
6061
* This arrangement is a bit tedious to maintain, but it's worth the trouble
6162
* so that we don't have to re-prepare simple expressions on each trip through
6263
* a function. (We assume the case to optimize is many repetitions of a
6364
* function within a transaction.)
6465
*/
65-
typedef struct SimpleEstateStackEntry
66+
typedef struct SimpleEcontextStackEntry
6667
{
67-
EState *xact_eval_estate; /* EState for current xact level */
68-
long int xact_estate_simple_id; /* ID for xact_eval_estate */
69-
SubTransactionId xact_subxid; /* ID for current subxact */
70-
struct SimpleEstateStackEntry *next; /* next stack entry up */
71-
} SimpleEstateStackEntry;
68+
ExprContext *stack_econtext; /* a stacked econtext */
69+
SubTransactionId xact_subxid; /* ID for current subxact */
70+
struct SimpleEcontextStackEntry *next; /* next stack entry up */
71+
} SimpleEcontextStackEntry;
7272

73-
static SimpleEstateStackEntry *simple_estate_stack = NULL;
74-
static long int simple_estate_id_counter = 0;
73+
static EState *simple_eval_estate = NULL;
74+
static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
7575

7676
/************************************************************
7777
* Local function forward declarations
@@ -193,6 +193,7 @@ static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned,
193193
const char *msg);
194194
static void exec_set_found(PLpgSQL_execstate *estate, bool state);
195195
static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
196+
static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
196197
static void free_var(PLpgSQL_var *var);
197198
static void assign_text_var(PLpgSQL_var *var, const char *str);
198199
static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
@@ -452,8 +453,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
452453
((*plugin_ptr)->func_end) (&estate, func);
453454

454455
/* Clean up any leftover temporary memory */
455-
FreeExprContext(estate.eval_econtext);
456-
estate.eval_econtext = NULL;
456+
plpgsql_destroy_econtext(&estate);
457457
exec_eval_cleanup(&estate);
458458

459459
/*
@@ -718,8 +718,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
718718
((*plugin_ptr)->func_end) (&estate, func);
719719

720720
/* Clean up any leftover temporary memory */
721-
FreeExprContext(estate.eval_econtext);
722-
estate.eval_econtext = NULL;
721+
plpgsql_destroy_econtext(&estate);
723722
exec_eval_cleanup(&estate);
724723

725724
/*
@@ -984,8 +983,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
984983
MemoryContext oldcontext = CurrentMemoryContext;
985984
ResourceOwner oldowner = CurrentResourceOwner;
986985
ExprContext *old_eval_econtext = estate->eval_econtext;
987-
EState *old_eval_estate = estate->eval_estate;
988-
long int old_eval_estate_simple_id = estate->eval_estate_simple_id;
989986

990987
estate->err_text = gettext_noop("during statement block entry");
991988

@@ -1034,10 +1031,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
10341031
MemoryContextSwitchTo(oldcontext);
10351032
CurrentResourceOwner = oldowner;
10361033

1037-
/* Revert to outer eval_econtext */
1034+
/*
1035+
* Revert to outer eval_econtext. (The inner one was automatically
1036+
* cleaned up during subxact exit.)
1037+
*/
10381038
estate->eval_econtext = old_eval_econtext;
1039-
estate->eval_estate = old_eval_estate;
1040-
estate->eval_estate_simple_id = old_eval_estate_simple_id;
10411039

10421040
/*
10431041
* AtEOSubXact_SPI() should not have popped any SPI context, but
@@ -1064,8 +1062,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
10641062

10651063
/* Revert to outer eval_econtext */
10661064
estate->eval_econtext = old_eval_econtext;
1067-
estate->eval_estate = old_eval_estate;
1068-
estate->eval_estate_simple_id = old_eval_estate_simple_id;
10691065

10701066
/*
10711067
* If AtEOSubXact_SPI() popped any SPI context of the subxact, it
@@ -4333,6 +4329,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
43334329
Oid *rettype)
43344330
{
43354331
ExprContext *econtext = estate->eval_econtext;
4332+
LocalTransactionId curlxid = MyProc->lxid;
43364333
CachedPlanSource *plansource;
43374334
CachedPlan *cplan;
43384335
ParamListInfo paramLI;
@@ -4373,14 +4370,14 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
43734370

43744371
/*
43754372
* Prepare the expression for execution, if it's not been done already in
4376-
* the current eval_estate. (This will be forced to happen if we called
4373+
* the current transaction. (This will be forced to happen if we called
43774374
* exec_simple_check_plan above.)
43784375
*/
4379-
if (expr->expr_simple_id != estate->eval_estate_simple_id)
4376+
if (expr->expr_simple_lxid != curlxid)
43804377
{
43814378
expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
4382-
estate->eval_estate);
4383-
expr->expr_simple_id = estate->eval_estate_simple_id;
4379+
simple_eval_estate);
4380+
expr->expr_simple_lxid = curlxid;
43844381
}
43854382

43864383
/*
@@ -5103,7 +5100,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
51035100
*/
51045101
expr->expr_simple_expr = tle->expr;
51055102
expr->expr_simple_state = NULL;
5106-
expr->expr_simple_id = -1;
5103+
expr->expr_simple_lxid = InvalidLocalTransactionId;
51075104
/* Also stash away the expression result type */
51085105
expr->expr_simple_type = exprType((Node *) tle->expr);
51095106
}
@@ -5165,46 +5162,69 @@ exec_set_found(PLpgSQL_execstate *estate, bool state)
51655162
/*
51665163
* plpgsql_create_econtext --- create an eval_econtext for the current function
51675164
*
5168-
* We may need to create a new eval_estate too, if there's not one already
5169-
* for the current (sub) transaction. The EState will be cleaned up at
5170-
* (sub) transaction end.
5165+
* We may need to create a new simple_eval_estate too, if there's not one
5166+
* already for the current transaction. The EState will be cleaned up at
5167+
* transaction end.
51715168
*/
51725169
static void
51735170
plpgsql_create_econtext(PLpgSQL_execstate *estate)
51745171
{
5175-
SubTransactionId my_subxid = GetCurrentSubTransactionId();
5176-
SimpleEstateStackEntry *entry = simple_estate_stack;
5172+
SimpleEcontextStackEntry *entry;
51775173

5178-
/* Create new EState if not one for current subxact */
5179-
if (entry == NULL ||
5180-
entry->xact_subxid != my_subxid)
5174+
/*
5175+
* Create an EState for evaluation of simple expressions, if there's not
5176+
* one already in the current transaction. The EState is made a child of
5177+
* TopTransactionContext so it will have the right lifespan.
5178+
*/
5179+
if (simple_eval_estate == NULL)
51815180
{
51825181
MemoryContext oldcontext;
51835182

5184-
/* Stack entries are kept in TopTransactionContext for simplicity */
5185-
entry = (SimpleEstateStackEntry *)
5186-
MemoryContextAlloc(TopTransactionContext,
5187-
sizeof(SimpleEstateStackEntry));
5188-
5189-
/* But each EState should be a child of its CurTransactionContext */
5190-
oldcontext = MemoryContextSwitchTo(CurTransactionContext);
5191-
entry->xact_eval_estate = CreateExecutorState();
5183+
oldcontext = MemoryContextSwitchTo(TopTransactionContext);
5184+
simple_eval_estate = CreateExecutorState();
51925185
MemoryContextSwitchTo(oldcontext);
5186+
}
51935187

5194-
/* Assign a reasonably-unique ID to this EState */
5195-
entry->xact_estate_simple_id = simple_estate_id_counter++;
5196-
entry->xact_subxid = my_subxid;
5188+
/*
5189+
* Create a child econtext for the current function.
5190+
*/
5191+
estate->eval_econtext = CreateExprContext(simple_eval_estate);
51975192

5198-
entry->next = simple_estate_stack;
5199-
simple_estate_stack = entry;
5200-
}
5193+
/*
5194+
* Make a stack entry so we can clean up the econtext at subxact end.
5195+
* Stack entries are kept in TopTransactionContext for simplicity.
5196+
*/
5197+
entry = (SimpleEcontextStackEntry *)
5198+
MemoryContextAlloc(TopTransactionContext,
5199+
sizeof(SimpleEcontextStackEntry));
52015200

5202-
/* Link plpgsql estate to it */
5203-
estate->eval_estate = entry->xact_eval_estate;
5204-
estate->eval_estate_simple_id = entry->xact_estate_simple_id;
5201+
entry->stack_econtext = estate->eval_econtext;
5202+
entry->xact_subxid = GetCurrentSubTransactionId();
52055203

5206-
/* And create a child econtext for the current function */
5207-
estate->eval_econtext = CreateExprContext(estate->eval_estate);
5204+
entry->next = simple_econtext_stack;
5205+
simple_econtext_stack = entry;
5206+
}
5207+
5208+
/*
5209+
* plpgsql_destroy_econtext --- destroy function's econtext
5210+
*
5211+
* We check that it matches the top stack entry, and destroy the stack
5212+
* entry along with the context.
5213+
*/
5214+
static void
5215+
plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
5216+
{
5217+
SimpleEcontextStackEntry *next;
5218+
5219+
Assert(simple_econtext_stack != NULL);
5220+
Assert(simple_econtext_stack->stack_econtext == estate->eval_econtext);
5221+
5222+
next = simple_econtext_stack->next;
5223+
pfree(simple_econtext_stack);
5224+
simple_econtext_stack = next;
5225+
5226+
FreeExprContext(estate->eval_econtext);
5227+
estate->eval_econtext = NULL;
52085228
}
52095229

52105230
/*
@@ -5220,29 +5240,31 @@ plpgsql_xact_cb(XactEvent event, void *arg)
52205240
* If we are doing a clean transaction shutdown, free the EState (so that
52215241
* any remaining resources will be released correctly). In an abort, we
52225242
* expect the regular abort recovery procedures to release everything of
5223-
* interest. We don't need to free the individual stack entries since
5224-
* TopTransactionContext is about to go away anyway.
5225-
*
5226-
* Note: if plpgsql_subxact_cb is doing its job, there should be at most
5227-
* one stack entry, but we may as well code this as a loop.
5243+
* interest.
52285244
*/
52295245
if (event != XACT_EVENT_ABORT)
52305246
{
5231-
while (simple_estate_stack != NULL)
5232-
{
5233-
FreeExecutorState(simple_estate_stack->xact_eval_estate);
5234-
simple_estate_stack = simple_estate_stack->next;
5235-
}
5247+
/* Shouldn't be any econtext stack entries left at commit */
5248+
Assert(simple_econtext_stack == NULL);
5249+
5250+
if (simple_eval_estate)
5251+
FreeExecutorState(simple_eval_estate);
5252+
simple_eval_estate = NULL;
52365253
}
52375254
else
5238-
simple_estate_stack = NULL;
5255+
{
5256+
simple_econtext_stack = NULL;
5257+
simple_eval_estate = NULL;
5258+
}
52395259
}
52405260

52415261
/*
52425262
* plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup
52435263
*
5244-
* If a simple-expression EState was created in the current subtransaction,
5245-
* it has to be cleaned up.
5264+
* Make sure any simple-expression econtexts created in the current
5265+
* subtransaction get cleaned up. We have to do this explicitly because
5266+
* no other code knows which child econtexts of simple_eval_estate belong
5267+
* to which level of subxact.
52465268
*/
52475269
void
52485270
plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
@@ -5251,16 +5273,15 @@ plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
52515273
if (event == SUBXACT_EVENT_START_SUB)
52525274
return;
52535275

5254-
if (simple_estate_stack != NULL &&
5255-
simple_estate_stack->xact_subxid == mySubid)
5276+
while (simple_econtext_stack != NULL &&
5277+
simple_econtext_stack->xact_subxid == mySubid)
52565278
{
5257-
SimpleEstateStackEntry *next;
5279+
SimpleEcontextStackEntry *next;
52585280

5259-
if (event == SUBXACT_EVENT_COMMIT_SUB)
5260-
FreeExecutorState(simple_estate_stack->xact_eval_estate);
5261-
next = simple_estate_stack->next;
5262-
pfree(simple_estate_stack);
5263-
simple_estate_stack = next;
5281+
FreeExprContext(simple_econtext_stack->stack_econtext);
5282+
next = simple_econtext_stack->next;
5283+
pfree(simple_econtext_stack);
5284+
simple_econtext_stack = next;
52645285
}
52655286
}
52665287

src/pl/plpgsql/src/plpgsql.h

+5-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.109 2009/02/17 11:34:34 petere Exp $
11+
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.110 2009/04/09 02:57:53 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -205,12 +205,12 @@ typedef struct PLpgSQL_expr
205205
Oid expr_simple_type; /* result type Oid, if simple */
206206

207207
/*
208-
* if expr is simple AND prepared in current eval_estate,
209-
* expr_simple_state is valid. Test validity by seeing if expr_simple_id
210-
* matches eval_estate_simple_id.
208+
* if expr is simple AND prepared in current transaction,
209+
* expr_simple_state is valid. Test validity by seeing if expr_simple_lxid
210+
* matches current LXID.
211211
*/
212212
ExprState *expr_simple_state;
213-
long int expr_simple_id;
213+
LocalTransactionId expr_simple_lxid;
214214

215215
/* params to pass to expr */
216216
int nparams;
@@ -719,8 +719,6 @@ typedef struct
719719
uint32 eval_processed;
720720
Oid eval_lastoid;
721721
ExprContext *eval_econtext; /* for executing simple expressions */
722-
EState *eval_estate; /* EState containing eval_econtext */
723-
long int eval_estate_simple_id; /* ID for eval_estate */
724722

725723
/* status information for error context reporting */
726724
PLpgSQL_function *err_func; /* current func */

src/test/regress/expected/plpgsql.out

+39
Original file line numberDiff line numberDiff line change
@@ -3698,3 +3698,42 @@ NOTICE: f 0
36983698
(4 rows)
36993699

37003700
drop function rttest();
3701+
-- Test for proper cleanup at subtransaction exit. This example
3702+
-- exposed a bug in PG 8.2.
3703+
CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGER AS $$
3704+
DECLARE
3705+
v_var INTEGER;
3706+
BEGIN
3707+
BEGIN
3708+
v_var := (leaker_2(fail)).error_code;
3709+
EXCEPTION
3710+
WHEN others THEN RETURN 0;
3711+
END;
3712+
RETURN 1;
3713+
END;
3714+
$$ LANGUAGE plpgsql;
3715+
CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER)
3716+
RETURNS RECORD AS $$
3717+
BEGIN
3718+
IF fail THEN
3719+
RAISE EXCEPTION 'fail ...';
3720+
END IF;
3721+
error_code := 1;
3722+
new_id := 1;
3723+
RETURN;
3724+
END;
3725+
$$ LANGUAGE plpgsql;
3726+
SELECT * FROM leaker_1(false);
3727+
leaker_1
3728+
----------
3729+
1
3730+
(1 row)
3731+
3732+
SELECT * FROM leaker_1(true);
3733+
leaker_1
3734+
----------
3735+
0
3736+
(1 row)
3737+
3738+
DROP FUNCTION leaker_1(bool);
3739+
DROP FUNCTION leaker_2(bool);

0 commit comments

Comments
 (0)