On 14/02/12 01:35, Tom Lane wrote: > =?UTF-8?B?SmFuIFVyYmHFhHNraQ==?= <wulc...@wulczer.org> writes: >> It's not very comfortable, but >> I think PLyDict_FromTuple can be allowed to be non-reentrant. > > I think that's pretty short-sighted. Even if it's safe today (which > I am not 100% convinced of), there are plenty of foreseeable reasons > why it might^Wwill break in the future. > >> OTOH if we want to make it reentrant, some more tinkering would be in order. > > I think that's in order.
Here are the results of the tinkering. I came up with a stack of context structures that gets pushed when a PL/Python starts being executed and popped when it returns. At first they contained just a scratch memory context used by PLyDict_FromTuple. Then under the premise of confirming the usefulness of introducing such contexts I removed the global PLy_curr_procedure variable and changed all users to get the current procedure from the context. It seems to have worked, so the total count of global variables is unchanged - hooray! While testing I found one more leak, this time caused by allocating a structure for caching array type I/O functions and never freeing it. Attached as separate patch. Cheers, Jan
diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c index 4226dc7..46930b0 100644 *** a/src/pl/plpython/plpy_cursorobject.c --- b/src/pl/plpython/plpy_cursorobject.c *************** *** 14,19 **** --- 14,20 ---- #include "plpy_cursorobject.h" #include "plpy_elog.h" + #include "plpy_main.h" #include "plpy_planobject.h" #include "plpy_procedure.h" #include "plpy_resultobject.h" *************** PLy_cursor_query(const char *query) *** 121,126 **** --- 122,128 ---- { SPIPlanPtr plan; Portal portal; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); pg_verifymbstr(query, strlen(query), false); *************** PLy_cursor_query(const char *query) *** 129,136 **** elog(ERROR, "SPI_prepare failed: %s", SPI_result_code_string(SPI_result)); portal = SPI_cursor_open(NULL, plan, NULL, NULL, ! PLy_curr_procedure->fn_readonly); SPI_freeplan(plan); if (portal == NULL) --- 131,140 ---- elog(ERROR, "SPI_prepare failed: %s", SPI_result_code_string(SPI_result)); + Assert(exec_ctx->curr_proc != NULL); + portal = SPI_cursor_open(NULL, plan, NULL, NULL, ! exec_ctx->curr_proc->fn_readonly); SPI_freeplan(plan); if (portal == NULL) *************** PLy_cursor_plan(PyObject *ob, PyObject * *** 210,215 **** --- 214,220 ---- Portal portal; char *volatile nulls; volatile int j; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); if (nargs > 0) nulls = palloc(nargs * sizeof(char)); *************** PLy_cursor_plan(PyObject *ob, PyObject * *** 252,259 **** } } portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls, ! PLy_curr_procedure->fn_readonly); if (portal == NULL) elog(ERROR, "SPI_cursor_open() failed: %s", SPI_result_code_string(SPI_result)); --- 257,266 ---- } } + Assert(exec_ctx->curr_proc != NULL); + portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls, ! exec_ctx->curr_proc->fn_readonly); if (portal == NULL) elog(ERROR, "SPI_cursor_open() failed: %s", SPI_result_code_string(SPI_result)); diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c index 741980c..9909f23 100644 *** a/src/pl/plpython/plpy_elog.c --- b/src/pl/plpython/plpy_elog.c *************** *** 12,17 **** --- 12,18 ---- #include "plpy_elog.h" + #include "plpy_main.h" #include "plpy_procedure.h" *************** PLy_traceback(char **xmsg, char **tbmsg, *** 260,265 **** --- 261,267 ---- char *line; char *plain_filename; long plain_lineno; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); /* * The second frame points at the internal function, but to mimick *************** PLy_traceback(char **xmsg, char **tbmsg, *** 270,276 **** else fname = PyString_AsString(name); ! proname = PLy_procedure_name(PLy_curr_procedure); plain_filename = PyString_AsString(filename); plain_lineno = PyInt_AsLong(lineno); --- 272,280 ---- else fname = PyString_AsString(name); ! Assert(exec_ctx->curr_proc != NULL); ! ! proname = PLy_procedure_name(exec_ctx->curr_proc); plain_filename = PyString_AsString(filename); plain_lineno = PyInt_AsLong(lineno); *************** PLy_traceback(char **xmsg, char **tbmsg, *** 287,293 **** * function code object was compiled with "<string>" as the * filename */ ! if (PLy_curr_procedure && plain_filename != NULL && strcmp(plain_filename, "<string>") == 0) { /* --- 291,297 ---- * function code object was compiled with "<string>" as the * filename */ ! if (exec_ctx->curr_proc && plain_filename != NULL && strcmp(plain_filename, "<string>") == 0) { /* *************** PLy_traceback(char **xmsg, char **tbmsg, *** 299,305 **** * for. But we do not go as far as traceback.py in reading * the source of imported modules. */ ! line = get_source_line(PLy_curr_procedure->src, plain_lineno); if (line) { appendStringInfo(&tbstr, "\n %s", line); --- 303,309 ---- * for. But we do not go as far as traceback.py in reading * the source of imported modules. */ ! line = get_source_line(exec_ctx->curr_proc->src, plain_lineno); if (line) { appendStringInfo(&tbstr, "\n %s", line); diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c index ecf4996..280d3ed 100644 *** a/src/pl/plpython/plpy_exec.c --- b/src/pl/plpython/plpy_exec.c *************** PLy_function_delete_args(PLyProcedure *p *** 455,461 **** static void plpython_return_error_callback(void *arg) { ! if (PLy_curr_procedure) errcontext("while creating return value"); } --- 455,463 ---- static void plpython_return_error_callback(void *arg) { ! PLyExecutionContext *exec_ctx = PLy_current_execution_context(); ! ! if (exec_ctx->curr_proc) errcontext("while creating return value"); } *************** PLy_modify_tuple(PLyProcedure *proc, PyO *** 781,787 **** static void plpython_trigger_error_callback(void *arg) { ! if (PLy_curr_procedure) errcontext("while modifying trigger row"); } --- 783,791 ---- static void plpython_trigger_error_callback(void *arg) { ! PLyExecutionContext *exec_ctx = PLy_current_execution_context(); ! ! if (exec_ctx->curr_proc) errcontext("while modifying trigger row"); } diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c index ae9d87e..c0a2583 100644 *** a/src/pl/plpython/plpy_main.c --- b/src/pl/plpython/plpy_main.c *************** *** 12,17 **** --- 12,18 ---- #include "executor/spi.h" #include "miscadmin.h" #include "utils/guc.h" + #include "utils/memutils.h" #include "utils/syscache.h" #include "plpython.h" *************** static void plpython_error_callback(void *** 66,75 **** --- 67,80 ---- static void plpython_inline_error_callback(void *arg); static void PLy_init_interp(void); + static PLyExecutionContext *PLy_push_execution_context(void); + static void PLy_pop_execution_context(void); + static const int plpython_python_version = PY_MAJOR_VERSION; /* initialize global variables */ PyObject *PLy_interp_globals = NULL; + List *PLy_execution_contexts = NIL; void *************** _PG_init(void) *** 113,118 **** --- 118,124 ---- init_procedure_caches(); explicit_subtransactions = NIL; + PLy_execution_contexts = NIL; inited = true; } *************** Datum *** 179,192 **** plpython_call_handler(PG_FUNCTION_ARGS) { Datum retval; - PLyProcedure *save_curr_proc; ErrorContextCallback plerrcontext; if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); - save_curr_proc = PLy_curr_procedure; - /* * Setup error traceback support for ereport() */ --- 185,198 ---- plpython_call_handler(PG_FUNCTION_ARGS) { Datum retval; ErrorContextCallback plerrcontext; + PLyExecutionContext *exec_ctx; + + exec_ctx = PLy_push_execution_context(); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Setup error traceback support for ereport() */ *************** plpython_call_handler(PG_FUNCTION_ARGS) *** 200,222 **** if (CALLED_AS_TRIGGER(fcinfo)) { ! HeapTuple trv; proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true); ! PLy_curr_procedure = proc; trv = PLy_exec_trigger(fcinfo, proc); retval = PointerGetDatum(trv); } else { proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false); ! PLy_curr_procedure = proc; retval = PLy_exec_function(fcinfo, proc); } } PG_CATCH(); { ! PLy_curr_procedure = save_curr_proc; PyErr_Clear(); PG_RE_THROW(); } --- 206,228 ---- if (CALLED_AS_TRIGGER(fcinfo)) { ! HeapTuple trv; proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true); ! exec_ctx->curr_proc = proc; trv = PLy_exec_trigger(fcinfo, proc); retval = PointerGetDatum(trv); } else { proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false); ! exec_ctx->curr_proc = proc; retval = PLy_exec_function(fcinfo, proc); } } PG_CATCH(); { ! PLy_pop_execution_context(); PyErr_Clear(); PG_RE_THROW(); } *************** plpython_call_handler(PG_FUNCTION_ARGS) *** 225,231 **** /* Pop the error context stack */ error_context_stack = plerrcontext.previous; ! PLy_curr_procedure = save_curr_proc; return retval; } --- 231,237 ---- /* Pop the error context stack */ error_context_stack = plerrcontext.previous; ! PLy_pop_execution_context(); return retval; } *************** plpython_inline_handler(PG_FUNCTION_ARGS *** 244,258 **** InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)); FunctionCallInfoData fake_fcinfo; FmgrInfo flinfo; - PLyProcedure *save_curr_proc; PLyProcedure proc; ErrorContextCallback plerrcontext; if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); - save_curr_proc = PLy_curr_procedure; - /* * Setup error traceback support for ereport() */ --- 250,264 ---- InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)); FunctionCallInfoData fake_fcinfo; FmgrInfo flinfo; PLyProcedure proc; ErrorContextCallback plerrcontext; + PLyExecutionContext *exec_ctx; + + exec_ctx = PLy_push_execution_context(); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Setup error traceback support for ereport() */ *************** plpython_inline_handler(PG_FUNCTION_ARGS *** 273,285 **** PG_TRY(); { PLy_procedure_compile(&proc, codeblock->source_text); ! PLy_curr_procedure = &proc; PLy_exec_function(&fake_fcinfo, &proc); } PG_CATCH(); { PLy_procedure_delete(&proc); ! PLy_curr_procedure = save_curr_proc; PyErr_Clear(); PG_RE_THROW(); } --- 279,291 ---- PG_TRY(); { PLy_procedure_compile(&proc, codeblock->source_text); ! exec_ctx->curr_proc = &proc; PLy_exec_function(&fake_fcinfo, &proc); } PG_CATCH(); { PLy_procedure_delete(&proc); ! PLy_pop_execution_context(); PyErr_Clear(); PG_RE_THROW(); } *************** plpython_inline_handler(PG_FUNCTION_ARGS *** 289,296 **** /* Pop the error context stack */ error_context_stack = plerrcontext.previous; ! ! PLy_curr_procedure = save_curr_proc; PG_RETURN_VOID(); } --- 295,301 ---- /* Pop the error context stack */ error_context_stack = plerrcontext.previous; ! PLy_pop_execution_context(); PG_RETURN_VOID(); } *************** plpython2_inline_handler(PG_FUNCTION_ARG *** 303,308 **** --- 308,322 ---- } #endif /* PY_MAJOR_VERSION < 3 */ + PLyExecutionContext * + PLy_current_execution_context(void) + { + if (PLy_execution_contexts == NIL) + elog(ERROR, "no Python function is currently executing"); + + return linitial(PLy_execution_contexts); + } + static bool PLy_procedure_is_trigger(Form_pg_proc procStruct) { return (procStruct->prorettype == TRIGGEROID || *************** static bool PLy_procedure_is_trigger(For *** 313,321 **** static void plpython_error_callback(void *arg) { ! if (PLy_curr_procedure) errcontext("PL/Python function \"%s\"", ! PLy_procedure_name(PLy_curr_procedure)); } static void --- 327,337 ---- static void plpython_error_callback(void *arg) { ! PLyExecutionContext *exec_ctx = PLy_current_execution_context(); ! ! if (exec_ctx->curr_proc) errcontext("PL/Python function \"%s\"", ! PLy_procedure_name(exec_ctx->curr_proc)); } static void *************** plpython_inline_error_callback(void *arg *** 323,325 **** --- 339,371 ---- { errcontext("PL/Python anonymous code block"); } + + static PLyExecutionContext * + PLy_push_execution_context(void) + { + PLyExecutionContext *context = palloc(sizeof(PLyExecutionContext)); + + context->curr_proc = NULL; + context->scratch_ctx = AllocSetContextCreate(CurrentMemoryContext, + "PL/Python scratch context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + PLy_execution_contexts = lcons(context, PLy_execution_contexts); + return context; + } + + static void + PLy_pop_execution_context(void) + { + PLyExecutionContext *context; + + if (PLy_execution_contexts == NIL) + elog(ERROR, "no Python function is currently executing"); + + context = linitial(PLy_execution_contexts); + PLy_execution_contexts = list_delete_first(PLy_execution_contexts); + + MemoryContextDelete(context->scratch_ctx); + } diff --git a/src/pl/plpython/plpy_main.h b/src/pl/plpython/plpy_main.h index a71aead..7935100 100644 *** a/src/pl/plpython/plpy_main.h --- b/src/pl/plpython/plpy_main.h *************** *** 10,13 **** --- 10,27 ---- /* the interpreter's globals dict */ extern PyObject *PLy_interp_globals; + /* A stack of PL/Python execution contexts. Each time user-defined Python code + * is called, an execution context is created and put on the stack. After the + * Python code returns, the context is destroyed. */ + extern List *PLy_execution_contexts; + + typedef struct PLyExecutionContext + { + PLyProcedure *curr_proc; /* the currently executing procedure */ + MemoryContext scratch_ctx; /* a context to fo things like type I/O */ + } PLyExecutionContext; + + /* Get the current execution context */ + extern PLyExecutionContext *PLy_current_execution_context(void); + #endif /* PLPY_MAIN_H */ diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c index 229966a..7fb5f00 100644 *** a/src/pl/plpython/plpy_procedure.c --- b/src/pl/plpython/plpy_procedure.c *************** *** 22,30 **** #include "plpy_main.h" - PLyProcedure *PLy_curr_procedure = NULL; - - static HTAB *PLy_procedure_cache = NULL; static HTAB *PLy_trigger_cache = NULL; --- 22,27 ---- diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h index e986c7e..c7405e0 100644 *** a/src/pl/plpython/plpy_procedure.h --- b/src/pl/plpython/plpy_procedure.h *************** extern PLyProcedure *PLy_procedure_get(O *** 45,52 **** extern void PLy_procedure_compile(PLyProcedure *proc, const char *src); extern void PLy_procedure_delete(PLyProcedure *proc); - - /* currently active plpython function */ - extern PLyProcedure *PLy_curr_procedure; - #endif /* PLPY_PROCEDURE_H */ --- 45,48 ---- diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c index 0d63c4f..2448d9f 100644 *** a/src/pl/plpython/plpy_spi.c --- b/src/pl/plpython/plpy_spi.c *************** *** 18,23 **** --- 18,24 ---- #include "plpy_spi.h" #include "plpy_elog.h" + #include "plpy_main.h" #include "plpy_planobject.h" #include "plpy_plpymodule.h" #include "plpy_procedure.h" *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 238,243 **** --- 239,245 ---- { char *volatile nulls; volatile int j; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); if (nargs > 0) nulls = palloc(nargs * sizeof(char)); *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 280,287 **** } } rv = SPI_execute_plan(plan->plan, plan->values, nulls, ! PLy_curr_procedure->fn_readonly, limit); ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); if (nargs > 0) --- 282,291 ---- } } + Assert(exec_ctx->curr_proc != NULL); + rv = SPI_execute_plan(plan->plan, plan->values, nulls, ! exec_ctx->curr_proc->fn_readonly, limit); ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); if (nargs > 0) *************** PLy_spi_execute_query(char *query, long *** 347,354 **** PG_TRY(); { pg_verifymbstr(query, strlen(query), false); ! rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit); ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); PLy_spi_subtransaction_commit(oldcontext, oldowner); --- 351,362 ---- PG_TRY(); { + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + + Assert(exec_ctx->curr_proc != NULL); + pg_verifymbstr(query, strlen(query), false); ! rv = SPI_execute(query, exec_ctx->curr_proc->fn_readonly, limit); ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); PLy_spi_subtransaction_commit(oldcontext, oldowner); diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index d5cac9f..11ba9e8 100644 *** a/src/pl/plpython/plpy_typeio.c --- b/src/pl/plpython/plpy_typeio.c *************** *** 23,28 **** --- 23,29 ---- #include "plpy_typeio.h" #include "plpy_elog.h" + #include "plpy_main.h" /* I/O function caching */ *************** PyObject * *** 262,267 **** --- 263,270 ---- PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc) { PyObject *volatile dict; + MemoryContext oldcontext = CurrentMemoryContext; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); int i; if (info->is_rowtype != 1) *************** PLyDict_FromTuple(PLyTypeInfo *info, Hea *** 290,303 **** --- 293,310 ---- PyDict_SetItemString(dict, key, Py_None); else { + MemoryContextSwitchTo(exec_ctx->scratch_ctx); value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr); PyDict_SetItemString(dict, key, value); Py_DECREF(value); + MemoryContextSwitchTo(oldcontext); + MemoryContextReset(exec_ctx->scratch_ctx); } } } PG_CATCH(); { + MemoryContextSwitchTo(oldcontext); Py_DECREF(dict); PG_RE_THROW(); }
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index d5cac9f..914e033 100644 *** a/src/pl/plpython/plpy_typeio.c --- b/src/pl/plpython/plpy_typeio.c *************** PLy_typeinfo_dealloc(PLyTypeInfo *arg) *** 73,78 **** --- 73,88 ---- { if (arg->is_rowtype == 1) { + if (arg->in.r.natts > 0) + { + int i; + + for (i = 0; i < arg->in.r.natts; i++) + { + if (arg->in.r.atts[i].elm != NULL) + PLy_free(arg->in.r.atts[i].elm); + } + } if (arg->in.r.atts) PLy_free(arg->in.r.atts); if (arg->out.r.atts)
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers