Skip to content

Commit b006f4d

Browse files
committed
Prevent memory leaks from accumulating across printtup() calls.
Historically, printtup() has assumed that it could prevent memory leakage by pfree'ing the string result of each output function and manually managing detoasting of toasted values. This amounts to assuming that datatype output functions never leak any memory internally; an assumption we've already decided to be bogus elsewhere, for example in COPY OUT. range_out in particular is known to leak multiple kilobytes per call, as noted in bug #8573 from Godfried Vanluffelen. While we could go in and fix that leak, it wouldn't be very notationally convenient, and in any case there have been and undoubtedly will again be other leaks in other output functions. So what seems like the best solution is to run the output functions in a temporary memory context that can be reset after each row, as we're doing in COPY OUT. Some quick experimentation suggests this is actually a tad faster than the retail pfree's anyway. This patch fixes all the variants of printtup, except for debugtup() which is used in standalone mode. It doesn't seem worth worrying about query-lifespan leaks in standalone mode, and fixing that case would be a bit tedious since debugtup() doesn't currently have any startup or shutdown functions. While at it, remove manual detoast management from several other output-function call sites that had copied it from printtup(). This doesn't make a lot of difference right now, but in view of recent discussions about supporting "non-flattened" Datums, we're going to want that code gone eventually anyway. Back-patch to 9.2 where range_out was introduced. We might eventually decide to back-patch this further, but in the absence of known major leaks in older output functions, I'll refrain for now.
1 parent 84a05d4 commit b006f4d

File tree

4 files changed

+68
-137
lines changed

4 files changed

+68
-137
lines changed

src/backend/access/common/printtup.c

+55-81
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "tcop/pquery.h"
2222
#include "utils/lsyscache.h"
2323
#include "utils/memdebug.h"
24+
#include "utils/memutils.h"
2425

2526

2627
static void printtup_startup(DestReceiver *self, int operation,
@@ -61,6 +62,7 @@ typedef struct
6162
TupleDesc attrinfo; /* The attr info we are set up for */
6263
int nattrs;
6364
PrinttupAttrInfo *myinfo; /* Cached info about each attr */
65+
MemoryContext tmpcontext; /* Memory context for per-row workspace */
6466
} DR_printtup;
6567

6668
/* ----------------
@@ -87,6 +89,7 @@ printtup_create_DR(CommandDest dest)
8789
self->attrinfo = NULL;
8890
self->nattrs = 0;
8991
self->myinfo = NULL;
92+
self->tmpcontext = NULL;
9093

9194
return (DestReceiver *) self;
9295
}
@@ -124,6 +127,18 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
124127
DR_printtup *myState = (DR_printtup *) self;
125128
Portal portal = myState->portal;
126129

130+
/*
131+
* Create a temporary memory context that we can reset once per row to
132+
* recover palloc'd memory. This avoids any problems with leaks inside
133+
* datatype output routines, and should be faster than retail pfree's
134+
* anyway.
135+
*/
136+
myState->tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
137+
"printtup",
138+
ALLOCSET_DEFAULT_MINSIZE,
139+
ALLOCSET_DEFAULT_INITSIZE,
140+
ALLOCSET_DEFAULT_MAXSIZE);
141+
127142
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
128143
{
129144
/*
@@ -289,6 +304,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
289304
{
290305
TupleDesc typeinfo = slot->tts_tupleDescriptor;
291306
DR_printtup *myState = (DR_printtup *) self;
307+
MemoryContext oldcontext;
292308
StringInfoData buf;
293309
int natts = typeinfo->natts;
294310
int i;
@@ -300,8 +316,11 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
300316
/* Make sure the tuple is fully deconstructed */
301317
slot_getallattrs(slot);
302318

319+
/* Switch into per-row context so we can recover memory below */
320+
oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
321+
303322
/*
304-
* Prepare a DataRow message
323+
* Prepare a DataRow message (note buffer is in per-row context)
305324
*/
306325
pq_beginmessage(&buf, 'D');
307326

@@ -313,8 +332,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
313332
for (i = 0; i < natts; ++i)
314333
{
315334
PrinttupAttrInfo *thisState = myState->myinfo + i;
316-
Datum origattr = slot->tts_values[i],
317-
attr;
335+
Datum attr = slot->tts_values[i];
318336

319337
if (slot->tts_isnull[i])
320338
{
@@ -323,30 +341,15 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
323341
}
324342

325343
/*
326-
* If we have a toasted datum, forcibly detoast it here to avoid
327-
* memory leakage inside the type's output routine.
328-
*
329-
* Here we catch undefined bytes in tuples that are returned to the
344+
* Here we catch undefined bytes in datums that are returned to the
330345
* client without hitting disk; see comments at the related check in
331-
* PageAddItem(). Whether to test before or after detoast is somewhat
332-
* arbitrary, as is whether to test external/compressed data at all.
333-
* Undefined bytes in the pre-toast datum will have triggered Valgrind
334-
* errors in the compressor or toaster; any error detected here for
335-
* such datums would indicate an (unlikely) bug in a type-independent
336-
* facility. Therefore, this test is most useful for uncompressed,
337-
* non-external datums.
338-
*
339-
* We don't presently bother checking non-varlena datums for undefined
340-
* data. PageAddItem() does check them.
346+
* PageAddItem(). This test is most useful for uncompressed,
347+
* non-external datums, but we're quite likely to see such here when
348+
* testing new C functions.
341349
*/
342350
if (thisState->typisvarlena)
343-
{
344-
VALGRIND_CHECK_MEM_IS_DEFINED(origattr, VARSIZE_ANY(origattr));
345-
346-
attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
347-
}
348-
else
349-
attr = origattr;
351+
VALGRIND_CHECK_MEM_IS_DEFINED(DatumGetPointer(attr),
352+
VARSIZE_ANY(attr));
350353

351354
if (thisState->format == 0)
352355
{
@@ -355,7 +358,6 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
355358

356359
outputstr = OutputFunctionCall(&thisState->finfo, attr);
357360
pq_sendcountedtext(&buf, outputstr, strlen(outputstr), false);
358-
pfree(outputstr);
359361
}
360362
else
361363
{
@@ -366,15 +368,14 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
366368
pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
367369
pq_sendbytes(&buf, VARDATA(outputbytes),
368370
VARSIZE(outputbytes) - VARHDRSZ);
369-
pfree(outputbytes);
370371
}
371-
372-
/* Clean up detoasted copy, if any */
373-
if (DatumGetPointer(attr) != DatumGetPointer(origattr))
374-
pfree(DatumGetPointer(attr));
375372
}
376373

377374
pq_endmessage(&buf);
375+
376+
/* Return to caller's context, and flush row's temporary memory */
377+
MemoryContextSwitchTo(oldcontext);
378+
MemoryContextReset(myState->tmpcontext);
378379
}
379380

380381
/* ----------------
@@ -386,6 +387,7 @@ printtup_20(TupleTableSlot *slot, DestReceiver *self)
386387
{
387388
TupleDesc typeinfo = slot->tts_tupleDescriptor;
388389
DR_printtup *myState = (DR_printtup *) self;
390+
MemoryContext oldcontext;
389391
StringInfoData buf;
390392
int natts = typeinfo->natts;
391393
int i,
@@ -399,6 +401,9 @@ printtup_20(TupleTableSlot *slot, DestReceiver *self)
399401
/* Make sure the tuple is fully deconstructed */
400402
slot_getallattrs(slot);
401403

404+
/* Switch into per-row context so we can recover memory below */
405+
oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
406+
402407
/*
403408
* tell the frontend to expect new tuple data (in ASCII style)
404409
*/
@@ -430,34 +435,23 @@ printtup_20(TupleTableSlot *slot, DestReceiver *self)
430435
for (i = 0; i < natts; ++i)
431436
{
432437
PrinttupAttrInfo *thisState = myState->myinfo + i;
433-
Datum origattr = slot->tts_values[i],
434-
attr;
438+
Datum attr = slot->tts_values[i];
435439
char *outputstr;
436440

437441
if (slot->tts_isnull[i])
438442
continue;
439443

440444
Assert(thisState->format == 0);
441445

442-
/*
443-
* If we have a toasted datum, forcibly detoast it here to avoid
444-
* memory leakage inside the type's output routine.
445-
*/
446-
if (thisState->typisvarlena)
447-
attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
448-
else
449-
attr = origattr;
450-
451446
outputstr = OutputFunctionCall(&thisState->finfo, attr);
452447
pq_sendcountedtext(&buf, outputstr, strlen(outputstr), true);
453-
pfree(outputstr);
454-
455-
/* Clean up detoasted copy, if any */
456-
if (DatumGetPointer(attr) != DatumGetPointer(origattr))
457-
pfree(DatumGetPointer(attr));
458448
}
459449

460450
pq_endmessage(&buf);
451+
452+
/* Return to caller's context, and flush row's temporary memory */
453+
MemoryContextSwitchTo(oldcontext);
454+
MemoryContextReset(myState->tmpcontext);
461455
}
462456

463457
/* ----------------
@@ -474,6 +468,10 @@ printtup_shutdown(DestReceiver *self)
474468
myState->myinfo = NULL;
475469

476470
myState->attrinfo = NULL;
471+
472+
if (myState->tmpcontext)
473+
MemoryContextDelete(myState->tmpcontext);
474+
myState->tmpcontext = NULL;
477475
}
478476

479477
/* ----------------
@@ -536,39 +534,23 @@ debugtup(TupleTableSlot *slot, DestReceiver *self)
536534
TupleDesc typeinfo = slot->tts_tupleDescriptor;
537535
int natts = typeinfo->natts;
538536
int i;
539-
Datum origattr,
540-
attr;
537+
Datum attr;
541538
char *value;
542539
bool isnull;
543540
Oid typoutput;
544541
bool typisvarlena;
545542

546543
for (i = 0; i < natts; ++i)
547544
{
548-
origattr = slot_getattr(slot, i + 1, &isnull);
545+
attr = slot_getattr(slot, i + 1, &isnull);
549546
if (isnull)
550547
continue;
551548
getTypeOutputInfo(typeinfo->attrs[i]->atttypid,
552549
&typoutput, &typisvarlena);
553550

554-
/*
555-
* If we have a toasted datum, forcibly detoast it here to avoid
556-
* memory leakage inside the type's output routine.
557-
*/
558-
if (typisvarlena)
559-
attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
560-
else
561-
attr = origattr;
562-
563551
value = OidOutputFunctionCall(typoutput, attr);
564552

