diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index f30b2cd93330d72e0a412d6517aec08c96cc753e..f179922dd4bd4eb36212ebd91413ba28de44b2fc 100644
*** a/src/backend/executor/spi.c
--- b/src/backend/executor/spi.c
*************** SPI_connect(void)
*** 126,131 ****
--- 126,132 ----
  	_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();
*************** SPI_finish(void)
*** 166,172 ****
  	/* Restore memory context as it was before procedure call */
  	MemoryContextSwitchTo(_SPI_current->savedcxt);
  
! 	/* Release memory used in procedure call */
  	MemoryContextDelete(_SPI_current->execCxt);
  	_SPI_current->execCxt = NULL;
  	MemoryContextDelete(_SPI_current->procCxt);
--- 167,173 ----
  	/* Restore memory context as it was before procedure call */
  	MemoryContextSwitchTo(_SPI_current->savedcxt);
  
! 	/* Release memory used in procedure call (including tuptables) */
  	MemoryContextDelete(_SPI_current->execCxt);
  	_SPI_current->execCxt = NULL;
  	MemoryContextDelete(_SPI_current->procCxt);
*************** AtEOSubXact_SPI(bool isCommit, SubTransa
*** 282,292 ****
  	 */
  	if (_SPI_current && !isCommit)
  	{
  		/* 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;
  	}
  }
  
--- 283,316 ----
  	 */
  	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 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.
! 				 */
! 				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);
  	}
  }
  
*************** SPI_freetuple(HeapTuple tuple)
*** 1021,1028 ****
  void
  SPI_freetuptable(SPITupleTable *tuptable)
  {
! 	if (tuptable != NULL)
! 		MemoryContextDelete(tuptable->tuptabcxt);
  }
  
  
--- 1045,1089 ----
  void
  SPI_freetuptable(SPITupleTable *tuptable)
  {
! 	slist_mutable_iter siter;
! 	bool		found = false;
! 
! 	/* ignore call if NULL pointer */
! 	if (tuptable == NULL)
! 		return;
! 
! 	/* must be connected */
! 	if (_SPI_curid + 1 != _SPI_connected)
! 		elog(ERROR, "improper call to SPI_freetuptable");
! 	if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
! 		elog(ERROR, "SPI stack corrupted");
! 
! 	/* ensure tuptable is active, then remove it from list */
! 	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;
! 		}
! 	}
! 	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);
  }
  
  
*************** spi_dest_startup(DestReceiver *self, int
*** 1656,1661 ****
--- 1717,1724 ----
  	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,
*************** spi_dest_startup(DestReceiver *self, int
*** 1666,1673 ****
  	MemoryContextSwitchTo(tuptabcxt);
  
  	_SPI_current->tuptable = tuptable = (SPITupleTable *)
! 		palloc(sizeof(SPITupleTable));
  	tuptable->tuptabcxt = tuptabcxt;
  	tuptable->alloced = tuptable->free = 128;
  	tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
  	tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
--- 1729,1746 ----
  	MemoryContextSwitchTo(tuptabcxt);
  
  	_SPI_current->tuptable = tuptable = (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);
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index d4f1272cd81ae13eb52a16327357bfdf15ad7621..81310e377f6b769c5aa306cf9aab932813dbeef8 100644
*** a/src/include/executor/spi.h
--- b/src/include/executor/spi.h
***************
*** 13,18 ****
--- 13,19 ----
  #ifndef SPI_H
  #define SPI_H
  
+ #include "lib/ilist.h"
  #include "nodes/parsenodes.h"
  #include "utils/portal.h"
  
*************** typedef struct SPITupleTable
*** 24,29 ****
--- 25,32 ----
  	uint32		free;			/* # of free vals */
  	TupleDesc	tupdesc;		/* tuple descriptor */
  	HeapTuple  *vals;			/* tuples */
+ 	slist_node	next;			/* link for internal bookkeeping */
+ 	SubTransactionId subid;		/* subxact in which tuptable was created */
  } SPITupleTable;
  
  /* Plans are opaque structs for standard users of SPI */
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index ef7903abd09a94906d48bfcbf3ea223aacdd9423..7d4c9e963933401bf278363bf51a9db63c78086f 100644
*** a/src/include/executor/spi_priv.h
--- b/src/include/executor/spi_priv.h
*************** typedef struct
*** 23,30 ****
  	/* current results */
  	uint32		processed;		/* by Executor */
  	Oid			lastoid;
! 	SPITupleTable *tuptable;
  
  	MemoryContext procCxt;		/* procedure context */
  	MemoryContext execCxt;		/* executor context */
  	MemoryContext savedcxt;		/* context of SPI_connect's caller */
--- 23,32 ----
  	/* current results */
  	uint32		processed;		/* by Executor */
  	Oid			lastoid;
! 	SPITupleTable *tuptable;	/* tuptable currently being built */
  
+ 	/* resources of this execution context */
+ 	slist_head	tuptables;		/* list of all live SPITupleTables */
  	MemoryContext procCxt;		/* procedure context */
  	MemoryContext execCxt;		/* executor context */
  	MemoryContext savedcxt;		/* context of SPI_connect's caller */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 5b142e3bee6ccf929f3d4ec8ef3bf6c46467dc90..aa6b0af2e08ba59358ec8bfa7182bae5cef9cb8a 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1202,1208 ****
  			 */
  			SPI_restore_connection();
  
! 			/* Must clean up the econtext too */
  			exec_eval_cleanup(estate);
  
  			/* Look for a matching exception handler */
--- 1202,1214 ----
  			 */
  			SPI_restore_connection();
  
! 			/*
! 			 * Must clean up the econtext too.	However, any tuple table made
! 			 * in the subxact will have been thrown away by SPI during subxact
! 			 * abort, so we don't need to (and mustn't try to) free the
! 			 * eval_tuptable.
! 			 */
! 			estate->eval_tuptable = NULL;
  			exec_eval_cleanup(estate);
  
  			/* Look for a matching exception handler */
diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c
index 910e63b19954cae697043d1641365de9b480cdc0..2c458d35fdb1a19fad1a46670487d6468f63384b 100644
*** a/src/pl/plpython/plpy_cursorobject.c
--- b/src/pl/plpython/plpy_cursorobject.c
*************** PLy_cursor_iternext(PyObject *self)
*** 377,384 ****
  	}
  	PG_CATCH();
  	{
- 		SPI_freetuptable(SPI_tuptable);
- 
  		PLy_spi_subtransaction_abort(oldcontext, oldowner);
  		return NULL;
  	}
--- 377,382 ----
*************** PLy_cursor_fetch(PyObject *self, PyObjec
*** 461,468 ****
  	}
  	PG_CATCH();
  	{
- 		SPI_freetuptable(SPI_tuptable);
- 
  		PLy_spi_subtransaction_abort(oldcontext, oldowner);
  		return NULL;
  	}
--- 459,464 ----
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index ed1f21cd6a51a83bd57157f16af003654b47b46c..982bf84e0e544661f41d98ca7a6eeeddda288036 100644
*** a/src/pl/plpython/plpy_spi.c
--- b/src/pl/plpython/plpy_spi.c
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 439,445 ****
  		{
  			MemoryContextSwitchTo(oldcontext);
  			PLy_typeinfo_dealloc(&args);
- 			SPI_freetuptable(tuptable);
  			Py_DECREF(result);
  			PG_RE_THROW();
  		}
--- 439,444 ----
