diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 5785450..66b658e 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -1413,8 +1413,11 @@ echo -17 > /proc/self/oom_adj
       <para>
        This is the <firstterm>Smart Shutdown</firstterm> mode.
        After receiving <systemitem>SIGTERM</systemitem>, the server
-       disallows new connections, but lets existing sessions end their
-       work normally. It shuts down only after all of the sessions terminate.
+       disallows new connections and sends all existing server processes
+       <systemitem>SIGUSR2</systemitem>, which will cause them
+       to exit once any transaction currently in progress ends, or
+       at once if no transaction is currently in progress.
+       It shuts down only after all of the sessions terminate.
        If the server is in online backup mode, it additionally waits
        until online backup mode is no longer active.  While backup mode is
        active, new connections will still be allowed, but only to superusers
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index d59ee68..08768ba 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2126,6 +2126,8 @@ pmdie(SIGNAL_ARGS)
 			if (pmState == PM_RUN || pmState == PM_RECOVERY ||
 				pmState == PM_HOT_STANDBY || pmState == PM_STARTUP)
 			{
+				/* normal children are told to shut down at end of txn */
+				SignalSomeChildren(SIGUSR2, BACKEND_TYPE_NORMAL);
 				/* autovacuum workers are told to shut down immediately */
 				SignalSomeChildren(SIGTERM, BACKEND_TYPE_AUTOVAC);
 				/* and the autovac launcher too */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 51d623f..75ef141 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -2551,7 +2551,32 @@ quickdie(SIGNAL_ARGS)
 }
 
 /*
- * Shutdown signal from postmaster: abort transaction and exit
+ * Smart shutdown signal from postmaster: exit if or when no transaction is
+ * in progress
+ */
+static void
+smart_shutdown_handler(SIGNAL_ARGS)
+{
+	int			save_errno = errno;
+
+	/* Don't joggle the elbow of proc_exit */
+	if (!proc_exit_inprogress)
+	{
+		SmartShutdownPending = true;
+		if (IdleOutsideTransaction && SmartShutdownPending && DoingCommandRead)
+		{
+			IdleOutsideTransaction = false;
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+			 errmsg("terminating connection due to administrator command")));
+		}
+	}
+
+	errno = save_errno;
+}
+
+/*
+ * Fast shutdown signal from postmaster: abort transaction and exit
  * at soonest convenient time
  */
 void
@@ -3595,7 +3620,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 		 */
 		pqsignal(SIGPIPE, SIG_IGN);
 		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-		pqsignal(SIGUSR2, SIG_IGN);
+		pqsignal(SIGUSR2, smart_shutdown_handler);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 
 		/*
@@ -3883,6 +3908,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 
 				set_ps_display("idle", false);
 				pgstat_report_activity(STATE_IDLE, NULL);
+				IdleOutsideTransaction = true;
 			}
 
 			ReadyForQuery(whereToSendOutput);
@@ -3898,17 +3924,35 @@ PostgresMain(int argc, char *argv[], const char *username)
 		DoingCommandRead = true;
 
 		/*
-		 * (3) read a command (loop blocks here)
+		 * (3) If we're idle and not in a transaction, and a smart shutdown
+		 * has been requested, exit gracefully.
+		 */
+		if (IdleOutsideTransaction && SmartShutdownPending)
+		{
+			DoingCommandRead = false;
+			IdleOutsideTransaction = false;
+			ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+			 errmsg("terminating connection due to administrator command")));
+		}
+
+		/*
+		 * (4) read a command (loop blocks here)
 		 */
 		firstchar = ReadCommand(&input_message);
 
 		/*
-		 * (4) disable async signal conditions again.
+		 * (5) got a command, so no longer idle
+		 */
+		IdleOutsideTransaction = false;
+
+		/*
+		 * (6) disable async signal conditions again.
 		 */
 		DoingCommandRead = false;
 
 		/*
-		 * (5) check for any other interesting events that happened while we
+		 * (7) check for any other interesting events that happened while we
 		 * slept.
 		 */
 		if (got_SIGHUP)
@@ -3918,7 +3962,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 		}
 
 		/*
-		 * (6) process the command.  But ignore it if we're skipping till
+		 * (8) process the command.  But ignore it if we're skipping till
 		 * Sync.
 		 */
 		if (ignore_till_sync && firstchar != EOF)
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 4b66bd3..c48eb80 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -26,11 +26,13 @@
 
 ProtocolVersion FrontendProtocol;
 
+volatile bool SmartShutdownPending = false;
 volatile bool InterruptPending = false;
 volatile bool QueryCancelPending = false;
 volatile bool ProcDiePending = false;
 volatile bool ClientConnectionLost = false;
 volatile bool ImmediateInterruptOK = false;
+volatile bool IdleOutsideTransaction = false;
 volatile uint32 InterruptHoldoffCount = 0;
 volatile uint32 CritSectionCount = 0;
 
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index b186eed..203c2b7 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -72,6 +72,7 @@
 /* in globals.c */
 /* these are marked volatile because they are set by signal handlers: */
 extern PGDLLIMPORT volatile bool InterruptPending;
+extern volatile bool SmartShutdownPending;
 extern volatile bool QueryCancelPending;
 extern volatile bool ProcDiePending;
 
@@ -79,6 +80,7 @@ extern volatile bool ClientConnectionLost;
 
 /* these are marked volatile because they are examined by signal handlers: */
 extern volatile bool ImmediateInterruptOK;
+extern volatile bool IdleOutsideTransaction;
 extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
 extern PGDLLIMPORT volatile uint32 CritSectionCount;
 
