Commit a4d3a504 authored by Tom Lane's avatar Tom Lane

Eliminate memory leaks in plperl's spi_prepare() function.

Careless use of TopMemoryContext for I/O function data meant that repeated
use of spi_prepare and spi_freeplan would leak memory at the session level,
as per report from Christian Schröder.  In addition, spi_prepare
leaked a lot of transient data within the current plperl function's SPI
Proc context, which would be a problem for repeated use of spi_prepare
within a single plperl function call; and it wasn't terribly careful
about releasing permanent allocations in event of an error, either.

In passing, clean up some copy-and-pasteos in query-lookup error messages.

Alex Hunsaker and Tom Lane
parent cd7d00ad
...@@ -184,6 +184,7 @@ typedef struct plperl_call_data ...@@ -184,6 +184,7 @@ typedef struct plperl_call_data
typedef struct plperl_query_desc typedef struct plperl_query_desc
{ {
char qname[24]; char qname[24];
MemoryContext plan_cxt; /* context holding this struct */
SPIPlanPtr plan; SPIPlanPtr plan;
int nargs; int nargs;
Oid *argtypes; Oid *argtypes;
...@@ -3209,33 +3210,57 @@ plperl_spi_cursor_close(char *cursor) ...@@ -3209,33 +3210,57 @@ plperl_spi_cursor_close(char *cursor)
SV * SV *
plperl_spi_prepare(char *query, int argc, SV **argv) plperl_spi_prepare(char *query, int argc, SV **argv)
{ {
plperl_query_desc *qdesc; volatile SPIPlanPtr plan = NULL;
plperl_query_entry *hash_entry; volatile MemoryContext plan_cxt = NULL;
bool found; plperl_query_desc *volatile qdesc = NULL;
SPIPlanPtr plan; plperl_query_entry *volatile hash_entry = NULL;
int i;
MemoryContext oldcontext = CurrentMemoryContext; MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner; ResourceOwner oldowner = CurrentResourceOwner;
MemoryContext work_cxt;
bool found;
int i;
check_spi_usage_allowed(); check_spi_usage_allowed();
BeginInternalSubTransaction(NULL); BeginInternalSubTransaction(NULL);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
/************************************************************
* Allocate the new querydesc structure
************************************************************/
qdesc = (plperl_query_desc *) malloc(sizeof(plperl_query_desc));
MemSet(qdesc, 0, sizeof(plperl_query_desc));
snprintf(qdesc->qname, sizeof(qdesc->qname), "%p", qdesc);
qdesc->nargs = argc;
qdesc->argtypes = (Oid *) malloc(argc * sizeof(Oid));
qdesc->arginfuncs = (FmgrInfo *) malloc(argc * sizeof(FmgrInfo));
qdesc->argtypioparams = (Oid *) malloc(argc * sizeof(Oid));
PG_TRY(); PG_TRY();
{ {
CHECK_FOR_INTERRUPTS();
/************************************************************
* Allocate the new querydesc structure
*
* The qdesc struct, as well as all its subsidiary data, lives in its
* plan_cxt. But note that the SPIPlan does not.
************************************************************/
plan_cxt = AllocSetContextCreate(TopMemoryContext,
"PL/Perl spi_prepare query",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
MemoryContextSwitchTo(plan_cxt);
qdesc = (plperl_query_desc *) palloc0(sizeof(plperl_query_desc));
snprintf(qdesc->qname, sizeof(qdesc->qname), "%p", qdesc);
qdesc->plan_cxt = plan_cxt;
qdesc->nargs = argc;
qdesc->argtypes = (Oid *) palloc(argc * sizeof(Oid));
qdesc->arginfuncs = (FmgrInfo *) palloc(argc * sizeof(FmgrInfo));
qdesc->argtypioparams = (Oid *) palloc(argc * sizeof(Oid));
MemoryContextSwitchTo(oldcontext);
/************************************************************
* Do the following work in a short-lived context so that we don't
* leak a lot of memory in the PL/Perl function's SPI Proc context.
************************************************************/
work_cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/Perl spi_prepare workspace",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
MemoryContextSwitchTo(work_cxt);
/************************************************************ /************************************************************
* Resolve argument type names and then look them up by oid * Resolve argument type names and then look them up by oid
* in the system cache, and remember the required information * in the system cache, and remember the required information
...@@ -3256,7 +3281,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv) ...@@ -3256,7 +3281,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
getTypeInputInfo(typId, &typInput, &typIOParam); getTypeInputInfo(typId, &typInput, &typIOParam);
qdesc->argtypes[i] = typId; qdesc->argtypes[i] = typId;
perm_fmgr_info(typInput, &(qdesc->arginfuncs[i])); fmgr_info_cxt(typInput, &(qdesc->arginfuncs[i]), plan_cxt);
qdesc->argtypioparams[i] = typIOParam; qdesc->argtypioparams[i] = typIOParam;
} }
...@@ -3280,6 +3305,17 @@ plperl_spi_prepare(char *query, int argc, SV **argv) ...@@ -3280,6 +3305,17 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
elog(ERROR, "SPI_keepplan() failed"); elog(ERROR, "SPI_keepplan() failed");
qdesc->plan = plan; qdesc->plan = plan;
/************************************************************
* Insert a hashtable entry for the plan.
************************************************************/
hash_entry = hash_search(plperl_active_interp->query_hash,
qdesc->qname,
HASH_ENTER, &found);
hash_entry->query_data = qdesc;
/* Get rid of workspace */
MemoryContextDelete(work_cxt);
/* Commit the inner transaction, return to outer xact context */ /* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction(); ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
...@@ -3295,16 +3331,21 @@ plperl_spi_prepare(char *query, int argc, SV **argv) ...@@ -3295,16 +3331,21 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
{ {
ErrorData *edata; ErrorData *edata;
free(qdesc->argtypes);
free(qdesc->arginfuncs);
free(qdesc->argtypioparams);
free(qdesc);
/* Save error info */ /* Save error info */
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData(); edata = CopyErrorData();
FlushErrorState(); FlushErrorState();
/* Drop anything we managed to allocate */
if (hash_entry)
hash_search(plperl_active_interp->query_hash,
qdesc->qname,
HASH_REMOVE, NULL);
if (plan_cxt)
MemoryContextDelete(plan_cxt);
if (plan)
SPI_freeplan(plan);
/* Abort the inner transaction */ /* Abort the inner transaction */
RollbackAndReleaseCurrentSubTransaction(); RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
...@@ -3326,14 +3367,8 @@ plperl_spi_prepare(char *query, int argc, SV **argv) ...@@ -3326,14 +3367,8 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
PG_END_TRY(); PG_END_TRY();
/************************************************************ /************************************************************
* Insert a hashtable entry for the plan and return * Return the query's hash key to the caller.
* the key to the caller.
************************************************************/ ************************************************************/
hash_entry = hash_search(plperl_active_interp->query_hash, qdesc->qname,
HASH_ENTER, &found);
hash_entry->query_data = qdesc;
return cstr2sv(qdesc->qname); return cstr2sv(qdesc->qname);
} }
...@@ -3368,16 +3403,14 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv) ...@@ -3368,16 +3403,14 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv)
/************************************************************ /************************************************************
* Fetch the saved plan descriptor, see if it's o.k. * Fetch the saved plan descriptor, see if it's o.k.
************************************************************/ ************************************************************/
hash_entry = hash_search(plperl_active_interp->query_hash, query, hash_entry = hash_search(plperl_active_interp->query_hash, query,
HASH_FIND, NULL); HASH_FIND, NULL);
if (hash_entry == NULL) if (hash_entry == NULL)
elog(ERROR, "spi_exec_prepared: Invalid prepared query passed"); elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");
qdesc = hash_entry->query_data; qdesc = hash_entry->query_data;
if (qdesc == NULL) if (qdesc == NULL)
elog(ERROR, "spi_exec_prepared: panic - plperl query_hash value vanished"); elog(ERROR, "spi_exec_prepared: plperl query_hash value vanished");
if (qdesc->nargs != argc) if (qdesc->nargs != argc)
elog(ERROR, "spi_exec_prepared: expected %d argument(s), %d passed", elog(ERROR, "spi_exec_prepared: expected %d argument(s), %d passed",
...@@ -3509,12 +3542,11 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv) ...@@ -3509,12 +3542,11 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv)
hash_entry = hash_search(plperl_active_interp->query_hash, query, hash_entry = hash_search(plperl_active_interp->query_hash, query,
HASH_FIND, NULL); HASH_FIND, NULL);
if (hash_entry == NULL) if (hash_entry == NULL)
elog(ERROR, "spi_exec_prepared: Invalid prepared query passed"); elog(ERROR, "spi_query_prepared: Invalid prepared query passed");
qdesc = hash_entry->query_data; qdesc = hash_entry->query_data;
if (qdesc == NULL) if (qdesc == NULL)
elog(ERROR, "spi_query_prepared: panic - plperl query_hash value vanished"); elog(ERROR, "spi_query_prepared: plperl query_hash value vanished");
if (qdesc->nargs != argc) if (qdesc->nargs != argc)
elog(ERROR, "spi_query_prepared: expected %d argument(s), %d passed", elog(ERROR, "spi_query_prepared: expected %d argument(s), %d passed",
...@@ -3619,12 +3651,12 @@ plperl_spi_freeplan(char *query) ...@@ -3619,12 +3651,12 @@ plperl_spi_freeplan(char *query)
hash_entry = hash_search(plperl_active_interp->query_hash, query, hash_entry = hash_search(plperl_active_interp->query_hash, query,
HASH_FIND, NULL); HASH_FIND, NULL);
if (hash_entry == NULL) if (hash_entry == NULL)
elog(ERROR, "spi_exec_prepared: Invalid prepared query passed"); elog(ERROR, "spi_freeplan: Invalid prepared query passed");
qdesc = hash_entry->query_data; qdesc = hash_entry->query_data;
if (qdesc == NULL) if (qdesc == NULL)
elog(ERROR, "spi_exec_freeplan: panic - plperl query_hash value vanished"); elog(ERROR, "spi_freeplan: plperl query_hash value vanished");
plan = qdesc->plan;
/* /*
* free all memory before SPI_freeplan, so if it dies, nothing will be * free all memory before SPI_freeplan, so if it dies, nothing will be
...@@ -3633,11 +3665,7 @@ plperl_spi_freeplan(char *query) ...@@ -3633,11 +3665,7 @@ plperl_spi_freeplan(char *query)
hash_search(plperl_active_interp->query_hash, query, hash_search(plperl_active_interp->query_hash, query,
HASH_REMOVE, NULL); HASH_REMOVE, NULL);
plan = qdesc->plan; MemoryContextDelete(qdesc->plan_cxt);
free(qdesc->argtypes);
free(qdesc->arginfuncs);
free(qdesc->argtypioparams);
free(qdesc);
SPI_freeplan(plan); SPI_freeplan(plan);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment