Hi,

Currently, only one PG_WAIT_EXTENSION event can be used as a
wait event for extensions. Therefore, in environments with multiple
extensions are installed, it could take time to identify which
extension is the bottleneck.

So, I'd like to support new APIs to define custom wait events
for extensions. It's discussed in [1].

I made patches to realize it. Although I have some TODOs,
I want to know your feedbacks. Please feel free to comment.


# Implementation of new APIs

I implemented 2 new APIs for extensions.
* RequestNamedExtensionWaitEventTranche()
* GetNamedExtensionWaitEventTranche()

Extensions can request custom wait events by calling
RequestNamedExtensionWaitEventTranche(). After that, use
GetNamedExtensionWaitEventTranche() to get the wait event information.

The APIs usage example by extensions are following.

```
shmem_request_hook = shmem_request;
shmem_startup_hook = shmem_startup;

static void
shmem_request(void)
{
        /* request a custom wait event */
        RequestNamedExtensionWaitEventTranche("custom_wait_event");
}

static void
shmem_startup(void)
{
        /* get the wait event information */
custom_wait_event = GetNamedExtensionWaitEventTranche("custom_wait_event");
}

void
extension_funtion()
{
        (void) WaitLatch(MyLatch,
                                         WL_LATCH_SET | WL_TIMEOUT | 
WL_EXIT_ON_PM_DEATH,
                                         10L * 1000,
                                         custom_wait_event);      /* notify 
core with custom wait event */
        ResetLatch(MyLatch);
}
```


# Implementation overview

I referenced the implementation of
RequestNamedLWLockTranche()/GetNamedLWLockTranche().
(0001-Support-to-define-custom-wait-events-for-extensions.patch)

Extensions calls RequestNamedExtensionWaitEventTranche() in
shmem_request_hook() to request wait events to be used by each extension.

In the core, the requested wait events are dynamically registered in shared
memory. The extension then obtains the wait event information with
GetNamedExtensionWaitEventTranche() and uses the value to notify the core
that it is waiting.

When a string representing of the wait event is requested,
it returns the name defined by calling RequestNamedExtensionWaitEventTranche().


# PoC extension

I created the PoC extension and you can use it, as shown here:
(0002-Add-a-extension-to-test-custom-wait-event.patch)

1. start PostgreSQL with the following configuration
shared_preload_libraries = 'inject_wait_event'

2. check wait events periodically
psql-1=# SELECT query, wait_event_type, wait_event FROM pg_stat_activity WHERE backend_type = 'client backend' AND pid != pg_backend_pid() ;
psql-1=# \watch

3. execute a function to inject a wait event
psql-2=# CREATE EXTENSION inject_wait_event;
psql-2=# SELECT inject_wait_event();

4. check the custom wait event
You can see the following results of psql-1.

(..snip..)

            query            | wait_event_type | wait_event
-----------------------------+-----------------+------------
 SELECT inject_wait_event(); | Extension       | Extension
(1 row)

(..snip..)

(...about 10 seconds later ..)


            query            | wait_event_type |    wait_event
-----------------------------+-----------------+-------------------
SELECT inject_wait_event(); | Extension | custom_wait_event # requested wait event by the extension!
(1 row)

(..snip..)


# TODOs

* tests on windows (since I tested on Ubuntu 20.04 only)
* add custom wait events for existing contrib modules (ex. postgres_fdw)
* add regression code (but, it seems to be difficult)
* others? (Please let me know)


[1] https://www.postgresql.org/message-id/81290a48-b25c-22a5-31a6-3feff5864fe3%40gmail.com

Regards,

--
Masahiro Ikeda
NTT DATA CORPORATION
From 59a118402e5e59685fb9e0fb086872e25a405736 Mon Sep 17 00:00:00 2001
From: Masahiro Ikeda <masahiro.ikeda...@hco.ntt.co.jp>
Date: Thu, 15 Jun 2023 12:57:29 +0900
Subject: [PATCH 2/3] Support to define custom wait events for extensions.

Currently, only one PG_WAIT_EXTENSION event can be used as a
wait event for extensions. Therefore, in environments with multiple
extensions are installed, it could take time to identify bottlenecks.

