diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index a5ee209f91..2f826bc9b5 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -68,6 +68,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);
@@ -661,6 +662,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		 */
 		CreateAuxProcessResourceOwner();
 
+		on_shmem_exit(ReleaseLWLocks, 0);
 		StartupXLOG();
 		/* Release (and warn about) any buffer pins leaked in StartupXLOG */
 		ReleaseAuxProcessResources(true);
@@ -1171,6 +1173,23 @@ process_settings(Oid databaseid, Oid roleid)
 	table_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
