diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 490b121362f30f0f8d5100adcf436ebde6ff6837..266cd64f6fa5b627e41761be3bb957c7f699dc5b 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -674,12 +674,7 @@ ProcessRepliesIfAny(void)
 	int			r;
 	bool		received = false;
 
-	/*
-	 * If we already received a CopyDone from the frontend, any subsequent
-	 * message is the beginning of a new command, and should be processed in
-	 * the main processing loop.
-	 */
-	while (!streamingDoneReceiving)
+	for (;;)
 	{
 		r = pq_getbyte_if_available(&firstchar);
 		if (r < 0)
@@ -696,6 +691,19 @@ ProcessRepliesIfAny(void)
 			break;
 		}
 
+		/*
+		 * If we already received a CopyDone from the frontend, the frontend
+		 * should not send us anything until we've closed our end of the COPY.
+		 * XXX: In theory, the frontend could already send the next command
+		 * before receiving the CopyDone, but libpq doesn't currently allow
+		 * that.
+		 */
+		if (streamingDoneReceiving && firstchar != 'X')
+			ereport(FATAL,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("unexpected standby message type \"%c\", after receiving CopyDone",
+							firstchar)));
+
 		/* Handle the very limited subset of commands expected in this phase */
 		switch (firstchar)
 		{
@@ -1048,10 +1056,8 @@ WalSndLoop(void)
 			long		sleeptime = 10000;		/* 10 s */
 			int			wakeEvents;
 
-			wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_TIMEOUT;
-
-			if (!streamingDoneReceiving)
-				wakeEvents |= WL_SOCKET_READABLE;
+			wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_TIMEOUT |
+				WL_SOCKET_READABLE;
 
 			if (pq_is_send_pending())
 				wakeEvents |= WL_SOCKET_WRITEABLE;