Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1r0H1n-004j4V-3Y for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Nov 2023 08:01:39 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1r0H1l-00666E-Df for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Nov 2023 08:01:37 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1r0H1k-00665z-QE for pgsql-hackers@lists.postgresql.org; Tue, 07 Nov 2023 08:01:37 +0000 Received: from wout4-smtp.messagingengine.com ([64.147.123.20]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1r0H1g-004Mqy-B2 for pgsql-hackers@lists.postgresql.org; Tue, 07 Nov 2023 08:01:35 +0000 Received: from compute5.internal (compute5.nyi.internal [10.202.2.45]) by mailout.west.internal (Postfix) with ESMTP id B2D853200922; Tue, 7 Nov 2023 03:01:30 -0500 (EST) Received: from mailfrontend1 ([10.202.2.162]) by compute5.internal (MEProxy); Tue, 07 Nov 2023 03:01:30 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h= cc:cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:sender :subject:subject:to:to; s=fm3; t=1699344090; x=1699430490; bh=kz n2ZcrCs13vHRyTDF9gO1xH7TAhPfAFL8+aNpZnRRc=; b=E3pGVAa7sXvDAP17Rp +yG0obuUU3ijRoNNBGZG3oFa4lho+A6v+n07aRgTvH+I2EW3O81IzcB0aAfkrFEE o8ePNYQjqxtUvkfJKKLgCkp835NVRXkRMQqMgi02f3xrYW7aeU1L2hleDpcgiYSD 7aFyKCkD+S++pF449oMoR9mQCFtj7Odp0jN34TnMJFb3AHOKmdBPPDH1u3Jxb1+q NHFJYpFfgAqLGFIlX5Dxs4ILv+smHlSKpPkjns07Ft3+oj2AWaJdyLAgqVdR3aVi GGERi1SZCfF307t0Vy8ax/pOl9c6IZpO9o8NSNj7hne9rsWJLZC7FtughZ1NpHLz rCyA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:sender:subject :subject:to:to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender :x-sasl-enc; s=fm3; t=1699344090; x=1699430490; bh=kzn2ZcrCs13vH RyTDF9gO1xH7TAhPfAFL8+aNpZnRRc=; b=hdqR2+Szuenft9ymkMjMfe3DreY/l vSn8fDhVkhg/CJPy5WP4+6tjqxAIZbsrGd2m/SXLN2V9PEjcPy3PbAYt9eWpdIJp SzHUTfkZ4LjjN7wc2yetQBOl4p7MtmglaKwGkoW7+ey/DqN1hfWCpavCO75j5O7k 1gdjUFhN+PrPVOjwxIsBrzhPIAlYpAI0yo0I3TybH9P6TpbFt1gT1Ozq3ZHxS6LJ S7WteXUzfgXCLpaolzY+cAzdMLuHwCt7Q4O/llP/5Ji+MnuxGrm3Oa7xhHtAkHep KA0xsuIRDoW0rcUfUD/NWgyyx7/0lO1ld4g+20fBt+XAc1FSnR+hVKrqQ== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvkedrudduhedgudduudcutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd enfghrlhcuvffnffculdejtddmnecujfgurhepfffhvfevuffkfhggtggujgesghdtroer tddtvdenucfhrhhomhepofhitghhrggvlhcurfgrqhhuihgvrhcuoehmihgthhgrvghlse hprghquhhivghrrdighiiiqeenucggtffrrghtthgvrhhnpeffudelheeghfekfefgfedt hfekuddtvdfhudekgeegheeikefflefhleekheetfeenucevlhhushhtvghrufhiiigvpe dtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehmihgthhgrvghlsehprghquhhivghrrdig hiii X-ME-Proxy: Feedback-ID: i0fe9450f:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Tue, 7 Nov 2023 03:01:28 -0500 (EST) Date: Tue, 7 Nov 2023 17:01:16 +0900 From: Michael Paquier To: Nazir Bilal Yavuz Cc: Postgres hackers Subject: Re: Adding facility for injection points (or probe points?) for more advanced tests Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="7TXeHY91KO05Zh3w" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --7TXeHY91KO05Zh3w Content-Type: multipart/mixed; boundary="GuU21jJbo1igLt/t" Content-Disposition: inline --GuU21jJbo1igLt/t Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Mon, Nov 06, 2023 at 10:28:14PM +0300, Nazir Bilal Yavuz wrote: > I liked the idea; thanks for working on this! Thanks for the review. > What do you think about creating a function for updating the already > created injection point's callback or name (mostly callback)? For now, > you need to drop and recreate the injection point to change the > callback or the name. I am not sure if that's worth the addition. TBH, all the code I've seen that would benefit from these APIs just set up a cluster, register a few injection points with a module, and then run a set of tests. They can also remove points. So I'm just aiming for simplest for the moment. =20 > Here is my code correctness review: >=20 > diff --git a/meson_options.txt b/meson_options.txt > +option('injection_points', type: 'boolean', value: true, > + description: 'Enable injection points') > + >=20 > It is enabled by default while building with meson. Indeed, fixed. > diff --git a/src/backend/utils/misc/injection_point.c > b/src/backend/utils/misc/injection_point.c > + LWLockRelease(InjectionPointLock); > + > + /* If not found, do nothing? */ > + if (!found) > + return; >=20 > It would be good to log a warning message here. I don't think that's a good idea. If a code path defines a INJECTION_POINT_RUN() we'd get spurious warnings except if a point is always defined when the build switch is enabled. > I tried to compile that with -Dwerror=3Dtrue -Dinjection_points=3Dfalse > and got some errors (warnings): Right, fixed these three. > The test_injection_points test runs and passes although I set > -Dinjection_points=3Dfalse. That could be misleading, IMO the test > should be skipped if Postgres is not compiled with the injection > points. The test suite has been using an alternate output, but perhaps you are right that this has little value without the switch enabled anyway. I've made the processing optional when the option is not used for meson and ./configure (requires a variable in Makefile.global.in in the latter case), removing the alternate output. Please find v2. -- Michael --GuU21jJbo1igLt/t Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0001-Base-facility-for-injection-points.patch" Content-Transfer-Encoding: quoted-printable =46rom 4eaa22dae8888a452e4985c7419df153ad0dfbce Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Tue, 7 Nov 2023 17:00:49 +0900 Subject: [PATCH v2] Base facility for injection points Extension and backend developers can register custom callbacks that would be run on a user-defined fashion. This is hidden behind a =2E/configure and a meson switch, disabled by default. --- src/include/pg_config.h.in | 3 + src/include/utils/injection_point.h | 35 ++++ src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/lwlocknames.txt | 1 + .../utils/activity/wait_event_names.txt | 1 + src/backend/utils/misc/Makefile | 1 + src/backend/utils/misc/injection_point.c | 162 ++++++++++++++++++ src/backend/utils/misc/meson.build | 1 + src/test/modules/Makefile | 7 + src/test/modules/meson.build | 1 + .../modules/test_injection_points/.gitignore | 4 + .../modules/test_injection_points/Makefile | 22 +++ .../expected/test_injection_points.out | 45 +++++ .../modules/test_injection_points/meson.build | 37 ++++ .../sql/test_injection_points.sql | 15 ++ .../test_injection_points--1.0.sql | 36 ++++ .../test_injection_points.c | 88 ++++++++++ .../test_injection_points.control | 4 + doc/src/sgml/installation.sgml | 30 ++++ doc/src/sgml/xfunc.sgml | 56 ++++++ configure | 34 ++++ configure.ac | 7 + meson.build | 1 + meson_options.txt | 3 + src/Makefile.global.in | 1 + src/tools/pgindent/typedefs.list | 1 + 26 files changed, 599 insertions(+) create mode 100644 src/include/utils/injection_point.h create mode 100644 src/backend/utils/misc/injection_point.c create mode 100644 src/test/modules/test_injection_points/.gitignore create mode 100644 src/test/modules/test_injection_points/Makefile create mode 100644 src/test/modules/test_injection_points/expected/test_in= jection_points.out create mode 100644 src/test/modules/test_injection_points/meson.build create mode 100644 src/test/modules/test_injection_points/sql/test_injecti= on_points.sql create mode 100644 src/test/modules/test_injection_points/test_injection_p= oints--1.0.sql create mode 100644 src/test/modules/test_injection_points/test_injection_p= oints.c create mode 100644 src/test/modules/test_injection_points/test_injection_p= oints.control diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index d8a2985567..7a976821e5 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -701,6 +701,9 @@ /* Define to build with ICU support. (--with-icu) */ #undef USE_ICU =20 +/* Define to 1 to build with injection points. (--enable-injection-points)= */ +#undef USE_INJECTION_POINTS + /* Define to 1 to build with LDAP support. (--with-ldap) */ #undef USE_LDAP =20 diff --git a/src/include/utils/injection_point.h b/src/include/utils/inject= ion_point.h new file mode 100644 index 0000000000..a0cb30a111 --- /dev/null +++ b/src/include/utils/injection_point.h @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------------- + * injection_point.h + * Definitions related to injection points. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * src/include/utils/injection_point.h + * ---------- + */ +#ifndef INJECTION_POINT_H +#define INJECTION_POINT_H + +/* + * Typedef for callback function launched by an injection point. + */ +typedef void (*InjectionPointCallback) (const char *name); + +/* + * Injections points require --enable-injection-points. + */ +#ifdef USE_INJECTION_POINTS +#define INJECTION_POINT_RUN(name) InjectionPointRun(name) +#else +#define INJECTION_POINT_RUN(name) ((void) name) +#endif + +extern Size InjectionPointShmemSize(void); +extern void InjectionPointShmemInit(void); + +extern void InjectionPointCreate(const char *name, + InjectionPointCallback callback); +extern void InjectionPointRun(const char *name); +extern void InjectionPointDrop(const char *name); + +#endif /* INJECTION_POINT_H */ diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index a3d8eacb8d..12b8a42fd9 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -48,6 +48,7 @@ #include "storage/sinvaladt.h" #include "storage/spin.h" #include "utils/guc.h" +#include "utils/injection_point.h" #include "utils/snapmgr.h" #include "utils/wait_event.h" =20 @@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores) size =3D add_size(size, AsyncShmemSize()); size =3D add_size(size, StatsShmemSize()); size =3D add_size(size, WaitEventExtensionShmemSize()); + size =3D add_size(size, InjectionPointShmemSize()); #ifdef EXEC_BACKEND size =3D add_size(size, ShmemBackendArraySize()); #endif @@ -302,6 +304,7 @@ CreateSharedMemoryAndSemaphores(void) AsyncShmemInit(); StatsShmemInit(); WaitEventExtensionShmemInit(); + InjectionPointShmemInit(); =20 #ifdef EXEC_BACKEND =20 diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage= /lmgr/lwlocknames.txt index f72f2906ce..42a048746d 100644 --- a/src/backend/storage/lmgr/lwlocknames.txt +++ b/src/backend/storage/lmgr/lwlocknames.txt @@ -54,3 +54,4 @@ XactTruncationLock 44 WrapLimitsVacuumLock 46 NotifyQueueTailLock 47 WaitEventExtensionLock 48 +InjectionPointLock 49 diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/= utils/activity/wait_event_names.txt index d7995931bd..5631d29138 100644 --- a/src/backend/utils/activity/wait_event_names.txt +++ b/src/backend/utils/activity/wait_event_names.txt @@ -319,6 +319,7 @@ XactTruncation "Waiting to execute pg_xact_st= atus or update WrapLimitsVacuum "Waiting to update limits on transaction id and multixact= consumption." NotifyQueueTail "Waiting to update limit on NOTIFY mess= age storage." WaitEventExtension "Waiting to read or update custom wait events informati= on for extensions." +InjectionPoint "Waiting to read or update information related to injection= points." =20 XactBuffer "Waiting for I/O on a transaction status SLRU buffer." CommitTsBuffer "Waiting for I/O on a commit timestamp SLRU buffer." diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makef= ile index c2971c7678..d9f59785b9 100644 --- a/src/backend/utils/misc/Makefile +++ b/src/backend/utils/misc/Makefile @@ -21,6 +21,7 @@ OBJS =3D \ guc_funcs.o \ guc_tables.o \ help_config.o \ + injection_point.o \ pg_config.o \ pg_controldata.o \ pg_rusage.o \ diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/m= isc/injection_point.c new file mode 100644 index 0000000000..dd1cb6329a --- /dev/null +++ b/src/backend/utils/misc/injection_point.c @@ -0,0 +1,162 @@ +/*------------------------------------------------------------------------- + * + * injection_point.c + * Routines to control and run injection points in the code. + * + * Injection points can be used to call arbitrary callbacks in specific + * places of the code, registering callbacks that would be run in the code + * paths where a named injection point exists. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/injection_point.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "miscadmin.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" +#include "utils/hsearch.h" +#include "utils/injection_point.h" + +/* + * Hash table for storing injection points. + * + * InjectionPointHashByName is used to find an injection point by name. + */ +#ifdef USE_INJECTION_POINTS +static HTAB *InjectionPointHashByName; /* find points from names */ +#endif + +/* Field sizes */ +#define INJ_MAXLEN 64 + +typedef struct InjectionPointEntryByName +{ + char name[INJ_MAXLEN]; /* hash key */ + InjectionPointCallback callback; +} InjectionPointEntryByName; + +#define INJECTION_POINT_HASH_INIT_SIZE 16 +#define INJECTION_POINT_HASH_MAX_SIZE 128 + +/* + * Return the space for dynamic shared hash table. + */ +Size +InjectionPointShmemSize(void) +{ +#ifdef USE_INJECTION_POINTS + Size sz =3D 0; + + sz =3D add_size(sz, hash_estimate_size(INJECTION_POINT_HASH_MAX_SIZE, + sizeof(InjectionPointEntryByName))); + return sz; +#else + return 0; +#endif +} + +/* + * Allocate shmem space for dynamic shared hash. + */ +void +InjectionPointShmemInit(void) +{ +#ifdef USE_INJECTION_POINTS + HASHCTL info; + + /* key is a NULL-terminated string */ + info.keysize =3D sizeof(char[INJ_MAXLEN]); + info.entrysize =3D sizeof(InjectionPointEntryByName); + InjectionPointHashByName =3D ShmemInitHash("InjectionPoint hash by name", + INJECTION_POINT_HASH_INIT_SIZE, + INJECTION_POINT_HASH_MAX_SIZE, + &info, + HASH_ELEM | HASH_STRINGS); +#endif +} + +/* + * Insert a new injection point. + */ +void +InjectionPointCreate(const char *name, InjectionPointCallback callback) +{ +#ifdef USE_INJECTION_POINTS + InjectionPointEntryByName *entry_by_name; + bool found; + + if (strlen(name) >=3D INJ_MAXLEN) + elog(ERROR, "injection point name %s too long", name); + + /* + * Allocate and register a new injection point. A new point should not + * exist. For testing purposes this should be fine. + */ + LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE); + entry_by_name =3D (InjectionPointEntryByName *) + hash_search(InjectionPointHashByName, name, + HASH_ENTER, &found); + if (found) + { + LWLockRelease(InjectionPointLock); + elog(ERROR, "injection point \"%s\" already defined", name); + } + + /* Save the entry */ + memcpy(entry_by_name->name, name, sizeof(entry_by_name->name)); + entry_by_name->name[INJ_MAXLEN - 1] =3D '\0'; + entry_by_name->callback =3D callback; + + LWLockRelease(InjectionPointLock); +#else + elog(ERROR, "Injection points are not supported by this build"); +#endif +} + +void +InjectionPointDrop(const char *name) +{ +#ifdef USE_INJECTION_POINTS + bool found; + + LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE); + hash_search(InjectionPointHashByName, name, HASH_REMOVE, &found); + LWLockRelease(InjectionPointLock); + + if (!found) + elog(ERROR, "injection point \"%s\" not found", name); +#else + elog(ERROR, "Injection points are not supported by this build"); +#endif +} + +void +InjectionPointRun(const char *name) +{ +#ifdef USE_INJECTION_POINTS + InjectionPointEntryByName *entry_by_name; + bool found; + + LWLockAcquire(InjectionPointLock, LW_SHARED); + entry_by_name =3D (InjectionPointEntryByName *) + hash_search(InjectionPointHashByName, name, + HASH_FIND, &found); + LWLockRelease(InjectionPointLock); + + /* If not found, do nothing */ + if (!found) + return; + + /* Found, so just run the callback registered */ + entry_by_name->callback(entry_by_name->name); +#else + elog(ERROR, "Injection points are not supported by this build"); +#endif +} diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/me= son.build index f719c97c05..1438859b69 100644 --- a/src/backend/utils/misc/meson.build +++ b/src/backend/utils/misc/meson.build @@ -6,6 +6,7 @@ backend_sources +=3D files( 'guc_funcs.c', 'guc_tables.c', 'help_config.c', + 'injection_point.c', 'pg_config.c', 'pg_controldata.c', 'pg_rusage.c', diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index e81873cb5a..3839acc699 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -34,6 +34,13 @@ SUBDIRS =3D \ unsafe_tests \ worker_spi =20 + +ifeq ($(enable_injection_points),yes) +SUBDIRS +=3D test_injection_points +else +ALWAYS_SUBDIRS +=3D test_injection_points +endif + ifeq ($(with_ssl),openssl) SUBDIRS +=3D ssl_passphrase_callback else diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index fcd643f6f1..67e9eff878 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -16,6 +16,7 @@ subdir('test_custom_rmgrs') subdir('test_ddl_deparse') subdir('test_extensions') subdir('test_ginpostinglist') +subdir('test_injection_points') subdir('test_integerset') subdir('test_lfind') subdir('test_misc') diff --git a/src/test/modules/test_injection_points/.gitignore b/src/test/m= odules/test_injection_points/.gitignore new file mode 100644 index 0000000000..5dcb3ff972 --- /dev/null +++ b/src/test/modules/test_injection_points/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_injection_points/Makefile b/src/test/mod= ules/test_injection_points/Makefile new file mode 100644 index 0000000000..65bcdde782 --- /dev/null +++ b/src/test/modules/test_injection_points/Makefile @@ -0,0 +1,22 @@ +# src/test/modules/test_injection_points/Makefile + +MODULE_big =3D test_injection_points +OBJS =3D \ + $(WIN32RES) \ + test_injection_points.o +PGFILEDESC =3D "test_injection_points - test injection points" + +EXTENSION =3D test_injection_points +DATA =3D test_injection_points--1.0.sql +REGRESS =3D test_injection_points + +ifdef USE_PGXS +PG_CONFIG =3D pg_config +PGXS :=3D $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir =3D src/test/modules/test_injection_points +top_builddir =3D ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_injection_points/expected/test_injection= _points.out b/src/test/modules/test_injection_points/expected/test_injectio= n_points.out new file mode 100644 index 0000000000..c85639146c --- /dev/null +++ b/src/test/modules/test_injection_points/expected/test_injection_points= =2Eout @@ -0,0 +1,45 @@ +CREATE EXTENSION test_injection_points; +SELECT test_injection_points_create('TestInjectionBooh', 'booh'); +ERROR: incorrect mode "booh" for injection point creation +SELECT test_injection_points_create('TestInjectionError', 'error'); + test_injection_points_create=20 +------------------------------ +=20 +(1 row) + +SELECT test_injection_points_create('TestInjectionLog', 'notice'); + test_injection_points_create=20 +------------------------------ +=20 +(1 row) + +SELECT test_injection_points_run('TestInjectionBooh'); -- nothing happens + test_injection_points_run=20 +--------------------------- +=20 +(1 row) + +SELECT test_injection_points_run('TestInjectionLog'); +NOTICE: notice triggered for injection point TestInjectionLog + test_injection_points_run=20 +--------------------------- +=20 +(1 row) + +SELECT test_injection_points_run('TestInjectionError'); +ERROR: error triggered for injection point TestInjectionError +SELECT test_injection_points_drop('TestInjectionError'); -- ok + test_injection_points_drop=20 +---------------------------- +=20 +(1 row) + +SELECT test_injection_points_drop('TestInjectionLog'); -- ok + test_injection_points_drop=20 +---------------------------- +=20 +(1 row) + +SELECT test_injection_points_drop('TestInjectionLog'); -- fails +ERROR: injection point "TestInjectionLog" not found +DROP EXTENSION test_injection_points; diff --git a/src/test/modules/test_injection_points/meson.build b/src/test/= modules/test_injection_points/meson.build new file mode 100644 index 0000000000..7509a102ef --- /dev/null +++ b/src/test/modules/test_injection_points/meson.build @@ -0,0 +1,37 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not get_option('injection_points') + subdir_done() +endif + +test_injection_points_sources =3D files( + 'test_injection_points.c', +) + +if host_system =3D=3D 'windows' + test_injection_points_sources +=3D rc_lib_gen.process(win32ver_rc, extra= _args: [ + '--NAME', 'test_injection_points', + '--FILEDESC', 'test_injection_points - test injection points',]) +endif + +test_injection_points =3D shared_module('test_injection_points', + test_injection_points_sources, + kwargs: pg_test_mod_args, +) +test_install_libs +=3D test_injection_points + +test_install_data +=3D files( + 'test_injection_points.control', + 'test_injection_points--1.0.sql', +) + +tests +=3D { + 'name': 'test_injection_points', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_injection_points', + ], + }, +} diff --git a/src/test/modules/test_injection_points/sql/test_injection_poin= ts.sql b/src/test/modules/test_injection_points/sql/test_injection_points.s= ql new file mode 100644 index 0000000000..44135c7c37 --- /dev/null +++ b/src/test/modules/test_injection_points/sql/test_injection_points.sql @@ -0,0 +1,15 @@ +CREATE EXTENSION test_injection_points; + +SELECT test_injection_points_create('TestInjectionBooh', 'booh'); +SELECT test_injection_points_create('TestInjectionError', 'error'); +SELECT test_injection_points_create('TestInjectionLog', 'notice'); + +SELECT test_injection_points_run('TestInjectionBooh'); -- nothing happens +SELECT test_injection_points_run('TestInjectionLog'); +SELECT test_injection_points_run('TestInjectionError'); + +SELECT test_injection_points_drop('TestInjectionError'); -- ok +SELECT test_injection_points_drop('TestInjectionLog'); -- ok +SELECT test_injection_points_drop('TestInjectionLog'); -- fails + +DROP EXTENSION test_injection_points; diff --git a/src/test/modules/test_injection_points/test_injection_points--= 1.0.sql b/src/test/modules/test_injection_points/test_injection_points--1.0= =2Esql new file mode 100644 index 0000000000..c4c42b5b69 --- /dev/null +++ b/src/test/modules/test_injection_points/test_injection_points--1.0.sql @@ -0,0 +1,36 @@ +/* src/test/modules/test_injection_points/test_injection_points--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_injection_points" to load this file. \quit + +-- +-- test_injection_points_create() +-- +-- Creates an injection point using callbacks from one of the predefined +-- modes. +-- +CREATE FUNCTION test_injection_points_create(IN point_name TEXT, + IN mode text) +RETURNS void +AS 'MODULE_PATHNAME', 'test_injection_points_create' +LANGUAGE C STRICT PARALLEL UNSAFE; + +-- +-- test_injection_points_run() +-- +-- Executes an injection point. +-- +CREATE FUNCTION test_injection_points_run(IN point_name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_injection_points_run' +LANGUAGE C STRICT PARALLEL UNSAFE; + +-- +-- test_injection_points_drop() +-- +-- Drops an injection point. +-- +CREATE FUNCTION test_injection_points_drop(IN point_name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_injection_points_drop' +LANGUAGE C STRICT PARALLEL UNSAFE; diff --git a/src/test/modules/test_injection_points/test_injection_points.c= b/src/test/modules/test_injection_points/test_injection_points.c new file mode 100644 index 0000000000..a842d837e8 --- /dev/null +++ b/src/test/modules/test_injection_points/test_injection_points.c @@ -0,0 +1,88 @@ +/*------------------------------------------------------------------------= -- + * + * test_injection_points.c + * Code for testing injection points. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/test/modules/test_injection_points/test_injection_points.c + * + * Injection points are able to trigger user-defined callbacks in pre-defi= ned + * code paths. + * + * -----------------------------------------------------------------------= -- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/builtins.h" +#include "utils/injection_point.h" + +PG_MODULE_MAGIC; + +/* Set of callbacks available at point creation */ +static void +test_injection_error(const char *name) +{ + elog(ERROR, "error triggered for injection point %s", name); +} + +static void +test_injection_notice(const char *name) +{ + elog(NOTICE, "notice triggered for injection point %s", name); +} + +/* + * SQL function for creating an injection point. + */ +PG_FUNCTION_INFO_V1(test_injection_points_create); +Datum +test_injection_points_create(PG_FUNCTION_ARGS) +{ + char *name =3D text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *mode =3D text_to_cstring(PG_GETARG_TEXT_PP(1)); + InjectionPointCallback callback; + + if (strcmp(mode, "error") =3D=3D 0) + callback =3D test_injection_error; + else if (strcmp(mode, "notice") =3D=3D 0) + callback =3D test_injection_notice; + else + elog(ERROR, "incorrect mode \"%s\" for injection point creation", mode); + + InjectionPointCreate(name, callback); + + PG_RETURN_VOID(); +} + +/* + * SQL function for triggering an injection point. + */ +PG_FUNCTION_INFO_V1(test_injection_points_run); +Datum +test_injection_points_run(PG_FUNCTION_ARGS) +{ + char *name =3D text_to_cstring(PG_GETARG_TEXT_PP(0)); + + INJECTION_POINT_RUN(name); + + PG_RETURN_VOID(); +} + +/* + * SQL function for dropping an injection point. + */ +PG_FUNCTION_INFO_V1(test_injection_points_drop); +Datum +test_injection_points_drop(PG_FUNCTION_ARGS) +{ + char *name =3D text_to_cstring(PG_GETARG_TEXT_PP(0)); + + InjectionPointDrop(name); + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_injection_points/test_injection_points.c= ontrol b/src/test/modules/test_injection_points/test_injection_points.contr= ol new file mode 100644 index 0000000000..a13657cfc6 --- /dev/null +++ b/src/test/modules/test_injection_points/test_injection_points.control @@ -0,0 +1,4 @@ +comment =3D 'Test code for injection points' +default_version =3D '1.0' +module_pathname =3D '$libdir/test_injection_points' +relocatable =3D true diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index a3dc6eb855..d8840bce1f 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -1666,6 +1666,21 @@ build-postgresql: =20 + + + + + Compiles PostgreSQL with support for + injection points in the server. This is valuable to inject + user-defined code to force specific conditions to happen on the + server in pre-defined code paths. This option is disabled by defa= ult. + See for more deta= ils. + This option is only for developers to test specific concurrency + scenarios. + + + + @@ -3163,6 +3178,21 @@ ninja install =20 + + + + + Compiles PostgreSQL with support for + injection points in the server. This is valuable to inject + user-defined code to force specific conditions to happen on the + server in pre-defined code paths. This option is disabled by defa= ult. + See for more deta= ils. + This option is only for developers to test specific concurrency + scenarios. + + + + diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 89116ae74c..ded7b6c06b 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3510,6 +3510,62 @@ uint32 WaitEventExtensionNew(const char *wait_event_= name) =20 + + Injection Points + + + Add-ins can define injection points, that can register callbacks + to run user-defined code when going through a specific code path, + by calling: + +extern void InjectionPointCreate(const char *name, + InjectionPointCallback callback); + + + name is the name of the injection point. + Injection points are saved in a hash table in shared memory, and + last until the server is shut down. + + + + Here is an example of callback for + InjectionPointCallback: + +static void +custom_injection_callback(const char *name) +{ + elog(NOTICE, "%s: executed custom callback", name); +} + + + + + Once an injection point is defined, running it requires to use + the following macro to trigger the callback given in a wanted code + path: + +INJECTION_POINT_RUN(name); + + + + + Optionally, it is possible to drop injection points by calling: + +extern void InjectionPointDrop(const char *name); + + + + + Enabling injections points requires + from + configure or + + Using C++ for Extensibility =20 diff --git a/configure b/configure index c064115038..ce0ef0133d 100755 --- a/configure +++ b/configure @@ -759,6 +759,7 @@ CPPFLAGS LDFLAGS CFLAGS CC +enable_injection_points enable_tap_tests enable_dtrace DTRACEFLAGS @@ -839,6 +840,7 @@ enable_profiling enable_coverage enable_dtrace enable_tap_tests +enable_injection_points with_blocksize with_segsize with_segsize_blocks @@ -1532,6 +1534,8 @@ Optional Features: --enable-coverage build with coverage testing instrumentation --enable-dtrace build with DTrace support --enable-tap-tests enable TAP tests (requires Perl and IPC::Run) + --enable-injection-points + enable injection points (for testing) --enable-depend turn on automatic dependency tracking --enable-cassert enable assertion checks (for debugging) --disable-largefile omit support for large files @@ -3682,6 +3686,36 @@ fi =20 =20 =20 +# +# Injection points +# + + +# Check whether --enable-injection-points was given. +if test "${enable_injection_points+set}" =3D set; then : + enableval=3D$enable_injection_points; + case $enableval in + yes) + +$as_echo "#define USE_INJECTION_POINTS 1" >>confdefs.h + + ;; + no) + : + ;; + *) + as_fn_error $? "no argument expected for --enable-injection-points o= ption" "$LINENO" 5 + ;; + esac + +else + enable_injection_points=3Dno + +fi + + + + # # Block size # diff --git a/configure.ac b/configure.ac index f220b379b3..af13b6da69 100644 --- a/configure.ac +++ b/configure.ac @@ -250,6 +250,13 @@ PGAC_ARG_BOOL(enable, tap-tests, no, [enable TAP tests (requires Perl and IPC::Run)]) AC_SUBST(enable_tap_tests) =20 +# +# Injection points +# +PGAC_ARG_BOOL(enable, injection-points, no, [enable injection points (for = testing)], + [AC_DEFINE([USE_INJECTION_POINTS], 1, [Define to 1 to build = with injection points. (--enable-injection-points)])]) +AC_SUBST(enable_injection_points) + # # Block size # diff --git a/meson.build b/meson.build index 88a9d9051f..fc26a02bb1 100644 --- a/meson.build +++ b/meson.build @@ -431,6 +431,7 @@ meson_bin =3D find_program(meson_binpath, native: true) ############################################################### =20 cdata.set('USE_ASSERT_CHECKING', get_option('cassert') ? 1 : false) +cdata.set('USE_INJECTION_POINTS', get_option('injection_points') ? 1 : fal= se) =20 blocksize =3D get_option('blocksize').to_int() * 1024 =20 diff --git a/meson_options.txt b/meson_options.txt index d2f95cfec3..8d0d35636d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -43,6 +43,9 @@ option('cassert', type: 'boolean', value: false, option('tap_tests', type: 'feature', value: 'auto', description: 'Enable TAP tests') =20 +option('injection_points', type: 'boolean', value: false, + description: 'Enable injection points') + option('PG_TEST_EXTRA', type: 'string', value: '', description: 'Enable selected extra tests') =20 diff --git a/src/Makefile.global.in b/src/Makefile.global.in index b3ca6392a6..ceb5895d1b 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -203,6 +203,7 @@ enable_nls =3D @enable_nls@ enable_debug =3D @enable_debug@ enable_dtrace =3D @enable_dtrace@ enable_coverage =3D @enable_coverage@ +enable_injection_points =3D @enable_injection_points@ enable_tap_tests =3D @enable_tap_tests@ =20 python_includespec =3D @python_includespec@ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs= =2Elist index 87c1aee379..b069a84c61 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1137,6 +1137,7 @@ IdentLine IdentifierLookup IdentifySystemCmd IfStackElem +InjectionPointEntryByName ImportForeignSchemaStmt ImportForeignSchemaType ImportForeignSchema_function --=20 2.42.0 --GuU21jJbo1igLt/t-- --7TXeHY91KO05Zh3w Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmVJ7swACgkQnvQgOdby QH2fgQ/+OxRRdJAZBa8lP0+1j+J8okXmUoi2BDEor8UENE0SPCXusgI41GtEBmNK RtCwHU074om9vp9k6g8hlpHSmEibzovYZ985oGvcZJTD5kxITGvFC46Y6EcbWn96 JJif/WxRHuuoXGWJJP17lIPnT3MhOBHBKUv9KEAkOqP/fZobagYG9etkqnhWZWJf VxLRrIMJ4+Ie3cWF4YIbw8iVcrPODNuCyjBRSXwesyConsZAnbs9/JLq3ymK4exV dBL9aduMXnmJe8rOAQZw0pkIccHLLRbvoUPaKJTE9JyE/bj4rKn8Yn5sZ4vWGQ9r Ny02YP19HGv03Xi0yx7IlzdeoSslx7Pt/HUhoNnpeTkVNw9raJ/2AUlD2JQR9O29 W0pypyfeZoiJymVKRN+r5GrSHDneIjaLC43tMgM2wdeVBOYjoXX07DDYR8ZYCt81 tli4PI6e7AqFt9YblAxQr93apDheqbxCDz0QacCqkHu7v/3xDL7XZdE9Iu7S5FS0 d2f3xABhhvvhGZCVOAba/XZG1H48jZiCzsPdyJ39RNg5Hii24ZIWuwAfB1AhXgQ8 11+Frlb16wbqST/af3UL6ipSslsPjzgh1DtBD2DBB0F044p0ZPz/83s3Ef60YJdB jH41MN6fJD2hpWea5TAe52hDLMp6z3LA7OebdQAuJMMG6wCmTAc= =wo1h -----END PGP SIGNATURE----- --7TXeHY91KO05Zh3w--