Hi,

On 2020-09-08 19:30:40 -0700, Andres Freund wrote:
> It was fairly straight forward to implement the basics of
> INTERRUPTIBLE. However it seems like it might not actually address my
> main concern, i.e. making this reliably testable with isolation
> tester. At least not the way I envisioned it...
>
> My idea for the test was to have one isolationtester session start a
> seqscan, which would then reliably block a concurrent VACUUM (FREEZE,
> INTERRUPTIBLE). That'd then give a stable point to switch back to the
> first session, which would interrupt the VACUUM by doing a LOCK.
>
> But because there's no known waiting-for pid for a buffer pin wait, we
> currently don't detect that we're blocked :(.
>
>
> Now, it'd not be too hard to make pg_isolation_test_session_is_blocked
> also report being blocked when we're waiting for a buffer pin (ignoring
> interesting_pids), similar to the safe snapshot test.  However I'm
> worried that that could lead to "false" reports of blocking? But maybe
> that's a small enough risk because there's few unconditional cleanup
> lock acquisitions?
>
> Hacking such a wait condition test into
> pg_isolation_test_session_is_blocked() successfully allows my test to
> work for VACUUM.

Here's a patch series implementing that:

0001: Adds INTERRUPTIBLE to VACUUM ANALYZE
    There's quite a few XXX's inside. And it needs some none
    isolationtester test.
0002: Treat BufferPin as a waiting condition in isolationtest.
    That's the aforementioned workaround.
0003: A test, finally.
    But it only tests VACUUM, not yet ANALYZE. Perhaps also a test for
    not allowing interruptions, somehow?

Clearly WIP, but good enough for some comments, I hope?

A few comments:
- Right now there can only be one such blocking task, because
  PROC_IS_INTERRUPTIBLE is only set by vacuum / analyze, and the lock
  levels are self exclusive. But it's generically named now, so it seems
  just a matter of time until somebody adds that to other commands. I
  think it's ok to not add support for ProcSleep() killing multiple
  other processes?
- It's a bit annoying that normal user backends just see a generic
  "ERROR: canceling statement due to user request". Do we need something
  better?
- The order in which VACUUM parameters are documented & implemented
  seems fairly random. Perhaps it'd make sense to reorder
  alphabetically?


> Not yet sure about what a suitable way to test this for analyze would
> be, unless we'd also accept VacuumDelay as a wait condition :(. I guess
> one fairly easy way would be to include an expression index, and have
> the expression index acquire an unavailable lock. Which'd allow to
> switch to another session.

But here I've not yet done anything. That just seems too ugly & fragile.

Greetings,

Andres Freund
>From 29d4c1bdf536222c1e86c7502bbbf3fd49751a90 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 8 Sep 2020 20:47:38 -0700
Subject: [PATCH v1 1/3] WIP: Add INTERRUPTIBLE option to VACUUM & ANALYZE.

Partially because that's practically useful, partially because it's
useful for testing.

There's a few XXXs in the code, and there's a comment or two I have
intentionally not yet rewrapped.
---
 src/include/commands/vacuum.h       |  1 +
 src/include/storage/lock.h          |  6 ++--
 src/include/storage/proc.h          |  5 +--
 src/backend/commands/vacuum.c       | 14 +++++---
 src/backend/postmaster/autovacuum.c |  2 ++
 src/backend/storage/lmgr/deadlock.c | 54 ++++++++++++++++-------------
 src/backend/storage/lmgr/proc.c     | 24 +++++++------
 doc/src/sgml/ref/analyze.sgml       | 16 +++++++++
 doc/src/sgml/ref/vacuum.sgml        | 17 +++++++++
 9 files changed, 94 insertions(+), 45 deletions(-)

diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index d9475c99890..7e064ef45e9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -215,6 +215,7 @@ typedef struct VacuumParams
 	int			multixact_freeze_table_age; /* multixact age at which to scan
 											 * whole table */
 	bool		is_wraparound;	/* force a for-wraparound vacuum */
+	bool		is_interruptible;	/* allow cancelling on lock conflict */
 	int			log_min_duration;	/* minimum execution threshold in ms at
 									 * which  verbose logs are activated, -1
 									 * to use default */
diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h
index 1c3e9c1999f..f21f9f3f974 100644
--- a/src/include/storage/lock.h
+++ b/src/include/storage/lock.h
@@ -499,8 +499,8 @@ typedef enum
 	DS_NO_DEADLOCK,				/* no deadlock detected */
 	DS_SOFT_DEADLOCK,			/* deadlock avoided by queue rearrangement */
 	DS_HARD_DEADLOCK,			/* deadlock, no way out but ERROR */
-	DS_BLOCKED_BY_AUTOVACUUM	/* no deadlock; queue blocked by autovacuum
-								 * worker */
+	DS_BLOCKED_BY_INTERRUPTIBLE	/* no deadlock; queue blocked by interruptible
+								 * task (e.g. autovacuum worker) */
 } DeadLockState;
 
 /*
@@ -588,7 +588,7 @@ extern void lock_twophase_standby_recover(TransactionId xid, uint16 info,
 										  void *recdata, uint32 len);
 
 extern DeadLockState DeadLockCheck(PGPROC *proc);
-extern PGPROC *GetBlockingAutoVacuumPgproc(void);
+extern PGPROC *GetBlockingInterruptiblePgproc(void);
 extern void DeadLockReport(void) pg_attribute_noreturn();
 extern void RememberSimpleDeadLock(PGPROC *proc1,
 								   LOCKMODE lockmode,
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 9c9a50ae457..e660972a010 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -53,13 +53,14 @@ struct XidCache
  */
 #define		PROC_IS_AUTOVACUUM	0x01	/* is it an autovac worker? */
 #define		PROC_IN_VACUUM		0x02	/* currently running lazy vacuum */
-#define		PROC_VACUUM_FOR_WRAPAROUND	0x08	/* set by autovac only */
+#define		PROC_IS_INTERRUPTIBLE	0x08	/* vacuum / analyze may be interrupted
+										 * when locks conflict */
 #define		PROC_IN_LOGICAL_DECODING	0x10	/* currently doing logical
 												 * decoding outside xact */
 
 /* flags reset at EOXact */
 #define		PROC_VACUUM_STATE_MASK \
-	(PROC_IN_VACUUM | PROC_VACUUM_FOR_WRAPAROUND)
+	(PROC_IN_VACUUM | PROC_IS_INTERRUPTIBLE)
 
 /*
  * We allow a small number of "weak" relation locks (AccessShareLock,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ddeec870d81..df717d2951a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -123,6 +123,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 			verbose = defGetBoolean(opt);
 		else if (strcmp(opt->defname, "skip_locked") == 0)
 			skip_locked = defGetBoolean(opt);
+		else if (strcmp(opt->defname, "interruptible") == 0)
+			params.is_interruptible = defGetBoolean(opt);
 		else if (!vacstmt->is_vacuumcmd)
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -1754,19 +1756,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		 * contents of other tables is arguably broken, but we won't break it
 		 * here by violating transaction semantics.)
 		 *
-		 * We also set the VACUUM_FOR_WRAPAROUND flag, which is passed down by
-		 * autovacuum; it's used to avoid canceling a vacuum that was invoked
-		 * in an emergency.
+		 * We also set the INTERRUPTIBLE flag when user indicated or when
+		 * started by autovacuum (except for anti-wraparound autovacuum, which
+		 * we do not want to cancel), that governs whether the process may be
+		 * cancelled upon a lock conflict.
 		 *
 		 * Note: these flags remain set until CommitTransaction or
 		 * AbortTransaction.  We don't want to clear them until we reset
 		 * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
 		 * might appear to go backwards, which is probably Not Good.
 		 */
+		Assert(!params->is_wraparound || !params->is_interruptible);
 		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 		MyProc->vacuumFlags |= PROC_IN_VACUUM;
-		if (params->is_wraparound)
-			MyProc->vacuumFlags |= PROC_VACUUM_FOR_WRAPAROUND;
+		if (params->is_interruptible)
+			MyProc->vacuumFlags |= PROC_IS_INTERRUPTIBLE;
 		ProcGlobal->vacuumFlags[MyProc->pgxactoff] = MyProc->vacuumFlags;
 		LWLockRelease(ProcArrayLock);
 	}
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1b8cd7bacd4..9fdc3f8ade1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2899,6 +2899,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 		tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age;
 		tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
 		tab->at_params.is_wraparound = wraparound;
+		/* Allow interrupting autovacuum unless it's an emergency one */
+		tab->at_params.is_interruptible = !wraparound;
 		tab->at_params.log_min_duration = log_min_duration;
 		tab->at_vacuum_cost_limit = vac_cost_limit;
 		tab->at_vacuum_cost_delay = vac_cost_delay;
diff --git a/src/backend/storage/lmgr/deadlock.c b/src/backend/storage/lmgr/deadlock.c
index e1246b8a4da..770b871f761 100644
--- a/src/backend/storage/lmgr/deadlock.c
+++ b/src/backend/storage/lmgr/deadlock.c
@@ -124,8 +124,8 @@ static int	maxPossibleConstraints;
 static DEADLOCK_INFO *deadlockDetails;
 static int	nDeadlockDetails;
 
-/* PGPROC pointer of any blocking autovacuum worker found */
-static PGPROC *blocking_autovacuum_proc = NULL;
+/* PGPROC pointer of any blocking but interruptible process found */
+static PGPROC *blocking_interruptible_proc = NULL;
 
 
 /*
@@ -224,8 +224,8 @@ DeadLockCheck(PGPROC *proc)
 	nPossibleConstraints = 0;
 	nWaitOrders = 0;
 
-	/* Initialize to not blocked by an autovacuum worker */
-	blocking_autovacuum_proc = NULL;
+	/* Initialize to not blocked by an interruptible process */
+	blocking_interruptible_proc = NULL;
 
 	/* Search for deadlocks and possible fixes */
 	if (DeadLockCheckRecurse(proc))
@@ -278,24 +278,24 @@ DeadLockCheck(PGPROC *proc)
 	/* Return code tells caller if we had to escape a deadlock or not */
 	if (nWaitOrders > 0)
 		return DS_SOFT_DEADLOCK;
-	else if (blocking_autovacuum_proc != NULL)
-		return DS_BLOCKED_BY_AUTOVACUUM;
+	else if (blocking_interruptible_proc != NULL)
+		return DS_BLOCKED_BY_INTERRUPTIBLE;
 	else
 		return DS_NO_DEADLOCK;
 }
 
 /*
- * Return the PGPROC of the autovacuum that's blocking a process.
+ * Return the PGPROC of an interruptible process that's blocking a process.
  *
  * We reset the saved pointer as soon as we pass it back.
  */
 PGPROC *
-GetBlockingAutoVacuumPgproc(void)
+GetBlockingInterruptiblePgproc(void)
 {
 	PGPROC	   *ptr;
 
-	ptr = blocking_autovacuum_proc;
-	blocking_autovacuum_proc = NULL;
+	ptr = blocking_interruptible_proc;
+	blocking_interruptible_proc = NULL;
 
 	return ptr;
 }
@@ -606,30 +606,36 @@ FindLockCycleRecurseMember(PGPROC *checkProc,
 					}
 
 					/*
-					 * No deadlock here, but see if this proc is an autovacuum
+					 * No deadlock here, but see if this proc is an interruptible process
 					 * that is directly hard-blocking our own proc.  If so,
 					 * report it so that the caller can send a cancel signal
 					 * to it, if appropriate.  If there's more than one such
 					 * proc, it's indeterminate which one will be reported.
 					 *
-					 * We don't touch autovacuums that are indirectly blocking
+					 * We don't touch processes that are indirectly blocking
 					 * us; it's up to the direct blockee to take action.  This
 					 * rule simplifies understanding the behavior and ensures
-					 * that an autovacuum won't be canceled with less than
-					 * deadlock_timeout grace period.
+					 * such a process won't be canceled with a grace period of
+					 * less than deadlock_timeout.
 					 *
-					 * Note we read vacuumFlags without any locking.  This is
-					 * OK only for checking the PROC_IS_AUTOVACUUM flag,
-					 * because that flag is set at process start and never
-					 * reset.  There is logic elsewhere to avoid canceling an
-					 * autovacuum that is working to prevent XID wraparound
-					 * problems (which needs to read a different vacuumFlag
-					 * bit), but we don't do that here to avoid grabbing
-					 * ProcArrayLock.
+					 * Note we read vacuumFlags without any locking.  That is
+					 * ok because 32bit reads are atomic, because surrounding
+					 * operations provide strong enough memory barriers, and
+					 * because we re-check whether IS_INTERRUPTIBLE is still
+					 * set before actually cancelling the backend.
 					 */
 					if (checkProc == MyProc &&
-						proc->vacuumFlags & PROC_IS_AUTOVACUUM)
-						blocking_autovacuum_proc = proc;
+						proc->vacuumFlags & PROC_IS_INTERRUPTIBLE)
+					{
+						/*
+						 * XXX: At some point there could be more than one
+						 * proc blocking us here. Currently that's not
+						 * possible because of the lock levels used by VACUUM
+						 * / ANALYZE, which are the only ones to set this flag
+						 * currently, but ...
+						 */
+						blocking_interruptible_proc = proc;
+					}
 
 					/* We're done looking at this proclock */
 					break;
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 19a9f939492..0b288ac6f2d 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -1064,7 +1064,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
 	PROC_QUEUE *waitQueue = &(lock->waitProcs);
 	LOCKMASK	myHeldLocks = MyProc->heldLocks;
 	bool		early_deadlock = false;
-	bool		allow_autovacuum_cancel = true;
+	bool		allow_interruptible_cancel = true;
 	ProcWaitStatus myWaitStatus;
 	PGPROC	   *proc;
 	PGPROC	   *leader = MyProc->lockGroupLeader;
@@ -1304,23 +1304,21 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
 		myWaitStatus = *((volatile ProcWaitStatus *) &MyProc->waitStatus);
 
 		/*
-		 * If we are not deadlocked, but are waiting on an autovacuum-induced
-		 * task, send a signal to interrupt it.
+		 * If we are not deadlocked, but are waiting on an interruptible
+		 * (e.g. autovacuum-induced) task, send a signal to interrupt it.
 		 */
-		if (deadlock_state == DS_BLOCKED_BY_AUTOVACUUM && allow_autovacuum_cancel)
+		if (deadlock_state == DS_BLOCKED_BY_INTERRUPTIBLE && allow_interruptible_cancel)
 		{
-			PGPROC	   *autovac = GetBlockingAutoVacuumPgproc();
+			PGPROC	   *autovac = GetBlockingInterruptiblePgproc();
 			uint8		vacuumFlags;
 
 			LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 
 			/*
-			 * Only do it if the worker is not working to protect against Xid
-			 * wraparound.
+			 * Only do it if the process is still interruptible.
 			 */
 			vacuumFlags = ProcGlobal->vacuumFlags[autovac->pgxactoff];
-			if ((vacuumFlags & PROC_IS_AUTOVACUUM) &&
-				!(vacuumFlags & PROC_VACUUM_FOR_WRAPAROUND))
+			if (vacuumFlags & PROC_IS_INTERRUPTIBLE)
 			{
 				int			pid = autovac->pid;
 				StringInfoData locktagbuf;
@@ -1340,8 +1338,12 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
 				LWLockRelease(ProcArrayLock);
 
 				/* send the autovacuum worker Back to Old Kent Road */
+				/*
+				 * FIXME: do we want to continue to identify autovacuum here?
+				 * Could do so based on PROC_IS_AUTOVACUUM.
+				 */
 				ereport(DEBUG1,
-						(errmsg("sending cancel to blocking autovacuum PID %d",
+						(errmsg("sending cancel to blocking autovacuum|XXX PID %d",
 								pid),
 						 errdetail_log("%s", logbuf.data)));
 
@@ -1370,7 +1372,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
 				LWLockRelease(ProcArrayLock);
 
 			/* prevent signal from being resent more than once */
-			allow_autovacuum_cancel = false;
+			allow_interruptible_cancel = false;
 		}
 
 		/*
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 5ac3ba83219..c7f3f58c6da 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
 
     VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
     SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+    INTERRUPTIBLE [ <replaceable class="parameter">boolean</replaceable> ]
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -95,6 +96,21 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>INTERRUPTIBLE</literal></term>
+    <listitem>
+     <para>
+      Specifies whether <command>ANALYZE</command> may be cancelled when
+      another connection needs a lock conflicting with this.
+      <command>ANALYZE</command>. That can be useful to reduce the impact of
+      running <command>ANALYZE</command> in a busy system, by e.g. allowing
+      DDL commands to run before <command>ANALYZE</command> has finished.  If
+      the option is not specified <command>ANALYZE</command> is not
+      interruptible.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">boolean</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7ba..1d69041566e 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
     TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
     PARALLEL <replaceable class="parameter">integer</replaceable>
+    INTERRUPTIBLE [ <replaceable class="parameter">boolean</replaceable> ]
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -255,6 +256,22 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>INTERRUPTIBLE</literal></term>
+    <listitem>
+     <para>
+      Specifies whether <command>VACUUM</command> may be cancelled when
+      another connection needs a lock conflicting with this.
+      <command>VACUUM</command>. That can be useful to reduce the impact of
+      running <command>VACUUM</command> in a busy system, by e.g. allowing DDL
+      commands to run before <command>VACUUM</command> has finished.  This
+      option is currently ignored if the <literal>FULL</literal> option is
+      used. If the option is not specified <command>VACUUM</command> is not
+      interruptible.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">boolean</replaceable></term>
     <listitem>
-- 
2.25.0.114.g5b0ca878e0

>From 6613850df90f60a6e9daa0677bd81c64d5eb3107 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 8 Sep 2020 20:49:37 -0700
Subject: [PATCH v1 2/3] WIP: Treat BufferPin as a waiting condition in
 isolationtest.

---
 src/include/storage/procarray.h     |  1 +
 src/backend/storage/ipc/procarray.c | 24 ++++++++++++++++++++++++
 src/backend/utils/adt/lockfuncs.c   | 25 +++++++++++++++++++++++++
 3 files changed, 50 insertions(+)

diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index ea8a876ca45..62ee6833787 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -66,6 +66,7 @@ extern PGPROC *BackendPidGetProc(int pid);
 extern PGPROC *BackendPidGetProcWithLock(int pid);
 extern int	BackendXidGetPid(TransactionId xid);
 extern bool IsBackendPid(int pid);
+extern uint32 BackendPidGetWaitEvent(int pid);
 
 extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   bool excludeXmin0, bool allDbs, int excludeVacuum,
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 1c0cd6b2487..5fb67e0e217 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3023,6 +3023,30 @@ BackendPidGetProcWithLock(int pid)
 	return result;
 }
 
+/*
+ * BackendPidGetWaitEvent -- return wait event of a backend
+ *
+ * This return 0 both if there is no backend with the passed PID and if the
+ * backend exists but is not currently waiting.
+ */
+uint32
+BackendPidGetWaitEvent(int pid)
+{
+	PGPROC	   *proc;
+	uint32		raw_wait_event = 0;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+	proc = BackendPidGetProcWithLock(pid);
+
+	if (proc)
+		raw_wait_event = UINT32_ACCESS_ONCE(proc->wait_event_info);
+
+	LWLockRelease(ProcArrayLock);
+
+	return raw_wait_event;
+}
+
 /*
  * BackendXidGetPid -- get a backend's pid given its XID
  *
diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c
index f592292d067..1f41a85d496 100644
--- a/src/backend/utils/adt/lockfuncs.c
+++ b/src/backend/utils/adt/lockfuncs.c
@@ -17,7 +17,9 @@
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "storage/predicate_internals.h"
+#include "storage/procarray.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 
@@ -601,6 +603,7 @@ pg_isolation_test_session_is_blocked(PG_FUNCTION_ARGS)
 	int			num_interesting_pids;
 	int			num_blocking_pids;
 	int			dummy;
+	uint32		raw_wait_event;
 	int			i,
 				j;
 
@@ -653,6 +656,28 @@ pg_isolation_test_session_is_blocked(PG_FUNCTION_ARGS)
 	if (GetSafeSnapshotBlockingPids(blocked_pid, &dummy, 1) > 0)
 		PG_RETURN_BOOL(true);
 
+	raw_wait_event = BackendPidGetWaitEvent(blocked_pid);
+
+	if (raw_wait_event != 0)
+	{
+		const char*	wait_event_type;
+		const char*	wait_event;
+
+		/*
+		 * FIXME: probably better to match on the integer value itself. But
+		 * currently the class / event mask is not exposed outside pgstat.c...
+		 */
+		wait_event_type = pgstat_get_wait_event_type(raw_wait_event);
+		wait_event = pgstat_get_wait_event(raw_wait_event);
+
+		if (wait_event_type != NULL && wait_event_type != NULL &&
+			strcmp(wait_event_type, "BufferPin") == 0 &&
+			strcmp(wait_event, "BufferPin") == 0)
+		{
+			PG_RETURN_BOOL(true);
+		}
+	}
+
 	PG_RETURN_BOOL(false);
 }
 
-- 
2.25.0.114.g5b0ca878e0

>From c5061503e34fe4676388b17297a4c2f02354bcd5 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 8 Sep 2020 20:51:09 -0700
Subject: [PATCH v1 3/3] WIP: Test for VACUUM (INTERRUPTIBLE) cancellation
 working.

Needs ANALYZE support. Perhaps also a better approach for switching
sessions once the VACUUM has started (see previous commit).

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/test/isolation/expected/vacuum-cancel.out | 45 +++++++++++++++++++
 src/test/isolation/isolation_schedule         |  1 +
 src/test/isolation/specs/vacuum-cancel.spec   | 37 +++++++++++++++
 3 files changed, 83 insertions(+)
 create mode 100644 src/test/isolation/expected/vacuum-cancel.out
 create mode 100644 src/test/isolation/specs/vacuum-cancel.spec

diff --git a/src/test/isolation/expected/vacuum-cancel.out b/src/test/isolation/expected/vacuum-cancel.out
new file mode 100644
index 00000000000..2532fb4c7c7
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-cancel.out
@@ -0,0 +1,45 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_pin s2_delete s2_vacuum_interruptible s1_commit
+step s1_begin: BEGIN;
+step s1_pin: 
+    DECLARE pin_page CURSOR WITHOUT HOLD FOR SELECT * FROM test_vacuum_cancel;
+    FETCH NEXT FROM pin_page;
+
+data           
+
+somedata       
+step s2_delete: DELETE FROM test_vacuum_cancel;
+step s2_vacuum_interruptible: VACUUM (FREEZE, INTERRUPTIBLE) test_vacuum_cancel; <waiting ...>
+step s1_commit: COMMIT;
+step s2_vacuum_interruptible: <... completed>
+
+starting permutation: s1_begin s1_pin s2_delete s2_analyze_interruptible s1_commit
+step s1_begin: BEGIN;
+step s1_pin: 
+    DECLARE pin_page CURSOR WITHOUT HOLD FOR SELECT * FROM test_vacuum_cancel;
+    FETCH NEXT FROM pin_page;
+
+data           
+
+somedata       
+step s2_delete: DELETE FROM test_vacuum_cancel;
+step s2_analyze_interruptible: ANALYZE (INTERRUPTIBLE) test_vacuum_cancel;
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_pin s2_delete s2_vacuum_interruptible s1_lock s1_commit
+step s1_begin: BEGIN;
+step s1_pin: 
+    DECLARE pin_page CURSOR WITHOUT HOLD FOR SELECT * FROM test_vacuum_cancel;
+    FETCH NEXT FROM pin_page;
+
+data           
+
+somedata       
+step s2_delete: DELETE FROM test_vacuum_cancel;
+step s2_vacuum_interruptible: VACUUM (FREEZE, INTERRUPTIBLE) test_vacuum_cancel; <waiting ...>
+step s1_lock: LOCK test_vacuum_cancel; <waiting ...>
+step s1_lock: <... completed>
+step s2_vacuum_interruptible: <... completed>
+error in steps s1_lock s2_vacuum_interruptible: ERROR:  canceling statement due to user request
+step s1_commit: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 6acbb695ece..c02be96fb8c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -78,6 +78,7 @@ test: timeouts
 test: vacuum-concurrent-drop
 test: vacuum-conflict
 test: vacuum-skip-locked
+test: vacuum-cancel
 test: predicate-hash
 test: predicate-gist
 test: predicate-gin
diff --git a/src/test/isolation/specs/vacuum-cancel.spec b/src/test/isolation/specs/vacuum-cancel.spec
new file mode 100644
index 00000000000..e41877dce42
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-cancel.spec
@@ -0,0 +1,37 @@
+setup
+{
+    CREATE TABLE test_vacuum_cancel(data text);
+    /* don't want autovacuum clean up to-be-cleaned data concurrently */
+    ALTER TABLE test_vacuum_cancel SET (AUTOVACUUM_ENABLED = false);
+    /* insert some data  */
+    INSERT INTO test_vacuum_cancel VALUES('somedata');
+    INSERT INTO test_vacuum_cancel VALUES('otherdata');
+}
+
+teardown
+{
+    DROP TABLE test_vacuum_cancel;
+}
+
+session "s1"
+step "s1_begin" { BEGIN; }
+step "s1_pin" {
+    DECLARE pin_page CURSOR WITHOUT HOLD FOR SELECT * FROM test_vacuum_cancel;
+    FETCH NEXT FROM pin_page;
+}
+step "s1_lock" { LOCK test_vacuum_cancel; }
+step "s1_commit" { COMMIT; }
+
+session "s2"
+step "s2_delete" { DELETE FROM test_vacuum_cancel; }
+step "s2_vacuum_interruptible" { VACUUM (FREEZE, INTERRUPTIBLE) test_vacuum_cancel; }
+step "s2_analyze_interruptible" { ANALYZE (INTERRUPTIBLE) test_vacuum_cancel; }
+
+# First test pin release resolves issues
+permutation "s1_begin" "s1_pin" "s2_delete" "s2_vacuum_interruptible" "s1_commit"
+# XXX: This doesn't actually wait on the pin, need alternative wait trick
+permutation "s1_begin" "s1_pin" "s2_delete" "s2_analyze_interruptible" "s1_commit"
+
+# Then track that concurrent TRUNCATE interrupts VACUUM / ANALYZE
+permutation "s1_begin" "s1_pin" "s2_delete" "s2_vacuum_interruptible" "s1_lock" "s1_commit"
+#permutation "s1_begin" "s1_pin" "s2_delete" "s2_analyze_interruptible" "s3_truncate"
-- 
2.25.0.114.g5b0ca878e0

Reply via email to