This commit allows defining custom wait events for extensions and
introduces a new API called RequestNamedExtensionWaitEventTranche()/
GetNamedExtensionWaitEventTranche().

These refer to RequestNamedLWLockTranche()/GetNamedLWLockTranche(),
but do not require as much flexibility as LWLock and can be implemented
more simply.

The extension calls RequestNamedExtensionWaitEventTranche() in
shmem_request_hook() to request wait events to be used by each extension.
In the core, the requested wait events are dynamically registered in shared
memory. The extension then obtains the wait event information with
GetNamedExtensionWaitEventTranche() and uses the value to notify the core
that it is waiting.
---
 src/backend/postmaster/postmaster.c     |   6 +
 src/backend/storage/ipc/ipci.c          |   3 +
 src/backend/storage/ipc/shmem.c         |   3 +-
 src/backend/utils/activity/wait_event.c | 303 +++++++++++++++++++++++-
 src/include/utils/wait_event.h          |  32 +++
 5 files changed, 345 insertions(+), 2 deletions(-)

diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 4c49393fc5..50afa3aa14 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -516,6 +516,8 @@ typedef struct
 	int			NamedLWLockTrancheRequests;
 	NamedLWLockTranche *NamedLWLockTrancheArray;
 	LWLockPadded *MainLWLockArray;
+	int			NamedExtensionWaitEventTrancheRequests;
+	NamedExtensionWaitEventTranche *NamedExtensionWaitEventTrancheArray;
 	slock_t    *ProcStructLock;
 	PROC_HDR   *ProcGlobal;
 	PGPROC	   *AuxiliaryProcs;
@@ -6087,6 +6089,8 @@ save_backend_variables(BackendParameters *param, Port *port,
 	param->NamedLWLockTrancheRequests = NamedLWLockTrancheRequests;
 	param->NamedLWLockTrancheArray = NamedLWLockTrancheArray;
 	param->MainLWLockArray = MainLWLockArray;
+	param->NamedExtensionWaitEventTrancheRequests = NamedExtensionWaitEventTrancheRequests;
+	param->NamedExtensionWaitEventTrancheArray = NamedExtensionWaitEventTrancheArray;
 	param->ProcStructLock = ProcStructLock;
 	param->ProcGlobal = ProcGlobal;
 	param->AuxiliaryProcs = AuxiliaryProcs;
@@ -6320,6 +6324,8 @@ restore_backend_variables(BackendParameters *param, Port *port)
 	NamedLWLockTrancheRequests = param->NamedLWLockTrancheRequests;
 	NamedLWLockTrancheArray = param->NamedLWLockTrancheArray;
 	MainLWLockArray = param->MainLWLockArray;
+	NamedExtensionWaitEventTrancheRequests = param->NamedExtensionWaitEventTrancheRequests;
+	NamedExtensionWaitEventTrancheArray = param->NamedExtensionWaitEventTrancheArray;
 	ProcStructLock = param->ProcStructLock;
 	ProcGlobal = param->ProcGlobal;
 	AuxiliaryProcs = param->AuxiliaryProcs;
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 8f1ded7338..ed05121fa3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -49,6 +49,7 @@
 #include "storage/spin.h"
 #include "utils/guc.h"
 #include "utils/snapmgr.h"
+#include "utils/wait_event.h"
 
 /* GUCs */
 int			shared_memory_type = DEFAULT_SHARED_MEMORY_TYPE;
@@ -142,6 +143,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, SyncScanShmemSize());
 	size = add_size(size, AsyncShmemSize());
 	size = add_size(size, StatsShmemSize());
+	size = add_size(size, WaitEventShmemSize());
 #ifdef EXEC_BACKEND
 	size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -294,6 +296,7 @@ CreateSharedMemoryAndSemaphores(void)
 	SyncScanShmemInit();
 	AsyncShmemInit();
 	StatsShmemInit();
+	WaitEventShmemInit();
 
 #ifdef EXEC_BACKEND
 
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 5465fa1964..02c72ebbb1 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -85,7 +85,8 @@ static void *ShmemBase;			/* start address of shared memory */
 
 static void *ShmemEnd;			/* end+1 address of shared memory */
 
