Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Create a stack of pl/python "execution contexts".
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 13 Mar 2012 17:19:06 +0000 (13:19 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 13 Mar 2012 17:19:06 +0000 (13:19 -0400)
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

src/pl/plpython/plpy_cursorobject.c
src/pl/plpython/plpy_elog.c
src/pl/plpython/plpy_exec.c
src/pl/plpython/plpy_main.c
src/pl/plpython/plpy_main.h
src/pl/plpython/plpy_procedure.c
src/pl/plpython/plpy_procedure.h
src/pl/plpython/plpy_spi.c
src/pl/plpython/plpy_typeio.c

index 4226dc7d193d35a5f3bd66644a2983b2236d014b..e8240b63c901e8cefa232d0011dd5eeb17655559 100644 (file)
@@ -14,6 +14,7 @@
 #include "plpy_cursorobject.h"
 
 #include "plpy_elog.h"
+#include "plpy_main.h"
 #include "plpy_planobject.h"
 #include "plpy_procedure.h"
 #include "plpy_resultobject.h"
@@ -119,6 +120,7 @@ PLy_cursor_query(const char *query)
 
    PG_TRY();
    {
+       PLyExecutionContext *exec_ctx = PLy_current_execution_context();
        SPIPlanPtr  plan;
        Portal      portal;
 
@@ -130,7 +132,7 @@ PLy_cursor_query(const char *query)
                 SPI_result_code_string(SPI_result));
 
        portal = SPI_cursor_open(NULL, plan, NULL, NULL,
-                                PLy_curr_procedure->fn_readonly);
+                                exec_ctx->curr_proc->fn_readonly);
        SPI_freeplan(plan);
 
        if (portal == NULL)
@@ -207,6 +209,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
 
    PG_TRY();
    {
+       PLyExecutionContext *exec_ctx = PLy_current_execution_context();
        Portal      portal;
        char       *volatile nulls;
        volatile int j;
@@ -253,7 +256,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
        }
 
        portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
-                                PLy_curr_procedure->fn_readonly);
+                                exec_ctx->curr_proc->fn_readonly);
        if (portal == NULL)
            elog(ERROR, "SPI_cursor_open() failed: %s",
                 SPI_result_code_string(SPI_result));
index 741980c7c5674c129b04704ccd25be1331b684c8..2f04a8c0dba17645f8ae786a3bbae61776767a7f 100644 (file)
@@ -12,6 +12,7 @@
 
 #include "plpy_elog.h"
 
+#include "plpy_main.h"
 #include "plpy_procedure.h"
 
 
@@ -255,6 +256,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
        /* The first frame always points at <module>, skip it. */
        if (*tb_depth > 0)
        {
+           PLyExecutionContext *exec_ctx = PLy_current_execution_context();
            char       *proname;
            char       *fname;
            char       *line;
@@ -270,7 +272,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
            else
                fname = PyString_AsString(name);
 
-           proname = PLy_procedure_name(PLy_curr_procedure);
+           proname = PLy_procedure_name(exec_ctx->curr_proc);
            plain_filename = PyString_AsString(filename);
            plain_lineno = PyInt_AsLong(lineno);
 
@@ -287,7 +289,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
             * function code object was compiled with "<string>" as the
             * filename
             */
-           if (PLy_curr_procedure && plain_filename != NULL &&
+           if (exec_ctx->curr_proc && plain_filename != NULL &&
                strcmp(plain_filename, "<string>") == 0)
            {
                /*
@@ -299,7 +301,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
                 * 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);
+               line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
                if (line)
                {
                    appendStringInfo(&tbstr, "\n    %s", line);
index ecf4996e8cf31e424d9135d0fc4e9eef4557c29f..280d3ed1aca083cb623a642bc18099e102dbb623 100644 (file)
@@ -455,7 +455,9 @@ PLy_function_delete_args(PLyProcedure *proc)
 static void
 plpython_return_error_callback(void *arg)
 {
-   if (PLy_curr_procedure)
+   PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
+   if (exec_ctx->curr_proc)
        errcontext("while creating return value");
 }
 
@@ -781,7 +783,9 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
 static void
 plpython_trigger_error_callback(void *arg)
 {
-   if (PLy_curr_procedure)
+   PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
+   if (exec_ctx->curr_proc)
        errcontext("while modifying trigger row");
 }
 
index ae9d87e9a6329eed69b322d3de356dce0ac2b3b1..277dedc22d2dae8c568aa32283006835216ff9b2 100644 (file)
@@ -12,6 +12,7 @@
 #include "executor/spi.h"
 #include "miscadmin.h"
 #include "utils/guc.h"
+#include "utils/memutils.h"
 #include "utils/syscache.h"
 
 #include "plpython.h"
@@ -66,11 +67,17 @@ static void plpython_error_callback(void *arg);
 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;
 
+/* this doesn't need to be global; use PLy_current_execution_context() */
+static PLyExecutionContext *PLy_execution_contexts = NULL;
+
 
 void
 _PG_init(void)
@@ -114,6 +121,8 @@ _PG_init(void)
 
    explicit_subtransactions = NIL;
 
+   PLy_execution_contexts = NULL;
+
    inited = true;
 }
 
@@ -179,13 +188,20 @@ Datum
 plpython_call_handler(PG_FUNCTION_ARGS)
 {
    Datum       retval;
-   PLyProcedure *save_curr_proc;
+   PLyExecutionContext *exec_ctx;
    ErrorContextCallback plerrcontext;
 
+   /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
    if (SPI_connect() != SPI_OK_CONNECT)
        elog(ERROR, "SPI_connect failed");
 
-   save_curr_proc = PLy_curr_procedure;
+   /*
+    * Push execution context onto stack.  It is important that this get
+    * popped again, so avoid putting anything that could throw error between
+    * here and the PG_TRY.  (plpython_error_callback expects the stack entry
+    * to be there, so we have to make the context first.)
+    */
+   exec_ctx = PLy_push_execution_context();
 
    /*
     * Setup error traceback support for ereport()
@@ -203,20 +219,20 @@ plpython_call_handler(PG_FUNCTION_ARGS)
            HeapTuple   trv;
 
            proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true);
-           PLy_curr_procedure = proc;
+           exec_ctx->curr_proc = proc;
            trv = PLy_exec_trigger(fcinfo, proc);
            retval = PointerGetDatum(trv);
        }
        else
        {
            proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false);
-           PLy_curr_procedure = proc;
+           exec_ctx->curr_proc = proc;
            retval = PLy_exec_function(fcinfo, proc);
        }
    }
    PG_CATCH();
    {
-       PLy_curr_procedure = save_curr_proc;
+       PLy_pop_execution_context();
        PyErr_Clear();
        PG_RE_THROW();
    }
@@ -224,8 +240,8 @@ plpython_call_handler(PG_FUNCTION_ARGS)
 
    /* Pop the error context stack */
    error_context_stack = plerrcontext.previous;
-
-   PLy_curr_procedure = save_curr_proc;
+   /* ... and then the execution context */
+   PLy_pop_execution_context();
 
    return retval;
 }
@@ -244,22 +260,14 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
    InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
    FunctionCallInfoData fake_fcinfo;
    FmgrInfo    flinfo;
-   PLyProcedure *save_curr_proc;
    PLyProcedure proc;
+   PLyExecutionContext *exec_ctx;
    ErrorContextCallback plerrcontext;
 
+   /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
    if (SPI_connect() != SPI_OK_CONNECT)
        elog(ERROR, "SPI_connect failed");
 
-   save_curr_proc = PLy_curr_procedure;
-
-   /*
-    * Setup error traceback support for ereport()
-    */
-   plerrcontext.callback = plpython_inline_error_callback;
-   plerrcontext.previous = error_context_stack;
-   error_context_stack = &plerrcontext;
-
    MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
    MemSet(&flinfo, 0, sizeof(flinfo));
    fake_fcinfo.flinfo = &flinfo;
@@ -270,27 +278,44 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
    proc.pyname = PLy_strdup("__plpython_inline_block");
    proc.result.out.d.typoid = VOIDOID;
 
+   /*
+    * Push execution context onto stack.  It is important that this get
+    * popped again, so avoid putting anything that could throw error between
+    * here and the PG_TRY.  (plpython_inline_error_callback doesn't currently
+    * need the stack entry, but for consistency with plpython_call_handler
+    * we do it in this order.)
+    */
+   exec_ctx = PLy_push_execution_context();
+
+   /*
+    * Setup error traceback support for ereport()
+    */
+   plerrcontext.callback = plpython_inline_error_callback;
+   plerrcontext.previous = error_context_stack;
+   error_context_stack = &plerrcontext;
+
    PG_TRY();
    {
        PLy_procedure_compile(&proc, codeblock->source_text);
-       PLy_curr_procedure = &proc;
+       exec_ctx->curr_proc = &proc;
        PLy_exec_function(&fake_fcinfo, &proc);
    }
    PG_CATCH();
    {
+       PLy_pop_execution_context();
        PLy_procedure_delete(&proc);
-       PLy_curr_procedure = save_curr_proc;
        PyErr_Clear();
        PG_RE_THROW();
    }
    PG_END_TRY();
 
-   PLy_procedure_delete(&proc);
-
    /* Pop the error context stack */
    error_context_stack = plerrcontext.previous;
+   /* ... and then the execution context */
+   PLy_pop_execution_context();
 
-   PLy_curr_procedure = save_curr_proc;
+   /* Now clean up the transient procedure we made */
+   PLy_procedure_delete(&proc);
 
    PG_RETURN_VOID();
 }
@@ -313,9 +338,11 @@ static bool PLy_procedure_is_trigger(Form_pg_proc procStruct)
 static void
 plpython_error_callback(void *arg)
 {
-   if (PLy_curr_procedure)
+   PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
+   if (exec_ctx->curr_proc)
        errcontext("PL/Python function \"%s\"",
-                  PLy_procedure_name(PLy_curr_procedure));
+                  PLy_procedure_name(exec_ctx->curr_proc));
 }
 
 static void
@@ -323,3 +350,42 @@ plpython_inline_error_callback(void *arg)
 {
    errcontext("PL/Python anonymous code block");
 }
+
+PLyExecutionContext *
+PLy_current_execution_context(void)
+{
+   if (PLy_execution_contexts == NULL)
+       elog(ERROR, "no Python function is currently executing");
+
+   return PLy_execution_contexts;
+}
+
+static PLyExecutionContext *
+PLy_push_execution_context(void)
+{
+   PLyExecutionContext *context = PLy_malloc(sizeof(PLyExecutionContext));
+
+   context->curr_proc = NULL;
+   context->scratch_ctx = AllocSetContextCreate(TopTransactionContext,
+                                                "PL/Python scratch context",
+                                                ALLOCSET_DEFAULT_MINSIZE,
+                                                ALLOCSET_DEFAULT_INITSIZE,
+                                                ALLOCSET_DEFAULT_MAXSIZE);
+   context->next = PLy_execution_contexts;
+   PLy_execution_contexts = context;
+   return context;
+}
+
+static void
+PLy_pop_execution_context(void)
+{
+   PLyExecutionContext *context = PLy_execution_contexts;
+
+   if (context == NULL)
+       elog(ERROR, "no Python function is currently executing");
+
+   PLy_execution_contexts = context->next;
+
+   MemoryContextDelete(context->scratch_ctx);
+   PLy_free(context);
+}
index a71aead1769057ebab010a584cc3cdeacc16577d..cb214bf83c77c7e9173a3da98b1ca437eada1b77 100644 (file)
 /* 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.
+ */
+typedef struct PLyExecutionContext
+{
+   PLyProcedure    *curr_proc;     /* the currently executing procedure */
+   MemoryContext   scratch_ctx;    /* a context for things like type I/O */
+   struct PLyExecutionContext *next;   /* previous stack level */
+} PLyExecutionContext;
+
+/* Get the current execution context */
+extern PLyExecutionContext *PLy_current_execution_context(void);
+
 #endif /* PLPY_MAIN_H */
index 229966ad795bffa67331c10e6f030b0601dcc56a..7fb5f00e0f2108f48508ed103fb25bae8e134f31 100644 (file)
@@ -22,9 +22,6 @@
 #include "plpy_main.h"
 
 
-PLyProcedure *PLy_curr_procedure = NULL;
-
-
 static HTAB *PLy_procedure_cache = NULL;
 static HTAB *PLy_trigger_cache = NULL;
 
index e986c7ecc56a1734f4111331d8503383eea541be..c7405e064ec1b8e6646c0d515f610c4d5295551e 100644 (file)
@@ -45,8 +45,4 @@ extern PLyProcedure *PLy_procedure_get(Oid fn_oid, bool is_trigger);
 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 */
index 0d63c4f5ce850da5ed23f80a2512494d6dcdd1d4..a75839b93ea3c95250d45ca6744d910ce61bf7b2 100644 (file)
@@ -18,6 +18,7 @@
 #include "plpy_spi.h"
 
 #include "plpy_elog.h"
+#include "plpy_main.h"
 #include "plpy_planobject.h"
 #include "plpy_plpymodule.h"
 #include "plpy_procedure.h"
@@ -236,6 +237,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
 
    PG_TRY();
    {
+       PLyExecutionContext *exec_ctx = PLy_current_execution_context();
        char       *volatile nulls;
        volatile int j;
 
@@ -281,7 +283,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
        }
 
        rv = SPI_execute_plan(plan->plan, plan->values, nulls,
-                             PLy_curr_procedure->fn_readonly, limit);
+                             exec_ctx->curr_proc->fn_readonly, limit);
        ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
 
        if (nargs > 0)
@@ -347,8 +349,10 @@ PLy_spi_execute_query(char *query, long limit)
 
    PG_TRY();
    {
+       PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
        pg_verifymbstr(query, strlen(query), false);
-       rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
+       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);
index d5cac9f1f0dff7b735e6f80ed70e96a95e93b763..1cc9b1e210e09b3e2b962a75c7d80468ce4ab915 100644 (file)
@@ -23,6 +23,7 @@
 #include "plpy_typeio.h"
 
 #include "plpy_elog.h"
+#include "plpy_main.h"
 
 
 /* I/O function caching */
@@ -258,10 +259,15 @@ PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc)
    Assert(arg->is_rowtype == 1);
 }
 
+/*
+ * Transform a tuple into a Python dict object.
+ */
 PyObject *
 PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 {
    PyObject   *volatile dict;
+   PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+   MemoryContext oldcontext = CurrentMemoryContext;
    int         i;
 
    if (info->is_rowtype != 1)
@@ -273,6 +279,11 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 
    PG_TRY();
    {
+       /*
+        * Do the work in the scratch context to avoid leaking memory from
+        * the datatype output function calls.
+        */
+       MemoryContextSwitchTo(exec_ctx->scratch_ctx);
        for (i = 0; i < info->in.r.natts; i++)
        {
            char       *key;
@@ -295,9 +306,12 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
                Py_DECREF(value);
            }
        }
+       MemoryContextSwitchTo(oldcontext);
+       MemoryContextReset(exec_ctx->scratch_ctx);
    }
    PG_CATCH();
    {
+       MemoryContextSwitchTo(oldcontext);
        Py_DECREF(dict);
        PG_RE_THROW();
    }