Skip to content

Commit ed75380

Browse files
committed
Create a stack of pl/python "execution contexts".
This replaces the former global variable PLy_curr_procedure, and provides a place to stash per-call-level information. In particular we create a per-call-level scratch memory context. For the moment, the scratch context is just used to avoid leaking memory from datatype output function calls in PLyDict_FromTuple. There probably will be more use-cases in future. Although this is a fix for a pre-existing memory leakage bug, it seems sufficiently invasive to not want to back-patch; it feels better as part of the major rearrangement of plpython code that we've already done as part of 9.2. Jan Urbański
1 parent 2e46bf6 commit ed75380

File tree

9 files changed

+141
-40
lines changed

9 files changed

+141
-40
lines changed

src/pl/plpython/plpy_cursorobject.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "plpy_cursorobject.h"
1515

1616
#include "plpy_elog.h"
17+
#include "plpy_main.h"
1718
#include "plpy_planobject.h"
1819
#include "plpy_procedure.h"
1920
#include "plpy_resultobject.h"
@@ -119,6 +120,7 @@ PLy_cursor_query(const char *query)
119120

120121
PG_TRY();
121122
{
123+
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
122124
SPIPlanPtr plan;
123125
Portal portal;
124126

@@ -130,7 +132,7 @@ PLy_cursor_query(const char *query)
130132
SPI_result_code_string(SPI_result));
131133

132134
portal = SPI_cursor_open(NULL, plan, NULL, NULL,
133-
PLy_curr_procedure->fn_readonly);
135+
exec_ctx->curr_proc->fn_readonly);
134136
SPI_freeplan(plan);
135137

136138
if (portal == NULL)
@@ -207,6 +209,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
207209

208210
PG_TRY();
209211
{
212+
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
210213
Portal portal;
211214
char *volatile nulls;
212215
volatile int j;
@@ -253,7 +256,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
253256
}
254257

255258
portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
256-
PLy_curr_procedure->fn_readonly);
259+
exec_ctx->curr_proc->fn_readonly);
257260
if (portal == NULL)
258261
elog(ERROR, "SPI_cursor_open() failed: %s",
259262
SPI_result_code_string(SPI_result));

src/pl/plpython/plpy_elog.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "plpy_elog.h"
1414

15+
#include "plpy_main.h"
1516
#include "plpy_procedure.h"
1617

1718

@@ -255,6 +256,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
255256
/* The first frame always points at <module>, skip it. */
256257
if (*tb_depth > 0)
257258
{
259+
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
258260
char *proname;
259261
char *fname;
260262
char *line;
@@ -270,7 +272,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
270272
else
271273
fname = PyString_AsString(name);
272274

273-
proname = PLy_procedure_name(PLy_curr_procedure);
275+
proname = PLy_procedure_name(exec_ctx->curr_proc);
274276
plain_filename = PyString_AsString(filename);
275277
plain_lineno = PyInt_AsLong(lineno);
276278

@@ -287,7 +289,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
287289
* function code object was compiled with "<string>" as the
288290
* filename
289291
*/
290-
if (PLy_curr_procedure && plain_filename != NULL &&
292+
if (exec_ctx->curr_proc && plain_filename != NULL &&
291293
strcmp(plain_filename, "<string>") == 0)
292294
{
293295
/*
@@ -299,7 +301,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
299301
* for. But we do not go as far as traceback.py in reading
300302
* the source of imported modules.
301303
*/
302-
line = get_source_line(PLy_curr_procedure->src, plain_lineno);
304+
line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
303305
if (line)
304306
{
305307
appendStringInfo(&tbstr, "\n %s", line);

src/pl/plpython/plpy_exec.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,9 @@ PLy_function_delete_args(PLyProcedure *proc)
455455
static void
456456
plpython_return_error_callback(void *arg)
457457
{
458-
if (PLy_curr_procedure)
458+
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
459+
460+
if (exec_ctx->curr_proc)
459461
errcontext("while creating return value");
460462
}
461463

@@ -781,7 +783,9 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
781783
static void
782784
plpython_trigger_error_callback(void *arg)
783785
{
784-
if (PLy_curr_procedure)
786+
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
787+
788+
if (exec_ctx->curr_proc)
785789
errcontext("while modifying trigger row");
786790
}
787791

src/pl/plpython/plpy_main.c

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "executor/spi.h"
1313
#include "miscadmin.h"
1414
#include "utils/guc.h"
15+
#include "utils/memutils.h"
1516
#include "utils/syscache.h"
1617

1718
#include "plpython.h"
@@ -66,11 +67,17 @@ static void plpython_error_callback(void *arg);
6667
static void plpython_inline_error_callback(void *arg);
6768
static void PLy_init_interp(void);
6869

70+
static PLyExecutionContext *PLy_push_execution_context(void);
71+
static void PLy_pop_execution_context(void);
72+
6973
static const int plpython_python_version = PY_MAJOR_VERSION;
7074

7175
/* initialize global variables */
7276
PyObject *PLy_interp_globals = NULL;
7377

78+
/* this doesn't need to be global; use PLy_current_execution_context() */
79+
static PLyExecutionContext *PLy_execution_contexts = NULL;
80+
7481

7582
void
7683
_PG_init(void)
@@ -114,6 +121,8 @@ _PG_init(void)
114121

115122
explicit_subtransactions = NIL;
116123

124+
PLy_execution_contexts = NULL;
125+
117126
inited = true;
118127
}
119128

@@ -179,13 +188,20 @@ Datum
179188
plpython_call_handler(PG_FUNCTION_ARGS)
180189
{
181190
Datum retval;
182-
PLyProcedure *save_curr_proc;
191+
PLyExecutionContext *exec_ctx;
183192
ErrorContextCallback plerrcontext;
184193

194+
/* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
185195
if (SPI_connect() != SPI_OK_CONNECT)
186196
elog(ERROR, "SPI_connect failed");
187197

188-
save_curr_proc = PLy_curr_procedure;
198+
/*
199+
* Push execution context onto stack. It is important that this get
200+
* popped again, so avoid putting anything that could throw error between
201+
* here and the PG_TRY. (plpython_error_callback expects the stack entry
202+
* to be there, so we have to make the context first.)
203+
*/
204+
exec_ctx = PLy_push_execution_context();
189205

190206
/*
191207
* Setup error traceback support for ereport()
@@ -203,29 +219,29 @@ plpython_call_handler(PG_FUNCTION_ARGS)
203219
HeapTuple trv;
204220

205221
proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true);
206-
PLy_curr_procedure = proc;
222+
exec_ctx->curr_proc = proc;
207223
trv = PLy_exec_trigger(fcinfo, proc);
208224
retval = PointerGetDatum(trv);
209225
}
210226
else
211227
{
212228
proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false);
213-
PLy_curr_procedure = proc;
229+
exec_ctx->curr_proc = proc;
214230
retval = PLy_exec_function(fcinfo, proc);
215231
}
216232
}
217233
PG_CATCH();
218234
{
219-
PLy_curr_procedure = save_curr_proc;
235+
PLy_pop_execution_context();
220236
PyErr_Clear();
221237
PG_RE_THROW();
222238
}
223239
PG_END_TRY();
224240

225241
/* Pop the error context stack */
226242
error_context_stack = plerrcontext.previous;
227-
228-
PLy_curr_procedure = save_curr_proc;
243+
/* ... and then the execution context */
244+
PLy_pop_execution_context();
229245

230246
return retval;
231247
}
@@ -244,22 +260,14 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
244260
InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
245261
FunctionCallInfoData fake_fcinfo;
246262
FmgrInfo flinfo;
247-
PLyProcedure *save_curr_proc;
248263
PLyProcedure proc;
264+
PLyExecutionContext *exec_ctx;
249265
ErrorContextCallback plerrcontext;
250266

267+
/* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
251268
if (SPI_connect() != SPI_OK_CONNECT)
252269
elog(ERROR, "SPI_connect failed");
253270

254-
save_curr_proc = PLy_curr_procedure;
255-
256-
/*
257-
* Setup error traceback support for ereport()
258-
*/
259-
plerrcontext.callback = plpython_inline_error_callback;
260-
plerrcontext.previous = error_context_stack;
261-
error_context_stack = &plerrcontext;
262-
263271
MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
264272
MemSet(&flinfo, 0, sizeof(flinfo));
265273
fake_fcinfo.flinfo = &flinfo;
@@ -270,27 +278,44 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
270278
proc.pyname = PLy_strdup("__plpython_inline_block");
271279
proc.result.out.d.typoid = VOIDOID;
272280

281+
/*
282+
* Push execution context onto stack. It is important that this get
283+
* popped again, so avoid putting anything that could throw error between
284+
* here and the PG_TRY. (plpython_inline_error_callback doesn't currently
285+
* need the stack entry, but for consistency with plpython_call_handler
286+
* we do it in this order.)
287+
*/
288+
exec_ctx = PLy_push_execution_context();
289+
290+
/*
291+
* Setup error traceback support for ereport()
292+
*/
293+
plerrcontext.callback = plpython_inline_error_callback;
294+
plerrcontext.previous = error_context_stack;
295+
error_context_stack = &plerrcontext;
296+
273297
PG_TRY();
274298
{
275299
PLy_procedure_compile(&proc, codeblock->source_text);
276-
PLy_curr_procedure = &proc;
300+
exec_ctx->curr_proc = &proc;
277301
PLy_exec_function(&fake_fcinfo, &proc);
278302
}
279303
PG_CATCH();
280304
{
305+
PLy_pop_execution_context();
281306
PLy_procedure_delete(&proc);
282-
PLy_curr_procedure = save_curr_proc;
283307
PyErr_Clear();
284308
PG_RE_THROW();
285309
}
286310
PG_END_TRY();
287311

288-
PLy_procedure_delete(&proc);
289-
290312
/* Pop the error context stack */
291313
error_context_stack = plerrcontext.previous;
314+
/* ... and then the execution context */
315+
PLy_pop_execution_context();
292316

293-
PLy_curr_procedure = save_curr_proc;
317+
/* Now clean up the transient procedure we made */
318+
PLy_procedure_delete(&proc);
294319

295320
PG_RETURN_VOID();
296321
}
@@ -313,13 +338,54 @@ static bool PLy_procedure_is_trigger(Form_pg_proc procStruct)
313338
static void
314339
plpython_error_callback(void *arg)
315340
{
316-
if (PLy_curr_procedure)
341+
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
342+
343+
if (exec_ctx->curr_proc)
317344
errcontext("PL/Python function \"%s\"",
318-
PLy_procedure_name(PLy_curr_procedure));
345+
PLy_procedure_name(exec_ctx->curr_proc));
319346
}
320347

321348
static void
322349
plpython_inline_error_callback(void *arg)
323350
{
324351
errcontext("PL/Python anonymous code block");
325352
}
353+
354+
PLyExecutionContext *
355+
PLy_current_execution_context(void)
356+
{
357+
if (PLy_execution_contexts == NULL)
358+
elog(ERROR, "no Python function is currently executing");
359+
360+
return PLy_execution_contexts;
361+
}
362+
363+
static PLyExecutionContext *
364+
PLy_push_execution_context(void)
365+
{
366+
PLyExecutionContext *context = PLy_malloc(sizeof(PLyExecutionContext));
367+
368+
context->curr_proc = NULL;
369+
context->scratch_ctx = AllocSetContextCreate(TopTransactionContext,
370+
"PL/Python scratch context",
371+
ALLOCSET_DEFAULT_MINSIZE,
372+
ALLOCSET_DEFAULT_INITSIZE,
373+
ALLOCSET_DEFAULT_MAXSIZE);
374+
context->next = PLy_execution_contexts;
375+
PLy_execution_contexts = context;
376+
return context;
377+
}
378+
379+
static void
380+
PLy_pop_execution_context(void)
381+
{
382+
PLyExecutionContext *context = PLy_execution_contexts;
383+
384+
if (context == NULL)
385+
elog(ERROR, "no Python function is currently executing");
386+
387+
PLy_execution_contexts = context->next;
388+
389+
MemoryContextDelete(context->scratch_ctx);
390+
PLy_free(context);
391+
}

src/pl/plpython/plpy_main.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,19 @@
1010
/* the interpreter's globals dict */
1111
extern PyObject *PLy_interp_globals;
1212

13+
/*
14+
* A stack of PL/Python execution contexts. Each time user-defined Python code
15+
* is called, an execution context is created and put on the stack. After the
16+
* Python code returns, the context is destroyed.
17+
*/
18+
typedef struct PLyExecutionContext
19+
{
20+
PLyProcedure *curr_proc; /* the currently executing procedure */
21+
MemoryContext scratch_ctx; /* a context for things like type I/O */
22+
struct PLyExecutionContext *next; /* previous stack level */
23+
} PLyExecutionContext;
24+
25+
/* Get the current execution context */
26+
extern PLyExecutionContext *PLy_current_execution_context(void);
27+
1328
#endif /* PLPY_MAIN_H */

src/pl/plpython/plpy_procedure.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@
2222
#include "plpy_main.h"
2323

2424

25-
PLyProcedure *PLy_curr_procedure = NULL;
26-
27-
2825
static HTAB *PLy_procedure_cache = NULL;
2926
static HTAB *PLy_trigger_cache = NULL;
3027

src/pl/plpython/plpy_procedure.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,4 @@ extern PLyProcedure *PLy_procedure_get(Oid fn_oid, bool is_trigger);
4545
extern void PLy_procedure_compile(PLyProcedure *proc, const char *src);
4646
extern void PLy_procedure_delete(PLyProcedure *proc);
4747

48-
49-
/* currently active plpython function */
50-
extern PLyProcedure *PLy_curr_procedure;
51-
5248
#endif /* PLPY_PROCEDURE_H */

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy