1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-28 05:21:27 +03:00

Repair memory leaks in plpython.

PLy_spi_execute_plan (PLyPlan.execute) and PLy_cursor_plan
(plpy.cursor) use PLy_output_convert to convert Python values
into Datums that can be passed to the query-to-execute.  But they
failed to pay much attention to its warning that it can leave "cruft
generated along the way" behind.  Repeated use of these methods can
result in a substantial memory leak for the duration of the calling
plpython function.

To fix, make a temporary memory context to invoke PLy_output_convert
in.  This also lets us get rid of the rather fragile code that was
here for retail pfree's of the converted Datums.  Indeed, we don't
need the PLyPlanObject.values field anymore at all, though I left it
in place in the back branches in the name of ABI stability.

Mat Arye and Tom Lane, per report from Mat Arye.  Back-patch to all
supported branches.

Discussion: https://postgr.es/m/CADsUR0DvVgnZYWwnmKRK65MZg7YLUSTDLV61qdnrwtrAJgU6xw@mail.gmail.com
This commit is contained in:
Tom Lane 2025-01-11 11:45:56 -05:00
parent 8ed9bf0a32
commit e98df02df3
2 changed files with 49 additions and 61 deletions

View File

@ -142,7 +142,6 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
{ {
PLyCursorObject *cursor; PLyCursorObject *cursor;
volatile int nargs; volatile int nargs;
int i;
PLyPlanObject *plan; PLyPlanObject *plan;
PLyExecutionContext *exec_ctx = PLy_current_execution_context(); PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
@ -201,13 +200,30 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
PG_TRY(); PG_TRY();
{ {
Portal portal; Portal portal;
MemoryContext tmpcontext;
Datum *volatile values;
char *volatile nulls; char *volatile nulls;
volatile int j; volatile int j;
/*
* Converted arguments and associated cruft will be in this context,
* which is local to our subtransaction.
*/
tmpcontext = AllocSetContextCreate(CurTransactionContext,
"PL/Python temporary context",
ALLOCSET_SMALL_SIZES);
MemoryContextSwitchTo(tmpcontext);
if (nargs > 0) if (nargs > 0)
nulls = palloc(nargs * sizeof(char)); {
values = (Datum *) palloc(nargs * sizeof(Datum));
nulls = (char *) palloc(nargs * sizeof(char));
}
else else
{
values = NULL;
nulls = NULL; nulls = NULL;
}
for (j = 0; j < nargs; j++) for (j = 0; j < nargs; j++)
{ {
@ -219,7 +235,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
{ {
bool isnull; bool isnull;
plan->values[j] = PLy_output_convert(arg, elem, &isnull); values[j] = PLy_output_convert(arg, elem, &isnull);
nulls[j] = isnull ? 'n' : ' '; nulls[j] = isnull ? 'n' : ' ';
} }
PG_FINALLY(2); PG_FINALLY(2);
@ -229,7 +245,9 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
PG_END_TRY(2); PG_END_TRY(2);
} }
portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls, MemoryContextSwitchTo(oldcontext);
portal = SPI_cursor_open(NULL, plan->plan, values, nulls,
exec_ctx->curr_proc->fn_readonly); exec_ctx->curr_proc->fn_readonly);
if (portal == NULL) if (portal == NULL)
elog(ERROR, "SPI_cursor_open() failed: %s", elog(ERROR, "SPI_cursor_open() failed: %s",
@ -239,40 +257,18 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
PinPortal(portal); PinPortal(portal);
MemoryContextDelete(tmpcontext);
PLy_spi_subtransaction_commit(oldcontext, oldowner); PLy_spi_subtransaction_commit(oldcontext, oldowner);
} }
PG_CATCH(); PG_CATCH();
{ {
int k;
/* cleanup plan->values array */
for (k = 0; k < nargs; k++)
{
if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
plan->values[k] = PointerGetDatum(NULL);
}
}
Py_DECREF(cursor); Py_DECREF(cursor);
/* Subtransaction abort will remove the tmpcontext */
PLy_spi_subtransaction_abort(oldcontext, oldowner); PLy_spi_subtransaction_abort(oldcontext, oldowner);
return NULL; return NULL;
} }
PG_END_TRY(); PG_END_TRY();
for (i = 0; i < nargs; i++)
{
if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
plan->values[i] = PointerGetDatum(NULL);
}
}
Assert(cursor->portalname != NULL); Assert(cursor->portalname != NULL);
return (PyObject *) cursor; return (PyObject *) cursor;
} }

View File

@ -175,8 +175,7 @@ PyObject *
PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
{ {
volatile int nargs; volatile int nargs;
int i, int rv;
rv;
PLyPlanObject *plan; PLyPlanObject *plan;
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner; volatile ResourceOwner oldowner;
@ -222,13 +221,30 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
PG_TRY(); PG_TRY();
{ {
PLyExecutionContext *exec_ctx = PLy_current_execution_context(); PLyExecutionContext *exec_ctx = PLy_current_execution_context();
MemoryContext tmpcontext;
Datum *volatile values;
char *volatile nulls; char *volatile nulls;
volatile int j; volatile int j;
/*
* Converted arguments and associated cruft will be in this context,
* which is local to our subtransaction.
*/
tmpcontext = AllocSetContextCreate(CurTransactionContext,
"PL/Python temporary context",
ALLOCSET_SMALL_SIZES);
MemoryContextSwitchTo(tmpcontext);
if (nargs > 0) if (nargs > 0)
nulls = palloc(nargs * sizeof(char)); {
values = (Datum *) palloc(nargs * sizeof(Datum));
nulls = (char *) palloc(nargs * sizeof(char));
}
else else
{
values = NULL;
nulls = NULL; nulls = NULL;
}
for (j = 0; j < nargs; j++) for (j = 0; j < nargs; j++)
{ {
@ -240,7 +256,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
{ {
bool isnull; bool isnull;
plan->values[j] = PLy_output_convert(arg, elem, &isnull); values[j] = PLy_output_convert(arg, elem, &isnull);
nulls[j] = isnull ? 'n' : ' '; nulls[j] = isnull ? 'n' : ' ';
} }
PG_FINALLY(2); PG_FINALLY(2);
@ -250,47 +266,23 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
PG_END_TRY(2); PG_END_TRY(2);
} }
rv = SPI_execute_plan(plan->plan, plan->values, nulls, MemoryContextSwitchTo(oldcontext);
rv = SPI_execute_plan(plan->plan, values, nulls,
exec_ctx->curr_proc->fn_readonly, limit); exec_ctx->curr_proc->fn_readonly, limit);
ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
if (nargs > 0) MemoryContextDelete(tmpcontext);
pfree(nulls);
PLy_spi_subtransaction_commit(oldcontext, oldowner); PLy_spi_subtransaction_commit(oldcontext, oldowner);
} }
PG_CATCH(); PG_CATCH();
{ {
int k; /* Subtransaction abort will remove the tmpcontext */
/*
* cleanup plan->values array
*/
for (k = 0; k < nargs; k++)
{
if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
plan->values[k] = PointerGetDatum(NULL);
}
}
PLy_spi_subtransaction_abort(oldcontext, oldowner); PLy_spi_subtransaction_abort(oldcontext, oldowner);
return NULL; return NULL;
} }
PG_END_TRY(); PG_END_TRY();
for (i = 0; i < nargs; i++)
{
if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
plan->values[i] = PointerGetDatum(NULL);
}
}
if (rv < 0) if (rv < 0)
{ {
PLy_exception_set(PLy_exc_spi_error, PLy_exception_set(PLy_exc_spi_error,