Age Owner Branch data TLA Line data Source code
1 : : /* -------------------------------------------------------------------------
2 : : *
3 : : * worker_spi.c
4 : : * Sample background worker code that demonstrates various coding
5 : : * patterns: establishing a database connection; starting and committing
6 : : * transactions; using GUC variables, and heeding SIGHUP to reread
7 : : * the configuration file; reporting to pg_stat_activity; using the
8 : : * process latch to sleep and exit in case of postmaster death.
9 : : *
10 : : * This code connects to a database, creates a schema and table, and summarizes
11 : : * the numbers contained therein. To see it working, insert an initial value
12 : : * with "total" type and some initial value; then insert some other rows with
13 : : * "delta" type. Delta rows will be deleted by this worker and their values
14 : : * aggregated into the total.
15 : : *
16 : : * Copyright (c) 2013-2025, PostgreSQL Global Development Group
17 : : *
18 : : * IDENTIFICATION
19 : : * src/test/modules/worker_spi/worker_spi.c
20 : : *
21 : : * -------------------------------------------------------------------------
22 : : */
23 : : #include "postgres.h"
24 : :
25 : : /* These are always necessary for a bgworker */
26 : : #include "miscadmin.h"
27 : : #include "postmaster/bgworker.h"
28 : : #include "postmaster/interrupt.h"
29 : : #include "storage/latch.h"
30 : :
31 : : /* these headers are used by this particular worker's code */
32 : : #include "access/xact.h"
33 : : #include "catalog/pg_database.h"
34 : : #include "executor/spi.h"
35 : : #include "fmgr.h"
36 : : #include "lib/stringinfo.h"
37 : : #include "pgstat.h"
38 : : #include "tcop/utility.h"
39 : : #include "utils/acl.h"
40 : : #include "utils/builtins.h"
41 : : #include "utils/snapmgr.h"
42 : :
4709 alvherre@alvh.no-ip. 43 :CBC 5 : PG_MODULE_MAGIC;
44 : :
4487 rhaas@postgresql.org 45 : 8 : PG_FUNCTION_INFO_V1(worker_spi_launch);
46 : :
47 : : PGDLLEXPORT pg_noreturn void worker_spi_main(Datum main_arg);
48 : :
49 : : /* GUC variables */
50 : : static int worker_spi_naptime = 10;
51 : : static int worker_spi_total_workers = 2;
52 : : static char *worker_spi_database = NULL;
53 : : static char *worker_spi_role = NULL;
54 : :
55 : : /* value cached, fetched from shared memory */
56 : : static uint32 worker_spi_wait_event_main = 0;
57 : :
58 : : typedef struct worktable
59 : : {
60 : : const char *schema;
61 : : const char *name;
62 : : } worktable;
63 : :
64 : : /*
65 : : * Initialize workspace for a worker process: create the schema if it doesn't
66 : : * already exist.
67 : : */
68 : : static void
4709 alvherre@alvh.no-ip. 69 : 1 : initialize_worker_spi(worktable *table)
70 : : {
71 : : int ret;
72 : : int ntup;
73 : : bool isnull;
74 : : StringInfoData buf;
75 : :
4584 76 : 1 : SetCurrentStatementStartTimestamp();
4709 77 : 1 : StartTransactionCommand();
78 : 1 : SPI_connect();
79 : 1 : PushActiveSnapshot(GetTransactionSnapshot());
2980 peter_e@gmx.net 80 : 1 : pgstat_report_activity(STATE_RUNNING, "initializing worker_spi schema");
81 : :
82 : : /* XXX could we use CREATE SCHEMA IF NOT EXISTS? */
4709 alvherre@alvh.no-ip. 83 : 1 : initStringInfo(&buf);
84 : 1 : appendStringInfo(&buf, "select count(*) from pg_namespace where nspname = '%s'",
85 : : table->schema);
86 : :
1823 noah@leadboat.com 87 : 1 : debug_query_string = buf.data;
4709 alvherre@alvh.no-ip. 88 : 1 : ret = SPI_execute(buf.data, true, 0);
89 [ - + ]: 1 : if (ret != SPI_OK_SELECT)
4709 alvherre@alvh.no-ip. 90 [ # # ]:UBC 0 : elog(FATAL, "SPI_execute failed: error code %d", ret);
91 : :
4709 alvherre@alvh.no-ip. 92 [ - + ]:CBC 1 : if (SPI_processed != 1)
4709 alvherre@alvh.no-ip. 93 [ # # ]:UBC 0 : elog(FATAL, "not a singleton result");
94 : :
4438 stark@mit.edu 95 :CBC 1 : ntup = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
4709 alvherre@alvh.no-ip. 96 : 1 : SPI_tuptable->tupdesc,
97 : : 1, &isnull));
98 [ - + ]: 1 : if (isnull)
4709 alvherre@alvh.no-ip. 99 [ # # ]:UBC 0 : elog(FATAL, "null result");
100 : :
4709 alvherre@alvh.no-ip. 101 [ + - ]:CBC 1 : if (ntup == 0)
102 : : {
1823 noah@leadboat.com 103 : 1 : debug_query_string = NULL;
4709 alvherre@alvh.no-ip. 104 : 1 : resetStringInfo(&buf);
105 : 1 : appendStringInfo(&buf,
106 : : "CREATE SCHEMA \"%s\" "
107 : : "CREATE TABLE \"%s\" ("
108 : : " type text CHECK (type IN ('total', 'delta')), "
109 : : " value integer)"
110 : : "CREATE UNIQUE INDEX \"%s_unique_total\" ON \"%s\" (type) "
111 : : "WHERE type = 'total'",
112 : : table->schema, table->name, table->name, table->name);
113 : :
114 : : /* set statement start time */
4584 115 : 1 : SetCurrentStatementStartTimestamp();
116 : :
1823 noah@leadboat.com 117 : 1 : debug_query_string = buf.data;
4709 alvherre@alvh.no-ip. 118 : 1 : ret = SPI_execute(buf.data, false, 0);
119 : :
120 [ - + ]: 1 : if (ret != SPI_OK_UTILITY)
4709 alvherre@alvh.no-ip. 121 [ # # ]:UBC 0 : elog(FATAL, "failed to create my schema");
122 : :
1823 noah@leadboat.com 123 :CBC 1 : debug_query_string = NULL; /* rest is not statement-specific */
124 : : }
125 : :
4709 alvherre@alvh.no-ip. 126 : 1 : SPI_finish();
127 : 1 : PopActiveSnapshot();
128 : 1 : CommitTransactionCommand();
1823 noah@leadboat.com 129 : 1 : debug_query_string = NULL;
4584 alvherre@alvh.no-ip. 130 : 1 : pgstat_report_activity(STATE_IDLE, NULL);
4709 131 : 1 : }
132 : :
133 : : void
4487 rhaas@postgresql.org 134 : 3 : worker_spi_main(Datum main_arg)
135 : : {
136 : 3 : int index = DatumGetInt32(main_arg);
137 : : worktable *table;
138 : : StringInfoData buf;
139 : : char name[20];
140 : : Oid dboid;
141 : : Oid roleoid;
142 : : char *p;
754 michael@paquier.xyz 143 : 3 : bits32 flags = 0;
144 : :
4487 rhaas@postgresql.org 145 : 3 : table = palloc(sizeof(worktable));
146 : 3 : sprintf(name, "schema%d", index);
147 : 3 : table->schema = pstrdup(name);
148 : 3 : table->name = pstrdup("counted");
149 : :
150 : : /* fetch database and role OIDs, these are set for a dynamic worker */
754 michael@paquier.xyz 151 : 3 : p = MyBgworkerEntry->bgw_extra;
152 : 3 : memcpy(&dboid, p, sizeof(Oid));
153 : 3 : p += sizeof(Oid);
154 : 3 : memcpy(&roleoid, p, sizeof(Oid));
155 : 3 : p += sizeof(Oid);
156 : 3 : memcpy(&flags, p, sizeof(bits32));
157 : :
158 : : /* Establish signal handlers before unblocking signals. */
1796 fujii@postgresql.org 159 : 3 : pqsignal(SIGHUP, SignalHandlerForConfigReload);
160 : 3 : pqsignal(SIGTERM, die);
161 : :
162 : : /* We're now ready to receive signals */
4709 alvherre@alvh.no-ip. 163 : 3 : BackgroundWorkerUnblockSignals();
164 : :
165 : : /* Connect to our database */
754 michael@paquier.xyz 166 [ + - ]: 3 : if (OidIsValid(dboid))
167 : 3 : BackgroundWorkerInitializeConnectionByOid(dboid, roleoid, flags);
168 : : else
754 michael@paquier.xyz 169 :UBC 0 : BackgroundWorkerInitializeConnection(worker_spi_database,
170 : : worker_spi_role, flags);
171 : :
4709 alvherre@alvh.no-ip. 172 [ + - ]:CBC 1 : elog(LOG, "%s initialized with %s.%s",
173 : : MyBgworkerEntry->bgw_name, table->schema, table->name);
174 : 1 : initialize_worker_spi(table);
175 : :
176 : : /*
177 : : * Quote identifiers passed to us. Note that this must be done after
178 : : * initialize_worker_spi, because that routine assumes the names are not
179 : : * quoted.
180 : : *
181 : : * Note some memory might be leaked here.
182 : : */
183 : 1 : table->schema = quote_identifier(table->schema);
184 : 1 : table->name = quote_identifier(table->name);
185 : :
186 : 1 : initStringInfo(&buf);
187 : 1 : appendStringInfo(&buf,
188 : : "WITH deleted AS (DELETE "
189 : : "FROM %s.%s "
190 : : "WHERE type = 'delta' RETURNING value), "
191 : : "total AS (SELECT coalesce(sum(value), 0) as sum "
192 : : "FROM deleted) "
193 : : "UPDATE %s.%s "
194 : : "SET value = %s.value + total.sum "
195 : : "FROM total WHERE type = 'total' "
196 : : "RETURNING %s.value",
197 : : table->schema, table->name,
198 : : table->schema, table->name,
199 : : table->name,
200 : : table->name);
201 : :
202 : : /*
203 : : * Main loop: do this until SIGTERM is received and processed by
204 : : * ProcessInterrupts.
205 : : */
206 : : for (;;)
207 : 2 : {
208 : : int ret;
209 : :
210 : : /* First time, allocate or get the custom wait event */
806 michael@paquier.xyz 211 [ + + ]: 3 : if (worker_spi_wait_event_main == 0)
755 212 : 1 : worker_spi_wait_event_main = WaitEventExtensionNew("WorkerSpiMain");
213 : :
214 : : /*
215 : : * Background workers mustn't call usleep() or any direct equivalent:
216 : : * instead, they may wait on their process latch, which sleeps as
217 : : * necessary, but is awakened if postmaster dies. That way the
218 : : * background process goes away immediately in an emergency.
219 : : */
2531 alvherre@alvh.no-ip. 220 : 3 : (void) WaitLatch(MyLatch,
221 : : WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
222 : : worker_spi_naptime * 1000L,
223 : : worker_spi_wait_event_main);
3940 andres@anarazel.de 224 : 3 : ResetLatch(MyLatch);
225 : :
3066 226 [ + + ]: 3 : CHECK_FOR_INTERRUPTS();
227 : :
228 : : /*
229 : : * In case of a SIGHUP, just reload the configuration.
230 : : */
1796 fujii@postgresql.org 231 [ + + ]: 2 : if (ConfigReloadPending)
232 : : {
233 : 1 : ConfigReloadPending = false;
4535 bruce@momjian.us 234 : 1 : ProcessConfigFile(PGC_SIGHUP);
235 : : }
236 : :
237 : : /*
238 : : * Start a transaction on which we can run queries. Note that each
239 : : * StartTransactionCommand() call should be preceded by a
240 : : * SetCurrentStatementStartTimestamp() call, which sets both the time
241 : : * for the statement we're about the run, and also the transaction
242 : : * start time. Also, each other query sent to SPI should probably be
243 : : * preceded by SetCurrentStatementStartTimestamp(), so that statement
244 : : * start time is always up to date.
245 : : *
246 : : * The SPI_connect() call lets us run queries through the SPI manager,
247 : : * and the PushActiveSnapshot() call creates an "active" snapshot
248 : : * which is necessary for queries to have MVCC data to work on.
249 : : *
250 : : * The pgstat_report_activity() call makes our activity visible
251 : : * through the pgstat views.
252 : : */
4584 alvherre@alvh.no-ip. 253 : 2 : SetCurrentStatementStartTimestamp();
4709 254 : 2 : StartTransactionCommand();
255 : 2 : SPI_connect();
256 : 2 : PushActiveSnapshot(GetTransactionSnapshot());
1823 noah@leadboat.com 257 : 2 : debug_query_string = buf.data;
4584 alvherre@alvh.no-ip. 258 : 2 : pgstat_report_activity(STATE_RUNNING, buf.data);
259 : :
260 : : /* We can now execute queries via SPI */
4709 261 : 2 : ret = SPI_execute(buf.data, false, 0);
262 : :
263 [ - + ]: 2 : if (ret != SPI_OK_UPDATE_RETURNING)
4709 alvherre@alvh.no-ip. 264 [ # # ]:UBC 0 : elog(FATAL, "cannot select from table %s.%s: error code %d",
265 : : table->schema, table->name, ret);
266 : :
4709 alvherre@alvh.no-ip. 267 [ + + ]:CBC 2 : if (SPI_processed > 0)
268 : : {
269 : : bool isnull;
270 : : int32 val;
271 : :
272 : 1 : val = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[0],
4535 bruce@momjian.us 273 : 1 : SPI_tuptable->tupdesc,
274 : : 1, &isnull));
4709 alvherre@alvh.no-ip. 275 [ + - ]: 1 : if (!isnull)
276 [ + - ]: 1 : elog(LOG, "%s: count in %s.%s is now %d",
277 : : MyBgworkerEntry->bgw_name,
278 : : table->schema, table->name, val);
279 : : }
280 : :
281 : : /*
282 : : * And finish our transaction.
283 : : */
284 : 2 : SPI_finish();
285 : 2 : PopActiveSnapshot();
286 : 2 : CommitTransactionCommand();
1823 noah@leadboat.com 287 : 2 : debug_query_string = NULL;
1301 andres@anarazel.de 288 : 2 : pgstat_report_stat(true);
4584 alvherre@alvh.no-ip. 289 : 2 : pgstat_report_activity(STATE_IDLE, NULL);
290 : : }
291 : :
292 : : /* Not reachable */
293 : : }
294 : :
295 : : /*
296 : : * Entrypoint of this module.
297 : : *
298 : : * We register more than one worker process here, to demonstrate how that can
299 : : * be done.
300 : : */
301 : : void
4709 302 : 5 : _PG_init(void)
303 : : {
304 : : BackgroundWorker worker;
305 : :
306 : : /* get the configuration */
307 : :
308 : : /*
309 : : * These GUCs are defined even if this library is not loaded with
310 : : * shared_preload_libraries, for worker_spi_launch().
311 : : */
4584 312 : 5 : DefineCustomIntVariable("worker_spi.naptime",
313 : : "Duration between each check (in seconds).",
314 : : NULL,
315 : : &worker_spi_naptime,
316 : : 10,
317 : : 1,
318 : : INT_MAX,
319 : : PGC_SIGHUP,
320 : : 0,
321 : : NULL,
322 : : NULL,
323 : : NULL);
324 : :
830 michael@paquier.xyz 325 : 5 : DefineCustomStringVariable("worker_spi.database",
326 : : "Database to connect to.",
327 : : NULL,
328 : : &worker_spi_database,
329 : : "postgres",
330 : : PGC_SIGHUP,
331 : : 0,
332 : : NULL, NULL, NULL);
333 : :
754 334 : 5 : DefineCustomStringVariable("worker_spi.role",
335 : : "Role to connect with.",
336 : : NULL,
337 : : &worker_spi_role,
338 : : NULL,
339 : : PGC_SIGHUP,
340 : : 0,
341 : : NULL, NULL, NULL);
342 : :
4487 rhaas@postgresql.org 343 [ + + ]: 5 : if (!process_shared_preload_libraries_in_progress)
344 : 4 : return;
345 : :
4584 alvherre@alvh.no-ip. 346 : 1 : DefineCustomIntVariable("worker_spi.total_workers",
347 : : "Number of workers.",
348 : : NULL,
349 : : &worker_spi_total_workers,
350 : : 2,
351 : : 1,
352 : : 100,
353 : : PGC_POSTMASTER,
354 : : 0,
355 : : NULL,
356 : : NULL,
357 : : NULL);
358 : :
1345 tgl@sss.pgh.pa.us 359 : 1 : MarkGUCPrefixReserved("worker_spi");
360 : :
361 : : /* set up common data for all our workers */
3117 362 : 1 : memset(&worker, 0, sizeof(worker));
4709 alvherre@alvh.no-ip. 363 : 1 : worker.bgw_flags = BGWORKER_SHMEM_ACCESS |
364 : : BGWORKER_BACKEND_DATABASE_CONNECTION;
365 : 1 : worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
4584 366 : 1 : worker.bgw_restart_time = BGW_NEVER_RESTART;
3133 rhaas@postgresql.org 367 : 1 : sprintf(worker.bgw_library_name, "worker_spi");
368 : 1 : sprintf(worker.bgw_function_name, "worker_spi_main");
4173 369 : 1 : worker.bgw_notify_pid = 0;
370 : :
371 : : /*
372 : : * Now fill in worker-specific data, and do the actual registrations.
373 : : *
374 : : * bgw_extra can optionally include a database OID, a role OID and a set
375 : : * of flags. This is left empty here to fallback to the related GUCs at
376 : : * startup (0 for the bgworker flags).
377 : : */
1483 peter@eisentraut.org 378 [ + + ]: 4 : for (int i = 1; i <= worker_spi_total_workers; i++)
379 : : {
2980 peter_e@gmx.net 380 : 3 : snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi worker %d", i);
381 : 3 : snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi");
4487 rhaas@postgresql.org 382 : 3 : worker.bgw_main_arg = Int32GetDatum(i);
383 : :
4584 alvherre@alvh.no-ip. 384 : 3 : RegisterBackgroundWorker(&worker);
385 : : }
386 : : }
387 : :
388 : : /*
389 : : * Dynamically launch an SPI worker.
390 : : */
391 : : Datum
4487 rhaas@postgresql.org 392 : 6 : worker_spi_launch(PG_FUNCTION_ARGS)
393 : : {
394 : 6 : int32 i = PG_GETARG_INT32(0);
754 michael@paquier.xyz 395 : 6 : Oid dboid = PG_GETARG_OID(1);
396 : 6 : Oid roleoid = PG_GETARG_OID(2);
397 : : BackgroundWorker worker;
398 : : BackgroundWorkerHandle *handle;
399 : : BgwHandleStatus status;
400 : : pid_t pid;
401 : : char *p;
402 : 6 : bits32 flags = 0;
403 : 6 : ArrayType *arr = PG_GETARG_ARRAYTYPE_P(3);
404 : : Size ndim;
405 : : int nelems;
406 : : Datum *datum_flags;
407 : :
3117 tgl@sss.pgh.pa.us 408 : 6 : memset(&worker, 0, sizeof(worker));
4487 rhaas@postgresql.org 409 : 6 : worker.bgw_flags = BGWORKER_SHMEM_ACCESS |
410 : : BGWORKER_BACKEND_DATABASE_CONNECTION;
411 : 6 : worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
412 : 6 : worker.bgw_restart_time = BGW_NEVER_RESTART;
413 : 6 : sprintf(worker.bgw_library_name, "worker_spi");
414 : 6 : sprintf(worker.bgw_function_name, "worker_spi_main");
825 michael@paquier.xyz 415 : 6 : snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi dynamic worker %d", i);
416 : 6 : snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi dynamic");
4487 rhaas@postgresql.org 417 : 6 : worker.bgw_main_arg = Int32GetDatum(i);
418 : : /* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
4444 419 : 6 : worker.bgw_notify_pid = MyProcPid;
420 : :
421 : : /* extract flags, if any */
754 michael@paquier.xyz 422 : 6 : ndim = ARR_NDIM(arr);
423 [ - + ]: 6 : if (ndim > 1)
754 michael@paquier.xyz 424 [ # # ]:UBC 0 : ereport(ERROR,
425 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
426 : : errmsg("flags array must be one-dimensional")));
427 : :
754 michael@paquier.xyz 428 [ - + ]:CBC 6 : if (array_contains_nulls(arr))
754 michael@paquier.xyz 429 [ # # ]:UBC 0 : ereport(ERROR,
430 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
431 : : errmsg("flags array must not contain nulls")));
432 : :
754 michael@paquier.xyz 433 [ - + ]:CBC 6 : Assert(ARR_ELEMTYPE(arr) == TEXTOID);
434 : 6 : deconstruct_array_builtin(arr, TEXTOID, &datum_flags, NULL, &nelems);
435 : :
436 [ + + ]: 7 : for (i = 0; i < nelems; i++)
437 : : {
438 : 1 : char *optname = TextDatumGetCString(datum_flags[i]);
439 : :
440 [ + - ]: 1 : if (strcmp(optname, "ALLOWCONN") == 0)
441 : 1 : flags |= BGWORKER_BYPASS_ALLOWCONN;
747 michael@paquier.xyz 442 [ # # ]:UBC 0 : else if (strcmp(optname, "ROLELOGINCHECK") == 0)
443 : 0 : flags |= BGWORKER_BYPASS_ROLELOGINCHECK;
444 : : else
754 445 [ # # ]: 0 : ereport(ERROR,
446 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
447 : : errmsg("incorrect flag value found in array")));
448 : : }
449 : :
450 : : /*
451 : : * Register database and role to use for the worker started in bgw_extra.
452 : : * If none have been provided, this will fall back to the GUCs at startup.
453 : : */
754 michael@paquier.xyz 454 [ + + ]:CBC 6 : if (!OidIsValid(dboid))
455 : 1 : dboid = get_database_oid(worker_spi_database, false);
456 : :
457 : : /*
458 : : * worker_spi_role is NULL by default, so this gives to worker_spi_main()
459 : : * an invalid OID in this case.
460 : : */
461 [ + + - + ]: 6 : if (!OidIsValid(roleoid) && worker_spi_role)
754 michael@paquier.xyz 462 :UBC 0 : roleoid = get_role_oid(worker_spi_role, false);
463 : :
754 michael@paquier.xyz 464 :CBC 6 : p = worker.bgw_extra;
465 : 6 : memcpy(p, &dboid, sizeof(Oid));
466 : 6 : p += sizeof(Oid);
467 : 6 : memcpy(p, &roleoid, sizeof(Oid));
468 : 6 : p += sizeof(Oid);
469 : 6 : memcpy(p, &flags, sizeof(bits32));
470 : :
4444 rhaas@postgresql.org 471 [ - + ]: 6 : if (!RegisterDynamicBackgroundWorker(&worker, &handle))
4444 rhaas@postgresql.org 472 :UBC 0 : PG_RETURN_NULL();
473 : :
4444 rhaas@postgresql.org 474 :CBC 6 : status = WaitForBackgroundWorkerStartup(handle, &pid);
475 : :
476 [ - + ]: 6 : if (status == BGWH_STOPPED)
4444 rhaas@postgresql.org 477 [ # # ]:UBC 0 : ereport(ERROR,
478 : : (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
479 : : errmsg("could not start background process"),
480 : : errhint("More details may be available in the server log.")));
4444 rhaas@postgresql.org 481 [ - + ]:CBC 6 : if (status == BGWH_POSTMASTER_DIED)
4444 rhaas@postgresql.org 482 [ # # ]:UBC 0 : ereport(ERROR,
483 : : (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
484 : : errmsg("cannot start background processes without postmaster"),
485 : : errhint("Kill all remaining database processes and restart the database.")));
4444 rhaas@postgresql.org 486 [ - + ]:CBC 6 : Assert(status == BGWH_STARTED);
487 : :
488 : 6 : PG_RETURN_INT32(pid);
489 : : }
|