Line data Source code
1 : /*-------------------------------------------------------------------------
2 : * xid8funcs.c
3 : *
4 : * Export internal transaction IDs to user level.
5 : *
6 : * Note that only top-level transaction IDs are exposed to user sessions.
7 : * This is important because xid8s frequently persist beyond the global
8 : * xmin horizon, or may even be shipped to other machines, so we cannot
9 : * rely on being able to correlate subtransaction IDs with their parents
10 : * via functions such as SubTransGetTopmostTransaction().
11 : *
12 : * These functions are used to support the txid_XXX functions and the newer
13 : * pg_current_xact_id, pg_current_snapshot and related fmgr functions, since
14 : * the only difference between them is whether they expose xid8 or int8 values
15 : * to users. The txid_XXX variants should eventually be dropped.
16 : *
17 : *
18 : * Copyright (c) 2003-2023, PostgreSQL Global Development Group
19 : * Author: Jan Wieck, Afilias USA INC.
20 : * 64-bit txids: Marko Kreen, Skype Technologies
21 : *
22 : * src/backend/utils/adt/xid8funcs.c
23 : *
24 : *-------------------------------------------------------------------------
25 : */
26 :
27 : #include "postgres.h"
28 :
29 : #include "access/clog.h"
30 : #include "access/transam.h"
31 : #include "access/xact.h"
32 : #include "access/xlog.h"
33 : #include "funcapi.h"
34 : #include "lib/qunique.h"
35 : #include "libpq/pqformat.h"
36 : #include "miscadmin.h"
37 : #include "postmaster/postmaster.h"
38 : #include "storage/lwlock.h"
39 : #include "storage/procarray.h"
40 : #include "utils/builtins.h"
41 : #include "utils/memutils.h"
42 : #include "utils/snapmgr.h"
43 : #include "utils/xid8.h"
44 :
45 :
46 : /*
47 : * If defined, use bsearch() function for searching for xid8s in snapshots
48 : * that have more than the specified number of values.
49 : */
50 : #define USE_BSEARCH_IF_NXIP_GREATER 30
51 :
52 :
53 : /*
54 : * Snapshot containing FullTransactionIds.
55 : */
56 : typedef struct
57 : {
58 : /*
59 : * 4-byte length hdr, should not be touched directly.
60 : *
61 : * Explicit embedding is ok as we want always correct alignment anyway.
62 : */
63 : int32 __varsz;
64 :
65 : uint32 nxip; /* number of fxids in xip array */
66 : FullTransactionId xmin;
67 : FullTransactionId xmax;
68 : /* in-progress fxids, xmin <= xip[i] < xmax: */
69 : FullTransactionId xip[FLEXIBLE_ARRAY_MEMBER];
70 : } pg_snapshot;
71 :
72 : #define PG_SNAPSHOT_SIZE(nxip) \
73 : (offsetof(pg_snapshot, xip) + sizeof(FullTransactionId) * (nxip))
74 : #define PG_SNAPSHOT_MAX_NXIP \
75 : ((MaxAllocSize - offsetof(pg_snapshot, xip)) / sizeof(FullTransactionId))
76 :
77 : /*
78 : * Compile-time limits on the procarray (MAX_BACKENDS processes plus
79 : * MAX_BACKENDS prepared transactions) guarantee nxip won't be too large.
80 : */
81 : StaticAssertDecl(MAX_BACKENDS * 2 <= PG_SNAPSHOT_MAX_NXIP,
82 : "possible overflow in pg_current_snapshot()");
83 :
84 :
85 : /*
86 : * Helper to get a TransactionId from a 64-bit xid with wraparound detection.
87 : *
88 : * It is an ERROR if the xid is in the future. Otherwise, returns true if
89 : * the transaction is still new enough that we can determine whether it
90 : * committed and false otherwise. If *extracted_xid is not NULL, it is set
91 : * to the low 32 bits of the transaction ID (i.e. the actual XID, without the
92 : * epoch).
93 : *
94 : * The caller must hold XactTruncationLock since it's dealing with arbitrary
95 : * XIDs, and must continue to hold it until it's done with any clog lookups
96 : * relating to those XIDs.
97 : */
98 : static bool
99 84 : TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
100 : {
101 84 : uint32 xid_epoch = EpochFromFullTransactionId(fxid);
102 84 : TransactionId xid = XidFromFullTransactionId(fxid);
103 : uint32 now_epoch;
104 : TransactionId now_epoch_next_xid;
105 : FullTransactionId now_fullxid;
106 :
107 84 : now_fullxid = ReadNextFullTransactionId();
108 84 : now_epoch_next_xid = XidFromFullTransactionId(now_fullxid);
109 84 : now_epoch = EpochFromFullTransactionId(now_fullxid);
110 :
111 84 : if (extracted_xid != NULL)
112 84 : *extracted_xid = xid;
113 :
114 84 : if (!TransactionIdIsValid(xid))
115 0 : return false;
116 :
117 : /* For non-normal transaction IDs, we can ignore the epoch. */
118 84 : if (!TransactionIdIsNormal(xid))
119 24 : return true;
120 :
121 : /* If the transaction ID is in the future, throw an error. */
122 60 : if (!FullTransactionIdPrecedes(fxid, now_fullxid))
123 12 : ereport(ERROR,
124 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
125 : errmsg("transaction ID %llu is in the future",
126 : (unsigned long long) U64FromFullTransactionId(fxid))));
127 :
128 : /*
129 : * TransamVariables->oldestClogXid is protected by XactTruncationLock, but
130 : * we don't acquire that lock here. Instead, we require the caller to
131 : * acquire it, because the caller is presumably going to look up the
132 : * returned XID. If we took and released the lock within this function, a
133 : * CLOG truncation could occur before the caller finished with the XID.
134 : */
135 : Assert(LWLockHeldByMe(XactTruncationLock));
136 :
137 : /*
138 : * If the transaction ID has wrapped around, it's definitely too old to
139 : * determine the commit status. Otherwise, we can compare it to
140 : * TransamVariables->oldestClogXid to determine whether the relevant CLOG
141 : * entry is guaranteed to still exist.
142 : */
143 48 : if (xid_epoch + 1 < now_epoch
144 48 : || (xid_epoch + 1 == now_epoch && xid < now_epoch_next_xid)
145 48 : || TransactionIdPrecedes(xid, TransamVariables->oldestClogXid))
146 12 : return false;
147 :
148 36 : return true;
149 : }
150 :
151 : /*
152 : * Convert a TransactionId obtained from a snapshot held by the caller to a
153 : * FullTransactionId. Use next_fxid as a reference FullTransactionId, so that
154 : * we can compute the high order bits. It must have been obtained by the
155 : * caller with ReadNextFullTransactionId() after the snapshot was created.
156 : */
157 : static FullTransactionId
158 60 : widen_snapshot_xid(TransactionId xid, FullTransactionId next_fxid)
159 : {
160 60 : TransactionId next_xid = XidFromFullTransactionId(next_fxid);
161 60 : uint32 epoch = EpochFromFullTransactionId(next_fxid);
162 :
163 : /* Special transaction ID. */
164 60 : if (!TransactionIdIsNormal(xid))
165 0 : return FullTransactionIdFromEpochAndXid(0, xid);
166 :
167 : /*
168 : * The 64 bit result must be <= next_fxid, since next_fxid hadn't been
169 : * issued yet when the snapshot was created. Every TransactionId in the
170 : * snapshot must therefore be from the same epoch as next_fxid, or the
171 : * epoch before. We know this because next_fxid is never allow to get
172 : * more than one epoch ahead of the TransactionIds in any snapshot.
173 : */
174 60 : if (xid > next_xid)
175 0 : epoch--;
176 :
177 60 : return FullTransactionIdFromEpochAndXid(epoch, xid);
178 : }
179 :
180 : /*
181 : * txid comparator for qsort/bsearch
182 : */
183 : static int
184 2688 : cmp_fxid(const void *aa, const void *bb)
185 : {
186 2688 : FullTransactionId a = *(const FullTransactionId *) aa;
187 2688 : FullTransactionId b = *(const FullTransactionId *) bb;
188 :
189 2688 : if (FullTransactionIdPrecedes(a, b))
190 648 : return -1;
191 2040 : if (FullTransactionIdPrecedes(b, a))
192 1668 : return 1;
193 372 : return 0;
194 : }
195 :
196 : /*
197 : * Sort a snapshot's txids, so we can use bsearch() later. Also remove
198 : * any duplicates.
199 : *
200 : * For consistency of on-disk representation, we always sort even if bsearch
201 : * will not be used.
202 : */
203 : static void
204 24 : sort_snapshot(pg_snapshot *snap)
205 : {
206 24 : if (snap->nxip > 1)
207 : {
208 0 : qsort(snap->xip, snap->nxip, sizeof(FullTransactionId), cmp_fxid);
209 0 : snap->nxip = qunique(snap->xip, snap->nxip, sizeof(FullTransactionId),
210 : cmp_fxid);
211 : }
212 24 : }
213 :
214 : /*
215 : * check fxid visibility.
216 : */
217 : static bool
218 1020 : is_visible_fxid(FullTransactionId value, const pg_snapshot *snap)
219 : {
220 1020 : if (FullTransactionIdPrecedes(value, snap->xmin))
221 132 : return true;
222 888 : else if (!FullTransactionIdPrecedes(value, snap->xmax))
223 168 : return false;
224 : #ifdef USE_BSEARCH_IF_NXIP_GREATER
225 720 : else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER)
226 : {
227 : void *res;
228 :
229 600 : res = bsearch(&value, snap->xip, snap->nxip, sizeof(FullTransactionId),
230 : cmp_fxid);
231 : /* if found, transaction is still in progress */
232 600 : return (res) ? false : true;
233 : }
234 : #endif
235 : else
236 : {
237 : uint32 i;
238 :
239 360 : for (i = 0; i < snap->nxip; i++)
240 : {
241 288 : if (FullTransactionIdEquals(value, snap->xip[i]))
242 48 : return false;
243 : }
244 72 : return true;
245 : }
246 : }
247 :
248 : /*
249 : * helper functions to use StringInfo for pg_snapshot creation.
250 : */
251 :
252 : static StringInfo
253 186 : buf_init(FullTransactionId xmin, FullTransactionId xmax)
254 : {
255 : pg_snapshot snap;
256 : StringInfo buf;
257 :
258 186 : snap.xmin = xmin;
259 186 : snap.xmax = xmax;
260 186 : snap.nxip = 0;
261 :
262 186 : buf = makeStringInfo();
263 186 : appendBinaryStringInfo(buf, &snap, PG_SNAPSHOT_SIZE(0));
264 186 : return buf;
265 : }
266 :
267 : static void
268 624 : buf_add_txid(StringInfo buf, FullTransactionId fxid)
269 : {
270 624 : pg_snapshot *snap = (pg_snapshot *) buf->data;
271 :
272 : /* do this before possible realloc */
273 624 : snap->nxip++;
274 :
275 624 : appendBinaryStringInfo(buf, &fxid, sizeof(fxid));
276 624 : }
277 :
278 : static pg_snapshot *
279 150 : buf_finalize(StringInfo buf)
280 : {
281 150 : pg_snapshot *snap = (pg_snapshot *) buf->data;
282 :
283 150 : SET_VARSIZE(snap, buf->len);
284 :
285 : /* buf is not needed anymore */
286 150 : buf->data = NULL;
287 150 : pfree(buf);
288 :
289 150 : return snap;
290 : }
291 :
292 : /*
293 : * parse snapshot from cstring
294 : */
295 : static pg_snapshot *
296 234 : parse_snapshot(const char *str, Node *escontext)
297 : {
298 : FullTransactionId xmin;
299 : FullTransactionId xmax;
300 234 : FullTransactionId last_val = InvalidFullTransactionId;
301 : FullTransactionId val;
302 234 : const char *str_start = str;
303 : char *endp;
304 : StringInfo buf;
305 :
306 234 : xmin = FullTransactionIdFromU64(strtou64(str, &endp, 10));
307 234 : if (*endp != ':')
308 0 : goto bad_format;
309 234 : str = endp + 1;
310 :
311 234 : xmax = FullTransactionIdFromU64(strtou64(str, &endp, 10));
312 234 : if (*endp != ':')
313 0 : goto bad_format;
314 234 : str = endp + 1;
315 :
316 : /* it should look sane */
317 234 : if (!FullTransactionIdIsValid(xmin) ||
318 222 : !FullTransactionIdIsValid(xmax) ||
319 210 : FullTransactionIdPrecedes(xmax, xmin))
320 48 : goto bad_format;
321 :
322 : /* allocate buffer */
323 186 : buf = buf_init(xmin, xmax);
324 :
325 : /* loop over values */
326 822 : while (*str != '\0')
327 : {
328 : /* read next value */
329 672 : val = FullTransactionIdFromU64(strtou64(str, &endp, 10));
330 672 : str = endp;
331 :
332 : /* require the input to be in order */
333 672 : if (FullTransactionIdPrecedes(val, xmin) ||
334 660 : FullTransactionIdFollowsOrEquals(val, xmax) ||
335 660 : FullTransactionIdPrecedes(val, last_val))
336 36 : goto bad_format;
337 :
338 : /* skip duplicates */
339 636 : if (!FullTransactionIdEquals(val, last_val))
340 624 : buf_add_txid(buf, val);
341 636 : last_val = val;
342 :
343 636 : if (*str == ',')
344 516 : str++;
345 120 : else if (*str != '\0')
346 0 : goto bad_format;
347 : }
348 :
349 150 : return buf_finalize(buf);
350 :
351 84 : bad_format:
352 84 : ereturn(escontext, NULL,
353 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
354 : errmsg("invalid input syntax for type %s: \"%s\"",
355 : "pg_snapshot", str_start)));
356 : }
357 :
358 : /*
359 : * pg_current_xact_id() returns xid8
360 : *
361 : * Return the current toplevel full transaction ID.
362 : * If the current transaction does not have one, one is assigned.
363 : */
364 : Datum
365 5706 : pg_current_xact_id(PG_FUNCTION_ARGS)
366 : {
367 : /*
368 : * Must prevent during recovery because if an xid is not assigned we try
369 : * to assign one, which would fail. Programs already rely on this function
370 : * to always return a valid current xid, so we should not change this to
371 : * return NULL or similar invalid xid.
372 : */
373 5706 : PreventCommandDuringRecovery("pg_current_xact_id()");
374 :
375 5706 : PG_RETURN_FULLTRANSACTIONID(GetTopFullTransactionId());
376 : }
377 :
378 : /*
379 : * Same as pg_current_xact_id() but doesn't assign a new xid if there
380 : * isn't one yet.
381 : */
382 : Datum
383 24 : pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS)
384 : {
385 24 : FullTransactionId topfxid = GetTopFullTransactionIdIfAny();
386 :
387 24 : if (!FullTransactionIdIsValid(topfxid))
388 12 : PG_RETURN_NULL();
389 :
390 12 : PG_RETURN_FULLTRANSACTIONID(topfxid);
391 : }
392 :
393 : /*
394 : * pg_current_snapshot() returns pg_snapshot
395 : *
396 : * Return current snapshot
397 : *
398 : * Note that only top-transaction XIDs are included in the snapshot.
399 : */
400 : Datum
401 24 : pg_current_snapshot(PG_FUNCTION_ARGS)
402 : {
403 : pg_snapshot *snap;
404 : uint32 nxip,
405 : i;
406 : Snapshot cur;
407 24 : FullTransactionId next_fxid = ReadNextFullTransactionId();
408 :
409 24 : cur = GetActiveSnapshot();
410 24 : if (cur == NULL)
411 0 : elog(ERROR, "no active snapshot set");
412 :
413 : /* allocate */
414 24 : nxip = cur->xcnt;
415 24 : snap = palloc(PG_SNAPSHOT_SIZE(nxip));
416 :
417 : /* fill */
418 24 : snap->xmin = widen_snapshot_xid(cur->xmin, next_fxid);
419 24 : snap->xmax = widen_snapshot_xid(cur->xmax, next_fxid);
420 24 : snap->nxip = nxip;
421 36 : for (i = 0; i < nxip; i++)
422 12 : snap->xip[i] = widen_snapshot_xid(cur->xip[i], next_fxid);
423 :
424 : /*
425 : * We want them guaranteed to be in ascending order. This also removes
426 : * any duplicate xids. Normally, an XID can only be assigned to one
427 : * backend, but when preparing a transaction for two-phase commit, there
428 : * is a transient state when both the original backend and the dummy
429 : * PGPROC entry reserved for the prepared transaction hold the same XID.
430 : */
431 24 : sort_snapshot(snap);
432 :
433 : /* set size after sorting, because it may have removed duplicate xips */
434 24 : SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(snap->nxip));
435 :
436 24 : PG_RETURN_POINTER(snap);
437 : }
438 :
439 : /*
440 : * pg_snapshot_in(cstring) returns pg_snapshot
441 : *
442 : * input function for type pg_snapshot
443 : */
444 : Datum
445 234 : pg_snapshot_in(PG_FUNCTION_ARGS)
446 : {
447 234 : char *str = PG_GETARG_CSTRING(0);
448 : pg_snapshot *snap;
449 :
450 234 : snap = parse_snapshot(str, fcinfo->context);
451 :
452 174 : PG_RETURN_POINTER(snap);
453 : }
454 :
455 : /*
456 : * pg_snapshot_out(pg_snapshot) returns cstring
457 : *
458 : * output function for type pg_snapshot
459 : */
460 : Datum
461 124 : pg_snapshot_out(PG_FUNCTION_ARGS)
462 : {
463 124 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
464 : StringInfoData str;
465 : uint32 i;
466 :
467 124 : initStringInfo(&str);
468 :
469 124 : appendStringInfo(&str, UINT64_FORMAT ":",
470 : U64FromFullTransactionId(snap->xmin));
471 124 : appendStringInfo(&str, UINT64_FORMAT ":",
472 : U64FromFullTransactionId(snap->xmax));
473 :
474 688 : for (i = 0; i < snap->nxip; i++)
475 : {
476 564 : if (i > 0)
477 464 : appendStringInfoChar(&str, ',');
478 564 : appendStringInfo(&str, UINT64_FORMAT,
479 : U64FromFullTransactionId(snap->xip[i]));
480 : }
481 :
482 124 : PG_RETURN_CSTRING(str.data);
483 : }
484 :
485 : /*
486 : * pg_snapshot_recv(internal) returns pg_snapshot
487 : *
488 : * binary input function for type pg_snapshot
489 : *
490 : * format: int4 nxip, int8 xmin, int8 xmax, int8 xip
491 : */
492 : Datum
493 0 : pg_snapshot_recv(PG_FUNCTION_ARGS)
494 : {
495 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
496 : pg_snapshot *snap;
497 0 : FullTransactionId last = InvalidFullTransactionId;
498 : int nxip;
499 : int i;
500 : FullTransactionId xmin;
501 : FullTransactionId xmax;
502 :
503 : /* load and validate nxip */
504 0 : nxip = pq_getmsgint(buf, 4);
505 0 : if (nxip < 0 || nxip > PG_SNAPSHOT_MAX_NXIP)
506 0 : goto bad_format;
507 :
508 0 : xmin = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
509 0 : xmax = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
510 0 : if (!FullTransactionIdIsValid(xmin) ||
511 0 : !FullTransactionIdIsValid(xmax) ||
512 0 : FullTransactionIdPrecedes(xmax, xmin))
513 0 : goto bad_format;
514 :
515 0 : snap = palloc(PG_SNAPSHOT_SIZE(nxip));
516 0 : snap->xmin = xmin;
517 0 : snap->xmax = xmax;
518 :
519 0 : for (i = 0; i < nxip; i++)
520 : {
521 : FullTransactionId cur =
522 0 : FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
523 :
524 0 : if (FullTransactionIdPrecedes(cur, last) ||
525 0 : FullTransactionIdPrecedes(cur, xmin) ||
526 0 : FullTransactionIdPrecedes(xmax, cur))
527 0 : goto bad_format;
528 :
529 : /* skip duplicate xips */
530 0 : if (FullTransactionIdEquals(cur, last))
531 : {
532 0 : i--;
533 0 : nxip--;
534 0 : continue;
535 : }
536 :
537 0 : snap->xip[i] = cur;
538 0 : last = cur;
539 : }
540 0 : snap->nxip = nxip;
541 0 : SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(nxip));
542 0 : PG_RETURN_POINTER(snap);
543 :
544 0 : bad_format:
545 0 : ereport(ERROR,
546 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
547 : errmsg("invalid external pg_snapshot data")));
548 : PG_RETURN_POINTER(NULL); /* keep compiler quiet */
549 : }
550 :
551 : /*
552 : * pg_snapshot_send(pg_snapshot) returns bytea
553 : *
554 : * binary output function for type pg_snapshot
555 : *
556 : * format: int4 nxip, u64 xmin, u64 xmax, u64 xip...
557 : */
558 : Datum
559 0 : pg_snapshot_send(PG_FUNCTION_ARGS)
560 : {
561 0 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
562 : StringInfoData buf;
563 : uint32 i;
564 :
565 0 : pq_begintypsend(&buf);
566 0 : pq_sendint32(&buf, snap->nxip);
567 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmin));
568 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmax));
569 0 : for (i = 0; i < snap->nxip; i++)
570 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xip[i]));
571 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
572 : }
573 :
574 : /*
575 : * pg_visible_in_snapshot(xid8, pg_snapshot) returns bool
576 : *
577 : * is txid visible in snapshot ?
578 : */
579 : Datum
580 1020 : pg_visible_in_snapshot(PG_FUNCTION_ARGS)
581 : {
582 1020 : FullTransactionId value = PG_GETARG_FULLTRANSACTIONID(0);
583 1020 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(1);
584 :
585 1020 : PG_RETURN_BOOL(is_visible_fxid(value, snap));
586 : }
587 :
588 : /*
589 : * pg_snapshot_xmin(pg_snapshot) returns xid8
590 : *
591 : * return snapshot's xmin
592 : */
593 : Datum
594 60 : pg_snapshot_xmin(PG_FUNCTION_ARGS)
595 : {
596 60 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
597 :
598 60 : PG_RETURN_FULLTRANSACTIONID(snap->xmin);
599 : }
600 :
601 : /*
602 : * pg_snapshot_xmax(pg_snapshot) returns xid8
603 : *
604 : * return snapshot's xmax
605 : */
606 : Datum
607 48 : pg_snapshot_xmax(PG_FUNCTION_ARGS)
608 : {
609 48 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
610 :
611 48 : PG_RETURN_FULLTRANSACTIONID(snap->xmax);
612 : }
613 :
614 : /*
615 : * pg_snapshot_xip(pg_snapshot) returns setof xid8
616 : *
617 : * return in-progress xid8s in snapshot.
618 : */
619 : Datum
620 492 : pg_snapshot_xip(PG_FUNCTION_ARGS)
621 : {
622 : FuncCallContext *fctx;
623 : pg_snapshot *snap;
624 : FullTransactionId value;
625 :
626 : /* on first call initialize fctx and get copy of snapshot */
627 492 : if (SRF_IS_FIRSTCALL())
628 : {
629 48 : pg_snapshot *arg = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
630 :
631 48 : fctx = SRF_FIRSTCALL_INIT();
632 :
633 : /* make a copy of user snapshot */
634 48 : snap = MemoryContextAlloc(fctx->multi_call_memory_ctx, VARSIZE(arg));
635 48 : memcpy(snap, arg, VARSIZE(arg));
636 :
637 48 : fctx->user_fctx = snap;
638 : }
639 :
640 : /* return values one-by-one */
641 492 : fctx = SRF_PERCALL_SETUP();
642 492 : snap = fctx->user_fctx;
643 492 : if (fctx->call_cntr < snap->nxip)
644 : {
645 444 : value = snap->xip[fctx->call_cntr];
646 444 : SRF_RETURN_NEXT(fctx, FullTransactionIdGetDatum(value));
647 : }
648 : else
649 : {
650 48 : SRF_RETURN_DONE(fctx);
651 : }
652 : }
653 :
654 : /*
655 : * Report the status of a recent transaction ID, or null for wrapped,
656 : * truncated away or otherwise too old XIDs.
657 : *
658 : * The passed epoch-qualified xid is treated as a normal xid, not a
659 : * multixact id.
660 : *
661 : * If it points to a committed subxact the result is the subxact status even
662 : * though the parent xact may still be in progress or may have aborted.
663 : */
664 : Datum
665 84 : pg_xact_status(PG_FUNCTION_ARGS)
666 : {
667 : const char *status;
668 84 : FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0);
669 : TransactionId xid;
670 :
671 : /*
672 : * We must protect against concurrent truncation of clog entries to avoid
673 : * an I/O error on SLRU lookup.
674 : */
675 84 : LWLockAcquire(XactTruncationLock, LW_SHARED);
676 84 : if (TransactionIdInRecentPast(fxid, &xid))
677 : {
678 : Assert(TransactionIdIsValid(xid));
679 :
680 : /*
681 : * Like when doing visibility checks on a row, check whether the
682 : * transaction is still in progress before looking into the CLOG.
683 : * Otherwise we would incorrectly return "committed" for a transaction
684 : * that is committing and has already updated the CLOG, but hasn't
685 : * removed its XID from the proc array yet. (See comment on that race
686 : * condition at the top of heapam_visibility.c)
687 : */
688 60 : if (TransactionIdIsInProgress(xid))
689 12 : status = "in progress";
690 48 : else if (TransactionIdDidCommit(xid))
691 36 : status = "committed";
692 : else
693 : {
694 : /* it must have aborted or crashed */
695 12 : status = "aborted";
696 : }
697 : }
698 : else
699 : {
700 12 : status = NULL;
701 : }
702 72 : LWLockRelease(XactTruncationLock);
703 :
704 72 : if (status == NULL)
705 12 : PG_RETURN_NULL();
706 : else
707 60 : PG_RETURN_TEXT_P(cstring_to_text(status));
708 : }
|