-slock_t    *ShmemLock;			/* spinlock for shared memory and LWLock
+slock_t    *ShmemLock;			/* spinlock for shared memory, LWLock
+								 * allocation, and named extension wait event
 								 * allocation */
 
 static HTAB *ShmemIndex = NULL; /* primary index hashtable for shmem */
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index 7940d64639..4bf725fbae 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -22,11 +22,18 @@
  */
 #include "postgres.h"
 
+#include "miscadmin.h"
+#include "port/pg_bitutils.h"
 #include "storage/lmgr.h"		/* for GetLockNameFromTagType */
 #include "storage/lwlock.h"		/* for GetLWLockIdentifier */
+#include "storage/spin.h"
+#include "utils/memutils.h"
 #include "utils/wait_event.h"
 
 
+/* We use the ShmemLock spinlock to protect ExtensionWaitEventCounter */
+extern slock_t *ShmemLock;
+
 static const char *pgstat_get_wait_activity(WaitEventActivity w);
 static const char *pgstat_get_wait_client(WaitEventClient w);
 static const char *pgstat_get_wait_ipc(WaitEventIPC w);
@@ -38,6 +45,46 @@ static uint32 local_my_wait_event_info;
 uint32	   *my_wait_event_info = &local_my_wait_event_info;
 
 
+/* struct representing the request for named tranche of extension wait event */
+typedef struct NamedExtensionWaitEventTrancheRequest
+{
+	char		tranche_name[NAMEDATALEN];
+}			NamedExtensionWaitEventTrancheRequest;
+
+static NamedExtensionWaitEventTrancheRequest * NamedExtensionWaitEventTrancheRequestArray = NULL;
+static int	NamedExtensionWaitEventTrancheRequestsAllocated = 0;
+
+/*
+ * NamedExtensionWaitEventTrancheRequests is both the valid length of the request array,
+ * and the length of the shared-memory NamedExtensionWaitEventTrancheArray later on.
+ * This variable and NamedExtensionWaitEventTrancheArray are non-static so that
+ * postmaster.c can copy them to child processes in EXEC_BACKEND builds.
+ */
+int			NamedExtensionWaitEventTrancheRequests = 0;
+
+/* points to data in shared memory */
+NamedExtensionWaitEventTranche *NamedExtensionWaitEventTrancheArray = NULL;
+
+static void InitializeExtensionWaitEventTranches(void);
+static void ExtensionWaitEventRegisterTranche(int tranche_id, const char *tranche_name);
+static int	ExtensionWaitEventNewTrancheId(void);
+
+/* first tranche ID for named tranche */
+#define NUM_BUILDIN_WAIT_EVENT_EXTENSION	\
+	(WAIT_EVENT_EXTENSION_FIRST_USER_DEFINED - WAIT_EVENT_EXTENSION)
+
+/*
+ * This is indexed by tranche ID minus NUM_BUILDIN_WAIT_EVENT_EXTENSION, and
+ * stores the names of all dynamically-created tranches known to the current
+ * process.  Any unused entries in the array will contain NULL.
+ */
+static const char **ExtensionWaitEventTrancheNames = NULL;
+static int	ExtensionWaitEventTrancheNamesAllocated = 0;
+
+static const char *GetExtensionIdentifier(uint32 classId, uint16 eventId);
+static const char *GetExtensionTrancheName(uint16 trancheId);
+
+
 /*
  * Configure wait event reporting to report wait events to *wait_event_info.
  * *wait_event_info needs to be valid until pgstat_reset_wait_event_storage()
@@ -165,7 +212,7 @@ pgstat_get_wait_event(uint32 wait_event_info)
 				break;
 			}
 		case PG_WAIT_EXTENSION:
-			event_name = "Extension";
+			event_name = GetExtensionIdentifier(classId, eventId);
 			break;
 		case PG_WAIT_IPC:
 			{
@@ -762,3 +809,257 @@ pgstat_get_wait_io(WaitEventIO w)
 
 	return event_name;
 }
+
+/*
+ * RequestNamedExtensionWaitEventTranche
+ *		Request that extra wait events for extensions be allocated
+ *      during postmaster startup.
+ *
+ * This may only be called via the shmem_request_hook of a library that is
+ * loaded into the postmaster via shared_preload_libraries.  Calls from
+ * elsewhere will fail.
+ *
+ * The tranche name will be user-visible as a wait event name, so try to
+ * use a name that fits the style for those.
+ */
+void
+RequestNamedExtensionWaitEventTranche(const char *tranche_name)
+{
+	NamedExtensionWaitEventTrancheRequest *request;
+
+	if (!process_shmem_requests_in_progress)
+		elog(FATAL, "cannot request additional wait events outside shmem_request_hook");
+
+	if (NamedExtensionWaitEventTrancheRequestArray == NULL)
+	{
+		NamedExtensionWaitEventTrancheRequestsAllocated = 16;
+		NamedExtensionWaitEventTrancheRequestArray = (NamedExtensionWaitEventTrancheRequest *)
+			MemoryContextAlloc(TopMemoryContext,
+							   NamedExtensionWaitEventTrancheRequestsAllocated
+							   * sizeof(NamedExtensionWaitEventTrancheRequest));
+	}
+
+	if (NamedExtensionWaitEventTrancheRequests >= NamedExtensionWaitEventTrancheRequestsAllocated)
+	{
+		int			i = pg_nextpower2_32(NamedExtensionWaitEventTrancheRequests + 1);
+
+		NamedExtensionWaitEventTrancheRequestArray = (NamedExtensionWaitEventTrancheRequest *)
+			repalloc(NamedExtensionWaitEventTrancheRequestArray,
+					 i * sizeof(NamedExtensionWaitEventTrancheRequest));
+		NamedExtensionWaitEventTrancheRequestsAllocated = i;
+	}
+
+	request = &NamedExtensionWaitEventTrancheRequestArray[NamedExtensionWaitEventTrancheRequests];
+	Assert(strlen(tranche_name) + 1 <= NAMEDATALEN);
+	strlcpy(request->tranche_name, tranche_name, NAMEDATALEN);
+	NamedExtensionWaitEventTrancheRequests++;
+}
+
+/*
+ * Compute shmem space needed for named wait event tranches.
+ */
+Size
+WaitEventShmemSize(void)
+{
+	Size		size;
+	int			i;
+
+	/* space for named tranches. */
+	size = mul_size(NamedExtensionWaitEventTrancheRequests, sizeof(NamedExtensionWaitEventTranche));
+
+	/* space for name of each tranche. */
+	for (i = 0; i < NamedExtensionWaitEventTrancheRequests; i++)
+		size = add_size(size, strlen(NamedExtensionWaitEventTrancheRequestArray[i].tranche_name) + 1);
+
+	return size;
+}
+
+/*
+ * Allocate shmem space for named wait event tranches and initialize it.
+ * We also register named wait event tranches here.
+ */
+void
+WaitEventShmemInit(void)
+{
+	if (!IsUnderPostmaster)
+	{
+		Size		spaceWaitEvent = WaitEventShmemSize();
+		int		   *ExtensionWaitEventCounter;
+		char	   *ptr;
+
+		/* Allocate space */
+		ptr = (char *) ShmemAlloc(spaceWaitEvent);
+
+		/* Leave room for dynamic allocation of tranches */
+		ptr += sizeof(int);
+
+		NamedExtensionWaitEventTrancheArray = (NamedExtensionWaitEventTranche *) ptr;
+
+		/*
+		 * Initialize the dynamic-allocation counter for tranches, which is
+		 * stored just before the first wait event.
+		 */
+		ExtensionWaitEventCounter = (int *) ((char *) NamedExtensionWaitEventTrancheArray - sizeof(int));
+		*ExtensionWaitEventCounter = NUM_BUILDIN_WAIT_EVENT_EXTENSION;
+
+		/* Initialize requested named wait event tranches */
+		InitializeExtensionWaitEventTranches();
+	}
+
+	/* Register named wait event tranches in the current process. */
+	for (int i = 0; i < NamedExtensionWaitEventTrancheRequests; i++)
+		ExtensionWaitEventRegisterTranche(NamedExtensionWaitEventTrancheArray[i].trancheId,
+										  NamedExtensionWaitEventTrancheArray[i].trancheName);
+}
+
+/*
+ * Initialize requested named wait event tranches.
+ */
+static void
+InitializeExtensionWaitEventTranches(void)
+{
+	/*
+	 * Copy the info about any named tranches into shared memory (so that
+	 * other processes can see it), and initialize the requested wait events.
+	 */
+	if (NamedExtensionWaitEventTrancheRequests > 0)
+	{
+		char	   *trancheNames;
+		int			i;
+
+		trancheNames = (char *) NamedExtensionWaitEventTrancheArray +
+			(NamedExtensionWaitEventTrancheRequests * sizeof(NamedExtensionWaitEventTranche));
+
+		for (i = 0; i < NamedExtensionWaitEventTrancheRequests; i++)
+		{
+			NamedExtensionWaitEventTrancheRequest *request;
+			NamedExtensionWaitEventTranche *tranche;
+			char	   *name;
+
+			request = &NamedExtensionWaitEventTrancheRequestArray[i];
+			tranche = &NamedExtensionWaitEventTrancheArray[i];
+
+			name = trancheNames;
+			trancheNames += strlen(request->tranche_name) + 1;
+			strcpy(name, request->tranche_name);
+			tranche->trancheId = ExtensionWaitEventNewTrancheId();
+			tranche->trancheName = name;
+		}
+	}
+}
+
+/*
+ * Allocate a new tranche ID.
+ */
+int
+ExtensionWaitEventNewTrancheId(void)
+{
+	int			result;
+	int		   *ExtensionWaitEventCounter;
+
+	ExtensionWaitEventCounter = (int *) ((char *) NamedExtensionWaitEventTrancheArray - sizeof(int));
+	SpinLockAcquire(ShmemLock);
+	result = (*ExtensionWaitEventCounter)++;
+	SpinLockRelease(ShmemLock);
+
+	return result;
+}
+
+void
+ExtensionWaitEventRegisterTranche(int tranche_id, const char *tranche_name)
+{
+	/* This should only be called for user-defined tranches. */
+	if (tranche_id < NUM_BUILDIN_WAIT_EVENT_EXTENSION)
+		return;
+
+	/* Convert to array index. */
+	tranche_id -= NUM_BUILDIN_WAIT_EVENT_EXTENSION;
+
+	/* If necessary, create or enlarge array. */
+	if (tranche_id >= ExtensionWaitEventTrancheNamesAllocated)
+	{
+		int			newalloc;
+
+		newalloc = pg_nextpower2_32(Max(8, tranche_id + 1));
+
+		if (ExtensionWaitEventTrancheNames == NULL)
+			ExtensionWaitEventTrancheNames = (const char **)
+				MemoryContextAllocZero(TopMemoryContext,
+									   newalloc * sizeof(char *));
+		else
+			ExtensionWaitEventTrancheNames =
+				repalloc0_array(ExtensionWaitEventTrancheNames, const char *, ExtensionWaitEventTrancheNamesAllocated, newalloc);
+		ExtensionWaitEventTrancheNamesAllocated = newalloc;
+	}
+
+	ExtensionWaitEventTrancheNames[tranche_id] = tranche_name;
+}
+
+/*
+ * GetNamedExtensionWaitEventTranche - returns the Extension wait event information
+ *      from the specified tranche.
+ *
+ * Caller needs to retrieve the requested number of Extensions starting from
+ * the base extension address returned by this API.  This can be used for
+ * tranches that are requested by using RequestNamedExtensionWaitEventTranche() API.
+ */
+uint32
+GetNamedExtensionWaitEventTranche(const char *tranche_name)
+{
+	uint32		wait_event_info;
+	int			pos;
+	int			i;
+
+	/* The wait event type is always PG_WAIT_EXTENSION. */
+	wait_event_info = PG_WAIT_EXTENSION;
+
+	/*
+	 * Obtain the name of Extension wait event belonging to requested
+	 * tranche_name in NamedExtensionWaitEventTrancheArray.
+	 */
+	pos = 0;
+	for (i = 0; i < NamedExtensionWaitEventTrancheRequests; i++)
+	{
+		if (strcmp(NamedExtensionWaitEventTrancheRequestArray[i].tranche_name,
+				   tranche_name) == 0)
+		{
+			wait_event_info |= NamedExtensionWaitEventTrancheArray[pos].trancheId;
+			return wait_event_info;
+		}
+
+		pos++;
+	}
+
+	elog(ERROR, "requested tranche is not registered");
+
+	/* just to keep compiler quiet */
+	return wait_event_info;
+}
+
+/*
+ * Return an identifier based on the Extension wait event.
+ */
+static const char *
+GetExtensionIdentifier(uint32 classId, uint16 eventId)
+{
+	Assert(classId == PG_WAIT_EXTENSION);
+	/* The event IDs are just tranche numbers. */
+	return GetExtensionTrancheName(eventId);
+}
+
+/*
+ * Return the name of an Extension tranche.
+ */
+static const char *
+GetExtensionTrancheName(uint16 trancheId)
+{
+	/* Build-in tranche? */
+	if (trancheId < NUM_BUILDIN_WAIT_EVENT_EXTENSION)
+		return "Extension";
+
+	/* It's an extension tranche, so look in ExtensionWaitEventTrancheNames[]. */
+	trancheId -= NUM_BUILDIN_WAIT_EVENT_EXTENSION;
+	Assert(trancheId < ExtensionWaitEventTrancheNamesAllocated);
+
+	return ExtensionWaitEventTrancheNames[trancheId];
+}
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 518d3b0a1f..fa42671bd5 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -70,6 +70,38 @@ typedef enum
 	WAIT_EVENT_WAL_SENDER_WRITE_DATA,
 } WaitEventClient;
 
