Skip to content

Commit 04eb75e

Browse files
committed
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
1 parent 7db0cd2 commit 04eb75e

File tree

1 file changed

+74
-96
lines changed

1 file changed

+74
-96
lines changed

contrib/pageinspect/gistfuncs.c

Lines changed: 74 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -92,65 +92,56 @@ gist_page_opaque_info(PG_FUNCTION_ARGS)
9292
return HeapTupleGetDatum(resultTuple);
9393
}
9494

95-
typedef struct gist_page_items_state
96-
{
97-
Page page;
98-
TupleDesc tupd;
99-
OffsetNumber offset;
100-
Relation rel;
101-
} gist_page_items_state;
102-
10395
Datum
10496
gist_page_items_bytea(PG_FUNCTION_ARGS)
10597
{
10698
bytea *raw_page = PG_GETARG_BYTEA_P(0);
107-
FuncCallContext *fctx;
108-
gist_page_items_state *inter_call_data;
99+
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
100+
bool randomAccess;
101+
TupleDesc tupdesc;
102+
Tuplestorestate *tupstore;
103+
MemoryContext oldcontext;
104+
Page page;
105+
OffsetNumber offset;
109106

110107
if (!superuser())
111108
ereport(ERROR,
112109
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
113110
errmsg("must be superuser to use raw page functions")));
114111

115-
if (SRF_IS_FIRSTCALL())
116-
{
117-
TupleDesc tupdesc;
118-
MemoryContext mctx;
119-
Page page;
120-
121-
fctx = SRF_FIRSTCALL_INIT();
122-
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
123-
124-
page = get_page_from_raw(raw_page);
125-
126-
inter_call_data = palloc(sizeof(gist_page_items_state));
112+
/* check to see if caller supports us returning a tuplestore */
113+
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
114+
ereport(ERROR,
115+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
116+
errmsg("set-valued function called in context that cannot accept a set")));
117+
if (!(rsinfo->allowedModes & SFRM_Materialize))
118+
ereport(ERROR,
119+
(errcode(ERRCODE_SYNTAX_ERROR),
120+
errmsg("materialize mode required, but it is not allowed in this context")));
127121

128-
/* Build a tuple descriptor for our result type */
129-
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
130-
elog(ERROR, "return type must be a row type");
122+
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
123+
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
131124

132-
if (GistPageIsDeleted(page))
133-
elog(NOTICE, "page is deleted");
125+
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
126+
elog(ERROR, "return type must be a row type");
134127

135-
inter_call_data->page = page;
136-
inter_call_data->tupd = tupdesc;
137-
inter_call_data->offset = FirstOffsetNumber;
128+
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
129+
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
130+
rsinfo->returnMode = SFRM_Materialize;
131+
rsinfo->setResult = tupstore;
132+
rsinfo->setDesc = tupdesc;
138133

139-
fctx->max_calls = PageGetMaxOffsetNumber(page);
140-
fctx->user_fctx = inter_call_data;
134+
MemoryContextSwitchTo(oldcontext);
141135

142-
MemoryContextSwitchTo(mctx);
143-
}
136+
page = get_page_from_raw(raw_page);
144137

145-
fctx = SRF_PERCALL_SETUP();
146-
inter_call_data = fctx->user_fctx;
138+
if (GistPageIsDeleted(page))
139+
elog(NOTICE, "page is deleted");
147140

148-
if (fctx->call_cntr < fctx->max_calls)
141+
for (offset = FirstOffsetNumber;
142+
offset <= PageGetMaxOffsetNumber(page);
143+
offset++)
149144
{
150-
Page page = inter_call_data->page;
151-
OffsetNumber offset = inter_call_data->offset;
152-
HeapTuple resultTuple;
153-
Datum result;
154145
Datum values[4];
155146
bool nulls[4];
156147
ItemId id;
@@ -177,74 +168,67 @@ gist_page_items_bytea(PG_FUNCTION_ARGS)
177168
memcpy(VARDATA(tuple_bytea), itup, tuple_len);
178169
values[3] = PointerGetDatum(tuple_bytea);
179170

180-
/* Build and return the result tuple. */
181-
resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
182-
result = HeapTupleGetDatum(resultTuple);
183-
184-
inter_call_data->offset++;
185-
SRF_RETURN_NEXT(fctx, result);
171+
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
186172
}
187173

188-
SRF_RETURN_DONE(fctx);
174+
return (Datum) 0;
189175
}
190176

