mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
pageinspect: Fix relcache leak in gist_page_items().
The gist_page_items() function opened the index relation on first call and closed it on the last call. But there's no guarantee that the function is run to completion, leading to a relcache leak and warning at the end of the transaction. To fix, refactor the function to return all the rows in one call, as a tuplestore. Reported-by: Tom Lane Discussion: https://www.postgresql.org/message-id/234863.1610916631%40sss.pgh.pa.us
This commit is contained in:
parent
7db0cd2145
commit
04eb75e783
@ -92,65 +92,56 @@ gist_page_opaque_info(PG_FUNCTION_ARGS)
|
|||||||
return HeapTupleGetDatum(resultTuple);
|
return HeapTupleGetDatum(resultTuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct gist_page_items_state
|
|
||||||
{
|
|
||||||
Page page;
|
|
||||||
TupleDesc tupd;
|
|
||||||
OffsetNumber offset;
|
|
||||||
Relation rel;
|
|
||||||
} gist_page_items_state;
|
|
||||||
|
|
||||||
Datum
|
Datum
|
||||||
gist_page_items_bytea(PG_FUNCTION_ARGS)
|
gist_page_items_bytea(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
||||||
FuncCallContext *fctx;
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||||
gist_page_items_state *inter_call_data;
|
bool randomAccess;
|
||||||
|
TupleDesc tupdesc;
|
||||||
|
Tuplestorestate *tupstore;
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
Page page;
|
||||||
|
OffsetNumber offset;
|
||||||
|
|
||||||
if (!superuser())
|
if (!superuser())
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||||
errmsg("must be superuser to use raw page functions")));
|
errmsg("must be superuser to use raw page functions")));
|
||||||
|
|
||||||
if (SRF_IS_FIRSTCALL())
|
/* check to see if caller supports us returning a tuplestore */
|
||||||
|
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("set-valued function called in context that cannot accept a set")));
|
||||||
|
if (!(rsinfo->allowedModes & SFRM_Materialize))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("materialize mode required, but it is not allowed in this context")));
|
||||||
|
|
||||||
|
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||||
|
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||||
|
|
||||||
|
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||||
|
elog(ERROR, "return type must be a row type");
|
||||||
|
|
||||||
|
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
|
||||||
|
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
||||||
|
rsinfo->returnMode = SFRM_Materialize;
|
||||||
|
rsinfo->setResult = tupstore;
|
||||||
|
rsinfo->setDesc = tupdesc;
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
page = get_page_from_raw(raw_page);
|
||||||
|
|
||||||
|
if (GistPageIsDeleted(page))
|
||||||
|
elog(NOTICE, "page is deleted");
|
||||||
|
|
||||||
|
for (offset = FirstOffsetNumber;
|
||||||
|
offset <= PageGetMaxOffsetNumber(page);
|
||||||
|
offset++)
|
||||||
{
|
{
|
||||||
TupleDesc tupdesc;
|
|
||||||
MemoryContext mctx;
|
|
||||||
Page page;
|
|
||||||
|
|
||||||
fctx = SRF_FIRSTCALL_INIT();
|
|
||||||
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
|
|
||||||
|
|
||||||
page = get_page_from_raw(raw_page);
|
|
||||||
|
|
||||||
inter_call_data = palloc(sizeof(gist_page_items_state));
|
|
||||||
|
|
||||||
/* Build a tuple descriptor for our result type */
|
|
||||||
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
|
||||||
elog(ERROR, "return type must be a row type");
|
|
||||||
|
|
||||||
if (GistPageIsDeleted(page))
|
|
||||||
elog(NOTICE, "page is deleted");
|
|
||||||
|
|
||||||
inter_call_data->page = page;
|
|
||||||
inter_call_data->tupd = tupdesc;
|
|
||||||
inter_call_data->offset = FirstOffsetNumber;
|
|
||||||
|
|
||||||
fctx->max_calls = PageGetMaxOffsetNumber(page);
|
|
||||||
fctx->user_fctx = inter_call_data;
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(mctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fctx = SRF_PERCALL_SETUP();
|
|
||||||
inter_call_data = fctx->user_fctx;
|
|
||||||
|
|
||||||
if (fctx->call_cntr < fctx->max_calls)
|
|
||||||
{
|
|
||||||
Page page = inter_call_data->page;
|
|
||||||
OffsetNumber offset = inter_call_data->offset;
|
|
||||||
HeapTuple resultTuple;
|
|
||||||
Datum result;
|
|
||||||
Datum values[4];
|
Datum values[4];
|
||||||
bool nulls[4];
|
bool nulls[4];
|
||||||
ItemId id;
|
ItemId id;
|
||||||
@ -177,15 +168,10 @@ gist_page_items_bytea(PG_FUNCTION_ARGS)
|
|||||||
memcpy(VARDATA(tuple_bytea), itup, tuple_len);
|
memcpy(VARDATA(tuple_bytea), itup, tuple_len);
|
||||||
values[3] = PointerGetDatum(tuple_bytea);
|
values[3] = PointerGetDatum(tuple_bytea);
|
||||||
|
|
||||||
/* Build and return the result tuple. */
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||||
resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
|
|
||||||
result = HeapTupleGetDatum(resultTuple);
|
|
||||||
|
|
||||||
inter_call_data->offset++;
|
|
||||||
SRF_RETURN_NEXT(fctx, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SRF_RETURN_DONE(fctx);
|
return (Datum) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Datum
|
Datum
|
||||||
@ -193,58 +179,56 @@ gist_page_items(PG_FUNCTION_ARGS)
|
|||||||
{
|
{
|
||||||
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
||||||
Oid indexRelid = PG_GETARG_OID(1);
|
Oid indexRelid = PG_GETARG_OID(1);
|
||||||
FuncCallContext *fctx;
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||||
gist_page_items_state *inter_call_data;
|
bool randomAccess;
|
||||||
|
Relation indexRel;
|
||||||
|
TupleDesc tupdesc;
|
||||||
|
Tuplestorestate *tupstore;
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
Page page;
|
||||||
|
OffsetNumber offset;
|
||||||
|
|
||||||
if (!superuser())
|
if (!superuser())
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||||
errmsg("must be superuser to use raw page functions")));
|
errmsg("must be superuser to use raw page functions")));
|
||||||
|
|
||||||
if (SRF_IS_FIRSTCALL())
|
/* check to see if caller supports us returning a tuplestore */
|
||||||
|
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("set-valued function called in context that cannot accept a set")));
|
||||||
|
if (!(rsinfo->allowedModes & SFRM_Materialize))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("materialize mode required, but it is not allowed in this context")));
|
||||||
|
|
||||||
|
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||||
|
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||||
|
|
||||||
|
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||||
|
elog(ERROR, "return type must be a row type");
|
||||||
|
|
||||||
|
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
|
||||||
|
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
||||||
|
rsinfo->returnMode = SFRM_Materialize;
|
||||||
|
rsinfo->setResult = tupstore;
|
||||||
|
rsinfo->setDesc = tupdesc;
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
/* Open the relation */
|
||||||
|
indexRel = index_open(indexRelid, AccessShareLock);
|
||||||
|
|
||||||
|
page = get_page_from_raw(raw_page);
|
||||||
|
|
||||||
|
if (GistPageIsDeleted(page))
|
||||||
|
elog(NOTICE, "page is deleted");
|
||||||
|
|
||||||
|
for (offset = FirstOffsetNumber;
|
||||||
|
offset <= PageGetMaxOffsetNumber(page);
|
||||||
|
offset++)
|
||||||
{
|
{
|
||||||
Relation indexRel;
|
|
||||||
TupleDesc tupdesc;
|
|
||||||
MemoryContext mctx;
|
|
||||||
Page page;
|
|
||||||
|
|
||||||
fctx = SRF_FIRSTCALL_INIT();
|
|
||||||
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
|
|
||||||
|
|
||||||
page = get_page_from_raw(raw_page);
|
|
||||||
|
|
||||||
inter_call_data = palloc(sizeof(gist_page_items_state));
|
|
||||||
|
|
||||||
/* Open the relation */
|
|
||||||
indexRel = index_open(indexRelid, AccessShareLock);
|
|
||||||
|
|
||||||
/* Build a tuple descriptor for our result type */
|
|
||||||
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
|
||||||
elog(ERROR, "return type must be a row type");
|
|
||||||
|
|
||||||
if (GistPageIsDeleted(page))
|
|
||||||
elog(NOTICE, "page is deleted");
|
|
||||||
|
|
||||||
inter_call_data->page = page;
|
|
||||||
inter_call_data->tupd = tupdesc;
|
|
||||||
inter_call_data->offset = FirstOffsetNumber;
|
|
||||||
inter_call_data->rel = indexRel;
|
|
||||||
|
|
||||||
fctx->max_calls = PageGetMaxOffsetNumber(page);
|
|
||||||
fctx->user_fctx = inter_call_data;
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(mctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fctx = SRF_PERCALL_SETUP();
|
|
||||||
inter_call_data = fctx->user_fctx;
|
|
||||||
|
|
||||||
if (fctx->call_cntr < fctx->max_calls)
|
|
||||||
{
|
|
||||||
Page page = inter_call_data->page;
|
|
||||||
OffsetNumber offset = inter_call_data->offset;
|
|
||||||
HeapTuple resultTuple;
|
|
||||||
Datum result;
|
|
||||||
Datum values[4];
|
Datum values[4];
|
||||||
bool nulls[4];
|
bool nulls[4];
|
||||||
ItemId id;
|
ItemId id;
|
||||||
@ -260,11 +244,10 @@ gist_page_items(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
itup = (IndexTuple) PageGetItem(page, id);
|
itup = (IndexTuple) PageGetItem(page, id);
|
||||||
|
|
||||||
index_deform_tuple(itup, RelationGetDescr(inter_call_data->rel),
|
index_deform_tuple(itup, RelationGetDescr(indexRel),
|
||||||
itup_values, itup_isnull);
|
itup_values, itup_isnull);
|
||||||
|
|
||||||
key_desc = BuildIndexValueDescription(inter_call_data->rel, itup_values,
|
key_desc = BuildIndexValueDescription(indexRel, itup_values, itup_isnull);
|
||||||
itup_isnull);
|
|
||||||
|
|
||||||
memset(nulls, 0, sizeof(nulls));
|
memset(nulls, 0, sizeof(nulls));
|
||||||
|
|
||||||
@ -273,15 +256,10 @@ gist_page_items(PG_FUNCTION_ARGS)
|
|||||||
values[2] = Int32GetDatum((int) IndexTupleSize(itup));
|
values[2] = Int32GetDatum((int) IndexTupleSize(itup));
|
||||||
values[3] = CStringGetTextDatum(key_desc);
|
values[3] = CStringGetTextDatum(key_desc);
|
||||||
|
|
||||||
/* Build and return the result tuple. */
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||||
resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
|
|
||||||
result = HeapTupleGetDatum(resultTuple);
|
|
||||||
|
|
||||||
inter_call_data->offset++;
|
|
||||||
SRF_RETURN_NEXT(fctx, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
relation_close(inter_call_data->rel, AccessShareLock);
|
relation_close(indexRel, AccessShareLock);
|
||||||
|
|
||||||
SRF_RETURN_DONE(fctx);
|
return (Datum) 0;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user