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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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