summaryrefslogtreecommitdiff
path: root/src/backend/storage/ipc/sinval.c
diff options
context:
space:
mode:
authorAndres Freund <andres@anarazel.de>2015-02-03 22:25:20 +0100
committerAndres Freund <andres@anarazel.de>2015-02-03 22:25:20 +0100
commit4f85fde8eb860f263384fffdca660e16e77c7f76 (patch)
tree9d9d18a368f4e7db987140d6dfd384b9e2e51b0a /src/backend/storage/ipc/sinval.c
parent387da18874afa17156ee3af63766f17efb53c4b9 (diff)
Introduce and use infrastructure for interrupt processing during client reads.
Up to now large swathes of backend code ran inside signal handlers while reading commands from the client, to allow for speedy reaction to asynchronous events. Most prominently shared invalidation and NOTIFY handling. That means that complex code like the starting/stopping of transactions is run in signal handlers... The required code was fragile and verbose, and is likely to contain bugs. That approach also severely limited what could be done while communicating with the client. As the read might be from within openssl it wasn't safely possible to trigger an error, e.g. to cancel a backend in idle-in-transaction state. We did that in some cases, namely fatal errors, nonetheless. Now that FE/BE communication in the backend employs non-blocking sockets and latches to block, we can quite simply interrupt reads from signal handlers by setting the latch. That allows us to signal an interrupted read, which is supposed to be retried after returning from within the ssl library. As signal handlers now only need to set the latch to guarantee timely interrupt processing, remove a fair amount of complicated & fragile code from async.c and sinval.c. We could now actually start to process some kinds of interrupts, like sinval ones, more often that before, but that seems better done separately. This work will hopefully allow to handle cases like being blocked by sending data, interrupting idle transactions and similar to be implemented without too much effort. In addition to allowing getting rid of ImmediateInterruptOK, that is. Author: Andres Freund Reviewed-By: Heikki Linnakangas
Diffstat (limited to 'src/backend/storage/ipc/sinval.c')
-rw-r--r--src/backend/storage/ipc/sinval.c223
1 files changed, 43 insertions, 180 deletions
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index e6b0d498c14..67ec5152a84 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -18,6 +18,7 @@
#include "commands/async.h"
#include "miscadmin.h"
#include "storage/ipc.h"
+#include "storage/proc.h"
#include "storage/sinvaladt.h"
#include "utils/inval.h"
@@ -32,19 +33,12 @@ uint64 SharedInvalidMessageCounter;
* through a cache reset exercise. This is done by sending
* PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind.
*
- * State for catchup events consists of two flags: one saying whether
- * the signal handler is currently allowed to call ProcessCatchupEvent
- * directly, and one saying whether the signal has occurred but the handler
- * was not allowed to call ProcessCatchupEvent at the time.
- *
- * NB: the "volatile" on these declarations is critical! If your compiler
- * does not grok "volatile", you'd be best advised to compile this file
- * with all optimization turned off.
+ * The signal handler will set a interrupt pending flag and will set the
+ * processes latch. Whenever starting to read from the client, or when
+ * interrupted while doing so, ProcessClientReadInterrupt() will call
+ * ProcessCatchupEvent().
*/
-static volatile int catchupInterruptEnabled = 0;
-static volatile int catchupInterruptOccurred = 0;
-
-static void ProcessCatchupEvent(void);
+volatile sig_atomic_t catchupInterruptPending = false;
/*
@@ -141,9 +135,9 @@ ReceiveSharedInvalidMessages(
* catchup signal this way avoids creating spikes in system load for what
* should be just a background maintenance activity.
*/
- if (catchupInterruptOccurred)
+ if (catchupInterruptPending)
{
- catchupInterruptOccurred = 0;
+ catchupInterruptPending = false;
elog(DEBUG4, "sinval catchup complete, cleaning queue");
SICleanupQueue(false, 0);
}
@@ -155,12 +149,9 @@ ReceiveSharedInvalidMessages(
*
* This is called when PROCSIG_CATCHUP_INTERRUPT is received.
*
- * If we are idle (catchupInterruptEnabled is set), we can safely
- * invoke ProcessCatchupEvent directly. Otherwise, just set a flag
- * to do it later. (Note that it's quite possible for normal processing
- * of the current transaction to cause ReceiveSharedInvalidMessages()
- * to be run later on; in that case the flag will get cleared again,
- * since there's no longer any reason to do anything.)
+ * We used to directly call ProcessCatchupEvent directly when idle. These days
+ * we just set a flag to do it later and notify the process of that fact by
+ * setting the process's latch.
*/
void
HandleCatchupInterrupt(void)
@@ -170,174 +161,46 @@ HandleCatchupInterrupt(void)
* you do here.
*/
- /* Don't joggle the elbow of proc_exit */
- if (proc_exit_inprogress)
- return;
-
- if (catchupInterruptEnabled)
- {
- bool save_ImmediateInterruptOK = ImmediateInterruptOK;
-
- /*
- * We may be called while ImmediateInterruptOK is true; turn it off
- * while messing with the catchup state. This prevents problems if
- * SIGINT or similar arrives while we're working. Just to be real
- * sure, bump the interrupt holdoff counter as well. That way, even
- * if something inside ProcessCatchupEvent() transiently sets
- * ImmediateInterruptOK (eg while waiting on a lock), we won't get
- * interrupted until we're done with the catchup interrupt.
- */
- ImmediateInterruptOK = false;
- HOLD_INTERRUPTS();
-
- /*
- * I'm not sure whether some flavors of Unix might allow another
- * SIGUSR1 occurrence to recursively interrupt this routine. To cope
- * with the possibility, we do the same sort of dance that
- * EnableCatchupInterrupt must do --- see that routine for comments.
- */
- catchupInterruptEnabled = 0; /* disable any recursive signal */
- catchupInterruptOccurred = 1; /* do at least one iteration */
- for (;;)
- {
- catchupInterruptEnabled = 1;
- if (!catchupInterruptOccurred)
- break;
- catchupInterruptEnabled = 0;
- if (catchupInterruptOccurred)
- {
- /* Here, it is finally safe to do stuff. */
- ProcessCatchupEvent();
- }
- }
+ catchupInterruptPending = true;
- /*
- * Restore the holdoff level and ImmediateInterruptOK, and check for
- * interrupts if needed.
- */
- RESUME_INTERRUPTS();
- ImmediateInterruptOK = save_ImmediateInterruptOK;
- if (save_ImmediateInterruptOK)
- CHECK_FOR_INTERRUPTS();
- }
- else
- {
- /*
- * In this path it is NOT SAFE to do much of anything, except this:
- */
- catchupInterruptOccurred = 1;
- }
+ /* make sure the event is processed in due course */
+ SetLatch(MyLatch);
}
/*
- * EnableCatchupInterrupt
- *
- * This is called by the PostgresMain main loop just before waiting
- * for a frontend command. We process any pending catchup events,
- * and enable the signal handler to process future events directly.
+ * ProcessCatchupInterrupt
*
- * NOTE: the signal handler starts out disabled, and stays so until
- * PostgresMain calls this the first time.
+ * The portion of catchup interrupt handling that runs outside of the signal
+ * handler, which allows it to actually process pending invalidations.
*/
void
-EnableCatchupInterrupt(void)
+ProcessCatchupInterrupt(void)
{
- /*
- * This code is tricky because we are communicating with a signal handler
- * that could interrupt us at any point. If we just checked
- * catchupInterruptOccurred and then set catchupInterruptEnabled, we could
- * fail to respond promptly to a signal that happens in between those two
- * steps. (A very small time window, perhaps, but Murphy's Law says you
- * can hit it...) Instead, we first set the enable flag, then test the
- * occurred flag. If we see an unserviced interrupt has occurred, we
- * re-clear the enable flag before going off to do the service work. (That
- * prevents re-entrant invocation of ProcessCatchupEvent() if another
- * interrupt occurs.) If an interrupt comes in between the setting and
- * clearing of catchupInterruptEnabled, then it will have done the service
- * work and left catchupInterruptOccurred zero, so we have to check again
- * after clearing enable. The whole thing has to be in a loop in case
- * another interrupt occurs while we're servicing the first. Once we get
- * out of the loop, enable is set and we know there is no unserviced
- * interrupt.
- *
- * NB: an overenthusiastic optimizing compiler could easily break this
- * code. Hopefully, they all understand what "volatile" means these days.
- */
- for (;;)
+ while (catchupInterruptPending)
{
- catchupInterruptEnabled = 1;
- if (!catchupInterruptOccurred)
- break;
- catchupInterruptEnabled = 0;
- if (catchupInterruptOccurred)
- ProcessCatchupEvent();
- }
-}
-
-/*
- * DisableCatchupInterrupt
- *
- * This is called by the PostgresMain main loop just after receiving
- * a frontend command. Signal handler execution of catchup events
- * is disabled until the next EnableCatchupInterrupt call.
- *
- * The PROCSIG_NOTIFY_INTERRUPT signal handler also needs to call this,
- * so as to prevent conflicts if one signal interrupts the other. So we
- * must return the previous state of the flag.
- */
-bool
-DisableCatchupInterrupt(void)
-{
- bool result = (catchupInterruptEnabled != 0);
-
- catchupInterruptEnabled = 0;
-
- return result;
-}
-
-/*
- * ProcessCatchupEvent
- *
- * Respond to a catchup event (PROCSIG_CATCHUP_INTERRUPT) from another
- * backend.
- *
- * This is called either directly from the PROCSIG_CATCHUP_INTERRUPT
- * signal handler, or the next time control reaches the outer idle loop
- * (assuming there's still anything to do by then).
- */
-static void
-ProcessCatchupEvent(void)
-{
- bool notify_enabled;
-
- /* Must prevent notify interrupt while I am running */
- notify_enabled = DisableNotifyInterrupt();
-
- /*
- * What we need to do here is cause ReceiveSharedInvalidMessages() to run,
- * which will do the necessary work and also reset the
- * catchupInterruptOccurred flag. If we are inside a transaction we can
- * just call AcceptInvalidationMessages() to do this. If we aren't, we
- * start and immediately end a transaction; the call to
- * AcceptInvalidationMessages() happens down inside transaction start.
- *
- * It is awfully tempting to just call AcceptInvalidationMessages()
- * without the rest of the xact start/stop overhead, and I think that
- * would actually work in the normal case; but I am not sure that things
- * would clean up nicely if we got an error partway through.
- */
- if (IsTransactionOrTransactionBlock())
- {
- elog(DEBUG4, "ProcessCatchupEvent inside transaction");
- AcceptInvalidationMessages();
- }
- else
- {
- elog(DEBUG4, "ProcessCatchupEvent outside transaction");
- StartTransactionCommand();
- CommitTransactionCommand();
+ /*
+ * What we need to do here is cause ReceiveSharedInvalidMessages() to
+ * run, which will do the necessary work and also reset the
+ * catchupInterruptPending flag. If we are inside a transaction we
+ * can just call AcceptInvalidationMessages() to do this. If we
+ * aren't, we start and immediately end a transaction; the call to
+ * AcceptInvalidationMessages() happens down inside transaction start.
+ *
+ * It is awfully tempting to just call AcceptInvalidationMessages()
+ * without the rest of the xact start/stop overhead, and I think that
+ * would actually work in the normal case; but I am not sure that things
+ * would clean up nicely if we got an error partway through.
+ */
+ if (IsTransactionOrTransactionBlock())
+ {
+ elog(DEBUG4, "ProcessCatchupEvent inside transaction");
+ AcceptInvalidationMessages();
+ }
+ else
+ {
+ elog(DEBUG4, "ProcessCatchupEvent outside transaction");
+ StartTransactionCommand();
+ CommitTransactionCommand();
+ }
}
-
- if (notify_enabled)
- EnableNotifyInterrupt();
}