565553
printatt((unsigned) i + 1, typeinfo->attrs[i], value);
566-
567-
pfree(value);
568-
569-
/* Clean up detoasted copy, if any */
570-
if (DatumGetPointer(attr) != DatumGetPointer(origattr))
571-
pfree(DatumGetPointer(attr));
572554
}
573555
printf("\t----\n");
574556
}
@@ -587,6 +569,7 @@ printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
587569
{
588570
TupleDesc typeinfo = slot->tts_tupleDescriptor;
589571
DR_printtup *myState = (DR_printtup *) self;
572+
MemoryContext oldcontext;
590573
StringInfoData buf;
591574
int natts = typeinfo->natts;
592575
int i,
@@ -600,6 +583,9 @@ printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
600583
/* Make sure the tuple is fully deconstructed */
601584
slot_getallattrs(slot);
602585

586+
/* Switch into per-row context so we can recover memory below */
587+
oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
588+
603589
/*
604590
* tell the frontend to expect new tuple data (in binary style)
605591
*/
@@ -631,35 +617,23 @@ printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
631617
for (i = 0; i < natts; ++i)
632618
{
633619
PrinttupAttrInfo *thisState = myState->myinfo + i;
634-
Datum origattr = slot->tts_values[i],
635-
attr;
620+
Datum attr = slot->tts_values[i];
636621
bytea *outputbytes;
637622

638623
if (slot->tts_isnull[i])
639624
continue;
640625

641626
Assert(thisState->format == 1);
642627

643-
/*
644-
* If we have a toasted datum, forcibly detoast it here to avoid
645-
* memory leakage inside the type's output routine.
646-
*/
647-
if (thisState->typisvarlena)
648-
attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
649-
else
650-
attr = origattr;
651-
652628
outputbytes = SendFunctionCall(&thisState->finfo, attr);
653-
/* We assume the result will not have been toasted */
654629
pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
655630
pq_sendbytes(&buf, VARDATA(outputbytes),
656631
VARSIZE(outputbytes) - VARHDRSZ);
657-
pfree(outputbytes);
658-
659-
/* Clean up detoasted copy, if any */
660-
if (DatumGetPointer(attr) != DatumGetPointer(origattr))
661-
pfree(DatumGetPointer(attr));
662632
}
663633

664634
pq_endmessage(&buf);
635+
636+
/* Return to caller's context, and flush row's temporary memory */
637+
MemoryContextSwitchTo(oldcontext);
638+
MemoryContextReset(myState->tmpcontext);
665639
}

src/backend/bootstrap/bootstrap.c

+8-4
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,6 @@ InsertOneValue(char *value, int i)
835835
Oid typioparam;
836836
Oid typinput;
837837
Oid typoutput;
838-
char *prt;
839838

840839
AssertArg(i >= 0 && i < MAXATTR);
841840

@@ -849,9 +848,14 @@ InsertOneValue(char *value, int i)
849848
&typinput, &typoutput);
850849

851850
values[i] = OidInputFunctionCall(typinput, value, typioparam, -1);
852-
prt = OidOutputFunctionCall(typoutput, values[i]);
853-
elog(DEBUG4, "inserted -> %s", prt);
854-
pfree(prt);
851+
852+
/*
853+
* We use ereport not elog here so that parameters aren't evaluated unless
854+
* the message is going to be printed, which generally it isn't
855+
*/
856+
ereport(DEBUG4,
857+
(errmsg_internal("inserted -> %s",
858+
OidOutputFunctionCall(typoutput, values[i]))));
855859
}
856860

857861
/* ----------------

src/backend/executor/spi.c

+3-20
Original file line numberDiff line numberDiff line change
@@ -869,9 +869,7 @@ SPI_fname(TupleDesc tupdesc, int fnumber)
869869
char *
870870
SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
871871
{
872-
char *result;
873-
Datum origval,
874-
val;
872+
Datum val;
875873
bool isnull;
876874
Oid typoid,
877875
foutoid;
@@ -886,7 +884,7 @@ SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
886884
return NULL;
887885
}
888886

889-
origval = heap_getattr(tuple, fnumber, tupdesc, &isnull);
887+
val = heap_getattr(tuple, fnumber, tupdesc, &isnull);
890888
if (isnull)
891889
return NULL;
892890

@@ -897,22 +895,7 @@ SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
897895

898896
getTypeOutputInfo(typoid, &foutoid, &typisvarlena);
899897

900-
/*
901-
* If we have a toasted datum, forcibly detoast it here to avoid memory
902-
* leakage inside the type's output routine.
903-
*/
904-
if (typisvarlena)
905-
val = PointerGetDatum(PG_DETOAST_DATUM(origval));
906-
else
907-
val = origval;
908-
909-
result = OidOutputFunctionCall(foutoid, val);
910-
911-
/* Clean up detoasted copy, if any */
912-
if (val != origval)
913-
pfree(DatumGetPointer(val));
914-
915-
return result;
898+
return OidOutputFunctionCall(foutoid, val);
916899
}
917900

918901
Datum

0 commit comments

Comments
 (0)