Implement cancellation of backends in IDLE IN TRANSACTION state.
authorAndres Freund <andres@anarazel.de>
Fri, 21 May 2010 18:17:11 +0000 (20:17 +0200)
committerAndres Freund <andres@anarazel.de>
Sat, 30 Oct 2010 08:39:33 +0000 (10:39 +0200)
Not having support for this was the reason for HS FATALing backends
which had a lock conflict for longer than
max_standby_(archive|standby)_delay as it couldnt cancel them without
them loosing sync with the frontend. As this is not the case anymore
fail the transaction "silently". Possibly one day the protocol can
cope with this...

src/backend/tcop/postgres.c

index cba90a9e727e6db29086121c815a93b409d2534c..505d1363a6004954740c935bef5d6e37c2482dc5 100644 (file)
@@ -177,6 +177,12 @@ static bool RecoveryConflictPending = false;
 static bool RecoveryConflictRetryable = true;
 static ProcSignalReason RecoveryConflictReason;
 
+/*
+ * Are we disallowed from sending a "ready for query" message right
+ * now because it would confuse the frontend?
+ */
+static bool silent_error_while_idle = false;
+
 /* ----------------------------------------------------------------
  *     decls for routines only used in this file
  * ----------------------------------------------------------------
@@ -2877,6 +2883,8 @@ RecoveryConflictInterrupt(ProcSignalReason reason)
 void
 ProcessInterrupts(void)
 {
+   int error = ERROR;
+
    /* OK to accept interrupt now? */
    if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
        return;
@@ -2949,18 +2957,24 @@ ProcessInterrupts(void)
            RecoveryConflictPending = false;
            DisableNotifyInterrupt();
            DisableCatchupInterrupt();
-           if (DoingCommandRead)
-               ereport(FATAL,
-                       (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-                        errmsg("terminating connection due to conflict with recovery"),
-                        errdetail_recovery_conflict(),
-                errhint("In a moment you should be able to reconnect to the"
-                        " database and repeat your command.")));
-           else
-               ereport(ERROR,
-                       (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-                errmsg("canceling statement due to conflict with recovery"),
-                        errdetail_recovery_conflict()));
+
+           if (DoingCommandRead){
+               /*
+                * We cant issue a normal ERROR here because the
+                * client doesnt expect the server to send an error at
+                * that point.
+                * We also may not send a "ready for query"/Z message
+                * because that would be unexpected as well.
+                */
+               silent_error_while_idle = true;
+               error |= LOG_NO_CLIENT;
+
+           }
+
+           ereport(error,
+                   (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+                    errmsg("canceling statement due to conflict with recovery"),
+                    errdetail_recovery_conflict()));
        }
 
        /*
@@ -2968,15 +2982,18 @@ ProcessInterrupts(void)
         * request --- sending an extra error message won't accomplish
         * anything.  Otherwise, go ahead and throw the error.
         */
-       if (!DoingCommandRead)
+       if (DoingCommandRead)
        {
-           ImmediateInterruptOK = false;       /* not idle anymore */
-           DisableNotifyInterrupt();
-           DisableCatchupInterrupt();
-           ereport(ERROR,
-                   (errcode(ERRCODE_QUERY_CANCELED),
-                    errmsg("canceling statement due to user request")));
+           silent_error_while_idle = true;
+           error |= LOG_NO_CLIENT;
        }
+
+       ImmediateInterruptOK = false;       /* not idle anymore */
+       DisableNotifyInterrupt();
+       DisableCatchupInterrupt();
+       ereport(error,
+               (errcode(ERRCODE_QUERY_CANCELED),
+                errmsg("canceling statement due to user request")));
    }
    /* If we get here, do nothing (probably, QueryCancelPending was reset) */
 }
@@ -3814,11 +3831,13 @@ PostgresMain(int argc, char *argv[], const char *username)
                set_ps_display("idle", false);
                pgstat_report_activity("<IDLE>");
            }
-
-           ReadyForQuery(whereToSendOutput);
-           send_ready_for_query = false;
+           if(!silent_error_while_idle){
+               ReadyForQuery(whereToSendOutput);
+               send_ready_for_query = false;
+           }
        }
 
+
        /*
         * (2) Allow asynchronous signals to be executed immediately if they
         * come in while we are waiting for client input. (This must be
@@ -3854,6 +3873,23 @@ PostgresMain(int argc, char *argv[], const char *username)
        if (ignore_till_sync && firstchar != EOF)
            continue;
 
+       if(silent_error_while_idle && !doing_extended_query_message)
+       {
+           /*
+            * When using the simple protocol:
+            * At this point we have processed a ReadCommand which means
+            * that we got some input from the frontend and thus can start
+            * spewing errors again
+
+            * When using the extended protocol:
+            * doing_extended_query_message is false after a 'sync'
+            * message - which is exactly the point after which we can
+            * start sending errors again.
+            */
+           silent_error_while_idle = false;
+       }
+
+
        switch (firstchar)
        {
            case 'Q':           /* simple query */