startup_deadlock_check.patch

text/x-patch

Filename: startup_deadlock_check.patch
Type: text/x-patch
Part: 0
Message: Hot Standby and deadlock detection

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
File+
src/backend/storage/ipc/procsignal.c 3 0
src/backend/storage/ipc/standby.c 27 0
src/backend/storage/lmgr/proc.c 31 0
src/backend/tcop/postgres.c 14 0
src/backend/utils/misc/guc.c 1 0
src/include/storage/proc.h 2 0
src/include/storage/procsignal.h 1 0
src/include/storage/standby.h 2 0
*** a/src/backend/storage/ipc/procsignal.c
--- b/src/backend/storage/ipc/procsignal.c
***************
*** 272,277 **** procsignal_sigusr1_handler(SIGNAL_ARGS)
--- 272,280 ----
  	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT))
  		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT);
  
+ 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK))
+ 		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ 
  	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
  		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
  
*** a/src/backend/storage/ipc/standby.c
--- b/src/backend/storage/ipc/standby.c
***************
*** 127,132 **** WaitExceedsMaxStandbyDelay(void)
--- 127,135 ----
  	long	delay_secs;
  	int		delay_usecs;
  
+ 	if (MaxStandbyDelay == -1)
+ 		return false;
+ 
  	/* Are we past max_standby_delay? */
  	TimestampDifference(GetLatestXLogTime(), GetCurrentTimestamp(),
  						&delay_secs, &delay_usecs);
***************
*** 372,378 **** ResolveRecoveryConflictWithBufferPin(void)
  	 * Signal immediately or set alarm for later.
  	 */
  	if (MaxStandbyDelay == 0)
! 		SendRecoveryConflictWithBufferPin();
  	else
  	{
  		TimestampTz now;
--- 375,391 ----
  	 * Signal immediately or set alarm for later.
  	 */
  	if (MaxStandbyDelay == 0)
! 		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
! 	else if (MaxStandbyDelay == -1)
! 	{
! 		/*
! 		 * Set the deadlock timer only, we don't timeout in any other case
! 		 */
! 		if (enable_standby_deadlock_alarm())
! 			sig_alarm_enabled = true;
! 		else
! 			elog(FATAL, "could not set timer for process wakeup");
! 	}
  	else
  	{
  		TimestampTz now;
***************
*** 386,392 **** ResolveRecoveryConflictWithBufferPin(void)
  							&standby_delay_secs, &standby_delay_usecs);
  
  		if (standby_delay_secs >= MaxStandbyDelay)
! 			SendRecoveryConflictWithBufferPin();
  		else
  		{
  			TimestampTz fin_time;			/* Expected wake-up time by timer */
--- 399,405 ----
  							&standby_delay_secs, &standby_delay_usecs);
  
  		if (standby_delay_secs >= MaxStandbyDelay)
! 			SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
  		else
  		{
  			TimestampTz fin_time;			/* Expected wake-up time by timer */
***************
*** 394,399 **** ResolveRecoveryConflictWithBufferPin(void)
--- 407,419 ----
  			int		timer_delay_usecs = 0;
  
  			/*
+ 			 * Send out a request to check for buffer pin deadlocks before we wait.
+ 			 * This is fairly cheap, so no need to wait for deadlock timeout before
+ 			 * trying to send it out.
+ 			 */
+ 			SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ 
+ 			/*
  			 * How much longer we should wait?
  			 */
  			timer_delay_secs = MaxStandbyDelay - standby_delay_secs;
***************
*** 435,449 **** ResolveRecoveryConflictWithBufferPin(void)
  }
  
  void
! SendRecoveryConflictWithBufferPin(void)
  {
  	/*
  	 * We send signal to all backends to ask them if they are holding
  	 * the buffer pin which is delaying the Startup process. We must
  	 * not set the conflict flag yet, since most backends will be innocent.
  	 * Let the SIGUSR1 handling in each backend decide their own fate.
  	 */
! 	CancelDBBackends(InvalidOid, PROCSIG_RECOVERY_CONFLICT_BUFFERPIN, false);
  }
  
  /*
--- 455,472 ----
  }
  
  void
! SendRecoveryConflictWithBufferPin(ProcSignalReason reason)
  {
+ 	Assert(reason == PROCSIG_RECOVERY_CONFLICT_BUFFERPIN ||
+ 		   reason == PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ 
  	/*
  	 * We send signal to all backends to ask them if they are holding
  	 * the buffer pin which is delaying the Startup process. We must
  	 * not set the conflict flag yet, since most backends will be innocent.
  	 * Let the SIGUSR1 handling in each backend decide their own fate.
  	 */
! 	CancelDBBackends(InvalidOid, reason, false);
  }
  
  /*
*** a/src/backend/storage/lmgr/proc.c
--- b/src/backend/storage/lmgr/proc.c
***************
*** 45,50 ****
--- 45,51 ----
  #include "storage/pmsignal.h"
  #include "storage/proc.h"
  #include "storage/procarray.h"
+ #include "storage/procsignal.h"
  #include "storage/spin.h"
  
  
***************
*** 77,82 **** static volatile bool standby_timeout_active = false;
--- 78,84 ----
  static volatile bool statement_timeout_active = false;
  static volatile bool deadlock_timeout_active = false;
  static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
+ static volatile ProcSignalReason action_at_standby_timeout;
  volatile bool cancel_from_timeout = false;
  
  /* timeout_start_time is set when log_lock_waits is true */
***************
*** 556,561 **** HaveNFreeProcs(int n)
--- 558,572 ----
  	return (n <= 0);
  }
  
+ bool
+ IsWaitingForLock(void)
+ {
+ 	if (lockAwaited == NULL)
+ 		return false;
+ 
+ 	return true;
+ }
+ 
  /*
   * Cancel any pending wait for lock, when aborting a transaction.
   *
***************
*** 1618,1624 **** enable_standby_sig_alarm(long delay_s, int delay_us, TimestampTz fin_time)
  	Assert(delay_s >= 0 && delay_us >= 0);
  
  	statement_fin_time = fin_time;
! 
  	standby_timeout_active = true;
  
  	MemSet(&timeval, 0, sizeof(struct itimerval));
--- 1629,1635 ----
  	Assert(delay_s >= 0 && delay_us >= 0);
  
  	statement_fin_time = fin_time;
! 	action_at_standby_timeout = PROCSIG_RECOVERY_CONFLICT_BUFFERPIN;
  	standby_timeout_active = true;
  
  	MemSet(&timeval, 0, sizeof(struct itimerval));
***************
*** 1630,1635 **** enable_standby_sig_alarm(long delay_s, int delay_us, TimestampTz fin_time)
--- 1641,1664 ----
  }
  
  bool
+ enable_standby_deadlock_alarm(void)
+ {
+ 	struct itimerval timeval;
+ 
+ 	statement_fin_time = TimestampTzPlusMilliseconds(GetCurrentTimestamp(),
+ 													(DeadlockTimeout * 1000));
+ 	action_at_standby_timeout = PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK;
+ 	standby_timeout_active = true;
+ 
+ 	MemSet(&timeval, 0, sizeof(struct itimerval));
+ 	timeval.it_value.tv_sec = DeadlockTimeout;
+ 	timeval.it_value.tv_usec = 0;
+ 	if (setitimer(ITIMER_REAL, &timeval, NULL))
+ 		return false;
+ 	return true;
+ }
+ 
+ bool
  disable_standby_sig_alarm(void)
  {
  	/*
***************
*** 1671,1677 **** CheckStandbyTimeout(void)
  	now = GetCurrentTimestamp();
  
  	if (now >= statement_fin_time)
! 		SendRecoveryConflictWithBufferPin();
  	else
  	{
  		/* Not time yet, so (re)schedule the interrupt */
--- 1700,1706 ----
  	now = GetCurrentTimestamp();
  
  	if (now >= statement_fin_time)
! 		SendRecoveryConflictWithBufferPin(action_at_standby_timeout);
  	else
  	{
  		/* Not time yet, so (re)schedule the interrupt */
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
***************
*** 2278,2283 **** errdetail_recovery_conflict(void)
--- 2278,2286 ----
  		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
  				errdetail("User query might have needed to see row versions that must be removed.");
  				break;
+ 		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+ 				errdetail("User transaction caused buffer deadlock with recovery.");
+ 				break;
  		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
  				errdetail("User was connected to a database that must be dropped.");
  				break;
***************
*** 2754,2759 **** RecoveryConflictInterrupt(ProcSignalReason reason)
--- 2757,2771 ----
  		RecoveryConflictReason = reason;
  		switch (reason)
  		{
+ 			case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+ 					/*
+ 					 * If we aren't waiting for a lock we can never deadlock.
+ 					 */
+ 					if (!IsWaitingForLock())
+ 						return;
+ 
+ 					/* Intentional drop through to check wait for pin */
+ 
  			case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
  					/*
  					 * If we aren't blocking the Startup process there is
***************
*** 2819,2824 **** RecoveryConflictInterrupt(ProcSignalReason reason)
--- 2831,2838 ----
  					elog(FATAL, "Unknown conflict mode");
  		}
  
+ 		Assert(RecoveryConflictPending && (QueryCancelPending || ProcDiePending));
+ 
  		/*
  		 * If it's safe to interrupt, and we're waiting for input or a lock,
  		 * service the interrupt immediately
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
***************
*** 1392,1398 **** static struct config_int ConfigureNamesInt[] =
  			NULL
  		},
  		&MaxStandbyDelay,
! 		30, 0, INT_MAX, NULL, NULL
  	},
  
  	{
--- 1392,1398 ----
  			NULL
  		},
  		&MaxStandbyDelay,
! 		30, -1, INT_MAX, NULL, NULL
  	},
  
  	{
*** a/src/include/storage/proc.h
--- b/src/include/storage/proc.h
***************
*** 189,194 **** extern void ProcQueueInit(PROC_QUEUE *queue);
--- 189,195 ----
  extern int	ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable);
  extern PGPROC *ProcWakeup(PGPROC *proc, int waitStatus);
  extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock);
+ extern bool IsWaitingForLock(void);
  extern void LockWaitCancel(void);
  
  extern void ProcWaitForSignal(void);
***************
*** 199,204 **** extern bool disable_sig_alarm(bool is_statement_timeout);
--- 200,206 ----
  extern void handle_sig_alarm(SIGNAL_ARGS);
  
  extern bool enable_standby_sig_alarm(long delay_s, int delay_us, TimestampTz fin_time);
+ extern bool enable_standby_deadlock_alarm(void);
  extern bool disable_standby_sig_alarm(void);
  extern void handle_standby_sig_alarm(SIGNAL_ARGS);
  
*** a/src/include/storage/procsignal.h
--- b/src/include/storage/procsignal.h
***************
*** 38,43 **** typedef enum
--- 38,44 ----
  	PROCSIG_RECOVERY_CONFLICT_LOCK,
  	PROCSIG_RECOVERY_CONFLICT_SNAPSHOT,
  	PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
+ 	PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
  
  	NUM_PROCSIGNALS				/* Must be last! */
  } ProcSignalReason;
*** a/src/include/storage/standby.h
--- b/src/include/storage/standby.h
***************
*** 16,21 ****
--- 16,22 ----
  
  #include "access/xlog.h"
  #include "storage/lock.h"
+ #include "storage/procsignal.h"
  #include "storage/relfilenode.h"
  
  extern int	vacuum_defer_cleanup_age;
***************
*** 30,36 **** extern void ResolveRecoveryConflictWithTablespace(Oid tsid);
  extern void ResolveRecoveryConflictWithDatabase(Oid dbid);
  
  extern void ResolveRecoveryConflictWithBufferPin(void);
! extern void SendRecoveryConflictWithBufferPin(void);
  extern void CheckRecoveryConflictDeadlock(LWLockId partitionLock);
  
  /*
--- 31,37 ----
  extern void ResolveRecoveryConflictWithDatabase(Oid dbid);
  
  extern void ResolveRecoveryConflictWithBufferPin(void);
! extern void SendRecoveryConflictWithBufferPin(ProcSignalReason reason);
  extern void CheckRecoveryConflictDeadlock(LWLockId partitionLock);
  
  /*