/src/postgres/src/backend/libpq/pqmq.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * pqmq.c |
4 | | * Use the frontend/backend protocol for communication over a shm_mq |
5 | | * |
6 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * src/backend/libpq/pqmq.c |
10 | | * |
11 | | *------------------------------------------------------------------------- |
12 | | */ |
13 | | |
14 | | #include "postgres.h" |
15 | | |
16 | | #include "access/parallel.h" |
17 | | #include "commands/repack.h" |
18 | | #include "libpq/libpq.h" |
19 | | #include "libpq/pqformat.h" |
20 | | #include "libpq/pqmq.h" |
21 | | #include "miscadmin.h" |
22 | | #include "pgstat.h" |
23 | | #include "replication/logicalworker.h" |
24 | | #include "storage/latch.h" |
25 | | #include "tcop/tcopprot.h" |
26 | | #include "utils/builtins.h" |
27 | | #include "utils/wait_event.h" |
28 | | |
29 | | static shm_mq_handle *pq_mq_handle = NULL; |
30 | | static bool pq_mq_busy = false; |
31 | | static pid_t pq_mq_parallel_leader_pid = 0; |
32 | | static ProcNumber pq_mq_parallel_leader_proc_number = INVALID_PROC_NUMBER; |
33 | | |
34 | | static void pq_cleanup_redirect_to_shm_mq(dsm_segment *seg, Datum arg); |
35 | | static void mq_comm_reset(void); |
36 | | static int mq_flush(void); |
37 | | static int mq_flush_if_writable(void); |
38 | | static bool mq_is_send_pending(void); |
39 | | static int mq_putmessage(char msgtype, const char *s, size_t len); |
40 | | static void mq_putmessage_noblock(char msgtype, const char *s, size_t len); |
41 | | |
42 | | static const PQcommMethods PqCommMqMethods = { |
43 | | .comm_reset = mq_comm_reset, |
44 | | .flush = mq_flush, |
45 | | .flush_if_writable = mq_flush_if_writable, |
46 | | .is_send_pending = mq_is_send_pending, |
47 | | .putmessage = mq_putmessage, |
48 | | .putmessage_noblock = mq_putmessage_noblock |
49 | | }; |
50 | | |
51 | | /* |
52 | | * Arrange to redirect frontend/backend protocol messages to a shared-memory |
53 | | * message queue. |
54 | | */ |
55 | | void |
56 | | pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh) |
57 | 0 | { |
58 | 0 | PqCommMethods = &PqCommMqMethods; |
59 | 0 | pq_mq_handle = mqh; |
60 | 0 | whereToSendOutput = DestRemote; |
61 | 0 | FrontendProtocol = PG_PROTOCOL_LATEST; |
62 | 0 | on_dsm_detach(seg, pq_cleanup_redirect_to_shm_mq, (Datum) 0); |
63 | 0 | } |
64 | | |
65 | | /* |
66 | | * When the DSM that contains our shm_mq goes away, we need to stop sending |
67 | | * messages to it. |
68 | | */ |
69 | | static void |
70 | | pq_cleanup_redirect_to_shm_mq(dsm_segment *seg, Datum arg) |
71 | 0 | { |
72 | 0 | if (pq_mq_handle != NULL) |
73 | 0 | { |
74 | 0 | pfree(pq_mq_handle); |
75 | 0 | pq_mq_handle = NULL; |
76 | 0 | } |
77 | 0 | whereToSendOutput = DestNone; |
78 | 0 | } |
79 | | |
80 | | /* |
81 | | * Arrange to SendProcSignal() to the parallel leader each time we transmit |
82 | | * message data via the shm_mq. |
83 | | */ |
84 | | void |
85 | | pq_set_parallel_leader(pid_t pid, ProcNumber procNumber) |
86 | 0 | { |
87 | 0 | Assert(PqCommMethods == &PqCommMqMethods); |
88 | 0 | pq_mq_parallel_leader_pid = pid; |
89 | 0 | pq_mq_parallel_leader_proc_number = procNumber; |
90 | 0 | } |
91 | | |
92 | | static void |
93 | | mq_comm_reset(void) |
94 | 0 | { |
95 | | /* Nothing to do. */ |
96 | 0 | } |
97 | | |
98 | | static int |
99 | | mq_flush(void) |
100 | 0 | { |
101 | | /* Nothing to do. */ |
102 | 0 | return 0; |
103 | 0 | } |
104 | | |
105 | | static int |
106 | | mq_flush_if_writable(void) |
107 | 0 | { |
108 | | /* Nothing to do. */ |
109 | 0 | return 0; |
110 | 0 | } |
111 | | |
112 | | static bool |
113 | | mq_is_send_pending(void) |
114 | 0 | { |
115 | | /* There's never anything pending. */ |
116 | 0 | return 0; |
117 | 0 | } |
118 | | |
119 | | /* |
120 | | * Transmit a libpq protocol message to the shared memory message queue |
121 | | * selected via pq_mq_handle. We don't include a length word, because the |
122 | | * receiver will know the length of the message from shm_mq_receive(). |
123 | | */ |
124 | | static int |
125 | | mq_putmessage(char msgtype, const char *s, size_t len) |
126 | 0 | { |
127 | 0 | shm_mq_iovec iov[2]; |
128 | 0 | shm_mq_result result; |
129 | | |
130 | | /* |
131 | | * If we're sending a message, and we have to wait because the queue is |
132 | | * full, and then we get interrupted, and that interrupt results in trying |
133 | | * to send another message, we respond by detaching the queue. There's no |
134 | | * way to return to the original context, but even if there were, just |
135 | | * queueing the message would amount to indefinitely postponing the |
136 | | * response to the interrupt. So we do this instead. |
137 | | */ |
138 | 0 | if (pq_mq_busy) |
139 | 0 | { |
140 | 0 | if (pq_mq_handle != NULL) |
141 | 0 | { |
142 | 0 | shm_mq_detach(pq_mq_handle); |
143 | 0 | pq_mq_handle = NULL; |
144 | 0 | } |
145 | 0 | return EOF; |
146 | 0 | } |
147 | | |
148 | | /* |
149 | | * If the message queue is already gone, just ignore the message. This |
150 | | * doesn't necessarily indicate a problem; for example, DEBUG messages can |
151 | | * be generated late in the shutdown sequence, after all DSMs have already |
152 | | * been detached. |
153 | | */ |
154 | 0 | if (pq_mq_handle == NULL) |
155 | 0 | return 0; |
156 | | |
157 | 0 | pq_mq_busy = true; |
158 | |
|
159 | 0 | iov[0].data = &msgtype; |
160 | 0 | iov[0].len = 1; |
161 | 0 | iov[1].data = s; |
162 | 0 | iov[1].len = len; |
163 | |
|
164 | 0 | for (;;) |
165 | 0 | { |
166 | | /* |
167 | | * Immediately notify the receiver by passing force_flush as true so |
168 | | * that the shared memory value is updated before we send the parallel |
169 | | * message signal right after this. |
170 | | */ |
171 | 0 | Assert(pq_mq_handle != NULL); |
172 | 0 | result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true); |
173 | |
|
174 | 0 | if (pq_mq_parallel_leader_pid != 0) |
175 | 0 | { |
176 | 0 | if (IsLogicalParallelApplyWorker()) |
177 | 0 | SendProcSignal(pq_mq_parallel_leader_pid, |
178 | 0 | PROCSIG_PARALLEL_APPLY_MESSAGE, |
179 | 0 | pq_mq_parallel_leader_proc_number); |
180 | 0 | else if (AmRepackWorker()) |
181 | 0 | SendProcSignal(pq_mq_parallel_leader_pid, |
182 | 0 | PROCSIG_REPACK_MESSAGE, |
183 | 0 | pq_mq_parallel_leader_proc_number); |
184 | 0 | else |
185 | 0 | { |
186 | 0 | Assert(IsParallelWorker()); |
187 | 0 | SendProcSignal(pq_mq_parallel_leader_pid, |
188 | 0 | PROCSIG_PARALLEL_MESSAGE, |
189 | 0 | pq_mq_parallel_leader_proc_number); |
190 | 0 | } |
191 | 0 | } |
192 | |
|
193 | 0 | if (result != SHM_MQ_WOULD_BLOCK) |
194 | 0 | break; |
195 | | |
196 | 0 | (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, |
197 | 0 | WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE); |
198 | 0 | ResetLatch(MyLatch); |
199 | 0 | CHECK_FOR_INTERRUPTS(); |
200 | 0 | } |
201 | |
|
202 | 0 | pq_mq_busy = false; |
203 | |
|
204 | 0 | Assert(result == SHM_MQ_SUCCESS || result == SHM_MQ_DETACHED); |
205 | 0 | if (result != SHM_MQ_SUCCESS) |
206 | 0 | return EOF; |
207 | 0 | return 0; |
208 | 0 | } |
209 | | |
210 | | static void |
211 | | mq_putmessage_noblock(char msgtype, const char *s, size_t len) |
212 | 0 | { |
213 | | /* |
214 | | * While the shm_mq machinery does support sending a message in |
215 | | * non-blocking mode, there's currently no way to try sending beginning to |
216 | | * send the message that doesn't also commit us to completing the |
217 | | * transmission. This could be improved in the future, but for now we |
218 | | * don't need it. |
219 | | */ |
220 | 0 | elog(ERROR, "not currently supported"); |
221 | 0 | } |
222 | | |
223 | | /* |
224 | | * Parse an ErrorResponse or NoticeResponse payload and populate an ErrorData |
225 | | * structure with the results. |
226 | | */ |
227 | | void |
228 | | pq_parse_errornotice(StringInfo msg, ErrorData *edata) |
229 | 0 | { |
230 | | /* Initialize edata with reasonable defaults. */ |
231 | 0 | MemSet(edata, 0, sizeof(ErrorData)); |
232 | 0 | edata->elevel = ERROR; |
233 | 0 | edata->assoc_context = CurrentMemoryContext; |
234 | | |
235 | | /* Loop over fields and extract each one. */ |
236 | 0 | for (;;) |
237 | 0 | { |
238 | 0 | char code = pq_getmsgbyte(msg); |
239 | 0 | const char *value; |
240 | |
|
241 | 0 | if (code == '\0') |
242 | 0 | { |
243 | 0 | pq_getmsgend(msg); |
244 | 0 | break; |
245 | 0 | } |
246 | 0 | value = pq_getmsgrawstring(msg); |
247 | |
|
248 | 0 | switch (code) |
249 | 0 | { |
250 | 0 | case PG_DIAG_SEVERITY: |
251 | | /* ignore, trusting we'll get a nonlocalized version */ |
252 | 0 | break; |
253 | 0 | case PG_DIAG_SEVERITY_NONLOCALIZED: |
254 | 0 | if (strcmp(value, "DEBUG") == 0) |
255 | 0 | { |
256 | | /* |
257 | | * We can't reconstruct the exact DEBUG level, but |
258 | | * presumably it was >= client_min_messages, so select |
259 | | * DEBUG1 to ensure we'll pass it on to the client. |
260 | | */ |
261 | 0 | edata->elevel = DEBUG1; |
262 | 0 | } |
263 | 0 | else if (strcmp(value, "LOG") == 0) |
264 | 0 | { |
265 | | /* |
266 | | * It can't be LOG_SERVER_ONLY, or the worker wouldn't |
267 | | * have sent it to us; so LOG is the correct value. |
268 | | */ |
269 | 0 | edata->elevel = LOG; |
270 | 0 | } |
271 | 0 | else if (strcmp(value, "INFO") == 0) |
272 | 0 | edata->elevel = INFO; |
273 | 0 | else if (strcmp(value, "NOTICE") == 0) |
274 | 0 | edata->elevel = NOTICE; |
275 | 0 | else if (strcmp(value, "WARNING") == 0) |
276 | 0 | edata->elevel = WARNING; |
277 | 0 | else if (strcmp(value, "ERROR") == 0) |
278 | 0 | edata->elevel = ERROR; |
279 | 0 | else if (strcmp(value, "FATAL") == 0) |
280 | 0 | edata->elevel = FATAL; |
281 | 0 | else if (strcmp(value, "PANIC") == 0) |
282 | 0 | edata->elevel = PANIC; |
283 | 0 | else |
284 | 0 | elog(ERROR, "unrecognized error severity: \"%s\"", value); |
285 | 0 | break; |
286 | 0 | case PG_DIAG_SQLSTATE: |
287 | 0 | if (strlen(value) != 5) |
288 | 0 | elog(ERROR, "invalid SQLSTATE: \"%s\"", value); |
289 | 0 | edata->sqlerrcode = MAKE_SQLSTATE(value[0], value[1], value[2], |
290 | 0 | value[3], value[4]); |
291 | 0 | break; |
292 | 0 | case PG_DIAG_MESSAGE_PRIMARY: |
293 | 0 | edata->message = pstrdup(value); |
294 | 0 | break; |
295 | 0 | case PG_DIAG_MESSAGE_DETAIL: |
296 | 0 | edata->detail = pstrdup(value); |
297 | 0 | break; |
298 | 0 | case PG_DIAG_MESSAGE_HINT: |
299 | 0 | edata->hint = pstrdup(value); |
300 | 0 | break; |
301 | 0 | case PG_DIAG_STATEMENT_POSITION: |
302 | 0 | edata->cursorpos = pg_strtoint32(value); |
303 | 0 | break; |
304 | 0 | case PG_DIAG_INTERNAL_POSITION: |
305 | 0 | edata->internalpos = pg_strtoint32(value); |
306 | 0 | break; |
307 | 0 | case PG_DIAG_INTERNAL_QUERY: |
308 | 0 | edata->internalquery = pstrdup(value); |
309 | 0 | break; |
310 | 0 | case PG_DIAG_CONTEXT: |
311 | 0 | edata->context = pstrdup(value); |
312 | 0 | break; |
313 | 0 | case PG_DIAG_SCHEMA_NAME: |
314 | 0 | edata->schema_name = pstrdup(value); |
315 | 0 | break; |
316 | 0 | case PG_DIAG_TABLE_NAME: |
317 | 0 | edata->table_name = pstrdup(value); |
318 | 0 | break; |
319 | 0 | case PG_DIAG_COLUMN_NAME: |
320 | 0 | edata->column_name = pstrdup(value); |
321 | 0 | break; |
322 | 0 | case PG_DIAG_DATATYPE_NAME: |
323 | 0 | edata->datatype_name = pstrdup(value); |
324 | 0 | break; |
325 | 0 | case PG_DIAG_CONSTRAINT_NAME: |
326 | 0 | edata->constraint_name = pstrdup(value); |
327 | 0 | break; |
328 | 0 | case PG_DIAG_SOURCE_FILE: |
329 | 0 | edata->filename = pstrdup(value); |
330 | 0 | break; |
331 | 0 | case PG_DIAG_SOURCE_LINE: |
332 | 0 | edata->lineno = pg_strtoint32(value); |
333 | 0 | break; |
334 | 0 | case PG_DIAG_SOURCE_FUNCTION: |
335 | 0 | edata->funcname = pstrdup(value); |
336 | 0 | break; |
337 | 0 | default: |
338 | 0 | elog(ERROR, "unrecognized error field code: %d", code); |
339 | 0 | break; |
340 | 0 | } |
341 | 0 | } |
342 | 0 | } |