+/* ----------
+ * Wait Events - Extension
+ *
+ * Use this category when the server process is waiting for some condition
+ * defined by an extension module.
+ *
+ * Extensions can define custom wait events by calling RequestNamedExtensionWaitEventTranche()
+ * during postmaster startup.  Subsequently, call GetNamedExtensionWaitEventTranche() to
+ * obtain the wait event information requested.
+ * ----------
+ */
+typedef enum
+{
+	WAIT_EVENT_EXTENSION = PG_WAIT_EXTENSION,
+	WAIT_EVENT_EXTENSION_FIRST_USER_DEFINED
+}			WaitEventExtension;
+
+extern void WaitEventShmemInit(void);
+extern Size WaitEventShmemSize(void);
+extern void RequestNamedExtensionWaitEventTranche(const char *tranche_name);
+extern uint32 GetNamedExtensionWaitEventTranche(const char *tranche_name);
+
+/* struct for storing named tranche information */
+typedef struct NamedExtensionWaitEventTranche
+{
+	uint16		trancheId;
+	char	   *trancheName;
+}			NamedExtensionWaitEventTranche;
+
+extern PGDLLIMPORT NamedExtensionWaitEventTranche * NamedExtensionWaitEventTrancheArray;
+extern PGDLLIMPORT int NamedExtensionWaitEventTrancheRequests;
+
 /* ----------
  * Wait Events - IPC
  *
-- 
2.25.1

From 65e25d4b27bbb6d0934872310c24ee19f89e9631 Mon Sep 17 00:00:00 2001
From: Masahiro Ikeda <masahiro.ikeda...@hco.ntt.co.jp>
Date: Thu, 15 Jun 2023 13:16:00 +0900
Subject: [PATCH 3/3] Add docs to define custom wait events

---
 doc/src/sgml/monitoring.sgml |  7 +++++++
 doc/src/sgml/xfunc.sgml      | 22 +++++++++++++++++++---
 2 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 5cfdc70c03..aefdc03dbd 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1257,6 +1257,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    </tgroup>
   </table>
 
+  <note>
+   <para>
+    Extensions can add <literal>Extension</literal> types to the list shown in
+    <xref linkend="wait-event-extension-table"/>.
+   </para>
+  </note>
+
   <table id="wait-event-io-table">
    <title>Wait Events of Type <literal>IO</literal></title>
    <tgroup cols="2">
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 9620ea9ae3..162d50e273 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3397,16 +3397,16 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
    </sect2>
 
    <sect2 id="xfunc-shared-addin">
-    <title>Shared Memory and LWLocks</title>
+    <title>Shared Memory allocated by addins</title>
 
     <para>
-     Add-ins can reserve LWLocks and an allocation of shared memory on server
+     Add-ins can reserve LWLocks, wait events, and an allocation of shared memory on server
      startup.  The add-in's shared library must be preloaded by specifying
      it in
      <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
      The shared library should register a <literal>shmem_request_hook</literal>
      in its <function>_PG_init</function> function.  This
-     <literal>shmem_request_hook</literal> can reserve LWLocks or shared memory.
+     <literal>shmem_request_hook</literal> can reserve LWLocks, wait events, or shared memory.
      Shared memory is reserved by calling:
 <programlisting>
 void RequestAddinShmemSpace(int size)
@@ -3451,6 +3451,22 @@ if (!ptr)
 }
 </programlisting>
     </para>
+    <para>
+     wait events are reserved by calling:
+<programlisting>
+void RequestNamedExtensionWaitEventTranche(const char *tranche_name)
+</programlisting>
+     from your <literal>shmem_request_hook</literal>.  This will ensure that 
+     wait event is available under the name <literal>tranche_name</literal>,
+     which the wait event type is <literal>Extension</literal>.
+     Use <function>GetNamedExtensionWaitEventTranche</function>
+     to get a wait event information.
+    </para>
+    <para>
+     To avoid possible race-conditions, each backend should use the LWLock
+     <function>AddinShmemInitLock</function> when connecting to and initializing
+     its allocation of shared memory, same as LWLocks reservations above.
+    </para>
    </sect2>
 
    <sect2 id="extend-cpp">
-- 
2.25.1

From 97b1bc1ac379038701d1b57f8f64149b13fc351b Mon Sep 17 00:00:00 2001
From: Masahiro Ikeda <masahiro.ikeda...@hco.ntt.co.jp>
Date: Thu, 15 Jun 2023 11:24:01 +0900
Subject: [PATCH 1/3] Add a extension to test custom wait event

Since the extension is used for only development, it's not intended
to be commited in master branch.
---
 contrib/inject_wait_event/.gitignore          |   1 +
 contrib/inject_wait_event/Makefile            |  24 ++++
 .../inject_wait_event--1.0.sql                |  12 ++
 contrib/inject_wait_event/inject_wait_event.c | 119 ++++++++++++++++++
 .../inject_wait_event.control                 |   4 +
 5 files changed, 160 insertions(+)
 create mode 100644 contrib/inject_wait_event/.gitignore
 create mode 100644 contrib/inject_wait_event/Makefile
 create mode 100644 contrib/inject_wait_event/inject_wait_event--1.0.sql
 create mode 100644 contrib/inject_wait_event/inject_wait_event.c
 create mode 100644 contrib/inject_wait_event/inject_wait_event.control

diff --git a/contrib/inject_wait_event/.gitignore b/contrib/inject_wait_event/.gitignore
new file mode 100644
index 0000000000..43cea6480a
--- /dev/null
+++ b/contrib/inject_wait_event/.gitignore
@@ -0,0 +1 @@
+inject_wait_event
\ No newline at end of file
diff --git a/contrib/inject_wait_event/Makefile b/contrib/inject_wait_event/Makefile
new file mode 100644
index 0000000000..40221e7ac2
--- /dev/null
+++ b/contrib/inject_wait_event/Makefile
@@ -0,0 +1,24 @@
+# contrib/inject_wait_event/Makefile
+
+MODULE_big	= inject_wait_event
+
+OBJS = \
+	$(WIN32RES) \
+	inject_wait_event.o
+
+EXTENSION = inject_wait_event
+DATA = inject_wait_event--1.0.sql
+PGFILEDESC = "inject wait event for test"
+
+TAP_TESTS = 0
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/inject_wait_event
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/inject_wait_event/inject_wait_event--1.0.sql b/contrib/inject_wait_event/inject_wait_event--1.0.sql
new file mode 100644
index 0000000000..8ebab415c3
--- /dev/null
+++ b/contrib/inject_wait_event/inject_wait_event--1.0.sql
@@ -0,0 +1,12 @@
+/* contrib/inject_wait_event/inject_wait_event--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION inject_wait_event" to load this file. \quit
+
+--
+-- inject_wait_event()
+--
+CREATE FUNCTION inject_wait_event()
+RETURNS VOID
+AS 'MODULE_PATHNAME', 'inject_wait_event'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/inject_wait_event/inject_wait_event.c b/contrib/inject_wait_event/inject_wait_event.c
new file mode 100644
index 0000000000..b9673808d3
--- /dev/null
+++ b/contrib/inject_wait_event/inject_wait_event.c
@@ -0,0 +1,119 @@
+/*-------------------------------------------------------------------------
+ *
+ * inject_wait_event.c
+ *     Support a function to inject wait events for test
+ *
+ * Since the extension assumes to be used for only development,
+ * don't commit the master branch.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/latch.h"
+#include "storage/shmem.h"
+#include "utils/wait_event.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(inject_wait_event);
+
+typedef struct state
+{
+	uint32		custom_wait_event;	/* custom a wait event */
+}			state;
+
+static shmem_request_hook_type prev_shmem_request_hook = NULL;
+static shmem_request_hook_type prev_shmem_startup_hook = NULL;
+
+static state * ss = NULL;
+
+static void shmem_request(void);
+static void shmem_startup(void);
+static void shmem_startup(void);
+static Size memsize(void);
+
+void
+_PG_init(void)
+{
+	if (!process_shared_preload_libraries_in_progress)
+		return;
+
+	prev_shmem_request_hook = shmem_request_hook;
+	shmem_request_hook = shmem_request;
+	prev_shmem_startup_hook = shmem_startup_hook;
+	shmem_startup_hook = shmem_startup;
+}
+
+static void
+shmem_request(void)
+{
+	if (prev_shmem_request_hook)
+		prev_shmem_request_hook();
+
+	RequestAddinShmemSpace(memsize());
+
+	/* define a custom wait event */
+	RequestNamedExtensionWaitEventTranche("custom_wait_event");
+}
+
+static void
+shmem_startup(void)
+{
+	bool		found;
+
+	if (prev_shmem_startup_hook)
+		prev_shmem_startup_hook();
+
+	ss = NULL;
+
+	/*
+	 * Create or attach to the shared memory state
+	 */
+	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+	ss = ShmemInitStruct("inject_wait_event",
+						 sizeof(state),
+						 &found);
+
+	if (!found)
+	{
+		ss->custom_wait_event = GetNamedExtensionWaitEventTranche("custom_wait_event");
+	}
+	LWLockRelease(AddinShmemInitLock);
+
+	return;
+}
+
+static Size
+memsize(void)
+{
+	Size		size;
+
+	size = MAXALIGN(sizeof(state));
+
+	return size;
+}
+
+Datum
+inject_wait_event(PG_FUNCTION_ARGS)
+{
+	/* default */
+	(void) WaitLatch(MyLatch,
+					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					 10L * 1000,	/* 10s */
+					 PG_WAIT_EXTENSION);
+	ResetLatch(MyLatch);
+
+	/* custom wait event */
+	(void) WaitLatch(MyLatch,
+					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					 10L * 1000,	/* 10s */
+					 ss->custom_wait_event);
+	ResetLatch(MyLatch);
+
+	PG_RETURN_VOID();
+}
diff --git a/contrib/inject_wait_event/inject_wait_event.control b/contrib/inject_wait_event/inject_wait_event.control
new file mode 100644
index 0000000000..3f1132185e
--- /dev/null
+++ b/contrib/inject_wait_event/inject_wait_event.control
@@ -0,0 +1,4 @@
+# inject_wait_event extension
+comment = 'a function for injecting wait event for test'
+default_version = '1.0'
+module_pathname = '$libdir/inject_wait_event'
-- 
2.25.1

Reply via email to