1717
1818#include "postgres.h"
1919
20+ #include <fcntl.h>
21+ #include <sys/stat.h>
22+
23+ #include "common/file_utils.h"
2024#include "fmgr.h"
2125#include "funcapi.h"
2226#include "injection_points.h"
2327#include "miscadmin.h"
2428#include "nodes/pg_list.h"
2529#include "nodes/value.h"
2630#include "storage/dsm_registry.h"
31+ #include "storage/fd.h"
2732#include "storage/ipc.h"
2833#include "storage/lwlock.h"
2934#include "storage/shmem.h"
@@ -41,6 +46,39 @@ PG_MODULE_MAGIC;
4146#define INJ_MAX_WAIT 8
4247#define INJ_NAME_MAXLEN 64
4348
49+ /*
50+ * Filesystem markers used to drive wait points without any SQL connection, for
51+ * code paths that run before the server can answer SQL (e.g. early postmaster
52+ * startup). They live under a directory in the data directory, one
53+ * subdirectory per attached point and one file per parked process:
54+ *
55+ * pg_injection_points/<point>/ present = <point> attached as a wait;
56+ * pg_injection_points/<point>/<pid> present = that process is parked here.
57+ *
58+ * A test attaches a point by creating its directory (scanned at startup), sees
59+ * who is waiting by listing the directory, and wakes a specific waiter by
60+ * removing its PID file. Everything is plain filesystem state checked with
61+ * stat(), so there is no platform-specific logic and no out-of-process access
62+ * to shared memory. Paths are relative to the data directory, which is the
63+ * working directory of the postmaster and every backend.
64+ */
65+ #define INJ_POINTS_DIR "pg_injection_points"
66+
67+ /* "pg_injection_points/<point>" */
68+ static void
69+ injection_point_dir (char * buf , size_t bufsize , const char * name )
70+ {
71+ snprintf (buf , bufsize , "%s/%s" , INJ_POINTS_DIR , name );
72+ }
73+
74+ /* "pg_injection_points/<point>/<pid>" */
75+ static void
76+ injection_point_waiter_path (char * buf , size_t bufsize ,
77+ const char * name , int pid )
78+ {
79+ snprintf (buf , bufsize , "%s/%s/%d" , INJ_POINTS_DIR , name , pid );
80+ }
81+
4482/*
4583 * List of injection points stored in TopMemoryContext attached
4684 * locally to this process.
@@ -113,6 +151,60 @@ injection_shmem_request(void *arg)
113151 );
114152}
115153
154+ /*
155+ * Scan INJ_POINTS_DIR for point subdirectories and attach each as a wait point.
156+ * Run once at postmaster startup so the points are in place before any process
157+ * - the postmaster's own startup sequence or its children - reaches them, all
158+ * without an SQL connection. A point can therefore be attached, by creating
159+ * its directory, before the server is started.
160+ */
161+ static void
162+ injection_points_preload (void )
163+ {
164+ DIR * dir ;
165+ struct dirent * de ;
166+
167+ dir = AllocateDir (INJ_POINTS_DIR );
168+ if (dir == NULL )
169+ return ; /* no directory means nothing attached */
170+
171+ while ((de = ReadDir (dir , INJ_POINTS_DIR )) != NULL )
172+ {
173+ InjectionPointCondition condition = {.type = INJ_CONDITION_ALWAYS };
174+ char path [MAXPGPATH ];
175+ struct stat st ;
176+
177+ if (strcmp (de -> d_name , "." ) == 0 || strcmp (de -> d_name , ".." ) == 0 )
178+ continue ;
179+ if (strlen (de -> d_name ) >= INJ_NAME_MAXLEN )
180+ continue ;
181+
182+ /* Each subdirectory names a wait point to attach. */
183+ injection_point_dir (path , sizeof (path ), de -> d_name );
184+ if (stat (path , & st ) != 0 || !S_ISDIR (st .st_mode ))
185+ continue ;
186+
187+ InjectionPointAttach (de -> d_name , "injection_points" , "injection_wait" ,
188+ & condition , sizeof (condition ));
189+ }
190+ FreeDir (dir );
191+ }
192+
193+ /*
194+ * proc_exit callback that removes INJ_POINTS_DIR so markers do not survive the
195+ * cluster. Forked children inherit this callback but must not run it, hence
196+ * the IsUnderPostmaster guard: only the lifecycle owner (postmaster, or a
197+ * standalone backend) cleans up.
198+ */
199+ static void
200+ injection_points_dir_cleanup (int code , Datum arg )
201+ {
202+ struct stat st ;
203+
204+ if (!IsUnderPostmaster && stat (INJ_POINTS_DIR , & st ) == 0 )
205+ (void ) rmtree (INJ_POINTS_DIR , true);
206+ }
207+
116208static void
117209injection_shmem_init (void * arg )
118210{
@@ -121,6 +213,13 @@ injection_shmem_init(void *arg)
121213 * initialization using a DSM.
122214 */
123215 injection_point_init_state (inj_state , NULL );
216+
217+ /*
218+ * Attach any wait points requested out of band through marker directories,
219+ * and make sure the directory does not outlive the cluster.
220+ */
221+ injection_points_preload ();
222+ on_proc_exit (injection_points_dir_cleanup , 0 );
124223}
125224
126225/*
@@ -219,14 +318,16 @@ injection_notice(const char *name, const void *private_data, void *arg)
219318 elog (NOTICE , "notice triggered for injection point %s" , name );
220319}
221320
222- /* Wait until injection_points_wakeup() is called */
321+ /* Wait until released by injection_points_wakeup() or a removed marker file */
223322void
224323injection_wait (const char * name , const void * private_data , void * arg )
225324{
226325 uint32 old_wait_counts = 0 ;
227326 int index = -1 ;
228327 uint32 injection_wait_event = 0 ;
229328 const InjectionPointCondition * condition = private_data ;
329+ char waiter_path [MAXPGPATH ];
330+ bool have_waiter_file = false;
230331
231332 if (inj_state == NULL )
232333 injection_init_shmem ();
@@ -262,7 +363,35 @@ injection_wait(const char *name, const void *private_data, void *arg)
262363 name );
263364
264365 /*
265- * Wait until the counter is bumped by injection_points_wakeup().
366+ * Publish our presence in the filesystem so an out-of-process test can see
367+ * that we are parked here and release us with no SQL connection: create
368+ * "pg_injection_points/<name>/<pid>" and wait until it is removed. The
369+ * directories are created on demand, so this works for points attached
370+ * through SQL too, not only those attached from a marker directory.
371+ */
372+ {
373+ char dirpath [MAXPGPATH ];
374+ int fd ;
375+
376+ injection_point_dir (dirpath , sizeof (dirpath ), name );
377+ (void ) MakePGDirectory (INJ_POINTS_DIR );
378+ (void ) MakePGDirectory (dirpath );
379+
380+ injection_point_waiter_path (waiter_path , sizeof (waiter_path ), name ,
381+ MyProcPid );
382+ fd = OpenTransientFile (waiter_path , O_RDWR | O_CREAT | O_TRUNC );
383+ if (fd >= 0 )
384+ {
385+ CloseTransientFile (fd );
386+ have_waiter_file = true;
387+ }
388+ }
389+
390+ /*
391+ * Wait until released, either by injection_points_wakeup() bumping the
392+ * counter or by our waiter file being removed. The latter needs no SQL
393+ * connection, so it works in code paths that run before the server can
394+ * answer queries.
266395 *
267396 * This loop starts with a short delay for responsiveness, enlarged to
268397 * ease the CPU workload in slower environments.
@@ -272,8 +401,10 @@ injection_wait(const char *name, const void *private_data, void *arg)
272401 pgstat_report_wait_start (injection_wait_event );
273402 {
274403 int delay_us = INJ_WAIT_INITIAL_US ;
404+ struct stat st ;
275405
276- while (pg_atomic_read_u32 (& inj_state -> wait_counts [index ]) == old_wait_counts )
406+ while (pg_atomic_read_u32 (& inj_state -> wait_counts [index ]) == old_wait_counts &&
407+ (!have_waiter_file || stat (waiter_path , & st ) == 0 ))
277408 {
278409 CHECK_FOR_INTERRUPTS ();
279410 pg_usleep (delay_us );
@@ -283,6 +414,10 @@ injection_wait(const char *name, const void *private_data, void *arg)
283414 }
284415 pgstat_report_wait_end ();
285416
417+ /* Clean up our waiter file; it may still exist after a counter wakeup. */
418+ if (have_waiter_file )
419+ (void ) unlink (waiter_path );
420+
286421 /* Remove this injection point from the waiters. */
287422 SpinLockAcquire (& inj_state -> lock );
288423 inj_state -> name [index ][0 ] = '\0' ;
0 commit comments