*
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.129 2010/02/08 04:33:54 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.130 2010/02/09 00:28:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                   LVRelStats *vacrelstats);
 static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
                 int tupindex, LVRelStats *vacrelstats);
-static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
+static bool lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
 static BlockNumber count_nondeletable_pages(Relation onerel,
                         LVRelStats *vacrelstats);
 static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
  *     and locked the relation.
  *
  *     The return value indicates whether this function has held off
- *     interrupts -- caller must RESUME_INTERRUPTS() after commit if true.
+ *     interrupts -- if true, caller must RESUME_INTERRUPTS() after commit.
  */
 bool
 lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
    /* Done with indexes */
    vac_close_indexes(nindexes, Irel, NoLock);
 
+   /* Vacuum the Free Space Map */
+   FreeSpaceMapVacuum(onerel);
+
    /*
     * Optionally truncate the relation.
     *
+    * NB: there should be as little code as possible after this point,
+    * to minimize the chance of failure as well as the time spent ignoring
+    * cancel/die interrupts.
+    *
     * Don't even think about it unless we have a shot at releasing a goodly
     * number of pages.  Otherwise, the time taken isn't worth it.
-    *
-    * Note that after we've truncated the heap, it's too late to abort the
-    * transaction; doing so would lose the sinval messages needed to tell
-    * the other backends about the table being shrunk.  We prevent interrupts
-    * in that case; caller is responsible for re-enabling them after
-    * committing the transaction.
     */
    possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
    if (possibly_freeable > 0 &&
        (possibly_freeable >= REL_TRUNCATE_MINIMUM ||
         possibly_freeable >= vacrelstats->rel_pages / REL_TRUNCATE_FRACTION))
-   {
-       HOLD_INTERRUPTS();
-       heldoff = true;
-       lazy_truncate_heap(onerel, vacrelstats);
-   }
-
-   /* Vacuum the Free Space Map */
-   FreeSpaceMapVacuum(onerel);
+       heldoff = lazy_truncate_heap(onerel, vacrelstats);
 
    /*
     * Update statistics in pg_class.  But only if we didn't skip any pages;
 
 /*
  * lazy_truncate_heap - try to truncate off any empty pages at the end
+ *
+ *     The return value indicates whether this function has held off
+ *     interrupts -- if true, caller must RESUME_INTERRUPTS() after commit.
  */
-static void
+static bool
 lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats)
 {
    BlockNumber old_rel_pages = vacrelstats->rel_pages;
     * possible considering we already hold a lower-grade lock).
     */
    if (!ConditionalLockRelation(onerel, AccessExclusiveLock))
-       return;
+       return false;
 
    /*
     * Now that we have exclusive lock, look to see if the rel has grown
        /* might as well use the latest news when we update pg_class stats */
        vacrelstats->rel_pages = new_rel_pages;
        UnlockRelation(onerel, AccessExclusiveLock);
-       return;
+       return false;
    }
 
    /*
    {
        /* can't do anything after all */
        UnlockRelation(onerel, AccessExclusiveLock);
-       return;
+       return false;
    }
 
    /*
-    * Okay to truncate.
+    * Prevent cancel/die interrupts from now till commit.  Once we have
+    * truncated, it is essential that we send the sinval message before
+    * releasing exclusive lock on the relation; both of which will
+    * happen during commit.  Other backends must receive the sinval
+    * message to reset their rd_targblock values before they can safely
+    * write to the table again.  While we can't positively guarantee
+    * no error before commit, we can at least prevent cancel interrupts.
+    *
+    * XXX it would be better if we had a way to send the inval message
+    * nontransactionally; an error after the truncate will mean that the
+    * message is lost.  Note however that turning this all into a critical
+    * section would not be an improvement.  Making it critical would mean
+    * that an error forces PANIC, whereas losing the sinval will at worst
+    * cause unexpected nonfatal errors in other sessions.
     */
-   RelationTruncate(onerel, new_rel_pages);
+   HOLD_INTERRUPTS();
 
    /* force relcache inval so all backends reset their rd_targblock */
    CacheInvalidateRelcache(onerel);
 
    /*
-    * Note: once we have truncated, we *must* keep the exclusive lock until
-    * commit.  The sinval message won't be sent until commit, and other
-    * backends must see it and reset their rd_targblock values before they
-    * can safely access the table again.
+    * Okay to truncate.  Do as little as possible between here and commit.
     */
+   RelationTruncate(onerel, new_rel_pages);
 
    /* update statistics */
    vacrelstats->rel_pages = new_rel_pages;
                    old_rel_pages, new_rel_pages),
             errdetail("%s.",
                       pg_rusage_show(&ru0))));
+
+   return true;                /* interrupts are held off */
 }
 
 /*