mirror of
https://github.com/postgres/postgres.git
synced 2025-05-05 09:19:17 +03:00
TransactionIdIsInProgress had a fast path to return 'false' if the single-item CLOG cache said that the transaction was known to be committed. However, that was wrong, because a transaction is first marked as committed in the CLOG but doesn't become visible to others until it has removed its XID from the proc array. That could lead to an error: ERROR: t_xmin is uncommitted in tuple to be updated or for an UPDATE to go ahead without blocking, before the previous UPDATE on the same row was made visible. The window is usually very short, but synchronous replication makes it much wider, because the wait for synchronous replica happens in that window. Another thing that makes it hard to hit is that it's hard to get such a commit-in-progress transaction into the single item CLOG cache. Normally, if you call TransactionIdIsInProgress on such a transaction, it determines that the XID is in progress without checking the CLOG and without populating the cache. One way to prime the cache is to explicitly call pg_xact_status() on the XID. Another way is to use a lot of subtransactions, so that the subxid cache in the proc array is overflown, making TransactionIdIsInProgress rely on pg_subtrans and CLOG checks. This has been broken ever since it was introduced in 2008, but the race condition is very hard to hit, especially without synchronous replication. There were a couple of reports of the error starting from summer 2021, but no one was able to find the root cause then. TransactionIdIsKnownCompleted() is now unused. In 'master', remove it, but I left it in place in backbranches in case it's used by extensions. Also change pg_xact_status() to check TransactionIdIsInProgress(). Previously, it only checked the CLOG, and returned "committed" before the transaction was actually made visible to other queries. Note that this also means that you cannot use pg_xact_status() to reproduce the bug anymore, even if the code wasn't fixed. Report and analysis by Konstantin Knizhnik. Patch by Simon Riggs, with the pg_xact_status() change added by me. Author: Simon Riggs Reviewed-by: Andres Freund Discussion: https://www.postgresql.org/message-id/flat/4da7913d-398c-e2ad-d777-f752cf7f0bbb%40garret.ru
709 lines
18 KiB
C
709 lines
18 KiB
C
/*-------------------------------------------------------------------------
|
|
* xid8funcs.c
|
|
*
|
|
* Export internal transaction IDs to user level.
|
|
*
|
|
* Note that only top-level transaction IDs are exposed to user sessions.
|
|
* This is important because xid8s frequently persist beyond the global
|
|
* xmin horizon, or may even be shipped to other machines, so we cannot
|
|
* rely on being able to correlate subtransaction IDs with their parents
|
|
* via functions such as SubTransGetTopmostTransaction().
|
|
*
|
|
* These functions are used to support the txid_XXX functions and the newer
|
|
* pg_current_xact, pg_current_snapshot and related fmgr functions, since the
|
|
* only difference between them is whether they expose xid8 or int8 values to
|
|
* users. The txid_XXX variants should eventually be dropped.
|
|
*
|
|
*
|
|
* Copyright (c) 2003-2022, PostgreSQL Global Development Group
|
|
* Author: Jan Wieck, Afilias USA INC.
|
|
* 64-bit txids: Marko Kreen, Skype Technologies
|
|
*
|
|
* src/backend/utils/adt/xid8funcs.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/clog.h"
|
|
#include "access/transam.h"
|
|
#include "access/xact.h"
|
|
#include "access/xlog.h"
|
|
#include "funcapi.h"
|
|
#include "lib/qunique.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "miscadmin.h"
|
|
#include "postmaster/postmaster.h"
|
|
#include "storage/lwlock.h"
|
|
#include "storage/procarray.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/snapmgr.h"
|
|
#include "utils/xid8.h"
|
|
|
|
|
|
/*
|
|
* If defined, use bsearch() function for searching for xid8s in snapshots
|
|
* that have more than the specified number of values.
|
|
*/
|
|
#define USE_BSEARCH_IF_NXIP_GREATER 30
|
|
|
|
|
|
/*
|
|
* Snapshot containing FullTransactionIds.
|
|
*/
|
|
typedef struct
|
|
{
|
|
/*
|
|
* 4-byte length hdr, should not be touched directly.
|
|
*
|
|
* Explicit embedding is ok as we want always correct alignment anyway.
|
|
*/
|
|
int32 __varsz;
|
|
|
|
uint32 nxip; /* number of fxids in xip array */
|
|
FullTransactionId xmin;
|
|
FullTransactionId xmax;
|
|
/* in-progress fxids, xmin <= xip[i] < xmax: */
|
|
FullTransactionId xip[FLEXIBLE_ARRAY_MEMBER];
|
|
} pg_snapshot;
|
|
|
|
#define PG_SNAPSHOT_SIZE(nxip) \
|
|
(offsetof(pg_snapshot, xip) + sizeof(FullTransactionId) * (nxip))
|
|
#define PG_SNAPSHOT_MAX_NXIP \
|
|
((MaxAllocSize - offsetof(pg_snapshot, xip)) / sizeof(FullTransactionId))
|
|
|
|
/*
|
|
* Helper to get a TransactionId from a 64-bit xid with wraparound detection.
|
|
*
|
|
* It is an ERROR if the xid is in the future. Otherwise, returns true if
|
|
* the transaction is still new enough that we can determine whether it
|
|
* committed and false otherwise. If *extracted_xid is not NULL, it is set
|
|
* to the low 32 bits of the transaction ID (i.e. the actual XID, without the
|
|
* epoch).
|
|
*
|
|
* The caller must hold XactTruncationLock since it's dealing with arbitrary
|
|
* XIDs, and must continue to hold it until it's done with any clog lookups
|
|
* relating to those XIDs.
|
|
*/
|
|
static bool
|
|
TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
|
|
{
|
|
uint32 xid_epoch = EpochFromFullTransactionId(fxid);
|
|
TransactionId xid = XidFromFullTransactionId(fxid);
|
|
uint32 now_epoch;
|
|
TransactionId now_epoch_next_xid;
|
|
FullTransactionId now_fullxid;
|
|
|
|
now_fullxid = ReadNextFullTransactionId();
|
|
now_epoch_next_xid = XidFromFullTransactionId(now_fullxid);
|
|
now_epoch = EpochFromFullTransactionId(now_fullxid);
|
|
|
|
if (extracted_xid != NULL)
|
|
*extracted_xid = xid;
|
|
|
|
if (!TransactionIdIsValid(xid))
|
|
return false;
|
|
|
|
/* For non-normal transaction IDs, we can ignore the epoch. */
|
|
if (!TransactionIdIsNormal(xid))
|
|
return true;
|
|
|
|
/* If the transaction ID is in the future, throw an error. */
|
|
if (!FullTransactionIdPrecedes(fxid, now_fullxid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("transaction ID %llu is in the future",
|
|
(unsigned long long) U64FromFullTransactionId(fxid))));
|
|
|
|
/*
|
|
* ShmemVariableCache->oldestClogXid is protected by XactTruncationLock,
|
|
* but we don't acquire that lock here. Instead, we require the caller to
|
|
* acquire it, because the caller is presumably going to look up the
|
|
* returned XID. If we took and released the lock within this function, a
|
|
* CLOG truncation could occur before the caller finished with the XID.
|
|
*/
|
|
Assert(LWLockHeldByMe(XactTruncationLock));
|
|
|
|
/*
|
|
* If the transaction ID has wrapped around, it's definitely too old to
|
|
* determine the commit status. Otherwise, we can compare it to
|
|
* ShmemVariableCache->oldestClogXid to determine whether the relevant
|
|
* CLOG entry is guaranteed to still exist.
|
|
*/
|
|
if (xid_epoch + 1 < now_epoch
|
|
|| (xid_epoch + 1 == now_epoch && xid < now_epoch_next_xid)
|
|
|| TransactionIdPrecedes(xid, ShmemVariableCache->oldestClogXid))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Convert a TransactionId obtained from a snapshot held by the caller to a
|
|
* FullTransactionId. Use next_fxid as a reference FullTransactionId, so that
|
|
* we can compute the high order bits. It must have been obtained by the
|
|
* caller with ReadNextFullTransactionId() after the snapshot was created.
|
|
*/
|
|
static FullTransactionId
|
|
widen_snapshot_xid(TransactionId xid, FullTransactionId next_fxid)
|
|
{
|
|
TransactionId next_xid = XidFromFullTransactionId(next_fxid);
|
|
uint32 epoch = EpochFromFullTransactionId(next_fxid);
|
|
|
|
/* Special transaction ID. */
|
|
if (!TransactionIdIsNormal(xid))
|
|
return FullTransactionIdFromEpochAndXid(0, xid);
|
|
|
|
/*
|
|
* The 64 bit result must be <= next_fxid, since next_fxid hadn't been
|
|
* issued yet when the snapshot was created. Every TransactionId in the
|
|
* snapshot must therefore be from the same epoch as next_fxid, or the
|
|
* epoch before. We know this because next_fxid is never allow to get
|
|
* more than one epoch ahead of the TransactionIds in any snapshot.
|
|
*/
|
|
if (xid > next_xid)
|
|
epoch--;
|
|
|
|
return FullTransactionIdFromEpochAndXid(epoch, xid);
|
|
}
|
|
|
|
/*
|
|
* txid comparator for qsort/bsearch
|
|
*/
|
|
static int
|
|
cmp_fxid(const void *aa, const void *bb)
|
|
{
|
|
FullTransactionId a = *(const FullTransactionId *) aa;
|
|
FullTransactionId b = *(const FullTransactionId *) bb;
|
|
|
|
if (FullTransactionIdPrecedes(a, b))
|
|
return -1;
|
|
if (FullTransactionIdPrecedes(b, a))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Sort a snapshot's txids, so we can use bsearch() later. Also remove
|
|
* any duplicates.
|
|
*
|
|
* For consistency of on-disk representation, we always sort even if bsearch
|
|
* will not be used.
|
|
*/
|
|
static void
|
|
sort_snapshot(pg_snapshot *snap)
|
|
{
|
|
if (snap->nxip > 1)
|
|
{
|
|
qsort(snap->xip, snap->nxip, sizeof(FullTransactionId), cmp_fxid);
|
|
snap->nxip = qunique(snap->xip, snap->nxip, sizeof(FullTransactionId),
|
|
cmp_fxid);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* check fxid visibility.
|
|
*/
|
|
static bool
|
|
is_visible_fxid(FullTransactionId value, const pg_snapshot *snap)
|
|
{
|
|
if (FullTransactionIdPrecedes(value, snap->xmin))
|
|
return true;
|
|
else if (!FullTransactionIdPrecedes(value, snap->xmax))
|
|
return false;
|
|
#ifdef USE_BSEARCH_IF_NXIP_GREATER
|
|
else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER)
|
|
{
|
|
void *res;
|
|
|
|
res = bsearch(&value, snap->xip, snap->nxip, sizeof(FullTransactionId),
|
|
cmp_fxid);
|
|
/* if found, transaction is still in progress */
|
|
return (res) ? false : true;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
uint32 i;
|
|
|
|
for (i = 0; i < snap->nxip; i++)
|
|
{
|
|
if (FullTransactionIdEquals(value, snap->xip[i]))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* helper functions to use StringInfo for pg_snapshot creation.
|
|
*/
|
|
|
|
static StringInfo
|
|
buf_init(FullTransactionId xmin, FullTransactionId xmax)
|
|
{
|
|
pg_snapshot snap;
|
|
StringInfo buf;
|
|
|
|
snap.xmin = xmin;
|
|
snap.xmax = xmax;
|
|
snap.nxip = 0;
|
|
|
|
buf = makeStringInfo();
|
|
appendBinaryStringInfo(buf, (char *) &snap, PG_SNAPSHOT_SIZE(0));
|
|
return buf;
|
|
}
|
|
|
|
static void
|
|
buf_add_txid(StringInfo buf, FullTransactionId fxid)
|
|
{
|
|
pg_snapshot *snap = (pg_snapshot *) buf->data;
|
|
|
|
/* do this before possible realloc */
|
|
snap->nxip++;
|
|
|
|
appendBinaryStringInfo(buf, (char *) &fxid, sizeof(fxid));
|
|
}
|
|
|
|
static pg_snapshot *
|
|
buf_finalize(StringInfo buf)
|
|
{
|
|
pg_snapshot *snap = (pg_snapshot *) buf->data;
|
|
|
|
SET_VARSIZE(snap, buf->len);
|
|
|
|
/* buf is not needed anymore */
|
|
buf->data = NULL;
|
|
pfree(buf);
|
|
|
|
return snap;
|
|
}
|
|
|
|
/*
|
|
* parse snapshot from cstring
|
|
*/
|
|
static pg_snapshot *
|
|
parse_snapshot(const char *str)
|
|
{
|
|
FullTransactionId xmin;
|
|
FullTransactionId xmax;
|
|
FullTransactionId last_val = InvalidFullTransactionId;
|
|
FullTransactionId val;
|
|
const char *str_start = str;
|
|
char *endp;
|
|
StringInfo buf;
|
|
|
|
xmin = FullTransactionIdFromU64(strtou64(str, &endp, 10));
|
|
if (*endp != ':')
|
|
goto bad_format;
|
|
str = endp + 1;
|
|
|
|
xmax = FullTransactionIdFromU64(strtou64(str, &endp, 10));
|
|
if (*endp != ':')
|
|
goto bad_format;
|
|
str = endp + 1;
|
|
|
|
/* it should look sane */
|
|
if (!FullTransactionIdIsValid(xmin) ||
|
|
!FullTransactionIdIsValid(xmax) ||
|
|
FullTransactionIdPrecedes(xmax, xmin))
|
|
goto bad_format;
|
|
|
|
/* allocate buffer */
|
|
buf = buf_init(xmin, xmax);
|
|
|
|
/* loop over values */
|
|
while (*str != '\0')
|
|
{
|
|
/* read next value */
|
|
val = FullTransactionIdFromU64(strtou64(str, &endp, 10));
|
|
str = endp;
|
|
|
|
/* require the input to be in order */
|
|
if (FullTransactionIdPrecedes(val, xmin) ||
|
|
FullTransactionIdFollowsOrEquals(val, xmax) ||
|
|
FullTransactionIdPrecedes(val, last_val))
|
|
goto bad_format;
|
|
|
|
/* skip duplicates */
|
|
if (!FullTransactionIdEquals(val, last_val))
|
|
buf_add_txid(buf, val);
|
|
last_val = val;
|
|
|
|
if (*str == ',')
|
|
str++;
|
|
else if (*str != '\0')
|
|
goto bad_format;
|
|
}
|
|
|
|
return buf_finalize(buf);
|
|
|
|
bad_format:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
"pg_snapshot", str_start)));
|
|
return NULL; /* keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* pg_current_xact_id() returns xid8
|
|
*
|
|
* Return the current toplevel full transaction ID.
|
|
* If the current transaction does not have one, one is assigned.
|
|
*/
|
|
Datum
|
|
pg_current_xact_id(PG_FUNCTION_ARGS)
|
|
{
|
|
/*
|
|
* Must prevent during recovery because if an xid is not assigned we try
|
|
* to assign one, which would fail. Programs already rely on this function
|
|
* to always return a valid current xid, so we should not change this to
|
|
* return NULL or similar invalid xid.
|
|
*/
|
|
PreventCommandDuringRecovery("pg_current_xact_id()");
|
|
|
|
PG_RETURN_FULLTRANSACTIONID(GetTopFullTransactionId());
|
|
}
|
|
|
|
/*
|
|
* Same as pg_current_xact_id() but doesn't assign a new xid if there
|
|
* isn't one yet.
|
|
*/
|
|
Datum
|
|
pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS)
|
|
{
|
|
FullTransactionId topfxid = GetTopFullTransactionIdIfAny();
|
|
|
|
if (!FullTransactionIdIsValid(topfxid))
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FULLTRANSACTIONID(topfxid);
|
|
}
|
|
|
|
/*
|
|
* pg_current_snapshot() returns pg_snapshot
|
|
*
|
|
* Return current snapshot
|
|
*
|
|
* Note that only top-transaction XIDs are included in the snapshot.
|
|
*/
|
|
Datum
|
|
pg_current_snapshot(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_snapshot *snap;
|
|
uint32 nxip,
|
|
i;
|
|
Snapshot cur;
|
|
FullTransactionId next_fxid = ReadNextFullTransactionId();
|
|
|
|
cur = GetActiveSnapshot();
|
|
if (cur == NULL)
|
|
elog(ERROR, "no active snapshot set");
|
|
|
|
/*
|
|
* Compile-time limits on the procarray (MAX_BACKENDS processes plus
|
|
* MAX_BACKENDS prepared transactions) guarantee nxip won't be too large.
|
|
*/
|
|
StaticAssertStmt(MAX_BACKENDS * 2 <= PG_SNAPSHOT_MAX_NXIP,
|
|
"possible overflow in pg_current_snapshot()");
|
|
|
|
/* allocate */
|
|
nxip = cur->xcnt;
|
|
snap = palloc(PG_SNAPSHOT_SIZE(nxip));
|
|
|
|
/* fill */
|
|
snap->xmin = widen_snapshot_xid(cur->xmin, next_fxid);
|
|
snap->xmax = widen_snapshot_xid(cur->xmax, next_fxid);
|
|
snap->nxip = nxip;
|
|
for (i = 0; i < nxip; i++)
|
|
snap->xip[i] = widen_snapshot_xid(cur->xip[i], next_fxid);
|
|
|
|
/*
|
|
* We want them guaranteed to be in ascending order. This also removes
|
|
* any duplicate xids. Normally, an XID can only be assigned to one
|
|
* backend, but when preparing a transaction for two-phase commit, there
|
|
* is a transient state when both the original backend and the dummy
|
|
* PGPROC entry reserved for the prepared transaction hold the same XID.
|
|
*/
|
|
sort_snapshot(snap);
|
|
|
|
/* set size after sorting, because it may have removed duplicate xips */
|
|
SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(snap->nxip));
|
|
|
|
PG_RETURN_POINTER(snap);
|
|
}
|
|
|
|
/*
|
|
* pg_snapshot_in(cstring) returns pg_snapshot
|
|
*
|
|
* input function for type pg_snapshot
|
|
*/
|
|
Datum
|
|
pg_snapshot_in(PG_FUNCTION_ARGS)
|
|
{
|
|
char *str = PG_GETARG_CSTRING(0);
|
|
pg_snapshot *snap;
|
|
|
|
snap = parse_snapshot(str);
|
|
|
|
PG_RETURN_POINTER(snap);
|
|
}
|
|
|
|
/*
|
|
* pg_snapshot_out(pg_snapshot) returns cstring
|
|
*
|
|
* output function for type pg_snapshot
|
|
*/
|
|
Datum
|
|
pg_snapshot_out(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
|
|
StringInfoData str;
|
|
uint32 i;
|
|
|
|
initStringInfo(&str);
|
|
|
|
appendStringInfo(&str, UINT64_FORMAT ":",
|
|
U64FromFullTransactionId(snap->xmin));
|
|
appendStringInfo(&str, UINT64_FORMAT ":",
|
|
U64FromFullTransactionId(snap->xmax));
|
|
|
|
for (i = 0; i < snap->nxip; i++)
|
|
{
|
|
if (i > 0)
|
|
appendStringInfoChar(&str, ',');
|
|
appendStringInfo(&str, UINT64_FORMAT,
|
|
U64FromFullTransactionId(snap->xip[i]));
|
|
}
|
|
|
|
PG_RETURN_CSTRING(str.data);
|
|
}
|
|
|
|
/*
|
|
* pg_snapshot_recv(internal) returns pg_snapshot
|
|
*
|
|
* binary input function for type pg_snapshot
|
|
*
|
|
* format: int4 nxip, int8 xmin, int8 xmax, int8 xip
|
|
*/
|
|
Datum
|
|
pg_snapshot_recv(PG_FUNCTION_ARGS)
|
|
{
|
|
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
|
pg_snapshot *snap;
|
|
FullTransactionId last = InvalidFullTransactionId;
|
|
int nxip;
|
|
int i;
|
|
FullTransactionId xmin;
|
|
FullTransactionId xmax;
|
|
|
|
/* load and validate nxip */
|
|
nxip = pq_getmsgint(buf, 4);
|
|
if (nxip < 0 || nxip > PG_SNAPSHOT_MAX_NXIP)
|
|
goto bad_format;
|
|
|
|
xmin = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
|
|
xmax = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
|
|
if (!FullTransactionIdIsValid(xmin) ||
|
|
!FullTransactionIdIsValid(xmax) ||
|
|
FullTransactionIdPrecedes(xmax, xmin))
|
|
goto bad_format;
|
|
|
|
snap = palloc(PG_SNAPSHOT_SIZE(nxip));
|
|
snap->xmin = xmin;
|
|
snap->xmax = xmax;
|
|
|
|
for (i = 0; i < nxip; i++)
|
|
{
|
|
FullTransactionId cur =
|
|
FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
|
|
|
|
if (FullTransactionIdPrecedes(cur, last) ||
|
|
FullTransactionIdPrecedes(cur, xmin) ||
|
|
FullTransactionIdPrecedes(xmax, cur))
|
|
goto bad_format;
|
|
|
|
/* skip duplicate xips */
|
|
if (FullTransactionIdEquals(cur, last))
|
|
{
|
|
i--;
|
|
nxip--;
|
|
continue;
|
|
}
|
|
|
|
snap->xip[i] = cur;
|
|
last = cur;
|
|
}
|
|
snap->nxip = nxip;
|
|
SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(nxip));
|
|
PG_RETURN_POINTER(snap);
|
|
|
|
bad_format:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
|
|
errmsg("invalid external pg_snapshot data")));
|
|
PG_RETURN_POINTER(NULL); /* keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* pg_snapshot_send(pg_snapshot) returns bytea
|
|
*
|
|
* binary output function for type pg_snapshot
|
|
*
|
|
* format: int4 nxip, u64 xmin, u64 xmax, u64 xip...
|
|
*/
|
|
Datum
|
|
pg_snapshot_send(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
|
|
StringInfoData buf;
|
|
uint32 i;
|
|
|
|
pq_begintypsend(&buf);
|
|
pq_sendint32(&buf, snap->nxip);
|
|
pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmin));
|
|
pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmax));
|
|
for (i = 0; i < snap->nxip; i++)
|
|
pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xip[i]));
|
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
|
}
|
|
|
|
/*
|
|
* pg_visible_in_snapshot(xid8, pg_snapshot) returns bool
|
|
*
|
|
* is txid visible in snapshot ?
|
|
*/
|
|
Datum
|
|
pg_visible_in_snapshot(PG_FUNCTION_ARGS)
|
|
{
|
|
FullTransactionId value = PG_GETARG_FULLTRANSACTIONID(0);
|
|
pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(1);
|
|
|
|
PG_RETURN_BOOL(is_visible_fxid(value, snap));
|
|
}
|
|
|
|
/*
|
|
* pg_snapshot_xmin(pg_snapshot) returns xid8
|
|
*
|
|
* return snapshot's xmin
|
|
*/
|
|
Datum
|
|
pg_snapshot_xmin(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
|
|
|
|
PG_RETURN_FULLTRANSACTIONID(snap->xmin);
|
|
}
|
|
|
|
/*
|
|
* pg_snapshot_xmax(pg_snapshot) returns xid8
|
|
*
|
|
* return snapshot's xmax
|
|
*/
|
|
Datum
|
|
pg_snapshot_xmax(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
|
|
|
|
PG_RETURN_FULLTRANSACTIONID(snap->xmax);
|
|
}
|
|
|
|
/*
|
|
* pg_snapshot_xip(pg_snapshot) returns setof xid8
|
|
*
|
|
* return in-progress xid8s in snapshot.
|
|
*/
|
|
Datum
|
|
pg_snapshot_xip(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext *fctx;
|
|
pg_snapshot *snap;
|
|
FullTransactionId value;
|
|
|
|
/* on first call initialize fctx and get copy of snapshot */
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
pg_snapshot *arg = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
|
|
|
|
fctx = SRF_FIRSTCALL_INIT();
|
|
|
|
/* make a copy of user snapshot */
|
|
snap = MemoryContextAlloc(fctx->multi_call_memory_ctx, VARSIZE(arg));
|
|
memcpy(snap, arg, VARSIZE(arg));
|
|
|
|
fctx->user_fctx = snap;
|
|
}
|
|
|
|
/* return values one-by-one */
|
|
fctx = SRF_PERCALL_SETUP();
|
|
snap = fctx->user_fctx;
|
|
if (fctx->call_cntr < snap->nxip)
|
|
{
|
|
value = snap->xip[fctx->call_cntr];
|
|
SRF_RETURN_NEXT(fctx, FullTransactionIdGetDatum(value));
|
|
}
|
|
else
|
|
{
|
|
SRF_RETURN_DONE(fctx);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Report the status of a recent transaction ID, or null for wrapped,
|
|
* truncated away or otherwise too old XIDs.
|
|
*
|
|
* The passed epoch-qualified xid is treated as a normal xid, not a
|
|
* multixact id.
|
|
*
|
|
* If it points to a committed subxact the result is the subxact status even
|
|
* though the parent xact may still be in progress or may have aborted.
|
|
*/
|
|
Datum
|
|
pg_xact_status(PG_FUNCTION_ARGS)
|
|
{
|
|
const char *status;
|
|
FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0);
|
|
TransactionId xid;
|
|
|
|
/*
|
|
* We must protect against concurrent truncation of clog entries to avoid
|
|
* an I/O error on SLRU lookup.
|
|
*/
|
|
LWLockAcquire(XactTruncationLock, LW_SHARED);
|
|
if (TransactionIdInRecentPast(fxid, &xid))
|
|
{
|
|
Assert(TransactionIdIsValid(xid));
|
|
|
|
/*
|
|
* Like when doing visiblity checks on a row, check whether the
|
|
* transaction is still in progress before looking into the CLOG.
|
|
* Otherwise we would incorrectly return "committed" for a transaction
|
|
* that is committing and has already updated the CLOG, but hasn't
|
|
* removed its XID from the proc array yet. (See comment on that race
|
|
* condition at the top of heapam_visibility.c)
|
|
*/
|
|
if (TransactionIdIsInProgress(xid))
|
|
status = "in progress";
|
|
else if (TransactionIdDidCommit(xid))
|
|
status = "committed";
|
|
else
|
|
{
|
|
/* it must have aborted or crashed */
|
|
status = "aborted";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = NULL;
|
|
}
|
|
LWLockRelease(XactTruncationLock);
|
|
|
|
if (status == NULL)
|
|
PG_RETURN_NULL();
|
|
else
|
|
PG_RETURN_TEXT_P(cstring_to_text(status));
|
|
}
|