Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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)