diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 62baaf0ab3..d74e8aa1d5 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -71,6 +71,7 @@ static HeapTuple GetDatabaseTupleByOid(Oid dboid);
 static void PerformAuthentication(Port *port);
 static void CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connections);
 static void InitCommunication(void);
+static void ReleaseLWLocks(int code, Datum arg);
 static void ShutdownPostgres(int code, Datum arg);
 static void StatementTimeoutHandler(void);
 static void LockTimeoutHandler(void);
@@ -653,6 +654,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		 * way, start up the XLOG machinery, and register to have it closed
 		 * down at exit.
 		 */
+		on_shmem_exit(ReleaseLWLocks, 0);
 		StartupXLOG();
 		on_shmem_exit(ShutdownXLOG, 0);
 	}
@@ -1214,6 +1216,23 @@ process_settings(Oid databaseid, Oid roleid)
 	heap_close(relsetting, AccessShareLock);
 }
 
+/*
+ * There are 2 types of buffer locks on-holding when AtProcExit_Buffers() is
+ * invoked in a bootstrap process or a standalone backend:
+ *  (1) Exceptions thrown during StartupXLOG()
+ *  (2) Exceptions thrown during exception-handling in ShutdownXLOG()
+ * So we need this on_shmem_exit callback for single user mode.
+ * For processes under postmaster, ShutdownAuxiliaryProcess() will release
+ * the lw-locks and ShutdownXLOG() is not registered as a callback, so there
+ * is no such issue. Also, please note this callback should be registered in
+ * the order after AtProcExit_buffers() and before ShutdownXLOG().
+ */
+static void
+ReleaseLWLocks(int code, Datum arg)
+{
+	LWLockReleaseAll();
+}
+
 /*
  * Backend-shutdown callback.  Do cleanup that we want to be sure happens
  * before all the supporting modules begin to nail their doors shut via
