Commit 04eb75e7 authored by Heikki Linnakangas's avatar Heikki Linnakangas

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
parent 7db0cd21
...@@ -92,65 +92,56 @@ gist_page_opaque_info(PG_FUNCTION_ARGS) ...@@ -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))
TupleDesc tupdesc; ereport(ERROR,
MemoryContext mctx; (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
Page page; errmsg("set-valued function called in context that cannot accept a set")));
if (!(rsinfo->allowedModes & SFRM_Materialize))
fctx = SRF_FIRSTCALL_INIT(); ereport(ERROR,
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("materialize mode required, but it is not allowed in this context")));
page = get_page_from_raw(raw_page);
inter_call_data = palloc(sizeof(gist_page_items_state));
/* Build a tuple descriptor for our result type */ /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
elog(ERROR, "return type must be a row type");
if (GistPageIsDeleted(page)) if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(NOTICE, "page is deleted"); elog(ERROR, "return type must be a row type");
inter_call_data->page = page; randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
inter_call_data->tupd = tupdesc; tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
inter_call_data->offset = FirstOffsetNumber; rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = tupdesc;
fctx->max_calls = PageGetMaxOffsetNumber(page); MemoryContextSwitchTo(oldcontext);
fctx->user_fctx = inter_call_data;
MemoryContextSwitchTo(mctx); page = get_page_from_raw(raw_page);
}
fctx = SRF_PERCALL_SETUP(); if (GistPageIsDeleted(page))
inter_call_data = fctx->user_fctx; elog(NOTICE, "page is deleted");
if (fctx->call_cntr < fctx->max_calls) for (offset = FirstOffsetNumber;
offset <= PageGetMaxOffsetNumber(page);
offset++)
{ {
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) ...@@ -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) ...@@ -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))
Relation indexRel; ereport(ERROR,
TupleDesc tupdesc; (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
MemoryContext mctx; errmsg("set-valued function called in context that cannot accept a set")));
Page page; if (!(rsinfo->allowedModes & SFRM_Materialize))
ereport(ERROR,
fctx = SRF_FIRSTCALL_INIT(); (errcode(ERRCODE_SYNTAX_ERROR),
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); errmsg("materialize mode required, but it is not allowed in this context")));
page = get_page_from_raw(raw_page);
inter_call_data = palloc(sizeof(gist_page_items_state));
/* Open the relation */ /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
indexRel = index_open(indexRelid, AccessShareLock); oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
/* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type");
elog(ERROR, "return type must be a row type");
if (GistPageIsDeleted(page)) randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
elog(NOTICE, "page is deleted"); tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = tupdesc;
inter_call_data->page = page; MemoryContextSwitchTo(oldcontext);
inter_call_data->tupd = tupdesc;
inter_call_data->offset = FirstOffsetNumber;
inter_call_data->rel = indexRel;
fctx->max_calls = PageGetMaxOffsetNumber(page); /* Open the relation */
fctx->user_fctx = inter_call_data; indexRel = index_open(indexRelid, AccessShareLock);
MemoryContextSwitchTo(mctx); page = get_page_from_raw(raw_page);
}
fctx = SRF_PERCALL_SETUP(); if (GistPageIsDeleted(page))
inter_call_data = fctx->user_fctx; elog(NOTICE, "page is deleted");
if (fctx->call_cntr < fctx->max_calls) for (offset = FirstOffsetNumber;
offset <= PageGetMaxOffsetNumber(page);
offset++)
{ {
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) ...@@ -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) ...@@ -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;
} }
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