diff options
author | Andres Freund <andres@anarazel.de> | 2015-02-03 22:25:20 +0100 |
---|---|---|
committer | Andres Freund <andres@anarazel.de> | 2015-02-03 22:25:20 +0100 |
commit | 4f85fde8eb860f263384fffdca660e16e77c7f76 (patch) | |
tree | 9d9d18a368f4e7db987140d6dfd384b9e2e51b0a /src/backend/tcop/postgres.c | |
parent | 387da18874afa17156ee3af63766f17efb53c4b9 (diff) | |
download | postgresql-4f85fde8eb860f263384fffdca660e16e77c7f76.tar.gz postgresql-4f85fde8eb860f263384fffdca660e16e77c7f76.zip |
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/tcop/postgres.c')
-rw-r--r-- | src/backend/tcop/postgres.c | 106 |
1 files changed, 40 insertions, 66 deletions
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 556e5633284..bcc4f243134 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -301,17 +301,25 @@ InteractiveBackend(StringInfo inBuf) * interactive_getc -- collect one character from stdin * * Even though we are not reading from a "client" process, we still want to - * respond to signals, particularly SIGTERM/SIGQUIT. Hence we must use - * prepare_for_client_read and client_read_ended. + * respond to signals, particularly SIGTERM/SIGQUIT. */ static int interactive_getc(void) { int c; - prepare_for_client_read(); + /* + * This will not process catchup interrupts or notifications while + * reading. But those can't really be relevant for a standalone backend + * anyway. To properly handle SIGTERM there's a hack in die() that + * directly processes interrupts at this stage... + */ + CHECK_FOR_INTERRUPTS(); + c = getc(stdin); - client_read_ended(); + + ProcessClientReadInterrupt(); + return c; } @@ -513,53 +521,33 @@ ReadCommand(StringInfo inBuf) } /* - * prepare_for_client_read -- set up to possibly block on client input + * ProcessClientReadInterrupt() - Process interrupts specific to client reads * - * This must be called immediately before any low-level read from the - * client connection. It is necessary to do it at a sufficiently low level - * that there won't be any other operations except the read kernel call - * itself between this call and the subsequent client_read_ended() call. - * In particular there mustn't be use of malloc() or other potentially - * non-reentrant libc functions. This restriction makes it safe for us - * to allow interrupt service routines to execute nontrivial code while - * we are waiting for input. - */ -void -prepare_for_client_read(void) -{ - if (DoingCommandRead) - { - /* Enable immediate processing of asynchronous signals */ - EnableNotifyInterrupt(); - EnableCatchupInterrupt(); - - /* Allow die interrupts to be processed while waiting */ - ImmediateInterruptOK = true; - - /* And don't forget to detect one that already arrived */ - CHECK_FOR_INTERRUPTS(); - } -} - -/* - * client_read_ended -- get out of the client-input state + * This is called just after low-level reads. That might be after the read + * finished successfully, or it was interrupted via interrupt. * - * This is called just after low-level reads. It must preserve errno! + * Must preserve errno! */ void -client_read_ended(void) +ProcessClientReadInterrupt(void) { + int save_errno = errno; + if (DoingCommandRead) { - int save_errno = errno; - - ImmediateInterruptOK = false; + /* Check for general interrupts that arrived while reading */ + CHECK_FOR_INTERRUPTS(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); + /* Process sinval catchup interrupts that happened while reading */ + if (catchupInterruptPending) + ProcessCatchupInterrupt(); - errno = save_errno; + /* Process sinval catchup interrupts that happened while reading */ + if (notifyInterruptPending) + ProcessNotifyInterrupt(); } + + errno = save_errno; } @@ -2626,6 +2614,15 @@ die(SIGNAL_ARGS) /* If we're still here, waken anything waiting on the process latch */ SetLatch(MyLatch); + /* + * If we're in single user mode, we want to quit immediately - we can't + * rely on latches as they wouldn't work when stdin/stdout is a + * file. Rather ugly, but it's unlikely to be worthwhile to invest much + * more effort just for the benefit of single user mode. + */ + if (DoingCommandRead && whereToSendOutput != DestRemote) + ProcessInterrupts(); + errno = save_errno; } @@ -2834,8 +2831,6 @@ ProcessInterrupts(void) QueryCancelPending = false; /* ProcDie trumps QueryCancel */ ImmediateInterruptOK = false; /* not idle anymore */ LockErrorCleanup(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); /* As in quickdie, don't risk sending to client during auth */ if (ClientAuthInProgress && whereToSendOutput == DestRemote) whereToSendOutput = DestNone; @@ -2871,8 +2866,6 @@ ProcessInterrupts(void) QueryCancelPending = false; /* lost connection trumps QueryCancel */ ImmediateInterruptOK = false; /* not idle anymore */ LockErrorCleanup(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); /* don't send to client, we already know the connection to be dead. */ whereToSendOutput = DestNone; ereport(FATAL, @@ -2892,8 +2885,6 @@ ProcessInterrupts(void) ImmediateInterruptOK = false; /* not idle anymore */ RecoveryConflictPending = false; LockErrorCleanup(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); pgstat_report_recovery_conflict(RecoveryConflictReason); ereport(FATAL, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), @@ -2926,8 +2917,6 @@ ProcessInterrupts(void) { ImmediateInterruptOK = false; /* not idle anymore */ LockErrorCleanup(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); /* As in quickdie, don't risk sending to client during auth */ if (whereToSendOutput == DestRemote) whereToSendOutput = DestNone; @@ -2945,8 +2934,6 @@ ProcessInterrupts(void) ImmediateInterruptOK = false; /* not idle anymore */ (void) get_timeout_indicator(STATEMENT_TIMEOUT, true); LockErrorCleanup(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("canceling statement due to lock timeout"))); @@ -2955,8 +2942,6 @@ ProcessInterrupts(void) { ImmediateInterruptOK = false; /* not idle anymore */ LockErrorCleanup(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling statement due to statement timeout"))); @@ -2965,8 +2950,6 @@ ProcessInterrupts(void) { ImmediateInterruptOK = false; /* not idle anymore */ LockErrorCleanup(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling autovacuum task"))); @@ -2976,8 +2959,6 @@ ProcessInterrupts(void) ImmediateInterruptOK = false; /* not idle anymore */ RecoveryConflictPending = false; LockErrorCleanup(); - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); pgstat_report_recovery_conflict(RecoveryConflictReason); ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), @@ -2994,13 +2975,12 @@ ProcessInterrupts(void) { ImmediateInterruptOK = false; /* not idle anymore */ LockErrorCleanup(); - 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) */ } @@ -3843,14 +3823,8 @@ PostgresMain(int argc, char *argv[], disable_all_timeouts(false); QueryCancelPending = false; /* second to avoid race condition */ - /* - * Turn off these interrupts too. This is only needed here and not in - * other exception-catching places since these interrupts are only - * enabled while we wait for client input. - */ + /* Not reading from the client anymore. */ DoingCommandRead = false; - DisableNotifyInterrupt(); - DisableCatchupInterrupt(); /* Make sure libpq is in a good state */ pq_comm_reset(); |