191177
Datum
192178
gist_page_items(PG_FUNCTION_ARGS)
193179
{
194180
bytea *raw_page = PG_GETARG_BYTEA_P(0);
195181
Oid indexRelid = PG_GETARG_OID(1);
196-
FuncCallContext *fctx;
197-
gist_page_items_state *inter_call_data;
182+
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
183+
bool randomAccess;
184+
Relation indexRel;
185+
TupleDesc tupdesc;
186+
Tuplestorestate *tupstore;
187+
MemoryContext oldcontext;
188+
Page page;
189+
OffsetNumber offset;
198190

199191
if (!superuser())
200192
ereport(ERROR,
201193
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
202194
errmsg("must be superuser to use raw page functions")));
203195

204-
if (SRF_IS_FIRSTCALL())
205-
{
206-
Relation indexRel;
207-
TupleDesc tupdesc;
208-
MemoryContext mctx;
209-
Page page;
210-
211-
fctx = SRF_FIRSTCALL_INIT();
212-
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
213-
214-
page = get_page_from_raw(raw_page);
215-
216-
inter_call_data = palloc(sizeof(gist_page_items_state));
196+
/* check to see if caller supports us returning a tuplestore */
197+
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
198+
ereport(ERROR,
199+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
200+
errmsg("set-valued function called in context that cannot accept a set")));
201+
if (!(rsinfo->allowedModes & SFRM_Materialize))
202+
ereport(ERROR,
203+
(errcode(ERRCODE_SYNTAX_ERROR),
204+
errmsg("materialize mode required, but it is not allowed in this context")));
217205

218-
/* Open the relation */
219-
indexRel = index_open(indexRelid, AccessShareLock);
206+
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
207+
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
220208

221-
/* Build a tuple descriptor for our result type */
222-
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
223-
elog(ERROR, "return type must be a row type");
209+
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
210+
elog(ERROR, "return type must be a row type");
224211

225-
if (GistPageIsDeleted(page))
226-
elog(NOTICE, "page is deleted");
212+
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
213+
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
214+
rsinfo->returnMode = SFRM_Materialize;
215+
rsinfo->setResult = tupstore;
216+
rsinfo->setDesc = tupdesc;
227217

228-
inter_call_data->page = page;
229-
inter_call_data->tupd = tupdesc;
230-
inter_call_data->offset = FirstOffsetNumber;
231-
inter_call_data->rel = indexRel;
218+
MemoryContextSwitchTo(oldcontext);
232219

233-
fctx->max_calls = PageGetMaxOffsetNumber(page);
234-
fctx->user_fctx = inter_call_data;
220+
/* Open the relation */
221+
indexRel = index_open(indexRelid, AccessShareLock);
235222

236-
MemoryContextSwitchTo(mctx);
237-
}
223+
page = get_page_from_raw(raw_page);
238224

239-
fctx = SRF_PERCALL_SETUP();
240-
inter_call_data = fctx->user_fctx;
225+
if (GistPageIsDeleted(page))
226+
elog(NOTICE, "page is deleted");
241227

242-
if (fctx->call_cntr < fctx->max_calls)
228+
for (offset = FirstOffsetNumber;
229+
offset <= PageGetMaxOffsetNumber(page);
230+
offset++)
243231
{
244-
Page page = inter_call_data->page;
245-
OffsetNumber offset = inter_call_data->offset;
246-
HeapTuple resultTuple;
247-
Datum result;
248232
Datum values[4];
249233
bool nulls[4];
250234
ItemId id;
@@ -260,11 +244,10 @@ gist_page_items(PG_FUNCTION_ARGS)
260244

261245
itup = (IndexTuple) PageGetItem(page, id);
262246

263-
index_deform_tuple(itup, RelationGetDescr(inter_call_data->rel),
247+
index_deform_tuple(itup, RelationGetDescr(indexRel),
264248
itup_values, itup_isnull);
265249

266-
key_desc = BuildIndexValueDescription(inter_call_data->rel, itup_values,
267-
itup_isnull);
250+
key_desc = BuildIndexValueDescription(indexRel, itup_values, itup_isnull);
268251

269252
memset(nulls, 0, sizeof(nulls));
270253

@@ -273,15 +256,10 @@ gist_page_items(PG_FUNCTION_ARGS)
273256
values[2] = Int32GetDatum((int) IndexTupleSize(itup));
274257
values[3] = CStringGetTextDatum(key_desc);
275258

276-
/* Build and return the result tuple. */
277-
resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
278-
result = HeapTupleGetDatum(resultTuple);
279-
280-
inter_call_data->offset++;
281-
SRF_RETURN_NEXT(fctx, result);
259+
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
282260
}
283261

284-
relation_close(inter_call_data->rel, AccessShareLock);
262+
relation_close(indexRel, AccessShareLock);
285263

286-
SRF_RETURN_DONE(fctx);
264+
return (Datum) 0;
287265
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy