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

Commit c7b849a

Browse files
committed
Prevent leakage of cached plans and execution trees in plpgsql DO blocks.
plpgsql likes to cache query plans and simple-expression execution state trees across calls. This is a considerable win for multiple executions of the same function. However, it's useless for DO blocks, since by definition those are executed only once and discarded. Nonetheless, we were allowing a DO block's expression execution trees to survive until end of transaction, resulting in a significant intra-transaction memory leak, as reported by Yeb Havinga. Worse, if the DO block exited with an error, the compiled form of the block's code was leaked till end of session --- along with subsidiary plancache entries. To fix, make DO blocks keep their expression execution trees in a private EState that's deleted at exit from the block, and add a PG_TRY block to plpgsql_inline_handler to make sure that memory cleanup happens even on error exits. Also add a regression test covering error handling in a DO block, because my first try at this broke that. (The test is not meant to prove that we don't leak memory anymore, though it could be used for that with a much larger loop count.) Ideally we'd back-patch this into all versions supporting DO blocks; but the patch needs to add a field to struct PLpgSQL_execstate, and that would break ABI compatibility for third-party plugins such as the plpgsql debugger. Given the small number of complaints so far, fixing this in HEAD only seems like an acceptable choice.
1 parent 80e3a47 commit c7b849a

File tree

5 files changed

+150
-22
lines changed

5 files changed

+150
-22
lines changed

src/pl/plpgsql/src/pl_exec.c

+48-19
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ typedef struct
6666
* so that we don't have to re-prepare simple expressions on each trip through
6767
* a function. (We assume the case to optimize is many repetitions of a
6868
* function within a transaction.)
69+
*
70+
* However, there's no value in trying to amortize simple expression setup
71+
* across multiple executions of a DO block (inline code block), since there
72+
* can never be any. If we use the shared EState for a DO block, the expr
73+
* state trees are effectively leaked till end of transaction, and that can
74+
* add up if the user keeps on submitting DO blocks. Therefore, each DO block
75+
* has its own simple-expression EState, which is cleaned up at exit from
76+
* plpgsql_inline_handler(). DO blocks still use the simple_econtext_stack,
77+
* though, so that subxact abort cleanup does the right thing.
6978
*/
7079
typedef struct SimpleEcontextStackEntry
7180
{
@@ -74,7 +83,7 @@ typedef struct SimpleEcontextStackEntry
7483
struct SimpleEcontextStackEntry *next; /* next stack entry up */
7584
} SimpleEcontextStackEntry;
7685

77-
static EState *simple_eval_estate = NULL;
86+
static EState *shared_simple_eval_estate = NULL;
7887
static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
7988

8089
/************************************************************
@@ -136,7 +145,8 @@ static int exec_stmt_dynfors(PLpgSQL_execstate *estate,
136145

137146
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
138147
PLpgSQL_function *func,
139-
ReturnSetInfo *rsi);
148+
ReturnSetInfo *rsi,
149+
EState *simple_eval_estate);
140150
static void exec_eval_cleanup(PLpgSQL_execstate *estate);
141151

142152
static void exec_prepare_plan(PLpgSQL_execstate *estate,
@@ -230,10 +240,17 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
230240
/* ----------
231241
* plpgsql_exec_function Called by the call handler for
232242
* function execution.
243+
*
244+
* This is also used to execute inline code blocks (DO blocks). The only
245+
* difference that this code is aware of is that for a DO block, we want
246+
* to use a private simple_eval_estate, which is created and passed in by
247+
* the caller. For regular functions, pass NULL, which implies using
248+
* shared_simple_eval_estate.
233249
* ----------
234250
*/
235251
Datum
236-
plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
252+
plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
253+
EState *simple_eval_estate)
237254
{
238255
PLpgSQL_execstate estate;
239256
ErrorContextCallback plerrcontext;
@@ -243,7 +260,8 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
243260
/*
244261
* Setup the execution state
245262
*/
246-
plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
263+
plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo,
264+
simple_eval_estate);
247265

248266
/*
249267
* Setup error traceback support for ereport()
@@ -503,7 +521,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
503521
/*
504522
* Setup the execution state
505523
*/
506-
plpgsql_estate_setup(&estate, func, NULL);
524+
plpgsql_estate_setup(&estate, func, NULL, NULL);
507525

508526
/*
509527
* Setup error traceback support for ereport()
@@ -782,7 +800,7 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
782800
/*
783801
* Setup the execution state
784802
*/
785-
plpgsql_estate_setup(&estate, func, NULL);
803+
plpgsql_estate_setup(&estate, func, NULL, NULL);
786804

787805
/*
788806
* Setup error traceback support for ereport()
@@ -3087,7 +3105,8 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
30873105
static void
30883106
plpgsql_estate_setup(PLpgSQL_execstate *estate,
30893107
PLpgSQL_function *func,
3090-
ReturnSetInfo *rsi)
3108+
ReturnSetInfo *rsi,
3109+
EState *simple_eval_estate)
30913110
{
30923111
/* this link will be restored at exit from plpgsql_call_handler */
30933112
func->cur_estate = estate;
@@ -3126,6 +3145,12 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
31263145
estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
31273146
/* caller is expected to fill the datums array */
31283147

3148+
/* set up for use of appropriate simple-expression EState */
3149+
if (simple_eval_estate)
3150+
estate->simple_eval_estate = simple_eval_estate;
3151+
else
3152+
estate->simple_eval_estate = shared_simple_eval_estate;
3153+
31293154
estate->eval_tuptable = NULL;
31303155
estate->eval_processed = 0;
31313156
estate->eval_lastoid = InvalidOid;
@@ -5148,7 +5173,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
51485173
*/
51495174
if (expr->expr_simple_lxid != curlxid)
51505175
{
5151-
oldcontext = MemoryContextSwitchTo(simple_eval_estate->es_query_cxt);
5176+
oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
51525177
expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr, NULL);
51535178
expr->expr_simple_in_use = false;
51545179
expr->expr_simple_lxid = curlxid;
@@ -6190,8 +6215,8 @@ exec_set_found(PLpgSQL_execstate *estate, bool state)
61906215
/*
61916216
* plpgsql_create_econtext --- create an eval_econtext for the current function
61926217
*
6193-
* We may need to create a new simple_eval_estate too, if there's not one
6194-
* already for the current transaction. The EState will be cleaned up at
6218+
* We may need to create a new shared_simple_eval_estate too, if there's not
6219+
* one already for the current transaction. The EState will be cleaned up at
61956220
* transaction end.
61966221
*/
61976222
static void
@@ -6203,20 +6228,25 @@ plpgsql_create_econtext(PLpgSQL_execstate *estate)
62036228
* Create an EState for evaluation of simple expressions, if there's not
62046229
* one already in the current transaction. The EState is made a child of
62056230
* TopTransactionContext so it will have the right lifespan.
6231+
*
6232+
* Note that this path is never taken when executing a DO block; the
6233+
* required EState was already made by plpgsql_inline_handler.
62066234
*/
6207-
if (simple_eval_estate == NULL)
6235+
if (estate->simple_eval_estate == NULL)
62086236
{
62096237
MemoryContext oldcontext;
62106238

6239+
Assert(shared_simple_eval_estate == NULL);
62116240
oldcontext = MemoryContextSwitchTo(TopTransactionContext);
6212-
simple_eval_estate = CreateExecutorState();
6241+
shared_simple_eval_estate = CreateExecutorState();
6242+
estate->simple_eval_estate = shared_simple_eval_estate;
62136243
MemoryContextSwitchTo(oldcontext);
62146244
}
62156245

62166246
/*
62176247
* Create a child econtext for the current function.
62186248
*/
6219-
estate->eval_econtext = CreateExprContext(simple_eval_estate);
6249+
estate->eval_econtext = CreateExprContext(estate->simple_eval_estate);
62206250

62216251
/*
62226252
* Make a stack entry so we can clean up the econtext at subxact end.
@@ -6275,14 +6305,14 @@ plpgsql_xact_cb(XactEvent event, void *arg)
62756305
/* Shouldn't be any econtext stack entries left at commit */
62766306
Assert(simple_econtext_stack == NULL);
62776307

6278-
if (simple_eval_estate)
6279-
FreeExecutorState(simple_eval_estate);
6280-
simple_eval_estate = NULL;
6308+
if (shared_simple_eval_estate)
6309+
FreeExecutorState(shared_simple_eval_estate);
6310+
shared_simple_eval_estate = NULL;
62816311
}
62826312
else if (event == XACT_EVENT_ABORT)
62836313
{
62846314
simple_econtext_stack = NULL;
6285-
simple_eval_estate = NULL;
6315+
shared_simple_eval_estate = NULL;
62866316
}
62876317
}
62886318

@@ -6291,8 +6321,7 @@ plpgsql_xact_cb(XactEvent event, void *arg)
62916321
*
62926322
* Make sure any simple-expression econtexts created in the current
62936323
* subtransaction get cleaned up. We have to do this explicitly because
6294-
* no other code knows which child econtexts of simple_eval_estate belong
6295-
* to which level of subxact.
6324+
* no other code knows which econtexts belong to which level of subxact.
62966325
*/
62976326
void
62986327
plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,

src/pl/plpgsql/src/pl_handler.c

+47-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
136136
retval = (Datum) 0;
137137
}
138138
else
139-
retval = plpgsql_exec_function(func, fcinfo);
139+
retval = plpgsql_exec_function(func, fcinfo, NULL);
140140
}
141141
PG_CATCH();
142142
{
@@ -175,6 +175,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
175175
PLpgSQL_function *func;
176176
FunctionCallInfoData fake_fcinfo;
177177
FmgrInfo flinfo;
178+
EState *simple_eval_estate;
178179
Datum retval;
179180
int rc;
180181

@@ -203,7 +204,51 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
203204
flinfo.fn_oid = InvalidOid;
204205
flinfo.fn_mcxt = CurrentMemoryContext;
205206

206-
retval = plpgsql_exec_function(func, &fake_fcinfo);
207+
/* Create a private EState for simple-expression execution */
208+
simple_eval_estate = CreateExecutorState();
209+
210+
/* And run the function */
211+
PG_TRY();
212+
{
213+
retval = plpgsql_exec_function(func, &fake_fcinfo, simple_eval_estate);
214+
}
215+
PG_CATCH();
216+
{
217+
/*
218+
* We need to clean up what would otherwise be long-lived resources
219+
* accumulated by the failed DO block, principally cached plans for
220+
* statements (which can be flushed with plpgsql_free_function_memory)
221+
* and execution trees for simple expressions, which are in the
222+
* private EState.
223+
*
224+
* Before releasing the private EState, we must clean up any
225+
* simple_econtext_stack entries pointing into it, which we can do by
226+
* invoking the subxact callback. (It will be called again later if
227+
* some outer control level does a subtransaction abort, but no harm
228+
* is done.) We cheat a bit knowing that plpgsql_subxact_cb does not
229+
* pay attention to its parentSubid argument.
230+
*/
231+
plpgsql_subxact_cb(SUBXACT_EVENT_ABORT_SUB,
232+
GetCurrentSubTransactionId(),
233+
0, NULL);
234+
235+
/* Clean up the private EState */
236+
FreeExecutorState(simple_eval_estate);
237+
238+
/* Function should now have no remaining use-counts ... */
239+
func->use_count--;
240+
Assert(func->use_count == 0);
241+
242+
/* ... so we can free subsidiary storage */
243+
plpgsql_free_function_memory(func);
244+
245+
/* And propagate the error */
246+
PG_RE_THROW();
247+
}
248+
PG_END_TRY();
249+
250+
/* Clean up the private EState */
251+
FreeExecutorState(simple_eval_estate);
207252

208253
/* Function should now have no remaining use-counts ... */
209254
func->use_count--;

src/pl/plpgsql/src/plpgsql.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -773,10 +773,14 @@ typedef struct PLpgSQL_execstate
773773
ResourceOwner tuple_store_owner;
774774
ReturnSetInfo *rsi;
775775

776+
/* the datums representing the function's local variables */
776777
int found_varno;
777778
int ndatums;
778779
PLpgSQL_datum **datums;
779780

781+
/* EState to use for "simple" expression evaluation */
782+
EState *simple_eval_estate;
783+
780784
/* temporary state for results from evaluation of query or expr */
781785
SPITupleTable *eval_tuptable;
782786
uint32 eval_processed;
@@ -943,7 +947,8 @@ extern Datum plpgsql_validator(PG_FUNCTION_ARGS);
943947
* ----------
944948
*/
945949
extern Datum plpgsql_exec_function(PLpgSQL_function *func,
946-
FunctionCallInfo fcinfo);
950+
FunctionCallInfo fcinfo,
951+
EState *simple_eval_estate);
947952
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
948953
TriggerData *trigdata);
949954
extern void plpgsql_exec_event_trigger(PLpgSQL_function *func,

src/test/regress/expected/plpgsql.out

+29
Original file line numberDiff line numberDiff line change
@@ -4651,6 +4651,35 @@ LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn...
46514651
^
46524652
QUERY: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
46534653
CONTEXT: PL/pgSQL function inline_code_block line 4 at FOR over SELECT rows
4654+
-- Check handling of errors thrown from/into anonymous code blocks.
4655+
do $outer$
4656+
begin
4657+
for i in 1..10 loop
4658+
begin
4659+
execute $ex$
4660+
do $$
4661+
declare x int = 0;
4662+
begin
4663+
x := 1 / x;
4664+
end;
4665+
$$;
4666+
$ex$;
4667+
exception when division_by_zero then
4668+
raise notice 'caught division by zero';
4669+
end;
4670+
end loop;
4671+
end;
4672+
$outer$;
4673+
NOTICE: caught division by zero
4674+
NOTICE: caught division by zero
4675+
NOTICE: caught division by zero
4676+
NOTICE: caught division by zero
4677+
NOTICE: caught division by zero
4678+
NOTICE: caught division by zero
4679+
NOTICE: caught division by zero
4680+
NOTICE: caught division by zero
4681+
NOTICE: caught division by zero
4682+
NOTICE: caught division by zero
46544683
-- Check variable scoping -- a var is not available in its own or prior
46554684
-- default expressions.
46564685
create function scope_test() returns int as $$

src/test/regress/sql/plpgsql.sql

+20
Original file line numberDiff line numberDiff line change
@@ -3751,6 +3751,26 @@ BEGIN
37513751
END LOOP;
37523752
END$$;
37533753
3754+
-- Check handling of errors thrown from/into anonymous code blocks.
3755+
do $outer$
3756+
begin
3757+
for i in 1..10 loop
3758+
begin
3759+
execute $ex$
3760+
do $$
3761+
declare x int = 0;
3762+
begin
3763+
x := 1 / x;
3764+
end;
3765+
$$;
3766+
$ex$;
3767+
exception when division_by_zero then
3768+
raise notice 'caught division by zero';
3769+
end;
3770+
end loop;
3771+
end;
3772+
$outer$;
3773+
37543774
-- Check variable scoping -- a var is not available in its own or prior
37553775
-- default expressions.
37563776

0 commit comments

Comments
 (0)