Skip to content

Commit af720b4

Browse files
committed
Change custom wait events to use dynamic shared hash tables
Currently, the names of the custom wait event must be registered for each backend, requiring all these to link to the shared memory area of an extension, even if these are not loaded with shared_preload_libraries. This patch relaxes the constraints related to this infrastructure by storing the wait events and their names in two dynamic hash tables in shared memory. This has the advantage to simplify the registration of custom wait events to a single routine call that returns an event ID ready for consumption: uint32 WaitEventExtensionNew(const char *wait_event_name); The caller of this routine can then cache locally the ID returned, to be used for pgstat_report_wait_start(), WaitLatch() or a similar routine. The implementation uses two hash tables: one with a key based on the event name to avoid duplicates and a second using the event ID as key for event lookups, like on pg_stat_activity. These tables can hold a minimum of 16 entries, and a maximum of 128 entries, which should be plenty enough. The code changes done in worker_spi show how things are simplified (most of the code removed in this commit comes from there): - worker_spi_init() is gone. - No more shared memory hooks required (size requested and initialization). - The custom wait event ID is cached in the process that needs to set it, with one single call to WaitEventExtensionNew() to retrieve it. Per suggestion from Andres Freund. Author: Masahiro Ikeda, with a few tweaks from me. Discussion: https://postgr.es/m/20230801032349.aaiuvhtrcvvcwzcx@awork3.anarazel.de
1 parent 2a8b40e commit af720b4

File tree

10 files changed

+165
-238
lines changed

10 files changed

+165
-238
lines changed

doc/src/sgml/monitoring.sgml

+2-3
Original file line numberDiff line numberDiff line change
@@ -1121,9 +1121,8 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
11211121
<literal>LWLock</literal> types
11221122
to the list shown in <xref linkend="wait-event-extension-table"/> and
11231123
<xref linkend="wait-event-lwlock-table"/>. In some cases, the name
1124-
assigned by an extension will not be available in all server processes;
1125-
so an <literal>Extension</literal> or <literal>LWLock</literal> wait
1126-
event might be reported as just
1124+
of <literal>LWLock</literal> assigned by an extension will not be
1125+
available in all server processes; It might be reported as just
11271126
<quote><literal>extension</literal></quote> rather than the
11281127
extension-assigned name.
11291128
</para>

doc/src/sgml/xfunc.sgml

+4-22
Original file line numberDiff line numberDiff line change
@@ -3454,33 +3454,15 @@ if (!ptr)
34543454
</sect2>
34553455

34563456
<sect2 id="xfunc-addin-wait-events">
3457-
<title>Shared Memory and Custom Wait Events</title>
3457+
<title>Custom Wait Events</title>
34583458

34593459
<para>
34603460
Add-ins can define custom wait events under the wait event type
3461-
<literal>Extension</literal>. The add-in's shared library must be
3462-
preloaded by specifying it in <literal>shared_preload_libraries</literal>,
3463-
and register a <literal>shmem_request_hook</literal> and a
3464-
<literal>shmem_startup_hook</literal> in its
3465-
<function>_PG_init</function> function.
3466-
<literal>shmem_request_hook</literal> can request a shared memory size
3467-
to be later used at startup by calling:
3461+
<literal>Extension</literal> by calling:
34683462
<programlisting>
3469-
void RequestAddinShmemSpace(int size)
3470-
</programlisting>
3471-
</para>
3472-
<para>
3473-
<literal>shmem_startup_hook</literal> can allocate in shared memory
3474-
custom wait events by calling while holding the LWLock
3475-
<function>AddinShmemInitLock</function> to avoid any race conditions:
3476-
<programlisting>
3477-
uint32 WaitEventExtensionNew(void)
3478-
</programlisting>
3479-
Next, each process needs to associate the wait event allocated previously
3480-
to a user-facing custom string, which is something done by calling:
3481-
<programlisting>
3482-
void WaitEventExtensionRegisterName(uint32 wait_event_info, const char *wait_event_name)
3463+
uint32 WaitEventExtensionNew(const char *wait_event_name)
34833464
</programlisting>
3465+
The wait event is associated to a user-facing custom string.
34843466
An example can be found in <filename>src/test/modules/worker_spi</filename>
34853467
in the PostgreSQL source tree.
34863468
</para>

src/backend/storage/lmgr/lwlocknames.txt

+1
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,4 @@ XactTruncationLock 44
5353
# 45 was XactTruncationLock until removal of BackendRandomLock
5454
WrapLimitsVacuumLock 46
5555
NotifyQueueTailLock 47
56+
WaitEventExtensionLock 48

src/backend/utils/activity/wait_event.c

+137-81
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,41 @@ uint32 *my_wait_event_info = &local_my_wait_event_info;
4545
#define WAIT_EVENT_CLASS_MASK 0xFF000000
4646
#define WAIT_EVENT_ID_MASK 0x0000FFFF
4747

48+
/*
49+
* Hash tables for storing custom wait event ids and their names in
50+
* shared memory.
51+
*
52+
* WaitEventExtensionHashById is used to find the name from a event id.
53+
* Any backend can search it to find custom wait events.
54+
*
55+
* WaitEventExtensionHashByName is used to find the event ID from a name.
56+
* It is used to ensure that no duplicated entries are registered.
57+
*
58+
* The size of the hash table is based on the assumption that
59+
* WAIT_EVENT_EXTENSION_BASH_INIT_SIZE is enough for most cases, and it seems
60+
* unlikely that the number of entries will reach
61+
* WAIT_EVENT_EXTENSION_BASH_MAX_SIZE.
62+
*/
63+
static HTAB *WaitEventExtensionHashById; /* find names from IDs */
64+
static HTAB *WaitEventExtensionHashByName; /* find IDs from names */
65+
66+
#define WAIT_EVENT_EXTENSION_HASH_INIT_SIZE 16
67+
#define WAIT_EVENT_EXTENSION_HASH_MAX_SIZE 128
68+
69+
/* hash table entries */
70+
typedef struct WaitEventExtensionEntryById
71+
{
72+
uint16 event_id; /* hash key */
73+
char wait_event_name[NAMEDATALEN]; /* custom wait event name */
74+
} WaitEventExtensionEntryById;
75+
76+
typedef struct WaitEventExtensionEntryByName
77+
{
78+
char wait_event_name[NAMEDATALEN]; /* hash key */
79+
uint16 event_id; /* wait event ID */
80+
} WaitEventExtensionEntryByName;
81+
82+
4883
/* dynamic allocation counter for custom wait events in extensions */
4984
typedef struct WaitEventExtensionCounterData
5085
{
@@ -59,58 +94,118 @@ static WaitEventExtensionCounterData *WaitEventExtensionCounter;
5994
#define NUM_BUILTIN_WAIT_EVENT_EXTENSION \
6095
(WAIT_EVENT_EXTENSION_FIRST_USER_DEFINED - WAIT_EVENT_EXTENSION)
6196

62-
/*
63-
* This is indexed by event ID minus NUM_BUILTIN_WAIT_EVENT_EXTENSION, and
64-
* stores the names of all dynamically-created event IDs known to the current
65-
* process. Any unused entries in the array will contain NULL.
66-
*/
67-
static const char **WaitEventExtensionNames = NULL;
68-
static int WaitEventExtensionNamesAllocated = 0;
97+
/* wait event info for extensions */
98+
#define WAIT_EVENT_EXTENSION_INFO(eventId) (PG_WAIT_EXTENSION | eventId)
6999

70100
static const char *GetWaitEventExtensionIdentifier(uint16 eventId);
71101

72102
/*
73-
* Return the space for dynamic allocation counter.
103+
* Return the space for dynamic shared hash tables and dynamic allocation counter.
74104
*/
75105
Size
76106
WaitEventExtensionShmemSize(void)
77107
{
78-
return sizeof(WaitEventExtensionCounterData);
108+
Size sz;
109+
110+
sz = MAXALIGN(sizeof(WaitEventExtensionCounterData));
111+
sz = add_size(sz, hash_estimate_size(WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
112+
sizeof(WaitEventExtensionEntryById)));
113+
sz = add_size(sz, hash_estimate_size(WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
114+
sizeof(WaitEventExtensionEntryByName)));
115+
return sz;
79116
}
80117

81118
/*
82-
* Allocate shmem space for dynamic allocation counter.
119+
* Allocate shmem space for dynamic shared hash and dynamic allocation counter.
83120
*/
84121
void
85122
WaitEventExtensionShmemInit(void)
86123
{
87124
bool found;
125+
HASHCTL info;
88126

89127
WaitEventExtensionCounter = (WaitEventExtensionCounterData *)
90128
ShmemInitStruct("WaitEventExtensionCounterData",
91-
WaitEventExtensionShmemSize(), &found);
129+
sizeof(WaitEventExtensionCounterData), &found);
92130

93131
if (!found)
94132
{
95133
/* initialize the allocation counter and its spinlock. */
96134
WaitEventExtensionCounter->nextId = NUM_BUILTIN_WAIT_EVENT_EXTENSION;
97135
SpinLockInit(&WaitEventExtensionCounter->mutex);
98136
}
137+
138+
/* initialize or attach the hash tables to store custom wait events */
139+
info.keysize = sizeof(uint16);
140+
info.entrysize = sizeof(WaitEventExtensionEntryById);
141+
WaitEventExtensionHashById = ShmemInitHash("WaitEventExtension hash by id",
142+
WAIT_EVENT_EXTENSION_HASH_INIT_SIZE,
143+
WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
144+
&info,
145+
HASH_ELEM | HASH_BLOBS);
146+
147+
/* key is a NULL-terminated string */
148+
info.keysize = sizeof(char[NAMEDATALEN]);
149+
info.entrysize = sizeof(WaitEventExtensionEntryByName);
150+
WaitEventExtensionHashByName = ShmemInitHash("WaitEventExtension hash by name",
151+
WAIT_EVENT_EXTENSION_HASH_INIT_SIZE,
152+
WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
153+
&info,
154+
HASH_ELEM | HASH_STRINGS);
99155
}
100156

