Re: Support to define custom wait events for extensions

Andres Freund <andres@anarazel.de>

From: Andres Freund <andres@anarazel.de>
To: Michael Paquier <michael@paquier.xyz>
Cc: Masahiro Ikeda <ikedamsh@oss.nttdata.com>, pgsql-hackers@lists.postgresql.org
Date: 2023-07-12T00:36:47Z
Lists: pgsql-hackers
Hi,

On 2023-07-11 16:45:26 +0900, Michael Paquier wrote:
> +/* ----------
> + * 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.  First, call
> + * WaitEventExtensionNewTranche() just once to obtain a new wait event
> + * tranche.  The ID is allocated from a shared counter.  Next, each
> + * individual process using the tranche should call
> + * WaitEventExtensionRegisterTranche() to associate that wait event with
> + * a name.

What does "tranche" mean here? For LWLocks it makes some sense, it's used for
a set of lwlocks, not an individual one. But here that doesn't really seem to
apply?


> + * It may seem strange that each process using the tranche must register it
> + * separately, but dynamic shared memory segments aren't guaranteed to be
> + * mapped at the same address in all coordinating backends, so storing the
> + * registration in the main shared memory segment wouldn't work for that case.
> + */

I don't really see how this applies to wait events? There's no pointers
here...


> +typedef enum
> +{
> +	WAIT_EVENT_EXTENSION = PG_WAIT_EXTENSION,
> +	WAIT_EVENT_EXTENSION_FIRST_USER_DEFINED
> +} WaitEventExtension;
> +
> +extern void WaitEventExtensionShmemInit(void);
> +extern Size WaitEventExtensionShmemSize(void);
> +
> +extern uint32 WaitEventExtensionNewTranche(void);
> +extern void WaitEventExtensionRegisterTranche(uint32 wait_event_info,

> -slock_t    *ShmemLock;			/* spinlock for shared memory and LWLock
> +slock_t    *ShmemLock;			/* spinlock for shared memory, LWLock
> +								 * allocation, and named extension wait event
>  								 * allocation */

I'm doubtful that it's a good idea to reuse the spinlock further. Given that
the patch adds WaitEventExtensionShmemInit(), why not just have a lock in
there?



> +/*
> + * Allocate a new event ID and return the wait event info.
> + */
> +uint32
> +WaitEventExtensionNewTranche(void)
> +{
> +	uint16		eventId;
> +
> +	SpinLockAcquire(ShmemLock);
> +	eventId = (*WaitEventExtensionCounter)++;
> +	SpinLockRelease(ShmemLock);
> +
> +	return PG_WAIT_EXTENSION | eventId;
> +}

It seems quite possible to run out space in WaitEventExtensionCounter, so this
should error out in that case.


> +/*
> + * Register a dynamic tranche name in the lookup table of the current process.
> + *
> + * This routine will save a pointer to the wait event tranche name passed
> + * as an argument, so the name should be allocated in a backend-lifetime context
> + * (shared memory, TopMemoryContext, static constant, or similar).
> + *
> + * The "wait_event_name" will be user-visible as a wait event name, so try to
> + * use a name that fits the style for those.
> + */
> +void
> +WaitEventExtensionRegisterTranche(uint32 wait_event_info,
> +								  const char *wait_event_name)
> +{
> +	uint16		eventId;
> +
> +	/* Check wait event class. */
> +	Assert((wait_event_info & 0xFF000000) == PG_WAIT_EXTENSION);
> +
> +	eventId = wait_event_info & 0x0000FFFF;
> +
> +	/* This should only be called for user-defined tranches. */
> +	if (eventId < NUM_BUILTIN_WAIT_EVENT_EXTENSION)
> +		return;

Why not assert out in that case then?


> +/*
> + * Return the name of an Extension wait event ID.
> + */
> +static const char *
> +GetWaitEventExtensionIdentifier(uint16 eventId)
> +{
> +	/* Build-in tranche? */

*built

> +	if (eventId < NUM_BUILTIN_WAIT_EVENT_EXTENSION)
> +		return "Extension";
> +
> +	/*
> +	 * It's an extension tranche, so look in WaitEventExtensionTrancheNames[].
> +	 * However, it's possible that the tranche has never been registered by
> +	 * calling WaitEventExtensionRegisterTranche() in the current process, in
> +	 * which case give up and return "Extension".
> +	 */
> +	eventId -= NUM_BUILTIN_WAIT_EVENT_EXTENSION;
> +
> +	if (eventId >= WaitEventExtensionTrancheNamesAllocated ||
> +		WaitEventExtensionTrancheNames[eventId] == NULL)
> +		return "Extension";

I'd return something different here, otherwise something that's effectively a
bug is not distinguishable from the built


> +++ b/src/test/modules/test_custom_wait_events/t/001_basic.pl
> @@ -0,0 +1,34 @@
> +# Copyright (c) 2023, PostgreSQL Global Development Group
> +
> +use strict;
> +use warnings;
> +
> +use PostgreSQL::Test::Cluster;
> +use PostgreSQL::Test::Utils;
> +use Test::More;
> +
> +my $node = PostgreSQL::Test::Cluster->new('main');
> +
> +$node->init;
> +$node->append_conf(
> +	'postgresql.conf',
> +	"shared_preload_libraries = 'test_custom_wait_events'"
> +);
> +$node->start;

I think this should also test registering a wait event later.


> @@ -0,0 +1,188 @@
> +/*--------------------------------------------------------------------------
> + *
> + * test_custom_wait_events.c
> + *		Code for testing custom wait events
> + *
> + * This code initializes a custom wait event in shmem_request_hook() and
> + * provide a function to launch a background worker waiting forever
> + * with the custom wait event.

Isn't this vast overkill?  Why not just have a simple C function that waits
with a custom wait event, until cancelled? That'd maybe 1/10th of the LOC.


Greetings,

Andres Freund



Commits

  1. Change custom wait events to use dynamic shared hash tables

  2. Support custom wait events for wait event type "Extension"

  3. Add WAIT_EVENT_{CLASS,ID}_MASK in wait_event.c

  4. worker_spi: Switch to TAP tests

  5. Simplify some conditions related to [LW]Lock in generate-wait_event_types.pl