Skip to content

Commit 9dbc6d2

Browse files
committed
Minor code review for tuple slot rewrite.
Avoid creating transiently-inconsistent slot states where possible, by not setting TTS_FLAG_SHOULDFREE until after the slot actually has a free'able tuple pointer, and by making sure that we reset tts_nvalid and related derived state before we replace the tuple contents. This would only matter if something were to examine the slot after we'd suffered some kind of error (e.g. out of memory) while manipulating the slot. We typically don't do that, so these changes might just be cosmetic --- but even if so, it seems like good future-proofing. Also remove some redundant Asserts, and add a couple for consistency. Back-patch to v12 where all this code was rewritten. Discussion: https://postgr.es/m/16095-c3ff2e5283b8dba5@postgresql.org
1 parent d4d0cd6 commit 9dbc6d2

File tree

2 files changed

+45
-41
lines changed

2 files changed

+45
-41
lines changed

src/backend/executor/execTuples.c

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
* At ExecutorStart()
2121
* ----------------
22-
22+
*
2323
* - ExecInitSeqScan() calls ExecInitScanTupleSlot() to construct a
2424
* TupleTableSlots for the tuples returned by the access method, and
2525
* ExecInitResultTypeTL() to define the node's return
@@ -272,7 +272,6 @@ tts_virtual_copy_heap_tuple(TupleTableSlot *slot)
272272
return heap_form_tuple(slot->tts_tupleDescriptor,
273273
slot->tts_values,
274274
slot->tts_isnull);
275-
276275
}
277276

278277
static MinimalTuple
@@ -334,6 +333,8 @@ tts_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
334333
{
335334
HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
336335

336+
Assert(!TTS_EMPTY(slot));
337+
337338
return heap_getsysattr(hslot->tuple, attnum,
338339
slot->tts_tupleDescriptor, isnull);
339340
}
@@ -346,14 +347,19 @@ tts_heap_materialize(TupleTableSlot *slot)
346347

347348
Assert(!TTS_EMPTY(slot));
348349

349-
/* This slot has it's tuple already materialized. Nothing to do. */
350+
/* If slot has its tuple already materialized, nothing to do. */
350351
if (TTS_SHOULDFREE(slot))
351352
return;
352353

353-
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
354-
355354
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
356355

356+
/*
357+
* Have to deform from scratch, otherwise tts_values[] entries could point
358+
* into the non-materialized tuple (which might be gone when accessed).
359+
*/
360+
slot->tts_nvalid = 0;
361+
hslot->off = 0;
362+
357363
if (!hslot->tuple)
358364
hslot->tuple = heap_form_tuple(slot->tts_tupleDescriptor,
359365
slot->tts_values,
@@ -368,12 +374,7 @@ tts_heap_materialize(TupleTableSlot *slot)
368374
hslot->tuple = heap_copytuple(hslot->tuple);
369375
}
370376

371-
/*
372-
* Have to deform from scratch, otherwise tts_values[] entries could point
373-
* into the non-materialized tuple (which might be gone when accessed).
374-
*/
375-
slot->tts_nvalid = 0;
376-
hslot->off = 0;
377+
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
377378

378379
MemoryContextSwitchTo(oldContext);
379380
}
@@ -436,7 +437,7 @@ tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree)
436437
slot->tts_nvalid = 0;
437438
hslot->tuple = tuple;
438439
hslot->off = 0;
439-
slot->tts_flags &= ~TTS_FLAG_EMPTY;
440+
slot->tts_flags &= ~(TTS_FLAG_EMPTY | TTS_FLAG_SHOULDFREE);
440441
slot->tts_tid = tuple->t_self;
441442

442443
if (shouldFree)
@@ -509,13 +510,19 @@ tts_minimal_materialize(TupleTableSlot *slot)
509510

510511
Assert(!TTS_EMPTY(slot));
511512

512-
/* This slot has it's tuple already materialized. Nothing to do. */
513+
/* If slot has its tuple already materialized, nothing to do. */
513514
if (TTS_SHOULDFREE(slot))
514515
return;
515516

516-
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
517517
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
518518

519+
/*
520+
* Have to deform from scratch, otherwise tts_values[] entries could point
521+
* into the non-materialized tuple (which might be gone when accessed).
522+
*/
523+
slot->tts_nvalid = 0;
524+
mslot->off = 0;
525+
519526
if (!mslot->mintuple)
520527
{
521528
mslot->mintuple = heap_form_minimal_tuple(slot->tts_tupleDescriptor,
@@ -532,19 +539,14 @@ tts_minimal_materialize(TupleTableSlot *slot)
532539
mslot->mintuple = heap_copy_minimal_tuple(mslot->mintuple);
533540
}
534541

542+
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
543+
535544
Assert(mslot->tuple == &mslot->minhdr);
536545

537546
mslot->minhdr.t_len = mslot->mintuple->t_len + MINIMAL_TUPLE_OFFSET;
538547
mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mslot->mintuple - MINIMAL_TUPLE_OFFSET);
539548

540549
MemoryContextSwitchTo(oldContext);
541-
542-
/*
543-
* Have to deform from scratch, otherwise tts_values[] entries could point
544-
* into the non-materialized tuple (which might be gone when accessed).
545-
*/
546-
slot->tts_nvalid = 0;
547-
mslot->off = 0;
548550
}
549551

550552
static void
@@ -615,8 +617,6 @@ tts_minimal_store_tuple(TupleTableSlot *slot, MinimalTuple mtup, bool shouldFree
615617

616618
if (shouldFree)
617619
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
618-
else
619-
Assert(!TTS_SHOULDFREE(slot));
620620
}
621621

622622

@@ -651,8 +651,6 @@ tts_buffer_heap_clear(TupleTableSlot *slot)
651651

652652
heap_freetuple(bslot->base.tuple);
653653
slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
654-
655-
Assert(!BufferIsValid(bslot->buffer));
656654
}
657655

658656
if (BufferIsValid(bslot->buffer))
@@ -681,6 +679,8 @@ tts_buffer_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
681679
{
682680
BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
683681

682+
Assert(!TTS_EMPTY(slot));
683+
684684
return heap_getsysattr(bslot->base.tuple, attnum,
685685
slot->tts_tupleDescriptor, isnull);
686686
}
@@ -693,14 +693,19 @@ tts_buffer_heap_materialize(TupleTableSlot *slot)
693693

694694
Assert(!TTS_EMPTY(slot));
695695

696-
/* If already materialized nothing to do. */
696+
/* If slot has its tuple already materialized, nothing to do. */
697697
if (TTS_SHOULDFREE(slot))
698698
return;
699699

700-
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
701-
702700
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
703701

702+
/*
703+
* Have to deform from scratch, otherwise tts_values[] entries could point
704+
* into the non-materialized tuple (which might be gone when accessed).
705+
*/
706+
bslot->base.off = 0;
707+
slot->tts_nvalid = 0;
708+
704709
if (!bslot->base.tuple)
705710
{
706711
/*
@@ -713,7 +718,6 @@ tts_buffer_heap_materialize(TupleTableSlot *slot)
713718
bslot->base.tuple = heap_form_tuple(slot->tts_tupleDescriptor,
714719
slot->tts_values,
715720
slot->tts_isnull);
716-
717721
}
718722
else
719723
{
@@ -723,19 +727,21 @@ tts_buffer_heap_materialize(TupleTableSlot *slot)
723727
* A heap tuple stored in a BufferHeapTupleTableSlot should have a
724728
* buffer associated with it, unless it's materialized or virtual.
725729
*/
726-
Assert(BufferIsValid(bslot->buffer));
727730
if (likely(BufferIsValid(bslot->buffer)))
728731
ReleaseBuffer(bslot->buffer);
729732
bslot->buffer = InvalidBuffer;
730733
}
731-
MemoryContextSwitchTo(oldContext);
732734

733735
/*
734-
* Have to deform from scratch, otherwise tts_values[] entries could point
735-
* into the non-materialized tuple (which might be gone when accessed).
736+
* We don't set TTS_FLAG_SHOULDFREE until after releasing the buffer, if
737+
* any. This avoids having a transient state that would fall foul of our
738+
* assertions that a slot with TTS_FLAG_SHOULDFREE doesn't own a buffer.
739+
* In the unlikely event that ReleaseBuffer() above errors out, we'd
740+
* effectively leak the copied tuple, but that seems fairly harmless.
736741
*/
737-
bslot->base.off = 0;
738-
slot->tts_nvalid = 0;
742+
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
743+
744+
MemoryContextSwitchTo(oldContext);
739745
}
740746

741747
static void
@@ -756,10 +762,10 @@ tts_buffer_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
756762
MemoryContext oldContext;
757763

758764
ExecClearTuple(dstslot);
759-
dstslot->tts_flags |= TTS_FLAG_SHOULDFREE;
760765
dstslot->tts_flags &= ~TTS_FLAG_EMPTY;
761766
oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt);
762767
bdstslot->base.tuple = ExecCopySlotHeapTuple(srcslot);
768+
dstslot->tts_flags |= TTS_FLAG_SHOULDFREE;
763769
MemoryContextSwitchTo(oldContext);
764770
}
765771
else
@@ -1444,10 +1450,10 @@ ExecForceStoreHeapTuple(HeapTuple tuple,
14441450
BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
14451451

14461452
ExecClearTuple(slot);
1447-
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
14481453
slot->tts_flags &= ~TTS_FLAG_EMPTY;
14491454
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
14501455
bslot->base.tuple = heap_copytuple(tuple);
1456+
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
14511457
MemoryContextSwitchTo(oldContext);
14521458

14531459
if (shouldFree)
@@ -1856,7 +1862,6 @@ slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum)
18561862
slot->tts_values[missattnum] = attrmiss[missattnum].am_value;
18571863
slot->tts_isnull[missattnum] = !attrmiss[missattnum].am_present;
18581864
}
1859-
18601865
}
18611866
}
18621867

src/include/executor/tuptable.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,8 @@ typedef struct BufferHeapTupleTableSlot
261261
/*
262262
* If buffer is not InvalidBuffer, then the slot is holding a pin on the
263263
* indicated buffer page; drop the pin when we release the slot's
264-
* reference to that buffer. (TTS_FLAG_SHOULDFREE should not be set be
265-
* false in such a case, since presumably tts_tuple is pointing at the
266-
* buffer page.)
264+
* reference to that buffer. (TTS_FLAG_SHOULDFREE should not be set in
265+
* such a case, since presumably tts_tuple is pointing into the buffer.)
267266
*/
268267
Buffer buffer; /* tuple's buffer, or InvalidBuffer */
269268
} BufferHeapTupleTableSlot;

0 commit comments

Comments
 (0)