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.96) (envelope-from ) id 1wh2Td-0000nQ-0i for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Jul 2026 09:52:30 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wh2Sc-0008GQ-1F for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Jul 2026 09:51:27 +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.96) (envelope-from ) id 1wh2Sb-0008GG-2G for pgsql-hackers@lists.postgresql.org; Tue, 07 Jul 2026 09:51:26 +0000 Received: from forwardcorp1b.mail.yandex.net ([2a02:6b8:c02:900:1:45:d181:df01]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wh2SY-000000000H0-3A66 for pgsql-hackers@lists.postgresql.org; Tue, 07 Jul 2026 09:51:25 +0000 Received: from mail-nwsmtp-smtp-corp-canary-81.sas.yp-c.yandex.net (mail-nwsmtp-smtp-corp-canary-81.sas.yp-c.yandex.net [IPv6:2a02:6b8:c1b:16:0:640:bc52:0]) by forwardcorp1b.mail.yandex.net (postfix) with ESMTPS id 12D72807E1; Tue, 07 Jul 2026 12:51:17 +0300 (MSK) Received: from smtpclient.apple (unknown [2a02:6bf:8080:32::1:f]) by mail-nwsmtp-smtp-corp-canary-81.sas.yp-c.yandex.net (smtpcorp) with ESMTPSA id FpEQmL2Yt4Y0-1HNHK4xR; Tue, 07 Jul 2026 12:51:16 +0300 X-Yandex-Fwd: 1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yandex-team.ru; s=default; t=1783417876; bh=nxX0uYprV4+gyHXhMC9eV8Ty3fqy40OrCQ+7OOnY8Sc=; h=References:To:Cc:In-Reply-To:Date:From:Message-Id:Subject; b=dQRJjprPGexRqKpFdDC5gB1etZDxiwz7i9r/OvNm/YvkCi7Y1A5IrFz4BOVOLpfX7 sM5OTvFZ7nKJQije93z84fk3cDWrygLEeVH650P2RmWzAC6jMyHEc5kW74guHzVdpV sf0ljzEIS1tF4mwTXpbcln1XLJhHBZG57IYUgOsk= Authentication-Results: mail-nwsmtp-smtp-corp-canary-81.sas.yp-c.yandex.net; dkim=pass header.i=@yandex-team.ru From: Andrey Borodin Message-Id: <45DAF9CC-FAFB-4AC3-9E31-6EB77644E671@yandex-team.ru> Content-Type: multipart/mixed; boundary="Apple-Mail=_36B7F92E-0DB3-4CF5-B3F8-4B1244543A8E" Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.600.51.1.1\)) Subject: Re: injection_points: Switch wait/wakeup to use atomics rather than latches Date: Tue, 7 Jul 2026 14:51:04 +0500 In-Reply-To: Cc: Robert Haas , Postgres hackers , Heikki Linnakangas To: Michael Paquier References: <46821C66-E6B2-420D-BB83-E20EF36665B8@yandex-team.ru> <688F6266-3901-4C84-B27A-AF5904801A3A@yandex-team.ru> <0C7D1930-F8BD-45D3-980B-841F1DBE1204@yandex-team.ru> X-Mailer: Apple Mail (2.3864.600.51.1.1) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Apple-Mail=_36B7F92E-0DB3-4CF5-B3F8-4B1244543A8E Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=us-ascii Hi Michael, You suggested driving the module's wait points through the filesystem instead of mapping shared memory out of process. Here is that, rebased on your atomics commit. It is a test-module-only change; the core injection point registry is untouched. This message and the patch is AI-editorialized for readability. Design ------ Control dirs and files live under pg_injection_points/ in the data directory: pg_injection_points// attaches as a wait point pg_injection_points// that backend is waiting at = The module scans this directory once at postmaster startup and attaches a wait point for each subdirectory, so a test can arm a point before the server is up (shared_preload_libraries is required for that). When a process reaches the point it creates its file and waits, polling with stat() until the file is removed, in addition to the existing wakeup counter. So an out-of-process test can: - arm a point with mkdir, before SQL is available; - see which backends are blocked by listing the directory; - wake one specific backend by unlinking its file. No SQL connection, no platform-specific code, and no out-of-process access to shared memory. The scan is deliberately one-shot at startup. If a consumer ever needs to (re)load points from the filesystem at runtime, that can be added later as a dedicated injection point action (say a "reload" type); I think we grow the facility only as the tests actually require. The test question ----------------- But in this project we grow the facility only when a test actually needs it, so the real question is which test justifies this one. The first case that seems to call for it is the ProcKill lock-group / procLatch recycle race [0]. On that thread you concluded that a wait point inside ProcKill() cannot use the latch-based wait, because the fix (84b9d6bceab6) now disowns the latch earlier, and that the test had to lean on statement_timeout to keep the leader parked long enough - which is an anti-pattern. You (IDK, maybe someone else) suggested switching the wait to a latch-free shmem flag on HEAD; that is your = atomics commit, and this patch builds on it. What the filesystem adds on top is the observability that was still awkward there. Instead of statement_timeout and an unreliable pg_stat_activity (or query_until banners a la 011_lock_stats.pl), the controller just waits for pg_injection_points// to appear and then wakes that exact PID. It also sidesteps the before_shmem_exit(injection_points_cleanup) hook that detaches a self-attached point before ProcKill runs, since the point is armed by the startup scan rather than by the victim. So the question is whether to rewrite that reproducer on top of these two patches. This design goes far beyond the minimal plan you sketched (latch-free wait, attach() with an optional PID, then the test), and that you were not sure the ProcKill case alone justifies the churn. Before writing it I would confirm it really needs the filesystem parts and is not better served by something simpler. Alternatively, we can use new capability for some other tests. WDYT? Thank you! Best regards, Andrey Borodin. [0] = https://www.postgresql.org/message-id/d2983796-2603-41b7-a66e-fc8489ddb954= @gmail.com --Apple-Mail=_36B7F92E-0DB3-4CF5-B3F8-4B1244543A8E Content-Disposition: attachment; filename=v2026-07-07-0002-injection_points-drive-wait-points-throu.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="v2026-07-07-0002-injection_points-drive-wait-points-throu.patch" Content-Transfer-Encoding: quoted-printable =46rom=208c9712d9dfcff54804781b95cd55675eb2688206=20Mon=20Sep=2017=20= 00:00:00=202001=0AFrom:=20Andrey=20Borodin=20=0ADate:=20= Tue,=2016=20Jun=202026=2015:17:28=20+0500=0ASubject:=20[PATCH=20= v2026-07-07=202/2]=20injection_points:=20drive=20wait=20points=20through=0A= =20the=20filesystem,=20without=20SQL=0A=0AAdd=20a=20filesystem-based=20= way=20to=20attach=20a=20wait=20point=20and=20release=20a=20specific=0A= waiter,=20for=20code=20paths=20that=20run=20before=20the=20server=20can=20= answer=20SQL=20(e.g.=20early=0Apostmaster=20startup).=20=20It=20works=20= alongside=20the=20SQL=20path=20and=20does=20not=20touch=20the=0Acore=20= registry.=0A=0AState=20lives=20under=20pg_injection_points/=20in=20the=20= data=20directory:=0A=0A=20=20=20=20pg_injection_points//=20=20=20=20= =20=20=20=20=20present=20->=20=20attached=20as=20a=20wait=0A=20=20= =20=20pg_injection_points//=20=20=20=20present=20->=20that=20= backend=20is=20parked=20here=0A=0AEach=20subdirectory=20is=20scanned=20= once=20at=20startup=20and=20attached,=20so=0Ashared_preload_libraries=20= is=20needed=20to=20arm=20a=20point=20before=20startup.=20=20A=20backend=0A= reaching=20the=20point=20publishes=20its=20=20file=20and=20polls=20= with=20stat()=20until=20it=20is=0Aremoved,=20besides=20watching=20the=20= wakeup=20counter;=20removing=20the=20file=20wakes=20that=20one=0A= backend,=20and=20listing=20the=20directory=20shows=20which=20backends=20= are=20blocked.=20=20The=20tree=0Ais=20dropped=20at=20shutdown.=0A---=0A=20= src/test/modules/injection_points/Makefile=20=20=20=20|=20=20=202=20+=0A=20= .../injection_points/injection_points.c=20=20=20=20=20=20=20|=20141=20= +++++++++++++++++-=0A=20src/test/modules/injection_points/meson.build=20= |=20=20=208=20+=0A=20.../t/001_wait_without_sql.pl=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20|=20=2086=20+++++++++++=0A=204=20files=20= changed,=20234=20insertions(+),=203=20deletions(-)=0A=20create=20mode=20= 100644=20src/test/modules/injection_points/t/001_wait_without_sql.pl=0A=0A= diff=20--git=20a/src/test/modules/injection_points/Makefile=20= b/src/test/modules/injection_points/Makefile=0Aindex=20= c01d2fb095c..3aca8c389de=20100644=0A---=20= a/src/test/modules/injection_points/Makefile=0A+++=20= b/src/test/modules/injection_points/Makefile=0A@@=20-24,6=20+24,8=20@@=20= ISOLATION=20=3D=20basic=20\=0A=20#=20some=20isolation=20tests=20require=20= wal_level=3Dreplica=0A=20ISOLATION_OPTS=20=3D=20--temp-config=20= $(top_srcdir)/src/test/modules/injection_points/extra.conf=0A=20=0A= +TAP_TESTS=20=3D=201=0A+=0A=20#=20The=20injection=20points=20are=20= cluster-wide,=20so=20disable=20installcheck=0A=20NO_INSTALLCHECK=20=3D=20= 1=0A=20=0Adiff=20--git=20= a/src/test/modules/injection_points/injection_points.c=20= b/src/test/modules/injection_points/injection_points.c=0Aindex=20= 9b8e1aaad0b..d0a7d987250=20100644=0A---=20= a/src/test/modules/injection_points/injection_points.c=0A+++=20= b/src/test/modules/injection_points/injection_points.c=0A@@=20-17,6=20= +17,10=20@@=0A=20=0A=20#include=20"postgres.h"=0A=20=0A+#include=20= =0A+#include=20=0A+=0A+#include=20= "common/file_utils.h"=0A=20#include=20"fmgr.h"=0A=20#include=20= "funcapi.h"=0A=20#include=20"injection_points.h"=0A@@=20-24,6=20+28,7=20= @@=0A=20#include=20"nodes/pg_list.h"=0A=20#include=20"nodes/value.h"=0A=20= #include=20"storage/dsm_registry.h"=0A+#include=20"storage/fd.h"=0A=20= #include=20"storage/ipc.h"=0A=20#include=20"storage/lwlock.h"=0A=20= #include=20"storage/shmem.h"=0A@@=20-41,6=20+46,39=20@@=20= PG_MODULE_MAGIC;=0A=20#define=20INJ_MAX_WAIT=098=0A=20#define=20= INJ_NAME_MAXLEN=0964=0A=20=0A+/*=0A+=20*=20Filesystem=20markers=20used=20= to=20drive=20wait=20points=20without=20any=20SQL=20connection,=20for=0A+=20= *=20code=20paths=20that=20run=20before=20the=20server=20can=20answer=20= SQL=20(e.g.=20early=20postmaster=0A+=20*=20startup).=20=20They=20live=20= under=20a=20directory=20in=20the=20data=20directory,=20one=0A+=20*=20= subdirectory=20per=20attached=20point=20and=20one=20file=20per=20parked=20= process:=0A+=20*=0A+=20*=09=20=20pg_injection_points//=20=20=20=20= =20=20=20=20=20present=20=3D=20=20attached=20as=20a=20wait;=0A+=20= *=09=20=20pg_injection_points//=20=20=20=20present=20=3D=20= that=20process=20is=20parked=20here.=0A+=20*=0A+=20*=20A=20test=20= attaches=20a=20point=20by=20creating=20its=20directory=20(scanned=20at=20= startup),=20sees=0A+=20*=20who=20is=20waiting=20by=20listing=20the=20= directory,=20and=20wakes=20a=20specific=20waiter=20by=0A+=20*=20removing=20= its=20PID=20file.=20=20Everything=20is=20plain=20filesystem=20state=20= checked=20with=0A+=20*=20stat(),=20so=20there=20is=20no=20= platform-specific=20logic=20and=20no=20out-of-process=20access=0A+=20*=20= to=20shared=20memory.=20=20Paths=20are=20relative=20to=20the=20data=20= directory,=20which=20is=20the=0A+=20*=20working=20directory=20of=20the=20= postmaster=20and=20every=20backend.=0A+=20*/=0A+#define=20INJ_POINTS_DIR=09= =09"pg_injection_points"=0A+=0A+/*=20"pg_injection_points/"=20*/=0A= +static=20void=0A+injection_point_dir(char=20*buf,=20size_t=20bufsize,=20= const=20char=20*name)=0A+{=0A+=09snprintf(buf,=20bufsize,=20"%s/%s",=20= INJ_POINTS_DIR,=20name);=0A+}=0A+=0A+/*=20= "pg_injection_points//"=20*/=0A+static=20void=0A= +injection_point_waiter_path(char=20*buf,=20size_t=20bufsize,=0A+=09=09=09= =09=09=09=09const=20char=20*name,=20int=20pid)=0A+{=0A+=09snprintf(buf,=20= bufsize,=20"%s/%s/%d",=20INJ_POINTS_DIR,=20name,=20pid);=0A+}=0A+=0A=20= /*=0A=20=20*=20List=20of=20injection=20points=20stored=20in=20= TopMemoryContext=20attached=0A=20=20*=20locally=20to=20this=20process.=0A= @@=20-113,6=20+151,60=20@@=20injection_shmem_request(void=20*arg)=0A=20=09= =09);=0A=20}=0A=20=0A+/*=0A+=20*=20Scan=20INJ_POINTS_DIR=20for=20point=20= subdirectories=20and=20attach=20each=20as=20a=20wait=20point.=0A+=20*=20= Run=20once=20at=20postmaster=20startup=20so=20the=20points=20are=20in=20= place=20before=20any=20process=0A+=20*=20-=20the=20postmaster's=20own=20= startup=20sequence=20or=20its=20children=20-=20reaches=20them,=20all=0A+=20= *=20without=20an=20SQL=20connection.=20=20A=20point=20can=20therefore=20= be=20attached,=20by=20creating=0A+=20*=20its=20directory,=20before=20the=20= server=20is=20started.=0A+=20*/=0A+static=20void=0A= +injection_points_preload(void)=0A+{=0A+=09DIR=09=09=20=20=20*dir;=0A+=09= struct=20dirent=20*de;=0A+=0A+=09dir=20=3D=20= AllocateDir(INJ_POINTS_DIR);=0A+=09if=20(dir=20=3D=3D=20NULL)=0A+=09=09= return;=09=09=09=09=09/*=20no=20directory=20means=20nothing=20attached=20= */=0A+=0A+=09while=20((de=20=3D=20ReadDir(dir,=20INJ_POINTS_DIR))=20!=3D=20= NULL)=0A+=09{=0A+=09=09InjectionPointCondition=20condition=20=3D=20= {.type=20=3D=20INJ_CONDITION_ALWAYS};=0A+=09=09char=09=09= path[MAXPGPATH];=0A+=09=09struct=20stat=20st;=0A+=0A+=09=09if=20= (strcmp(de->d_name,=20".")=20=3D=3D=200=20||=20strcmp(de->d_name,=20= "..")=20=3D=3D=200)=0A+=09=09=09continue;=0A+=09=09if=20= (strlen(de->d_name)=20>=3D=20INJ_NAME_MAXLEN)=0A+=09=09=09continue;=0A+=0A= +=09=09/*=20Each=20subdirectory=20names=20a=20wait=20point=20to=20= attach.=20*/=0A+=09=09injection_point_dir(path,=20sizeof(path),=20= de->d_name);=0A+=09=09if=20(stat(path,=20&st)=20!=3D=200=20||=20= !S_ISDIR(st.st_mode))=0A+=09=09=09continue;=0A+=0A+=09=09= InjectionPointAttach(de->d_name,=20"injection_points",=20= "injection_wait",=0A+=09=09=09=09=09=09=09=20&condition,=20= sizeof(condition));=0A+=09}=0A+=09FreeDir(dir);=0A+}=0A+=0A+/*=0A+=20*=20= proc_exit=20callback=20that=20removes=20INJ_POINTS_DIR=20so=20markers=20= do=20not=20survive=20the=0A+=20*=20cluster.=20=20Forked=20children=20= inherit=20this=20callback=20but=20must=20not=20run=20it,=20hence=0A+=20*=20= the=20IsUnderPostmaster=20guard:=20only=20the=20lifecycle=20owner=20= (postmaster,=20or=20a=0A+=20*=20standalone=20backend)=20cleans=20up.=0A+=20= */=0A+static=20void=0A+injection_points_dir_cleanup(int=20code,=20Datum=20= arg)=0A+{=0A+=09struct=20stat=20st;=0A+=0A+=09if=20(!IsUnderPostmaster=20= &&=20stat(INJ_POINTS_DIR,=20&st)=20=3D=3D=200)=0A+=09=09(void)=20= rmtree(INJ_POINTS_DIR,=20true);=0A+}=0A+=0A=20static=20void=0A=20= injection_shmem_init(void=20*arg)=0A=20{=0A@@=20-121,6=20+213,13=20@@=20= injection_shmem_init(void=20*arg)=0A=20=09=20*=20initialization=20using=20= a=20DSM.=0A=20=09=20*/=0A=20=09injection_point_init_state(inj_state,=20= NULL);=0A+=0A+=09/*=0A+=09=20*=20Attach=20any=20wait=20points=20= requested=20out=20of=20band=20through=20marker=20directories,=0A+=09=20*=20= and=20make=20sure=20the=20directory=20does=20not=20outlive=20the=20= cluster.=0A+=09=20*/=0A+=09injection_points_preload();=0A+=09= on_proc_exit(injection_points_dir_cleanup,=200);=0A=20}=0A=20=0A=20/*=0A= @@=20-219,7=20+318,7=20@@=20injection_notice(const=20char=20*name,=20= const=20void=20*private_data,=20void=20*arg)=0A=20=09=09elog(NOTICE,=20= "notice=20triggered=20for=20injection=20point=20%s",=20name);=0A=20}=0A=20= =0A-/*=20Wait=20until=20injection_points_wakeup()=20is=20called=20*/=0A= +/*=20Wait=20until=20released=20by=20injection_points_wakeup()=20or=20a=20= removed=20marker=20file=20*/=0A=20void=0A=20injection_wait(const=20char=20= *name,=20const=20void=20*private_data,=20void=20*arg)=0A=20{=0A@@=20= -227,6=20+326,8=20@@=20injection_wait(const=20char=20*name,=20const=20= void=20*private_data,=20void=20*arg)=0A=20=09int=09=09=09index=20=3D=20= -1;=0A=20=09uint32=09=09injection_wait_event=20=3D=200;=0A=20=09const=20= InjectionPointCondition=20*condition=20=3D=20private_data;=0A+=09char=09=09= waiter_path[MAXPGPATH];=0A+=09bool=09=09have_waiter_file=20=3D=20false;=0A= =20=0A=20=09if=20(inj_state=20=3D=3D=20NULL)=0A=20=09=09= injection_init_shmem();=0A@@=20-262,7=20+363,35=20@@=20= injection_wait(const=20char=20*name,=20const=20void=20*private_data,=20= void=20*arg)=0A=20=09=09=09=20name);=0A=20=0A=20=09/*=0A-=09=20*=20Wait=20= until=20the=20counter=20is=20bumped=20by=20injection_points_wakeup().=0A= +=09=20*=20Publish=20our=20presence=20in=20the=20filesystem=20so=20an=20= out-of-process=20test=20can=20see=0A+=09=20*=20that=20we=20are=20parked=20= here=20and=20release=20us=20with=20no=20SQL=20connection:=20create=0A+=09= =20*=20"pg_injection_points//"=20and=20wait=20until=20it=20is=20= removed.=20=20The=0A+=09=20*=20directories=20are=20created=20on=20= demand,=20so=20this=20works=20for=20points=20attached=0A+=09=20*=20= through=20SQL=20too,=20not=20only=20those=20attached=20from=20a=20marker=20= directory.=0A+=09=20*/=0A+=09{=0A+=09=09char=09=09dirpath[MAXPGPATH];=0A= +=09=09int=09=09=09fd;=0A+=0A+=09=09injection_point_dir(dirpath,=20= sizeof(dirpath),=20name);=0A+=09=09(void)=20= MakePGDirectory(INJ_POINTS_DIR);=0A+=09=09(void)=20= MakePGDirectory(dirpath);=0A+=0A+=09=09= injection_point_waiter_path(waiter_path,=20sizeof(waiter_path),=20name,=0A= +=09=09=09=09=09=09=09=09=09MyProcPid);=0A+=09=09fd=20=3D=20= OpenTransientFile(waiter_path,=20O_RDWR=20|=20O_CREAT=20|=20O_TRUNC);=0A= +=09=09if=20(fd=20>=3D=200)=0A+=09=09{=0A+=09=09=09= CloseTransientFile(fd);=0A+=09=09=09have_waiter_file=20=3D=20true;=0A+=09= =09}=0A+=09}=0A+=0A+=09/*=0A+=09=20*=20Wait=20until=20released,=20either=20= by=20injection_points_wakeup()=20bumping=20the=0A+=09=20*=20counter=20or=20= by=20our=20waiter=20file=20being=20removed.=20=20The=20latter=20needs=20= no=20SQL=0A+=09=20*=20connection,=20so=20it=20works=20in=20code=20paths=20= that=20run=20before=20the=20server=20can=0A+=09=20*=20answer=20queries.=0A= =20=09=20*=0A=20=09=20*=20This=20loop=20starts=20with=20a=20short=20= delay=20for=20responsiveness,=20enlarged=20to=0A=20=09=20*=20ease=20the=20= CPU=20workload=20in=20slower=20environments.=0A@@=20-272,8=20+401,10=20= @@=20injection_wait(const=20char=20*name,=20const=20void=20= *private_data,=20void=20*arg)=0A=20=09= pgstat_report_wait_start(injection_wait_event);=0A=20=09{=0A=20=09=09int=09= =09=09delay_us=20=3D=20INJ_WAIT_INITIAL_US;=0A+=09=09struct=20stat=20st;=0A= =20=0A-=09=09while=20(pg_atomic_read_u32(&inj_state->wait_counts[index])=20= =3D=3D=20old_wait_counts)=0A+=09=09while=20= (pg_atomic_read_u32(&inj_state->wait_counts[index])=20=3D=3D=20= old_wait_counts=20&&=0A+=09=09=09=20=20=20(!have_waiter_file=20||=20= stat(waiter_path,=20&st)=20=3D=3D=200))=0A=20=09=09{=0A=20=09=09=09= CHECK_FOR_INTERRUPTS();=0A=20=09=09=09pg_usleep(delay_us);=0A@@=20-283,6=20= +414,10=20@@=20injection_wait(const=20char=20*name,=20const=20void=20= *private_data,=20void=20*arg)=0A=20=09}=0A=20=09= pgstat_report_wait_end();=0A=20=0A+=09/*=20Clean=20up=20our=20waiter=20= file;=20it=20may=20still=20exist=20after=20a=20counter=20wakeup.=20*/=0A= +=09if=20(have_waiter_file)=0A+=09=09(void)=20unlink(waiter_path);=0A+=0A= =20=09/*=20Remove=20this=20injection=20point=20from=20the=20waiters.=20= */=0A=20=09SpinLockAcquire(&inj_state->lock);=0A=20=09= inj_state->name[index][0]=20=3D=20'\0';=0Adiff=20--git=20= a/src/test/modules/injection_points/meson.build=20= b/src/test/modules/injection_points/meson.build=0Aindex=20= 59dba1cb023..f6b53e1a33f=20100644=0A---=20= a/src/test/modules/injection_points/meson.build=0A+++=20= b/src/test/modules/injection_points/meson.build=0A@@=20-60,4=20+60,12=20= @@=20tests=20+=3D=20{=0A=20=20=20=20=20=20=20'--temp-config',=20= files('extra.conf'),=0A=20=20=20=20=20],=0A=20=20=20},=0A+=20=20'tap':=20= {=0A+=20=20=20=20'env':=20{=0A+=20=20=20=20=20=20= 'enable_injection_points':=20get_option('injection_points')=20?=20'yes'=20= :=20'no',=0A+=20=20=20=20},=0A+=20=20=20=20'tests':=20[=0A+=20=20=20=20=20= =20't/001_wait_without_sql.pl',=0A+=20=20=20=20],=0A+=20=20},=0A=20}=0A= diff=20--git=20= a/src/test/modules/injection_points/t/001_wait_without_sql.pl=20= b/src/test/modules/injection_points/t/001_wait_without_sql.pl=0Anew=20= file=20mode=20100644=0Aindex=2000000000000..8c33e99f24f=0A---=20= /dev/null=0A+++=20= b/src/test/modules/injection_points/t/001_wait_without_sql.pl=0A@@=20= -0,0=20+1,86=20@@=0A+#=20Copyright=20(c)=202026,=20PostgreSQL=20Global=20= Development=20Group=0A+=0A+#=20Exercise=20driving=20a=20wait=20injection=20= point=20purely=20through=20the=20filesystem,=20with=0A+#=20no=20SQL=20= used=20for=20the=20coordination=20itself.=20=20This=20is=20meant=20for=20= code=20paths=20that=0A+#=20run=20before=20the=20server=20can=20answer=20= queries=20(e.g.=20early=20postmaster=20startup).=0A+#=0A+#=20Layout=20= under=20the=20data=20directory:=0A+#=20=20=20= pg_injection_points//=20=20=20=20=20=20=20=20=20present=20=20->=20= =20attached=20as=20a=20wait=0A+#=20=20=20= pg_injection_points//=20=20=20=20present=20=20->=20that=20= backend=20is=20parked=20here=0A+#=20Removing=20the=20=20file=20= wakes=20that=20backend.=0A+=0A+use=20strict;=0A+use=20warnings=20FATAL=20= =3D>=20'all';=0A+=0A+use=20Time::HiRes=20qw(usleep);=0A+=0A+use=20= PostgreSQL::Test::Cluster;=0A+use=20PostgreSQL::Test::Utils;=0A+use=20= Test::More;=0A+=0A+if=20($ENV{enable_injection_points}=20ne=20'yes')=0A= +{=0A+=09plan=20skip_all=20=3D>=20'Injection=20points=20not=20supported=20= by=20this=20build';=0A+}=0A+=0A+my=20$point=20=3D=20'no-sql-wait';=0A+=0A= +my=20$node=20=3D=20PostgreSQL::Test::Cluster->new('node');=0A= +$node->init;=0A+$node->append_conf('postgresql.conf',=0A+=09= "shared_preload_libraries=20=3D=20'injection_points'");=0A+=0A+#=20= Attach=20the=20wait=20point=20before=20the=20server=20is=20even=20= running,=20without=20any=20SQL:=0A+#=20just=20create=20its=20directory.=20= =20The=20module=20scans=20these=20at=20startup.=0A+my=20$root=20=3D=20= $node->data_dir=20.=20'/pg_injection_points';=0A+my=20$pdir=20=3D=20= "$root/$point";=0A+mkdir=20$root=20or=20die=20"could=20not=20create=20= $root:=20$!";=0A+mkdir=20$pdir=20or=20die=20"could=20not=20create=20= $pdir:=20$!";=0A+=0A+$node->start;=0A+=0A+#=20The=20module=20attached=20= the=20point=20from=20its=20directory=20at=20startup;=20we=20never=20= called=0A+#=20injection_points_attach().=0A+$node->safe_psql('postgres',=20= 'CREATE=20EXTENSION=20injection_points;');=0A+=0A+my=20$listed=20=3D=20= $node->safe_psql('postgres',=0A+=09"SELECT=20count(*)=20FROM=20= injection_points_list()=20WHERE=20point_name=20=3D=20'$point';"=0A+);=0A= +is($listed,=20'1',=20'wait=20point=20attached=20from=20its=20directory,=20= without=20SQL');=0A+=0A+#=20Fire=20the=20point=20in=20a=20background=20= session;=20it=20must=20block=20in=20injection_wait().=0A+my=20$bg=20=3D=20= $node->background_psql('postgres');=0A+$bg->query_until(=0A+=09= qr/start/,=20qq[=0A+\\echo=20start=0A+SELECT=20= injection_points_run('$point');=0A+]);=0A+=0A+#=20Detect=20that=20the=20= backend=20is=20parked,=20purely=20through=20the=20filesystem:=20it=0A+#=20= publishes=20a=20file=20named=20after=20its=20PID=20inside=20the=20point=20= directory.=0A+my=20$waiter;=0A+foreach=20my=20$i=20(1=20..=201800)=0A+{=0A= +=09if=20(opendir(my=20$dh,=20$pdir))=0A+=09{=0A+=09=09($waiter)=20=3D=20= grep=20{=20/^\d+\z/=20}=20readdir($dh);=0A+=09=09closedir($dh);=0A+=09=09= last=20if=20defined=20$waiter;=0A+=09}=0A+=09usleep(100_000);=0A+}=0A= +ok(defined=20$waiter,=20'backend=20published=20its=20waiter=20file=20= (filesystem-observable)');=0A+=0A+#=20Wake=20that=20specific=20backend=20= without=20any=20SQL:=20remove=20its=20waiter=20file.=0A+unlink=20= "$pdir/$waiter"=20or=20die=20"could=20not=20remove=20waiter=20file:=20= $!";=0A+=0A+#=20The=20blocked=20statement=20now=20finishes,=20proving=20= the=20filesystem=20wakeup=20worked.=0A+like($bg->query_safe('SELECT=20= 1;'),=0A+=09qr/^1$/m,=20'backend=20released=20by=20removing=20its=20= waiter=20file,=20without=20SQL');=0A+$bg->quit;=0A+=0A+#=20The=20= directory=20does=20not=20survive=20the=20cluster.=0A+$node->stop;=0A= +ok(!-d=20$root,=20'injection=20points=20directory=20removed=20at=20= shutdown');=0A+=0A+done_testing();=0A--=20=0A2.50.1=20(Apple=20Git-155)=0A= =0A= --Apple-Mail=_36B7F92E-0DB3-4CF5-B3F8-4B1244543A8E Content-Disposition: attachment; filename=v2026-07-07-0001-injection_points-Switch-wait-wakeup-to-r.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="v2026-07-07-0001-injection_points-Switch-wait-wakeup-to-r.patch" Content-Transfer-Encoding: quoted-printable =46rom=209eed8010addde7425d675efa123a4b167925ae08=20Mon=20Sep=2017=20= 00:00:00=202001=0AFrom:=20Michael=20Paquier=20=0A= Date:=20Thu,=2028=20May=202026=2011:15:33=20+0900=0ASubject:=20[PATCH=20= v2026-07-07=201/2]=20injection_points:=20Switch=20wait/wakeup=20to=20= rely=0A=20on=20atomics=0A=0AThis=20removes=20the=20dependency=20based=20= on=20counters=20and=20environment=20variables,=0Areplacing=20the=20= waiting=20loop=20by=20a=20wait=20on=20an=20atomic=20counter,=20whose=20= check=0Aincreases=20over=20time=20in=20an=20exponential=20manner=20= (starts=20at=2010us,=20up=20to=0A100ms).=0A---=0A=20= .../injection_points/injection_points.c=20=20=20=20=20=20=20|=2053=20= ++++++++++---------=0A=201=20file=20changed,=2027=20insertions(+),=2026=20= deletions(-)=0A=0Adiff=20--git=20= a/src/test/modules/injection_points/injection_points.c=20= b/src/test/modules/injection_points/injection_points.c=0Aindex=20= ba282e3dcab..9b8e1aaad0b=20100644=0A---=20= a/src/test/modules/injection_points/injection_points.c=0A+++=20= b/src/test/modules/injection_points/injection_points.c=0A@@=20-23,11=20= +23,11=20@@=0A=20#include=20"miscadmin.h"=0A=20#include=20= "nodes/pg_list.h"=0A=20#include=20"nodes/value.h"=0A-#include=20= "storage/condition_variable.h"=0A=20#include=20"storage/dsm_registry.h"=0A= =20#include=20"storage/ipc.h"=0A=20#include=20"storage/lwlock.h"=0A=20= #include=20"storage/shmem.h"=0A+#include=20"storage/spin.h"=0A=20= #include=20"utils/builtins.h"=0A=20#include=20"utils/guc.h"=0A=20= #include=20"utils/injection_point.h"=0A@@=20-59,13=20+59,10=20@@=20= typedef=20struct=20InjectionPointSharedState=0A=20=09slock_t=09=09lock;=0A= =20=0A=20=09/*=20Counters=20advancing=20when=20injection_points_wakeup()=20= is=20called=20*/=0A-=09uint32=09=09wait_counts[INJ_MAX_WAIT];=0A+=09= pg_atomic_uint32=20wait_counts[INJ_MAX_WAIT];=0A=20=0A=20=09/*=20Names=20= of=20injection=20points=20attached=20to=20wait=20counters=20*/=0A=20=09= char=09=09name[INJ_MAX_WAIT][INJ_NAME_MAXLEN];=0A-=0A-=09/*=20Condition=20= variable=20used=20for=20waits=20and=20wakeups=20*/=0A-=09= ConditionVariable=20wait_point;=0A=20}=20InjectionPointSharedState;=0A=20= =0A=20/*=20Pointer=20to=20shared-memory=20state.=20*/=0A@@=20-102,9=20= +99,9=20@@=20injection_point_init_state(void=20*ptr,=20void=20*arg)=0A=20= =09InjectionPointSharedState=20*state=20=3D=20(InjectionPointSharedState=20= *)=20ptr;=0A=20=0A=20=09SpinLockInit(&state->lock);=0A-=09= memset(state->wait_counts,=200,=20sizeof(state->wait_counts));=0A=20=09= memset(state->name,=200,=20sizeof(state->name));=0A-=09= ConditionVariableInit(&state->wait_point);=0A+=09for=20(int=20i=20=3D=20= 0;=20i=20<=20INJ_MAX_WAIT;=20i++)=0A+=09=09= pg_atomic_init_u32(&state->wait_counts[i],=200);=0A=20}=0A=20=0A=20= static=20void=0A@@=20-222,7=20+219,7=20@@=20injection_notice(const=20= char=20*name,=20const=20void=20*private_data,=20void=20*arg)=0A=20=09=09= elog(NOTICE,=20"notice=20triggered=20for=20injection=20point=20%s",=20= name);=0A=20}=0A=20=0A-/*=20Wait=20on=20a=20condition=20variable,=20= awaken=20by=20injection_points_wakeup()=20*/=0A+/*=20Wait=20until=20= injection_points_wakeup()=20is=20called=20*/=0A=20void=0A=20= injection_wait(const=20char=20*name,=20const=20void=20*private_data,=20= void=20*arg)=0A=20{=0A@@=20-254,31=20+251,37=20@@=20injection_wait(const=20= char=20*name,=20const=20void=20*private_data,=20void=20*arg)=0A=20=09=09= {=0A=20=09=09=09index=20=3D=20i;=0A=20=09=09=09= strlcpy(inj_state->name[i],=20name,=20INJ_NAME_MAXLEN);=0A-=09=09=09= old_wait_counts=20=3D=20inj_state->wait_counts[i];=0A+=09=09=09= old_wait_counts=20=3D=20pg_atomic_read_u32(&inj_state->wait_counts[i]);=0A= =20=09=09=09break;=0A=20=09=09}=0A=20=09}=0A=20=09= SpinLockRelease(&inj_state->lock);=0A=20=0A=20=09if=20(index=20<=200)=0A= -=09=09elog(ERROR,=20"could=20not=20find=20free=20slot=20for=20wait=20of=20= injection=20point=20%s=20",=0A+=09=09elog(ERROR,=20"could=20not=20find=20= free=20slot=20for=20wait=20of=20injection=20point=20%s",=0A=20=09=09=09=20= name);=0A=20=0A-=09/*=20And=20sleep..=20*/=0A-=09= ConditionVariablePrepareToSleep(&inj_state->wait_point);=0A-=09for=20= (;;)=0A+=09/*=0A+=09=20*=20Wait=20until=20the=20counter=20is=20bumped=20= by=20injection_points_wakeup().=0A+=09=20*=0A+=09=20*=20This=20loop=20= starts=20with=20a=20short=20delay=20for=20responsiveness,=20enlarged=20= to=0A+=09=20*=20ease=20the=20CPU=20workload=20in=20slower=20= environments.=0A+=09=20*/=0A+#define=20INJ_WAIT_INITIAL_US=09=0910=09/*=20= 10us=20*/=0A+#define=20INJ_WAIT_MAX_US=09=09=09100000=09/*=20100ms=20*/=0A= +=09pgstat_report_wait_start(injection_wait_event);=0A=20=09{=0A-=09=09= uint32=09=09new_wait_counts;=0A+=09=09int=09=09=09delay_us=20=3D=20= INJ_WAIT_INITIAL_US;=0A=20=0A-=09=09SpinLockAcquire(&inj_state->lock);=0A= -=09=09new_wait_counts=20=3D=20inj_state->wait_counts[index];=0A-=09=09= SpinLockRelease(&inj_state->lock);=0A-=0A-=09=09if=20(old_wait_counts=20= !=3D=20new_wait_counts)=0A-=09=09=09break;=0A-=09=09= ConditionVariableSleep(&inj_state->wait_point,=20injection_wait_event);=0A= +=09=09while=20(pg_atomic_read_u32(&inj_state->wait_counts[index])=20=3D=3D= =20old_wait_counts)=0A+=09=09{=0A+=09=09=09CHECK_FOR_INTERRUPTS();=0A+=09= =09=09pg_usleep(delay_us);=0A+=09=09=09if=20(delay_us=20<=20= INJ_WAIT_MAX_US)=0A+=09=09=09=09delay_us=20*=3D=202;=0A+=09=09}=0A=20=09= }=0A-=09ConditionVariableCancelSleep();=0A+=09pgstat_report_wait_end();=0A= =20=0A=20=09/*=20Remove=20this=20injection=20point=20from=20the=20= waiters.=20*/=0A=20=09SpinLockAcquire(&inj_state->lock);=0A@@=20-443,7=20= +446,7=20@@=20injection_points_wakeup(PG_FUNCTION_ARGS)=0A=20=09if=20= (inj_state=20=3D=3D=20NULL)=0A=20=09=09injection_init_shmem();=0A=20=0A-=09= /*=20First=20bump=20the=20wait=20counter=20for=20the=20injection=20point=20= to=20wake=20up=20*/=0A+=09/*=20Find=20the=20injection=20point=20then=20= bump=20its=20wait=20counter=20*/=0A=20=09= SpinLockAcquire(&inj_state->lock);=0A=20=09for=20(int=20i=20=3D=200;=20i=20= <=20INJ_MAX_WAIT;=20i++)=0A=20=09{=0A@@=20-458,11=20+461,9=20@@=20= injection_points_wakeup(PG_FUNCTION_ARGS)=0A=20=09=09= SpinLockRelease(&inj_state->lock);=0A=20=09=09elog(ERROR,=20"could=20not=20= find=20injection=20point=20%s=20to=20wake=20up",=20name);=0A=20=09}=0A-=09= inj_state->wait_counts[index]++;=0A=20=09= SpinLockRelease(&inj_state->lock);=0A=20=0A-=09/*=20And=20broadcast=20= the=20change=20to=20the=20waiters=20*/=0A-=09= ConditionVariableBroadcast(&inj_state->wait_point);=0A+=09= pg_atomic_fetch_add_u32(&inj_state->wait_counts[index],=201);=0A=20=09= PG_RETURN_VOID();=0A=20}=0A=20=0A--=20=0A2.50.1=20(Apple=20Git-155)=0A=0A= --Apple-Mail=_36B7F92E-0DB3-4CF5-B3F8-4B1244543A8E--