101157
/*
102-
* Allocate a new event ID and return the wait event.
158+
* Allocate a new event ID and return the wait event info.
159+
*
160+
* If the wait event name is already defined, this does not allocate a new
161+
* entry; it returns the wait event information associated to the name.
103162
*/
104163
uint32
105-
WaitEventExtensionNew(void)
164+
WaitEventExtensionNew(const char *wait_event_name)
106165
{
107166
uint16 eventId;
167+
bool found;
168+
WaitEventExtensionEntryByName *entry_by_name;
169+
WaitEventExtensionEntryById *entry_by_id;
170+
171+
/* Check the limit of the length of the event name */
172+
if (strlen(wait_event_name) >= NAMEDATALEN)
173+
elog(ERROR,
174+
"cannot use custom wait event string longer than %u characters",
175+
NAMEDATALEN - 1);
176+
177+
/*
178+
* Check if the wait event info associated to the name is already defined,
179+
* and return it if so.
180+
*/
181+
LWLockAcquire(WaitEventExtensionLock, LW_SHARED);
182+
entry_by_name = (WaitEventExtensionEntryByName *)
183+
hash_search(WaitEventExtensionHashByName, wait_event_name,
184+
HASH_FIND, &found);
185+
LWLockRelease(WaitEventExtensionLock);
186+
if (found)
187+
return WAIT_EVENT_EXTENSION_INFO(entry_by_name->event_id);
108188

109-
Assert(LWLockHeldByMeInMode(AddinShmemInitLock, LW_EXCLUSIVE));
189+
/*
190+
* Allocate and register a new wait event. Recheck if the event name
191+
* exists, as it could be possible that a concurrent process has inserted
192+
* one with the same name since the LWLock acquired again here was
193+
* previously released.
194+
*/
195+
LWLockAcquire(WaitEventExtensionLock, LW_EXCLUSIVE);
196+
entry_by_name = (WaitEventExtensionEntryByName *)
197+
hash_search(WaitEventExtensionHashByName, wait_event_name,
198+
HASH_FIND, &found);
199+
if (found)
200+
{
201+
LWLockRelease(WaitEventExtensionLock);
202+
return WAIT_EVENT_EXTENSION_INFO(entry_by_name->event_id);
203+
}
110204

205+
/* Allocate a new event Id */
111206
SpinLockAcquire(&WaitEventExtensionCounter->mutex);
112207

113-
if (WaitEventExtensionCounter->nextId > PG_UINT16_MAX)
208+
if (WaitEventExtensionCounter->nextId >= WAIT_EVENT_EXTENSION_HASH_MAX_SIZE)
114209
{
115210
SpinLockRelease(&WaitEventExtensionCounter->mutex);
116211
ereport(ERROR,
@@ -122,64 +217,23 @@ WaitEventExtensionNew(void)
122217

123218
SpinLockRelease(&WaitEventExtensionCounter->mutex);
124219

125-
return PG_WAIT_EXTENSION | eventId;
126-
}
127-
128-
/*
129-
* Register a dynamic wait event name for extension in the lookup table
130-
* of the current process.
131-
*
132-
* This routine will save a pointer to the wait event name passed as an argument,
133-
* so the name should be allocated in a backend-lifetime context
134-
* (shared memory, TopMemoryContext, static constant, or similar).
135-
*
136-
* The "wait_event_name" will be user-visible as a wait event name, so try to
137-
* use a name that fits the style for those.
138-
*/
139-
void
140-
WaitEventExtensionRegisterName(uint32 wait_event_info,
141-
const char *wait_event_name)
142-
{
143-
uint32 classId;
144-
uint16 eventId;
145-
146-
classId = wait_event_info & WAIT_EVENT_CLASS_MASK;
147-
eventId = wait_event_info & WAIT_EVENT_ID_MASK;
148-
149-
/* Check the wait event class. */
150-
if (classId != PG_WAIT_EXTENSION)
151-
ereport(ERROR,
152-
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
153-
errmsg("invalid wait event class %u", classId));
154-
155-
/* This should only be called for user-defined wait event. */
156-
if (eventId < NUM_BUILTIN_WAIT_EVENT_EXTENSION)
157-
ereport(ERROR,
158-
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
159-
errmsg("invalid wait event ID %u", eventId));
220+
/* Register the new wait event */
221+
entry_by_id = (WaitEventExtensionEntryById *)
222+
hash_search(WaitEventExtensionHashById, &eventId,
223+
HASH_ENTER, &found);
224+
Assert(!found);
225+
strlcpy(entry_by_id->wait_event_name, wait_event_name,
226+
sizeof(entry_by_id->wait_event_name));
160227

161-
/* Convert to array index. */
162-
eventId -= NUM_BUILTIN_WAIT_EVENT_EXTENSION;
228+
entry_by_name = (WaitEventExtensionEntryByName *)
229+
hash_search(WaitEventExtensionHashByName, wait_event_name,
230+
HASH_ENTER, &found);
231+
Assert(!found);
232+
entry_by_name->event_id = eventId;
163233

164-
/* If necessary, create or enlarge array. */
165-
if (eventId >= WaitEventExtensionNamesAllocated)
166-
{
167-
uint32 newalloc;
168-
169-
newalloc = pg_nextpower2_32(Max(8, eventId + 1));
170-
171-
if (WaitEventExtensionNames == NULL)
172-
WaitEventExtensionNames = (const char **)
173-
MemoryContextAllocZero(TopMemoryContext,
174-
newalloc * sizeof(char *));
175-
else
176-
WaitEventExtensionNames =
177-
repalloc0_array(WaitEventExtensionNames, const char *,
178-
WaitEventExtensionNamesAllocated, newalloc);
179-
WaitEventExtensionNamesAllocated = newalloc;
180-
}
234+
LWLockRelease(WaitEventExtensionLock);
181235

182-
WaitEventExtensionNames[eventId] = wait_event_name;
236+
return WAIT_EVENT_EXTENSION_INFO(eventId);
183237
}
184238

185239
/*
@@ -188,23 +242,25 @@ WaitEventExtensionRegisterName(uint32 wait_event_info,
188242
static const char *
189243
GetWaitEventExtensionIdentifier(uint16 eventId)
190244
{
245+
bool found;
246+
WaitEventExtensionEntryById *entry;
247+
191248
/* Built-in event? */
192249
if (eventId < NUM_BUILTIN_WAIT_EVENT_EXTENSION)
193250
return "Extension";
194251

195-
/*
196-
* It is a user-defined wait event, so look at WaitEventExtensionNames[].
197-
* However, it is possible that the name has never been registered by
198-
* calling WaitEventExtensionRegisterName() in the current process, in
199-
* which case give up and return "extension".
200-
*/
201-
eventId -= NUM_BUILTIN_WAIT_EVENT_EXTENSION;
252+
/* It is a user-defined wait event, so lookup hash table. */
253+
LWLockAcquire(WaitEventExtensionLock, LW_SHARED);
254+
entry = (WaitEventExtensionEntryById *)
255+
hash_search(WaitEventExtensionHashById, &eventId,
256+
HASH_FIND, &found);
257+
LWLockRelease(WaitEventExtensionLock);
202258

203-
if (eventId >= WaitEventExtensionNamesAllocated ||
204-
WaitEventExtensionNames[eventId] == NULL)
205-
return "extension";
259+
if (!entry)
260+
elog(ERROR, "could not find custom wait event name for ID %u",
261+
eventId);
206262

207-
return WaitEventExtensionNames[eventId];
263+
return entry->wait_event_name;
208264
}
209265

210266

src/backend/utils/activity/wait_event_names.txt

+1
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ WAIT_EVENT_DOCONLY LogicalRepWorker "Waiting to read or update the state of logi
317317
WAIT_EVENT_DOCONLY XactTruncation "Waiting to execute <function>pg_xact_status</function> or update the oldest transaction ID available to it."
318318
WAIT_EVENT_DOCONLY WrapLimitsVacuum "Waiting to update limits on transaction id and multixact consumption."
319319
WAIT_EVENT_DOCONLY NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message storage."
320+
WAIT_EVENT_DOCONLY WaitEventExtension "Waiting to read or update custom wait events information for extensions."
320321

321322
WAIT_EVENT_DOCONLY XactBuffer "Waiting for I/O on a transaction status SLRU buffer."
322323
WAIT_EVENT_DOCONLY CommitTsBuffer "Waiting for I/O on a commit timestamp SLRU buffer."

src/include/utils/wait_event.h

+9-9
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ extern PGDLLIMPORT uint32 *my_wait_event_info;
4444
* Use this category when the server process is waiting for some condition
4545
* defined by an extension module.
4646
*
47-
* Extensions can define their own wait events in this category. First,
48-
* they should call WaitEventExtensionNew() to get one or more wait event
49-
* IDs that are allocated from a shared counter. These can be used directly
50-
* with pgstat_report_wait_start() or equivalent. Next, each individual
51-
* process should call WaitEventExtensionRegisterName() to associate a wait
52-
* event string to the number allocated previously.
47+
* Extensions can define their own wait events in this category. They should
48+
* call WaitEventExtensionNew() with a wait event string. If the wait event
49+
* associated to a string is already allocated, it returns the wait event
50+
* information to use. If not, it gets one wait event ID allocated from
51+
* a shared counter, associates the string to the ID in the shared dynamic
52+
* hash and returns the wait event information.
53+
*
54+
* The ID retrieved can be used with pgstat_report_wait_start() or equivalent.
5355
*/
5456
typedef enum
5557
{
@@ -60,9 +62,7 @@ typedef enum
6062
extern void WaitEventExtensionShmemInit(void);
6163
extern Size WaitEventExtensionShmemSize(void);
6264

63-
extern uint32 WaitEventExtensionNew(void);
64-
extern void WaitEventExtensionRegisterName(uint32 wait_event_info,
65-
const char *wait_event_name);
65+
extern uint32 WaitEventExtensionNew(const char *wait_event_name);
6666

6767
/* ----------
6868
* pgstat_report_wait_start() -

0 commit comments

Comments
 (0)