ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
kcmcp_supervisor.c
Go to the documentation of this file.
1/**
2 * @file kcmcp_supervisor.c
3 * @brief Background worker that launches and supervises the managed KCMCP
4 * knowledge-compiler server (the "managed mode" of the KCMCP client).
5 *
6 * When the @c provsql.kcmcp_server GUC is non-empty it is a shell command to
7 * start a KCMCP server (see @c doc/source/dev/kc-server-protocol.rst), with
8 * the literal @c {endpoint} replaced by a Unix-socket path this worker picks.
9 * The worker forks/execs that command in its own process group, publishes the
10 * endpoint in shared memory (read by the in-extension client,
11 * @c kcmcp_client.cpp, for a registry record whose @c endpoint is
12 * @c 'managed'), and supervises it: on the server's exit it relaunches, on a
13 * config reload it restarts a changed command, and on shutdown it kills the
14 * whole group. When the GUC is empty the worker simply idles on its latch.
15 *
16 * Modeled on @c RegisterProvSQLMMapWorker (src/provsql_mmap.c); like it, the
17 * worker needs only @c BGWORKER_SHMEM_ACCESS (no database connection): the GUC
18 * is a process-global and the endpoint lives in the shared segment.
19 */
20#include "postgres.h"
21#include "miscadmin.h"
22#include "pgstat.h" /* PG_WAIT_EXTENSION */
23#include "lib/stringinfo.h"
24#include "postmaster/bgworker.h"
25#include "postmaster/postmaster.h" /* PostPortNumber */
26#include "storage/latch.h"
27#include "storage/ipc.h"
28#include "utils/guc.h"
29
30#include <string.h>
31#include <signal.h>
32#include <unistd.h>
33#include <sys/wait.h>
34#include <errno.h>
35
36#include "provsql_utils.h"
37#include "provsql_shmem.h"
38#include "provsql_error.h"
39#include "provsql_config.h"
40
41#if PG_VERSION_NUM < 120000
42/* WL_EXIT_ON_PM_DEATH (have WaitLatch exit on postmaster death) was introduced
43 * in PostgreSQL 12; on PG 10/11 fall back to the older WL_POSTMASTER_DEATH and
44 * leave the supervise loop ourselves when WaitLatch reports it (see kcmcp_wait). */
45#define WL_EXIT_ON_PM_DEATH WL_POSTMASTER_DEATH
46#endif
47
48static volatile sig_atomic_t got_sigterm = false;
49static volatile sig_atomic_t got_sighup = false;
50
51static void kcmcp_sigterm(SIGNAL_ARGS)
52{
53 int save_errno = errno;
54 got_sigterm = true;
55 SetLatch(MyLatch);
56 errno = save_errno;
57}
58
59static void kcmcp_sighup(SIGNAL_ARGS)
60{
61 int save_errno = errno;
62 got_sighup = true;
63 SetLatch(MyLatch);
64 errno = save_errno;
65}
66
68{
69 static char buf[256];
70 buf[0] = '\0';
71 if (provsql_shared_state == NULL)
72 return buf;
74 strlcpy(buf, provsql_shared_state->kcmcp_endpoint, sizeof(buf));
76 return buf;
77}
78
79static void publish_endpoint(const char *endpoint)
80{
81 if (provsql_shared_state == NULL)
82 return;
84 strlcpy(provsql_shared_state->kcmcp_endpoint, endpoint,
85 sizeof(provsql_shared_state->kcmcp_endpoint));
87}
88
89/* Build the server command by replacing the first "{endpoint}" in the GUC
90 * template with @p endpoint. Returns a palloc'd string, or NULL if the
91 * template lacks the placeholder. */
92static char *build_server_command(const char *tmpl, const char *endpoint)
93{
94 const char *p = strstr(tmpl, "{endpoint}");
95 StringInfoData s;
96 if (p == NULL)
97 return NULL;
98 initStringInfo(&s);
99 appendBinaryStringInfo(&s, tmpl, p - tmpl);
100 appendStringInfoString(&s, endpoint);
101 appendStringInfoString(&s, p + strlen("{endpoint}"));
102 return s.data;
103}
104
105/* Fork/exec the server command in its own process group; returns the child
106 * pid, or -1 on failure. */
107static pid_t launch_server(const char *cmd)
108{
109#ifdef PROVSQL_NO_SUBPROCESS
110 (void) cmd;
111 return -1; /* no subprocesses in the WASM sandbox */
112#else
113 pid_t child;
114 fflush(NULL);
115 child = fork();
116 if (child < 0)
117 return -1;
118 if (child == 0) {
119 setpgid(0, 0);
120 execl("/bin/sh", "sh", "-c", cmd, (char *) NULL);
121 _exit(127);
122 }
123 setpgid(child, child);
124 return child;
125#endif
126}
127
128static void kill_server(pid_t child)
129{
130#ifdef PROVSQL_NO_SUBPROCESS
131 (void) child;
132#else
133 int status;
134 pid_t r;
135 if (child <= 0)
136 return;
137 killpg(child, SIGKILL);
138 do { r = waitpid(child, &status, 0); } while (r < 0 && errno == EINTR);
139#endif
140}
141
142/* Wait on the latch (and an optional timeout), resetting it. Returns true if
143 * the postmaster died: on PG >= 12 WL_EXIT_ON_PM_DEATH makes WaitLatch exit the
144 * process itself (so this never returns true); on PG 10/11 it degrades to
145 * WL_POSTMASTER_DEATH and WaitLatch returns with that bit set, which the caller
146 * turns into a clean exit from the supervise loop. */
147static bool kcmcp_wait(long timeout_ms)
148{
149 int events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
150 int rc;
151 if (timeout_ms >= 0)
152 events |= WL_TIMEOUT;
153 rc = WaitLatch(MyLatch, events, timeout_ms, PG_WAIT_EXTENSION);
154 ResetLatch(MyLatch);
155 return (rc & WL_POSTMASTER_DEATH) != 0;
156}
157
158PGDLLEXPORT void provsql_kcmcp_worker(Datum ignored);
159
160void provsql_kcmcp_worker(Datum ignored)
161{
162#ifdef PROVSQL_INPROCESS_STORE
163 /* No background workers in the single-process build; this entry point is
164 never registered (see RegisterProvSQLKCMCPWorker). Kept as an empty
165 symbol so nothing here imports postmaster-only globals
166 (PostPortNumber, the latch/bgworker API) that the WASM host does not
167 export. */
168 (void) ignored;
169#else
170 pid_t child = -1;
171 char endpoint[256];
172
173 (void) ignored;
174 pqsignal(SIGTERM, kcmcp_sigterm);
175 pqsignal(SIGHUP, kcmcp_sighup);
176 BackgroundWorkerUnblockSignals();
177
178 snprintf(endpoint, sizeof(endpoint), "unix:/tmp/.provsql-kcmcp-%d.sock",
179 PostPortNumber);
180
181 for (;;) {
182 bool configured = (provsql_kcmcp_server != NULL
183 && provsql_kcmcp_server[0] != '\0');
184
185 if (got_sigterm)
186 break;
187
188 if (got_sighup) {
189 got_sighup = false;
190 ProcessConfigFile(PGC_SIGHUP);
191 /* A changed command (or one just cleared) takes effect by recycling the
192 * child; the relaunch below picks up the new template. */
193 if (child > 0) {
194 kill_server(child);
195 child = -1;
197 }
198 continue;
199 }
200
201 if (!configured) {
202 if (child > 0) {
203 kill_server(child);
204 child = -1;
206 }
207 if (kcmcp_wait(-1))
208 break;
209 continue;
210 }
211
212 if (child <= 0) {
213 char *cmd = build_server_command(provsql_kcmcp_server, endpoint);
214 if (cmd == NULL) {
215 provsql_warning("provsql.kcmcp_server has no {endpoint} placeholder; "
216 "not launching the managed KCMCP server");
217 if (kcmcp_wait(-1))
218 break;
219 continue;
220 }
221 child = launch_server(cmd);
222 pfree(cmd);
223 if (child < 0) {
224 provsql_warning("could not fork the managed KCMCP server");
225 } else {
226 publish_endpoint(endpoint);
227 provsql_log("managed KCMCP server started (pid %d) on %s",
228 (int) child, endpoint);
229 }
230 }
231
232 /* Supervise: wake on the child's exit (SIGCHLD interrupts the wait), a
233 * signal, or the 1 s timeout, then reap non-blockingly. */
234 if (kcmcp_wait(1000))
235 break;
236
237 if (child > 0) {
238 int status;
239 pid_t w;
240#ifdef PROVSQL_NO_SUBPROCESS
241 w = -1; (void) status; /* unreachable: launch_server returns -1 */
242#else
243 w = waitpid(child, &status, WNOHANG);
244#endif
245 if (w == child) {
247 provsql_log("managed KCMCP server (pid %d) exited; relaunching",
248 (int) child);
249 child = -1; /* the next iteration relaunches */
250 }
251 }
252 }
253
254 if (child > 0)
255 kill_server(child);
257#endif /* PROVSQL_INPROCESS_STORE */
258}
259
261{
262#ifndef PROVSQL_INPROCESS_STORE
263 BackgroundWorker worker;
264 memset(&worker, 0, sizeof(worker));
265 snprintf(worker.bgw_name, BGW_MAXLEN, "ProvSQL KCMCP Supervisor");
266#if PG_VERSION_NUM >= 110000
267 snprintf(worker.bgw_type, BGW_MAXLEN, "ProvSQL KCMCP");
268#endif
269 worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
270 worker.bgw_start_time = BgWorkerStart_PostmasterStart;
271 worker.bgw_restart_time = 1;
272 snprintf(worker.bgw_library_name, BGW_MAXLEN, "provsql");
273 snprintf(worker.bgw_function_name, BGW_MAXLEN, "provsql_kcmcp_worker");
274 worker.bgw_main_arg = (Datum) 0;
275 worker.bgw_notify_pid = 0;
276 RegisterBackgroundWorker(&worker);
277#endif /* PROVSQL_INPROCESS_STORE */
278}
static volatile sig_atomic_t got_sigterm
static void publish_endpoint(const char *endpoint)
static char * build_server_command(const char *tmpl, const char *endpoint)
void RegisterProvSQLKCMCPWorker(void)
Register the supervisor background worker that launches and supervises the managed KCMCP server; call...
const char * provsql_kcmcp_managed_endpoint(void)
Read the live endpoint of the managed KCMCP server from shared memory (e.g.
static void kcmcp_sigterm(SIGNAL_ARGS)
static volatile sig_atomic_t got_sighup
static pid_t launch_server(const char *cmd)
#define WL_EXIT_ON_PM_DEATH
void provsql_kcmcp_worker(Datum ignored)
static void kcmcp_sighup(SIGNAL_ARGS)
static void kill_server(pid_t child)
static bool kcmcp_wait(long timeout_ms)
char * provsql_kcmcp_server
Launch command for the managed KCMCP server (with a {endpoint} placeholder); controlled by the provsq...
Definition provsql.c:94
Build-configuration switches shared across the C and C++ sources.
Uniform error-reporting macros for ProvSQL.
#define provsql_log(fmt,...)
Write a ProvSQL message to the server log only.
#define provsql_warning(fmt,...)
Emit a ProvSQL warning message (execution continues).
void provsql_shmem_unlock(void)
Release the ProvSQL LWLock.
void provsql_shmem_lock_exclusive(void)
Acquire the ProvSQL LWLock in exclusive mode.
provsqlSharedState * provsql_shared_state
Pointer to the ProvSQL shared-memory segment (set in provsql_shmem_startup).
void provsql_shmem_lock_shared(void)
Acquire the ProvSQL LWLock in shared mode.
Shared-memory segment and inter-process pipe management.
Core types, constants, and utilities shared across ProvSQL.