Fix two memory leaks around force-storing tuples in slots.

As reported by Tom, when ExecStoreMinimalTuple() had to perform a
conversion to store the minimal tuple in the slot, it forgot to
respect the shouldFree flag, and leaked the tuple into the current
memory context if true.  Fix that by freeing the tuple in that case.

Looking at the relevant code made me (Andres) realize that not having
the shouldFree parameter to ExecForceStoreHeapTuple() was a bad
idea. Some callers had to locally implement the necessary logic, and
in one case it was missing, creating a potential per-group leak in
non-hashed aggregation.

The choice to not free the tuple in ExecComputeStoredGenerated() is
not pretty, but not introduced by this commit - I'll start a separate
discussion about it.

Reported-By: Tom Lane
Discussion: https://postgr.es/m/366.1555382816@sss.pgh.pa.us
This commit is contained in:
Andres Freund 2019-04-19 11:33:37 -07:00
parent 4d5840cea9
commit 88e6ad3054
7 changed files with 41 additions and 20 deletions

View File

@ -3711,9 +3711,7 @@ store_returning_result(PgFdwModifyState *fmstate,
* The returning slot will not necessarily be suitable to store * The returning slot will not necessarily be suitable to store
* heaptuples directly, so allow for conversion. * heaptuples directly, so allow for conversion.
*/ */
ExecForceStoreHeapTuple(newtup, slot); ExecForceStoreHeapTuple(newtup, slot, true);
ExecMaterializeSlot(slot);
pfree(newtup);
} }
PG_CATCH(); PG_CATCH();
{ {

View File

@ -2586,7 +2586,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
} }
else if (newtuple != oldtuple) else if (newtuple != oldtuple)
{ {
ExecForceStoreHeapTuple(newtuple, slot); ExecForceStoreHeapTuple(newtuple, slot, false);
if (should_free) if (should_free)
heap_freetuple(oldtuple); heap_freetuple(oldtuple);
@ -2668,7 +2668,7 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
} }
else if (newtuple != oldtuple) else if (newtuple != oldtuple)
{ {
ExecForceStoreHeapTuple(newtuple, slot); ExecForceStoreHeapTuple(newtuple, slot, false);
if (should_free) if (should_free)
heap_freetuple(oldtuple); heap_freetuple(oldtuple);
@ -2797,7 +2797,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
else else
{ {
trigtuple = fdw_trigtuple; trigtuple = fdw_trigtuple;
ExecForceStoreHeapTuple(trigtuple, slot); ExecForceStoreHeapTuple(trigtuple, slot, false);
} }
LocTriggerData.type = T_TriggerData; LocTriggerData.type = T_TriggerData;
@ -2869,7 +2869,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
slot, slot,
NULL); NULL);
else else
ExecForceStoreHeapTuple(fdw_trigtuple, slot); ExecForceStoreHeapTuple(fdw_trigtuple, slot, false);
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
true, slot, NULL, NIL, NULL, true, slot, NULL, NIL, NULL,
@ -2898,7 +2898,7 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_oldtable = NULL;
LocTriggerData.tg_newtable = NULL; LocTriggerData.tg_newtable = NULL;
ExecForceStoreHeapTuple(trigtuple, slot); ExecForceStoreHeapTuple(trigtuple, slot, false);
for (i = 0; i < trigdesc->numtriggers; i++) for (i = 0; i < trigdesc->numtriggers; i++)
{ {
@ -3057,7 +3057,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
} }
else else
{ {
ExecForceStoreHeapTuple(fdw_trigtuple, oldslot); ExecForceStoreHeapTuple(fdw_trigtuple, oldslot, false);
trigtuple = fdw_trigtuple; trigtuple = fdw_trigtuple;
} }
@ -3107,7 +3107,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
} }
else if (newtuple != oldtuple) else if (newtuple != oldtuple)
{ {
ExecForceStoreHeapTuple(newtuple, newslot); ExecForceStoreHeapTuple(newtuple, newslot, false);
/* /*
* If the tuple returned by the trigger / being stored, is the old * If the tuple returned by the trigger / being stored, is the old
@ -3164,7 +3164,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
oldslot, oldslot,
NULL); NULL);
else if (fdw_trigtuple != NULL) else if (fdw_trigtuple != NULL)
ExecForceStoreHeapTuple(fdw_trigtuple, oldslot); ExecForceStoreHeapTuple(fdw_trigtuple, oldslot, false);
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
true, oldslot, newslot, recheckIndexes, true, oldslot, newslot, recheckIndexes,
@ -3192,7 +3192,7 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_oldtable = NULL;
LocTriggerData.tg_newtable = NULL; LocTriggerData.tg_newtable = NULL;
ExecForceStoreHeapTuple(trigtuple, oldslot); ExecForceStoreHeapTuple(trigtuple, oldslot, false);
for (i = 0; i < trigdesc->numtriggers; i++) for (i = 0; i < trigdesc->numtriggers; i++)
{ {
@ -3228,7 +3228,7 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
} }
else if (newtuple != oldtuple) else if (newtuple != oldtuple)
{ {
ExecForceStoreHeapTuple(newtuple, newslot); ExecForceStoreHeapTuple(newtuple, newslot, false);
if (should_free) if (should_free)
heap_freetuple(oldtuple); heap_freetuple(oldtuple);

View File

@ -1430,11 +1430,12 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
*/ */
void void
ExecForceStoreHeapTuple(HeapTuple tuple, ExecForceStoreHeapTuple(HeapTuple tuple,
TupleTableSlot *slot) TupleTableSlot *slot,
bool shouldFree)
{ {
if (TTS_IS_HEAPTUPLE(slot)) if (TTS_IS_HEAPTUPLE(slot))
{ {
ExecStoreHeapTuple(tuple, slot, false); ExecStoreHeapTuple(tuple, slot, shouldFree);
} }
else if (TTS_IS_BUFFERTUPLE(slot)) else if (TTS_IS_BUFFERTUPLE(slot))
{ {
@ -1447,6 +1448,9 @@ ExecForceStoreHeapTuple(HeapTuple tuple,
oldContext = MemoryContextSwitchTo(slot->tts_mcxt); oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
bslot->base.tuple = heap_copytuple(tuple); bslot->base.tuple = heap_copytuple(tuple);
MemoryContextSwitchTo(oldContext); MemoryContextSwitchTo(oldContext);
if (shouldFree)
pfree(tuple);
} }
else else
{ {
@ -1454,6 +1458,12 @@ ExecForceStoreHeapTuple(HeapTuple tuple,
heap_deform_tuple(tuple, slot->tts_tupleDescriptor, heap_deform_tuple(tuple, slot->tts_tupleDescriptor,
slot->tts_values, slot->tts_isnull); slot->tts_values, slot->tts_isnull);
ExecStoreVirtualTuple(slot); ExecStoreVirtualTuple(slot);
if (shouldFree)
{
ExecMaterializeSlot(slot);
pfree(tuple);
}
} }
} }
@ -1481,6 +1491,12 @@ ExecForceStoreMinimalTuple(MinimalTuple mtup,
heap_deform_tuple(&htup, slot->tts_tupleDescriptor, heap_deform_tuple(&htup, slot->tts_tupleDescriptor,
slot->tts_values, slot->tts_isnull); slot->tts_values, slot->tts_isnull);
ExecStoreVirtualTuple(slot); ExecStoreVirtualTuple(slot);
if (shouldFree)
{
ExecMaterializeSlot(slot);
pfree(mtup);
}
} }
} }

View File

@ -1806,7 +1806,7 @@ agg_retrieve_direct(AggState *aggstate)
* cleared from the slot. * cleared from the slot.
*/ */
ExecForceStoreHeapTuple(aggstate->grp_firstTuple, ExecForceStoreHeapTuple(aggstate->grp_firstTuple,
firstSlot); firstSlot, true);
aggstate->grp_firstTuple = NULL; /* don't keep two pointers */ aggstate->grp_firstTuple = NULL; /* don't keep two pointers */
/* set up for first advance_aggregates call */ /* set up for first advance_aggregates call */

View File

@ -205,7 +205,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
*/ */
Assert(slot->tts_tupleDescriptor->natts == Assert(slot->tts_tupleDescriptor->natts ==
scandesc->xs_hitupdesc->natts); scandesc->xs_hitupdesc->natts);
ExecForceStoreHeapTuple(scandesc->xs_hitup, slot); ExecForceStoreHeapTuple(scandesc->xs_hitup, slot, false);
} }
else if (scandesc->xs_itup) else if (scandesc->xs_itup)
StoreIndexTuple(slot, scandesc->xs_itup, scandesc->xs_itupdesc); StoreIndexTuple(slot, scandesc->xs_itup, scandesc->xs_itupdesc);

View File

@ -317,7 +317,12 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
oldtuple = ExecFetchSlotHeapTuple(slot, true, &should_free); oldtuple = ExecFetchSlotHeapTuple(slot, true, &should_free);
newtuple = heap_modify_tuple(oldtuple, tupdesc, values, nulls, replaces); newtuple = heap_modify_tuple(oldtuple, tupdesc, values, nulls, replaces);
ExecForceStoreHeapTuple(newtuple, slot); /*
* The tuple will be freed by way of the memory context - the slot might
* only be cleared after the context is reset, and we'd thus potentially
* double free.
*/
ExecForceStoreHeapTuple(newtuple, slot, false);
if (should_free) if (should_free)
heap_freetuple(oldtuple); heap_freetuple(oldtuple);
@ -979,7 +984,7 @@ ldelete:;
slot = ExecGetReturningSlot(estate, resultRelInfo); slot = ExecGetReturningSlot(estate, resultRelInfo);
if (oldtuple != NULL) if (oldtuple != NULL)
{ {
ExecForceStoreHeapTuple(oldtuple, slot); ExecForceStoreHeapTuple(oldtuple, slot, false);
} }
else else
{ {

View File

@ -306,7 +306,9 @@ extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
extern TupleTableSlot *ExecStoreHeapTuple(HeapTuple tuple, extern TupleTableSlot *ExecStoreHeapTuple(HeapTuple tuple,
TupleTableSlot *slot, TupleTableSlot *slot,
bool shouldFree); bool shouldFree);
extern void ExecForceStoreHeapTuple(HeapTuple tuple, TupleTableSlot *slot); extern void ExecForceStoreHeapTuple(HeapTuple tuple,
TupleTableSlot *slot,
bool shouldFree);
extern TupleTableSlot *ExecStoreBufferHeapTuple(HeapTuple tuple, extern TupleTableSlot *ExecStoreBufferHeapTuple(HeapTuple tuple,
TupleTableSlot *slot, TupleTableSlot *slot,
Buffer buffer); Buffer buffer);