On Thu, 2010-01-07 at 12:14 -0500, Tom Lane wrote:
> Simon Riggs <[email protected]> writes:
> > On Thu, 2010-01-07 at 14:45 +0100, Joachim Wieland wrote:
> >> @Simon: Is there a reason why you have not yet removed recoveryConflictMode
> >> from PGPROC?
>
> > Unfortunately we still need a mechanism to mark which backends have been
> > cancelled already. Transaction state for virtual transactions isn't
> > visible on the procarray, so we need something there to indicate that a
> > backend has been sent a conflict. Otherwise we'd end up waiting for it
> > endlessly. The name will be changing though.
>
> While we're discussing this: the current coding with
> AbortOutOfAnyTransaction within ProcessInterrupts is *utterly* unsafe.
> I realize that's just a toy placeholder, but getting rid of it has to be
> on the list of stop-ship items. Right at the moment I'd prefer to see
> CONFLICT_MODE_ERROR always turned into CONFLICT_MODE_FATAL than to
> imagine this is going to work.
Hmmm. Can you check my current attempt? This may suffer this problem.
If, so can you explain a little more for me? Thanks.
--
Simon Riggs www.2ndQuadrant.com
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index ea40420..e05792e 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -313,8 +313,7 @@ IsTransactionState(void)
/*
* IsAbortedTransactionBlockState
*
- * This returns true if we are currently running a query
- * within an aborted transaction block.
+ * This returns true if we are within an aborted transaction block.
*/
bool
IsAbortedTransactionBlockState(void)
@@ -2692,6 +2691,48 @@ AbortCurrentTransaction(void)
}
/*
+ * AbortTransactionAndAnySubtransactions
+ *
+ * Similar to AbortCurrentTransaction but if any subtransactions
+ * in progress we abort them *and* all of their parents. So this is
+ * used when the caller wishes to make the abort untrappable by the user.
+ * After this has run IsAbortedTransactionBlockState() will be true.
+ */
+void
+AbortTransactionAndAnySubtransactions(void)
+{
+ TransactionState s = CurrentTransactionState;
+
+ switch (s->blockState)
+ {
+ case TBLOCK_DEFAULT:
+ case TBLOCK_STARTED:
+ case TBLOCK_BEGIN:
+ case TBLOCK_INPROGRESS:
+ case TBLOCK_END:
+ case TBLOCK_ABORT:
+ case TBLOCK_SUBABORT:
+ case TBLOCK_ABORT_END:
+ case TBLOCK_ABORT_PENDING:
+ case TBLOCK_PREPARE:
+ case TBLOCK_SUBABORT_END:
+ case TBLOCK_SUBABORT_RESTART:
+ AbortCurrentTransaction();
+ break;
+
+ case TBLOCK_SUBINPROGRESS:
+ case TBLOCK_SUBBEGIN:
+ case TBLOCK_SUBEND:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBRESTART:
+ AbortSubTransaction();
+ CleanupSubTransaction();
+ AbortTransactionAndAnySubtransactions();
+ break;
+ }
+}
+
+/*
* PreventTransactionChain
*
* This routine is to be called by statements that must not run inside
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 85f14f6..e2e64dd 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -324,6 +324,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
/* must be cleared with xid/xmin: */
proc->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
proc->inCommit = false; /* be sure this is cleared in abort */
+ proc->recoveryConflictPending = false;
/* Clear the subtransaction-XID cache too while holding the lock */
proc->subxids.nxids = 0;
@@ -350,6 +351,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
/* must be cleared with xid/xmin: */
proc->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
proc->inCommit = false; /* be sure this is cleared in abort */
+ proc->recoveryConflictPending = false;
Assert(proc->subxids.nxids == 0);
Assert(proc->subxids.overflowed == false);
@@ -377,7 +379,7 @@ ProcArrayClearTransaction(PGPROC *proc)
proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
- proc->recoveryConflictMode = 0;
+ proc->recoveryConflictPending = false;
/* redundant, but just in case */
proc->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -1665,7 +1667,7 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid,
if (proc->pid == 0)
continue;
- if (skipExistingConflicts && proc->recoveryConflictMode > 0)
+ if (skipExistingConflicts && proc->recoveryConflictPending)
continue;
if (!OidIsValid(dbOid) ||
@@ -1704,7 +1706,7 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid,
* Returns pid of the process signaled, or 0 if not found.
*/
pid_t
-CancelVirtualTransaction(VirtualTransactionId vxid, int cancel_mode)
+CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode)
{
ProcArrayStruct *arrayP = procArray;
int index;
@@ -1722,28 +1724,22 @@ CancelVirtualTransaction(VirtualTransactionId vxid, int cancel_mode)
if (procvxid.backendId == vxid.backendId &&
procvxid.localTransactionId == vxid.localTransactionId)
{
- /*
- * Issue orders for the proc to read next time it receives SIGINT
- */
- if (proc->recoveryConflictMode < cancel_mode)
- proc->recoveryConflictMode = cancel_mode;
-
+ proc->recoveryConflictPending = true;
pid = proc->pid;
+ if (pid != 0)
+ {
+ /*
+ * Kill the pid if it's still here. If not, that's what we wanted
+ * so ignore any errors.
+ */
+ (void) SendProcSignal(pid, sigmode, vxid.backendId);
+ }
break;
}
}
LWLockRelease(ProcArrayLock);
- if (pid != 0)
- {
- /*
- * Kill the pid if it's still here. If not, that's what we wanted
- * so ignore any errors.
- */
- kill(pid, SIGINT);
- }
-
return pid;
}
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 480af00..c4007fe 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -24,6 +24,8 @@
#include "storage/procsignal.h"
#include "storage/shmem.h"
#include "storage/sinval.h"
+#include "storage/standby.h"
+#include "tcop/tcopprot.h"
/*
@@ -258,5 +260,11 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
HandleNotifyInterrupt();
+ if (CheckProcSignal(PROCSIG_CONFLICT_ERROR_INTERRUPT))
+ RecoveryConflictInterrupt(CONFLICT_MODE_ERROR);
+
+ if (CheckProcSignal(PROCSIG_CONFLICT_FATAL_INTERRUPT))
+ RecoveryConflictInterrupt(CONFLICT_MODE_FATAL);
+
errno = save_errno;
}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 0b4e022..0ea7a32 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -159,8 +159,6 @@ WaitExceedsMaxStandbyDelay(void)
* recovery processing. Judgement has already been passed on it within
* a specific rmgr. Here we just issue the orders to the procs. The procs
* then throw the required error as instructed.
- *
- * We may ask for a specific cancel_mode, typically ERROR or FATAL.
*/
void
ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
@@ -218,12 +216,16 @@ ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
if (WaitExceedsMaxStandbyDelay())
{
pid_t pid;
+ ProcSignalReason sigmode = PROCSIG_CONFLICT_ERROR_INTERRUPT;
+
+ if (cancel_mode == CONFLICT_MODE_FATAL)
+ sigmode = PROCSIG_CONFLICT_FATAL_INTERRUPT;
/*
* Now find out who to throw out of the balloon.
*/
Assert(VirtualTransactionIdIsValid(*waitlist));
- pid = CancelVirtualTransaction(*waitlist, cancel_mode);
+ pid = CancelVirtualTransaction(*waitlist, sigmode);
if (pid != 0)
{
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 352ff08..a708369 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -318,7 +318,7 @@ InitProcess(void)
MyProc->waitProcLock = NULL;
for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
SHMQueueInit(&(MyProc->myProcLocks[i]));
- MyProc->recoveryConflictMode = 0;
+ MyProc->recoveryConflictPending = false;
/*
* We might be reusing a semaphore that belonged to a failed process. So
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 3bddf49..3ebde79 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -172,6 +172,9 @@ static int UseNewLine = 1; /* Use newlines query delimiters (the default) */
static int UseNewLine = 0; /* Use EOF as query delimiters */
#endif /* TCOP_DONTUSENEWLINE */
+/* whether we were cancelled during recovery by conflict processing or not */
+static bool RecoveryConflictPending = false;
+static bool TransactionCancelPending = false;
/* ----------------------------------------------------------------
* decls for routines only used in this file
@@ -185,6 +188,7 @@ static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
+static int errdetail_abort(void);
static void start_xact_command(void);
static void finish_xact_command(void);
static bool IsTransactionExitStmt(Node *parsetree);
@@ -945,7 +949,8 @@ exec_simple_query(const char *query_string)
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
- "commands ignored until end of transaction block")));
+ "commands ignored until end of transaction block"),
+ errdetail_abort()));
/* Make sure we are in a transaction command */
start_xact_command();
@@ -1254,7 +1259,8 @@ exec_parse_message(const char *query_string, /* string to execute */
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
- "commands ignored until end of transaction block")));
+ "commands ignored until end of transaction block"),
+ errdetail_abort()));
/*
* Set up a snapshot if parse analysis/planning will need one.
@@ -1534,7 +1540,8 @@ exec_bind_message(StringInfo input_message)
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
- "commands ignored until end of transaction block")));
+ "commands ignored until end of transaction block"),
+ errdetail_abort()));
/*
* Create the portal. Allow silent replacement of an existing portal only
@@ -1975,7 +1982,8 @@ exec_execute_message(const char *portal_name, long max_rows)
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
- "commands ignored until end of transaction block")));
+ "commands ignored until end of transaction block"),
+ errdetail_abort()));
/* Check for cancel signal before we start execution */
CHECK_FOR_INTERRUPTS();
@@ -2236,6 +2244,20 @@ errdetail_params(ParamListInfo params)
}
/*
+ * errdetail_abort
+ *
+ * Add an errdetail() line showing abort reason, if any.
+ */
+static int
+errdetail_abort(void)
+{
+ if (MyProc->recoveryConflictPending)
+ errdetail("abort reason: recovery conflict");
+
+ return 0;
+}
+
+/*
* exec_describe_statement_message
*
* Process a "Describe" message for a prepared statement
@@ -2292,7 +2314,8 @@ exec_describe_statement_message(const char *stmt_name)
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
- "commands ignored until end of transaction block")));
+ "commands ignored until end of transaction block"),
+ errdetail_abort()));
if (whereToSendOutput != DestRemote)
return; /* can't actually do anything... */
@@ -2372,7 +2395,8 @@ exec_describe_portal_message(const char *portal_name)
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
- "commands ignored until end of transaction block")));
+ "commands ignored until end of transaction block"),
+ errdetail_abort()));
if (whereToSendOutput != DestRemote)
return; /* can't actually do anything... */
@@ -2643,9 +2667,59 @@ StatementCancelHandler(SIGNAL_ARGS)
* If it's safe to interrupt, and we're waiting for a lock, service
* the interrupt immediately. No point in interrupting if we're
* waiting for input, however.
+ */
+ if (ImmediateInterruptOK && InterruptHoldoffCount == 0 &&
+ CritSectionCount == 0 && !DoingCommandRead)
+ {
+ /* bump holdoff count to make ProcessInterrupts() a no-op */
+ /* until we are done getting ready for it */
+ InterruptHoldoffCount++;
+ LockWaitCancel(); /* prevent CheckDeadLock from running */
+ DisableNotifyInterrupt();
+ DisableCatchupInterrupt();
+ InterruptHoldoffCount--;
+ ProcessInterrupts();
+ }
+ }
+
+ errno = save_errno;
+}
+
+void
+RecoveryConflictInterrupt(int conflict_mode)
+{
+ int save_errno = errno;
+
+ /*
+ * Don't joggle the elbow of proc_exit
+ */
+ if (!proc_exit_inprogress)
+ {
+ switch (conflict_mode)
+ {
+ case CONFLICT_MODE_FATAL:
+ ProcDiePending = true;
+ break;
+
+ case CONFLICT_MODE_ERROR:
+ if (IsAbortedTransactionBlockState())
+ return;
+ TransactionCancelPending = true;
+ break;
+
+ default:
+ elog(ERROR, "Unknown conflict mode");
+ }
+
+ RecoveryConflictPending = true;
+ InterruptPending = true;
+
+ /*
+ * If it's safe to interrupt, and we're waiting for input or a lock,
+ * service the interrupt immediately. Same as in die()
*/
- if (InterruptHoldoffCount == 0 && CritSectionCount == 0 &&
- (DoingCommandRead || ImmediateInterruptOK))
+ if (ImmediateInterruptOK && InterruptHoldoffCount == 0 &&
+ CritSectionCount == 0)
{
/* bump holdoff count to make ProcessInterrupts() a no-op */
/* until we are done getting ready for it */
@@ -2709,6 +2783,10 @@ ProcessInterrupts(void)
ereport(FATAL,
(errcode(ERRCODE_ADMIN_SHUTDOWN),
errmsg("terminating autovacuum process due to administrator command")));
+ else if (RecoveryConflictPending)
+ ereport(NOTICE,
+ (errcode(ERRCODE_ADMIN_SHUTDOWN),
+ errmsg("terminating connection due to conflict with recovery")));
else
ereport(FATAL,
(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -2735,59 +2813,42 @@ ProcessInterrupts(void)
ereport(ERROR,
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling autovacuum task")));
- else
- {
- int cancelMode = MyProc->recoveryConflictMode;
-
- /*
- * XXXHS: We don't yet have a clean way to cancel an
- * idle-in-transaction session, so make it FATAL instead.
- * This isn't as bad as it looks because we don't issue a
- * CONFLICT_MODE_ERROR for a session with proc->xmin == 0
- * on cleanup conflicts. There's a possibility that we
- * marked somebody as a conflict and then they go idle.
- */
- if (DoingCommandRead && IsTransactionBlock() &&
- cancelMode == CONFLICT_MODE_ERROR)
- {
- cancelMode = CONFLICT_MODE_FATAL;
- }
-
- switch (cancelMode)
- {
- case CONFLICT_MODE_FATAL:
- Assert(RecoveryInProgress());
- ereport(FATAL,
- (errcode(ERRCODE_QUERY_CANCELED),
- errmsg("canceling session due to conflict with recovery")));
-
- case CONFLICT_MODE_ERROR:
- /*
- * We are aborting because we need to release
- * locks. So we need to abort out of all
- * subtransactions to make sure we release
- * all locks at whatever their level.
- *
- * XXX Should we try to examine the
- * transaction tree and cancel just enough
- * subxacts to remove locks? Doubt it.
- */
- Assert(RecoveryInProgress());
- AbortOutOfAnyTransaction();
- ereport(ERROR,
- (errcode(ERRCODE_QUERY_CANCELED),
- errmsg("canceling statement due to conflict with recovery")));
- return;
-
- default:
- /* No conflict pending, so fall through */
- break;
- }
-
+ else
ereport(ERROR,
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling statement due to user request")));
- }
+ }
+ if (TransactionCancelPending)
+ {
+ TransactionCancelPending = false;
+ ImmediateInterruptOK = false; /* not idle anymore */
+
+ /*
+ * Cancel the transaction whether or not it was idle, so don't mention
+ * the word idle in the message.
+ */
+ if (RecoveryConflictPending)
+ ereport(NOTICE,
+ (errcode(ERRCODE_QUERY_CANCELED),
+ errmsg("canceling transaction due to conflict with recovery")));
+ else
+ ereport(NOTICE,
+ (errcode(ERRCODE_QUERY_CANCELED),
+ errmsg("canceling transaction due to administrator request")));
+
+ /*
+ * Indicate transaction aborted by recovery so we can use the appropriate
+ * error message when we enter the aborted state.
+ */
+ AbortTransactionAndAnySubtransactions();
+ RecoveryConflictPending = false;
+
+ /*
+ * Set the display correctly now, rather than wait for client wait loop
+ * to cycle round and reset display at some later time.
+ */
+ set_ps_display("idle in transaction (aborted)", false);
+ pgstat_report_activity("<IDLE> in transaction (aborted)");
}
/* If we get here, do nothing (probably, QueryCancelPending was reset) */
}
@@ -3603,7 +3664,12 @@ PostgresMain(int argc, char *argv[], const char *username)
*/
if (send_ready_for_query)
{
- if (IsTransactionOrTransactionBlock())
+ if (IsAbortedTransactionBlockState())
+ {
+ set_ps_display("idle in transaction (aborted)", false);
+ pgstat_report_activity("<IDLE> in transaction (aborted)");
+ }
+ else if (IsTransactionOrTransactionBlock())
{
set_ps_display("idle in transaction", false);
pgstat_report_activity("<IDLE> in transaction");
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 6eee8ca..89481a3 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -204,6 +204,7 @@ extern bool IsTransactionBlock(void);
extern bool IsTransactionOrTransactionBlock(void);
extern char TransactionBlockStatusCode(void);
extern void AbortOutOfAnyTransaction(void);
+extern void AbortTransactionAndAnySubtransactions(void);
extern void PreventTransactionChain(bool isTopLevel, const char *stmtType);
extern void RequireTransactionChain(bool isTopLevel, const char *stmtType);
extern bool IsInTransactionChain(bool isTopLevel);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 2298bf2..8105d39 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -96,11 +96,11 @@ struct PGPROC
uint8 vacuumFlags; /* vacuum-related flags, see above */
/*
- * While in hot standby mode, setting recoveryConflictMode instructs
- * the backend to commit suicide. Possible values are the same as those
- * passed to ResolveRecoveryConflictWithVirtualXIDs().
+ * While in hot standby mode, shows that a conflict signal has been sent
+ * for the current transaction. Set/cleared while holding ProcArrayLock,
+ * though not required. Accessed without lock, if needed.
*/
- int recoveryConflictMode;
+ bool recoveryConflictPending;
/* Info about LWLock the process is currently waiting for, if any. */
bool lwWaiting; /* true if waiting for an LW lock */
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index 1cee639..ec358b1 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -15,6 +15,7 @@
#define PROCARRAY_H
#include "storage/lock.h"
+#include "storage/procsignal.h"
#include "storage/standby.h"
#include "utils/snapshot.h"
@@ -58,8 +59,7 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
int *nvxids);
extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin,
Oid dbOid, bool skipExistingConflicts);
-extern pid_t CancelVirtualTransaction(VirtualTransactionId vxid,
- int cancel_mode);
+extern pid_t CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode);
extern int CountActiveBackends(void);
extern int CountDBBackends(Oid databaseid);
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 7f3b65e..59a2f97 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -31,6 +31,8 @@ typedef enum
{
PROCSIG_CATCHUP_INTERRUPT, /* sinval catchup interrupt */
PROCSIG_NOTIFY_INTERRUPT, /* listen/notify interrupt */
+ PROCSIG_CONFLICT_ERROR_INTERRUPT, /* recovery conflict error */
+ PROCSIG_CONFLICT_FATAL_INTERRUPT, /* recovery conflict fatal */
NUM_PROCSIGNALS /* Must be last! */
} ProcSignalReason;
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 38df3d7..d6a7f0c 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -26,6 +26,7 @@ extern int vacuum_defer_cleanup_age;
extern void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
char *reason, int cancel_mode);
+extern void HandleConflictInterrupt(int conflict_mode);
extern void InitRecoveryTransactionEnvironment(void);
extern void ShutdownRecoveryTransactionEnvironment(void);
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index d7ede2b..fc5ab9e 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -63,6 +63,7 @@ extern bool assign_max_stack_depth(int newval, bool doit, GucSource source);
extern void die(SIGNAL_ARGS);
extern void quickdie(SIGNAL_ARGS);
extern void StatementCancelHandler(SIGNAL_ARGS);
+extern void RecoveryConflictInterrupt(int conflict_mode);
extern void FloatExceptionHandler(SIGNAL_ARGS);
extern void prepare_for_client_read(void);
extern void client_read_ended(void);
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers