diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 8d2cd4da58c1d1ef7f56a4c0c3917123694f4114..4fc3b1d3d90b1da43c4f3eb46a159ac1a74a65c7 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.233 2002/08/06 02:36:34 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.234 2002/08/13 20:14:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1298,7 +1298,8 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
 		usable_free_size = 0;
 	}
 
-	if (usable_free_size > 0 && num_vtlinks > 0)
+	/* don't bother to save vtlinks if we will not call repair_frag */
+	if (fraged_pages->num_pages > 0 && num_vtlinks > 0)
 	{
 		qsort((char *) vtlinks, num_vtlinks, sizeof(VTupleLinkData),
 			  vac_cmp_vtlinks);
@@ -1322,7 +1323,6 @@ Re-using: Free/Avail. Space %.0f/%.0f; EndEmpty/Avail. Pages %u/%u.\n\t%s",
 		 free_size, usable_free_size,
 		 empty_end_pages, fraged_pages->num_pages,
 		 vac_show_rusage(&ru0));
-
 }
 
 
@@ -1577,42 +1577,65 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 			 * If this tuple is in the chain of tuples created in updates
 			 * by "recent" transactions then we have to move all chain of
 			 * tuples to another places.
+			 *
+			 * NOTE: this test is not 100% accurate: it is possible for
+			 * a tuple to be an updated one with recent xmin, and yet not
+			 * have a corresponding tuple in the vtlinks list.  Presumably
+			 * there was once a parent tuple with xmax matching the xmin,
+			 * but it's possible that that tuple has been removed --- for
+			 * example, if it had xmin = xmax then HeapTupleSatisfiesVacuum
+			 * would deem it removable as soon as the xmin xact completes.
+			 *
+			 * To be on the safe side, we abandon the repair_frag process if
+			 * we cannot find the parent tuple in vtlinks.  This may be overly
+			 * conservative; AFAICS it would be safe to move the chain.
 			 */
-			if ((tuple.t_data->t_infomask & HEAP_UPDATED &&
+			if (((tuple.t_data->t_infomask & HEAP_UPDATED) &&
 			 !TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data),
 			                        OldestXmin)) ||
-				(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) &&
+				(!(tuple.t_data->t_infomask & (HEAP_XMAX_INVALID |
+											   HEAP_MARKED_FOR_UPDATE)) &&
 				 !(ItemPointerEquals(&(tuple.t_self),
 									 &(tuple.t_data->t_ctid)))))
 			{
 				Buffer		Cbuf = buf;
+				bool		freeCbuf = false;
+				bool		chain_move_failed = false;
 				Page		Cpage;
 				ItemId		Citemid;
 				ItemPointerData Ctid;
 				HeapTupleData tp = tuple;
 				Size		tlen = tuple_len;
-				VTupleMove	vtmove = (VTupleMove)
-				palloc(100 * sizeof(VTupleMoveData));
-				int			num_vtmove = 0;
-				int			free_vtmove = 100;
+				VTupleMove	vtmove;
+				int			num_vtmove;
+				int			free_vtmove;
 				VacPage		to_vacpage = NULL;
 				int			to_item = 0;
-				bool		freeCbuf = false;
 				int			ti;
 
-				if (vacrelstats->vtlinks == NULL)
-					elog(ERROR, "No one parent tuple was found");
 				if (cur_buffer != InvalidBuffer)
 				{
 					WriteBuffer(cur_buffer);
 					cur_buffer = InvalidBuffer;
 				}
 
+				/* Quick exit if we have no vtlinks to search in */
+				if (vacrelstats->vtlinks == NULL)
+				{
+					elog(WARNING, "Parent item in update-chain not found - can't continue repair_frag");
+					break;	/* out of walk-along-page loop */
+				}
+
+				vtmove = (VTupleMove) palloc(100 * sizeof(VTupleMoveData));
+				num_vtmove = 0;
+				free_vtmove = 100;
+
 				/*
 				 * If this tuple is in the begin/middle of the chain then
 				 * we have to move to the end of chain.
 				 */
-				while (!(tp.t_data->t_infomask & HEAP_XMAX_INVALID) &&
+				while (!(tp.t_data->t_infomask & (HEAP_XMAX_INVALID |
+												  HEAP_MARKED_FOR_UPDATE)) &&
 					   !(ItemPointerEquals(&(tp.t_self),
 										   &(tp.t_data->t_ctid))))
 				{
@@ -1636,22 +1659,35 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 						 * in scan_heap(), but it's not implemented at the
 						 * moment and so we just stop shrinking here.
 						 */
-						ReleaseBuffer(Cbuf);
-						pfree(vtmove);
-						vtmove = NULL;
 						elog(WARNING, "Child itemid in update-chain marked as unused - can't continue repair_frag");
-						break;
+						chain_move_failed = true;
+						break;	/* out of loop to move to chain end */
 					}
 					tp.t_datamcxt = NULL;
 					tp.t_data = (HeapTupleHeader) PageGetItem(Cpage, Citemid);
 					tp.t_self = Ctid;
 					tlen = tp.t_len = ItemIdGetLength(Citemid);
 				}
-				if (vtmove == NULL)
-					break;
-				/* first, can chain be moved ? */
+				if (chain_move_failed)
+				{
+					if (freeCbuf)
+						ReleaseBuffer(Cbuf);
+					pfree(vtmove);
+					break;		/* out of walk-along-page loop */
+				}
+
+				/*
+				 * Check if all items in chain can be moved
+				 */
 				for (;;)
 				{
+					Buffer		Pbuf;
+					Page		Ppage;
+					ItemId		Pitemid;
+					HeapTupleData Ptp;
+					VTupleLinkData vtld,
+							   *vtlp;
+
 					if (to_vacpage == NULL ||
 						!enough_space(to_vacpage, tlen))
 					{
@@ -1664,13 +1700,8 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 						if (i == num_fraged_pages)
 						{
 							/* can't move item anywhere */
-							for (i = 0; i < num_vtmove; i++)
-							{
-								Assert(vtmove[i].vacpage->offsets_used > 0);
-								(vtmove[i].vacpage->offsets_used)--;
-							}
-							num_vtmove = 0;
-							break;
+							chain_move_failed = true;
+							break; /* out of check-all-items loop */
 						}
 						to_item = i;
 						to_vacpage = fraged_pages->pagedesc[to_item];
@@ -1682,9 +1713,10 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 					if (free_vtmove == 0)
 					{
 						free_vtmove = 1000;
-						vtmove = (VTupleMove) repalloc(vtmove,
-											 (free_vtmove + num_vtmove) *
-												 sizeof(VTupleMoveData));
+						vtmove = (VTupleMove)
+							repalloc(vtmove,
+									 (free_vtmove + num_vtmove) *
+									 sizeof(VTupleMoveData));
 					}
 					vtmove[num_vtmove].tid = tp.t_self;
 					vtmove[num_vtmove].vacpage = to_vacpage;
@@ -1695,113 +1727,96 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 					free_vtmove--;
 					num_vtmove++;
 
-					/* All done ? */
+					/* At beginning of chain? */
 					if (!(tp.t_data->t_infomask & HEAP_UPDATED) ||
 					    TransactionIdPrecedes(HeapTupleHeaderGetXmin(tp.t_data),
 					                          OldestXmin))
 						break;
 
-					/* Well, try to find tuple with old row version */
-					for (;;)
+					/* No, move to tuple with prior row version */
+					vtld.new_tid = tp.t_self;
+					vtlp = (VTupleLink)
+						vac_bsearch((void *) &vtld,
+									(void *) (vacrelstats->vtlinks),
+									vacrelstats->num_vtlinks,
+									sizeof(VTupleLinkData),
+									vac_cmp_vtlinks);
+					if (vtlp == NULL)
 					{
-						Buffer		Pbuf;
-						Page		Ppage;
-						ItemId		Pitemid;
-						HeapTupleData Ptp;
-						VTupleLinkData vtld,
-								   *vtlp;
-
-						vtld.new_tid = tp.t_self;
-						vtlp = (VTupleLink)
-							vac_bsearch((void *) &vtld,
-										(void *) (vacrelstats->vtlinks),
-										vacrelstats->num_vtlinks,
-										sizeof(VTupleLinkData),
-										vac_cmp_vtlinks);
-						if (vtlp == NULL)
-							elog(ERROR, "Parent tuple was not found");
-						tp.t_self = vtlp->this_tid;
-						Pbuf = ReadBuffer(onerel,
-								ItemPointerGetBlockNumber(&(tp.t_self)));
-						Ppage = BufferGetPage(Pbuf);
-						Pitemid = PageGetItemId(Ppage,
-							   ItemPointerGetOffsetNumber(&(tp.t_self)));
-						if (!ItemIdIsUsed(Pitemid))
-							elog(ERROR, "Parent itemid marked as unused");
-						Ptp.t_datamcxt = NULL;
-						Ptp.t_data = (HeapTupleHeader) PageGetItem(Ppage, Pitemid);
-						Assert(ItemPointerEquals(&(vtld.new_tid),
-												 &(Ptp.t_data->t_ctid)));
-
-						/*
-						 * Read above about cases when
-						 * !ItemIdIsUsed(Citemid) (child item is
-						 * removed)... Due to the fact that at the moment
-						 * we don't remove unuseful part of update-chain,
-						 * it's possible to get too old parent row here.
-						 * Like as in the case which caused this problem,
-						 * we stop shrinking here. I could try to find
-						 * real parent row but want not to do it because
-						 * of real solution will be implemented anyway,
-						 * latter, and we are too close to 6.5 release. -
-						 * vadim 06/11/99
-						 */
-						if (!(TransactionIdEquals(HeapTupleHeaderGetXmax(Ptp.t_data),
-												  HeapTupleHeaderGetXmin(tp.t_data))))
-						{
-							if (freeCbuf)
-								ReleaseBuffer(Cbuf);
-							freeCbuf = false;
-							ReleaseBuffer(Pbuf);
-							for (i = 0; i < num_vtmove; i++)
-							{
-								Assert(vtmove[i].vacpage->offsets_used > 0);
-								(vtmove[i].vacpage->offsets_used)--;
-							}
-							num_vtmove = 0;
-							elog(WARNING, "Too old parent tuple found - can't continue repair_frag");
-							break;
-						}
-#ifdef NOT_USED					/* I'm not sure that this will wotk
-								 * properly... */
+						/* see discussion above */
+						elog(WARNING, "Parent item in update-chain not found - can't continue repair_frag");
+						chain_move_failed = true;
+						break; /* out of check-all-items loop */
+					}
+					tp.t_self = vtlp->this_tid;
+					Pbuf = ReadBuffer(onerel,
+									  ItemPointerGetBlockNumber(&(tp.t_self)));
+					Ppage = BufferGetPage(Pbuf);
+					Pitemid = PageGetItemId(Ppage,
+											ItemPointerGetOffsetNumber(&(tp.t_self)));
+					/* this can't happen since we saw tuple earlier: */
+					if (!ItemIdIsUsed(Pitemid))
+						elog(ERROR, "Parent itemid marked as unused");
+					Ptp.t_datamcxt = NULL;
+					Ptp.t_data = (HeapTupleHeader) PageGetItem(Ppage, Pitemid);
+
+					/* ctid should not have changed since we saved it */
+					Assert(ItemPointerEquals(&(vtld.new_tid),
+											 &(Ptp.t_data->t_ctid)));
 
-						/*
-						 * If this tuple is updated version of row and it
-						 * was created by the same transaction then no one
-						 * is interested in this tuple - mark it as
-						 * removed.
-						 */
-						if (Ptp.t_data->t_infomask & HEAP_UPDATED &&
-							TransactionIdEquals(HeapTupleHeaderGetXmin(Ptp.t_data),
-												HeapTupleHeaderGetXmax(Ptp.t_data)))
-						{
-							Ptp.t_data->t_infomask &=
-								~(HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID | HEAP_MOVED_IN);
-							Ptp.t_data->t_infomask |= HEAP_MOVED_OFF;
-							HeapTupleHeaderSetXvac(Ptp.t_data, myXID);
-							WriteBuffer(Pbuf);
-							continue;
-						}
-#endif
-						tp.t_datamcxt = Ptp.t_datamcxt;
-						tp.t_data = Ptp.t_data;
-						tlen = tp.t_len = ItemIdGetLength(Pitemid);
-						if (freeCbuf)
-							ReleaseBuffer(Cbuf);
-						Cbuf = Pbuf;
-						freeCbuf = true;
-						break;
+					/*
+					 * Read above about cases when
+					 * !ItemIdIsUsed(Citemid) (child item is
+					 * removed)... Due to the fact that at the moment
+					 * we don't remove unuseful part of update-chain,
+					 * it's possible to get too old parent row here.
+					 * Like as in the case which caused this problem,
+					 * we stop shrinking here. I could try to find
+					 * real parent row but want not to do it because
+					 * of real solution will be implemented anyway,
+					 * later, and we are too close to 6.5 release. -
+					 * vadim 06/11/99
+					 */
+					if (!(TransactionIdEquals(HeapTupleHeaderGetXmax(Ptp.t_data),
+											  HeapTupleHeaderGetXmin(tp.t_data))))
+					{
+						ReleaseBuffer(Pbuf);
+						elog(WARNING, "Too old parent tuple found - can't continue repair_frag");
+						chain_move_failed = true;
+						break; /* out of check-all-items loop */
 					}
-					if (num_vtmove == 0)
-						break;
-				}
+					tp.t_datamcxt = Ptp.t_datamcxt;
+					tp.t_data = Ptp.t_data;
+					tlen = tp.t_len = ItemIdGetLength(Pitemid);
+					if (freeCbuf)
+						ReleaseBuffer(Cbuf);
+					Cbuf = Pbuf;
+					freeCbuf = true;
+				} /* end of check-all-items loop */
+
 				if (freeCbuf)
 					ReleaseBuffer(Cbuf);
-				if (num_vtmove == 0)	/* chain can't be moved */
+				freeCbuf = false;
+
+				if (chain_move_failed)
 				{
+					/*
+					 * Undo changes to offsets_used state.  We don't bother
+					 * cleaning up the amount-free state, since we're not
+					 * going to do any further tuple motion.
+					 */
+					for (i = 0; i < num_vtmove; i++)
+					{
+						Assert(vtmove[i].vacpage->offsets_used > 0);
+						(vtmove[i].vacpage->offsets_used)--;
+					}
 					pfree(vtmove);
-					break;
+					break;		/* out of walk-along-page loop */
 				}
+
+				/*
+				 * Okay, move the whle tuple chain
+				 */
 				ItemPointerSetInvalid(&Ctid);
 				for (ti = 0; ti < num_vtmove; ti++)
 				{
@@ -1962,12 +1977,15 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 
 					WriteBuffer(cur_buffer);
 					WriteBuffer(Cbuf);
-				}
+				} /* end of move-the-tuple-chain loop */
+
 				cur_buffer = InvalidBuffer;
 				pfree(vtmove);
 				chain_tuple_moved = true;
+
+				/* advance to next tuple in walk-along-page loop */
 				continue;
-			}
+			} /* end of is-tuple-in-chain test */
 
 			/* try to find new page for this tuple */
 			if (cur_buffer == InvalidBuffer ||
@@ -2089,10 +2107,19 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 			}
 		}						/* walk along page */
 
+		/*
+		 * If we broke out of the walk-along-page loop early (ie, still have
+		 * offnum <= maxoff), then we failed to move some tuple off
+		 * this page.  No point in shrinking any more, so clean up and
+		 * exit the per-page loop.
+		 */
 		if (offnum < maxoff && keep_tuples > 0)
 		{
 			OffsetNumber off;
 
+			/*
+			 * Fix vacpage state for any unvisited tuples remaining on page
+			 */
 			for (off = OffsetNumberNext(offnum);
 				 off <= maxoff;
 				 off = OffsetNumberNext(off))
@@ -2154,7 +2181,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 			ReleaseBuffer(buf);
 
 		if (offnum <= maxoff)
-			break;				/* some item(s) left */
+			break;				/* had to quit early, see above note */
 
 	}							/* walk along relation */