<para>
<function>SPI_freetuptable</function> frees a row set created by a
prior SPI command execution function, such as
- <function>SPI_execute</>. Therefore, this function is usually called
- with the global variable <varname>SPI_tupletable</varname> as
+ <function>SPI_execute</>. Therefore, this function is often called
+ with the global variable <varname>SPI_tuptable</varname> as
argument.
</para>
multiple commands and does not want to keep the results of earlier
commands around until it ends. Note that any unfreed row sets will
be freed anyway at <function>SPI_finish</>.
+ Also, if a subtransaction is started and then aborted within execution
+ of a SPI procedure, SPI automatically frees any row sets created while
+ the subtransaction was running.
+ </para>
+
+ <para>
+ Beginning in <productname>PostgreSQL</> 9.3,
+ <function>SPI_freetuptable</function> contains guard logic to protect
+ against duplicate deletion requests for the same row set. In previous
+ releases, duplicate deletions would lead to crashes.
</para>
</refsect1>
<term><literal>SPITupleTable * <parameter>tuptable</parameter></literal></term>
<listitem>
<para>
- pointer to row set to free
+ pointer to row set to free, or NULL to do nothing
</para>
</listitem>
</varlistentry>
_SPI_current->processed = 0;
_SPI_current->lastoid = InvalidOid;
_SPI_current->tuptable = NULL;
+ slist_init(&_SPI_current->tuptables);
_SPI_current->procCxt = NULL; /* in case we fail to create 'em */
_SPI_current->execCxt = NULL;
_SPI_current->connectSubid = GetCurrentSubTransactionId();
/* Restore memory context as it was before procedure call */
MemoryContextSwitchTo(_SPI_current->savedcxt);
- /* Release memory used in procedure call */
+ /* Release memory used in procedure call (including tuptables) */
MemoryContextDelete(_SPI_current->execCxt);
_SPI_current->execCxt = NULL;
MemoryContextDelete(_SPI_current->procCxt);
*/
if (_SPI_current && !isCommit)
{
+ slist_mutable_iter siter;
+
/* free Executor memory the same as _SPI_end_call would do */
MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
- /* throw away any partially created tuple-table */
- SPI_freetuptable(_SPI_current->tuptable);
- _SPI_current->tuptable = NULL;
+
+ /* throw away any tuple tables created within current subxact */
+ slist_foreach_modify(siter, &_SPI_current->tuptables)
+ {
+ SPITupleTable *tuptable;
+
+ tuptable = slist_container(SPITupleTable, next, siter.cur);
+ if (tuptable->subid >= mySubid)
+ {
+ /*
+ * If we used SPI_freetuptable() here, its internal search of
+ * the tuptables list would make this operation O(N^2).
+ * Instead, just free the tuptable manually. This should
+ * match what SPI_freetuptable() does.
+ */
+ slist_delete_current(&siter);
+ if (tuptable == _SPI_current->tuptable)
+ _SPI_current->tuptable = NULL;
+ if (tuptable == SPI_tuptable)
+ SPI_tuptable = NULL;
+ MemoryContextDelete(tuptable->tuptabcxt);
+ }
+ }
+ /* in particular we should have gotten rid of any in-progress table */
+ Assert(_SPI_current->tuptable == NULL);
}
}
void
SPI_freetuptable(SPITupleTable *tuptable)
{
- if (tuptable != NULL)
- MemoryContextDelete(tuptable->tuptabcxt);
+ bool found = false;
+
+ /* ignore call if NULL pointer */
+ if (tuptable == NULL)
+ return;
+
+ /*
+ * Since this function might be called during error recovery, it seems
+ * best not to insist that the caller be actively connected. We just
+ * search the topmost SPI context, connected or not.
+ */
+ if (_SPI_connected >= 0)
+ {
+ slist_mutable_iter siter;
+
+ if (_SPI_current != &(_SPI_stack[_SPI_connected]))
+ elog(ERROR, "SPI stack corrupted");
+
+ /* find tuptable in active list, then remove it */
+ slist_foreach_modify(siter, &_SPI_current->tuptables)
+ {
+ SPITupleTable *tt;
+
+ tt = slist_container(SPITupleTable, next, siter.cur);
+ if (tt == tuptable)
+ {
+ slist_delete_current(&siter);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Refuse the deletion if we didn't find it in the topmost SPI context.
+ * This is primarily a guard against double deletion, but might prevent
+ * other errors as well. Since the worst consequence of not deleting a
+ * tuptable would be a transient memory leak, this is just a WARNING.
+ */
+ if (!found)
+ {
+ elog(WARNING, "attempt to delete invalid SPITupleTable %p", tuptable);
+ return;
+ }
+
+ /* for safety, reset global variables that might point at tuptable */
+ if (tuptable == _SPI_current->tuptable)
+ _SPI_current->tuptable = NULL;
+ if (tuptable == SPI_tuptable)
+ SPI_tuptable = NULL;
+
+ /* release all memory belonging to tuptable */
+ MemoryContextDelete(tuptable->tuptabcxt);
}
if (_SPI_current->tuptable != NULL)
elog(ERROR, "improper call to spi_dest_startup");
+ /* We create the tuple table context as a child of procCxt */
+
oldcxt = _SPI_procmem(); /* switch to procedure memory context */
tuptabcxt = AllocSetContextCreate(CurrentMemoryContext,
MemoryContextSwitchTo(tuptabcxt);
_SPI_current->tuptable = tuptable = (SPITupleTable *)
- palloc(sizeof(SPITupleTable));
+ palloc0(sizeof(SPITupleTable));
tuptable->tuptabcxt = tuptabcxt;
+ tuptable->subid = GetCurrentSubTransactionId();
+
+ /*
+ * The tuptable is now valid enough to be freed by AtEOSubXact_SPI, so put
+ * it onto the SPI context's tuptables list. This will ensure it's not
+ * leaked even in the unlikely event the following few lines fail.
+ */
+ slist_push_head(&_SPI_current->tuptables, &tuptable->next);
+
+ /* set up initial allocations */
tuptable->alloced = tuptable->free = 128;
tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
tuptable->tupdesc = CreateTupleDescCopy(typeinfo);