From a8ce974cddef3957b0615d743a1d090d12e81d50 Mon Sep 17 00:00:00 2001 From: Simon Riggs Date: Sat, 16 Jan 2010 10:05:59 +0000 Subject: Teach standby conflict resolution to use SIGUSR1 Conflict reason is passed through directly to the backend, so we can take decisions about the effect of the conflict based upon the local state. No specific changes, as yet, though this prepares for later work. CancelVirtualTransaction() sends signals while holding ProcArrayLock. Introduce errdetail_abort() to give message detail explaining that the abort was caused by conflict processing. Remove CONFLICT_MODE states in favour of using PROCSIG_RECOVERY_CONFLICT states directly, for clarity. --- src/backend/tcop/postgres.c | 201 ++++++++++++++++++++++++++++++++------------ 1 file changed, 146 insertions(+), 55 deletions(-) (limited to 'src/backend/tcop/postgres.c') diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index ad8678e2200..8d55e3575a0 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.582 2010/01/15 09:19:04 heikki Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.583 2010/01/16 10:05:50 sriggs Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -63,7 +63,6 @@ #include "storage/proc.h" #include "storage/procsignal.h" #include "storage/sinval.h" -#include "storage/standby.h" #include "tcop/fastpath.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -173,6 +172,8 @@ 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; /* ---------------------------------------------------------------- * decls for routines only used in this file @@ -186,6 +187,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); @@ -944,7 +946,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(); @@ -1253,7 +1256,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. @@ -1533,7 +1537,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 @@ -1974,7 +1979,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(); @@ -2234,6 +2240,20 @@ errdetail_params(ParamListInfo params) return 0; } +/* + * 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 * @@ -2291,7 +2311,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... */ @@ -2371,7 +2392,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... */ @@ -2678,6 +2700,97 @@ SigHupHandler(SIGNAL_ARGS) got_SIGHUP = true; } +/* + * RecoveryConflictInterrupt: out-of-line portion of recovery conflict + * handling ollowing receipt of SIGUSR1. Designed to be similar to die() + * and StatementCancelHandler(). Called only by a normal user backend + * that begins a transaction during recovery. + */ +void +RecoveryConflictInterrupt(ProcSignalReason reason) +{ + int save_errno = errno; + + /* + * Don't joggle the elbow of proc_exit + */ + if (!proc_exit_inprogress) + { + switch (reason) + { + case PROCSIG_RECOVERY_CONFLICT_LOCK: + case PROCSIG_RECOVERY_CONFLICT_TABLESPACE: + case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: + /* + * If we aren't in a transaction any longer then ignore. + */ + if (!IsTransactionOrTransactionBlock()) + return; + + /* + * If we can abort just the current subtransaction then we + * are OK to throw an ERROR to resolve the conflict. Otherwise + * drop through to the FATAL case. + * + * XXX other times that we can throw just an ERROR *may* be + * PROCSIG_RECOVERY_CONFLICT_LOCK + * if no locks are held in parent transactions + * + * PROCSIG_RECOVERY_CONFLICT_SNAPSHOT + * if no snapshots are held by parent transactions + * and the transaction is not serializable + * + * PROCSIG_RECOVERY_CONFLICT_TABLESPACE + * if no temp files or cursors open in parent transactions + */ + if (!IsSubTransaction()) + { + /* + * If we already aborted then we no longer need to cancel. + * We do this here since we do not wish to ignore aborted + * subtransactions, which must cause FATAL, currently. + */ + if (IsAbortedTransactionBlockState()) + return; + + RecoveryConflictPending = true; + QueryCancelPending = true; + InterruptPending = true; + break; + } + + /* Intentional drop through to session cancel */ + + case PROCSIG_RECOVERY_CONFLICT_DATABASE: + RecoveryConflictPending = true; + ProcDiePending = true; + InterruptPending = true; + break; + + default: + elog(FATAL, "Unknown conflict mode"); + } + + /* + * If it's safe to interrupt, and we're waiting for input or a lock, + * service the interrupt immediately + */ + if (ImmediateInterruptOK && InterruptHoldoffCount == 0 && + CritSectionCount == 0) + { + /* 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; +} /* * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro @@ -2707,6 +2820,10 @@ ProcessInterrupts(void) ereport(FATAL, (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("terminating autovacuum process due to administrator command"))); + else if (RecoveryConflictPending) + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("terminating connection due to conflict with recovery"))); else ereport(FATAL, (errcode(ERRCODE_ADMIN_SHUTDOWN), @@ -2745,57 +2862,26 @@ ProcessInterrupts(void) (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling autovacuum task"))); } + if (RecoveryConflictPending) { - int cancelMode = MyProc->recoveryConflictMode; + ImmediateInterruptOK = false; /* not idle anymore */ + DisableNotifyInterrupt(); + DisableCatchupInterrupt(); - /* - * 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) + if (DoingCommandRead) { - cancelMode = CONFLICT_MODE_FATAL; + ProcDiePending = false; + QueryCancelPending = false; + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("terminating connection due to conflict with recovery"))); } - - switch (cancelMode) + else { - case CONFLICT_MODE_FATAL: - ImmediateInterruptOK = false; /* not idle anymore */ - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); - 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. - */ - ImmediateInterruptOK = false; /* not idle anymore */ - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); - Assert(RecoveryInProgress()); - AbortOutOfAnyTransaction(); - ereport(ERROR, - (errcode(ERRCODE_QUERY_CANCELED), - errmsg("canceling statement due to conflict with recovery"))); - - default: - /* No conflict pending, so fall through */ - break; + QueryCancelPending = false; + ereport(ERROR, + (errcode(ERRCODE_QUERY_CANCELED), + errmsg("canceling statement due to conflict with recovery"))); } } @@ -3637,7 +3723,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(" in transaction (aborted)"); + } + else if (IsTransactionOrTransactionBlock()) { set_ps_display("idle in transaction", false); pgstat_report_activity(" in transaction"); -- cgit v1.2.3