On 22 March 2017 at 09:49, Craig Ringer <cr...@2ndquadrant.com> wrote:
>> Overall, though, I think that 0001 looks far better than any previous >> iteration. It's simple. It looks safe. It seems unlikely to break >> anything that works now. Woo hoo! > > Funny that this started with "hey, here's a simple, non-invasive > function for looking up the status of an arbitrary xid". Changes made per discussion. Removed the comments on TransactionIdDidCommit and TransactionIdDidAbort . It's not going to be relevant for the immense majority of callers anyway, and callers that are looking up arbitrary user supplied XIDs will (hopefully) be looking at TransactionIdInRecentPast anyway. I'll be leaving the 'xid' vs 'bigint' issues elsewhere in Pg for next release, nowhere near time for that now. -- Craig Ringer http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training & Services
From 5766e6506e569b0d91e22e90c5e6786ce9fefdf6 Mon Sep 17 00:00:00 2001 From: Craig Ringer <cr...@2ndquadrant.com> Date: Mon, 23 Jan 2017 13:25:30 +0800 Subject: [PATCH 1/3] Fix race between clog truncation and lookup There was previously no way to look up an arbitrary xid without running the risk of having clog truncated out from under you. This hasn't previously been a problem because anything looking up xids in clog knows they're protected by datminxid, but that's not the case for arbitrary user-supplied XIDs. clog is truncated before we advanced oldestXid under XidGenLock, so holding XidGenLock during a clog lookup is insufficient to prevent the race. There's no way to look up a SLRU with soft-failure; attempting a lookup produces an I/O error. There's also no safe way to trap and swallow the SLRU lookup error due mainly to locking issues. To address this, introduce a copy of oldestXid, oldestClogXid, that is advanced before clog truncation under a new LWLock, CLogTruncationLock. Lookups of arbitrary XIDs must take and hold CLogTruncationLock to prevent concurrent advance of the minimum valid xid in clog. This race also exists in a worse form on standby servers. On a standby we only advance oldestXid when we replay the next checkpoint, so there's a much larger window between clog truncation and subsequent updating of the limit. Fix this by recording the oldest xid in clog truncation records and applying the update to oldestClogXid under ClogTruncationLock before replaying the clog truncation. No attempt is made to eagerly update oldestXid on the standby, so it may fall behind oldestClogXid until the next checkpoint. Note that there's no need to take ClogTruncationLock for normal clog lookups protected by datfrozenxid, only if accepting arbitrary XIDs that might not be protected by vacuum thresholds. --- doc/src/sgml/monitoring.sgml | 4 +++ src/backend/access/rmgrdesc/clogdesc.c | 12 +++++++-- src/backend/access/transam/clog.c | 46 +++++++++++++++++++++++++------- src/backend/access/transam/transam.c | 4 +-- src/backend/access/transam/varsup.c | 23 +++++++++++++++- src/backend/access/transam/xlog.c | 11 ++++++++ src/backend/commands/vacuum.c | 2 +- src/backend/storage/lmgr/lwlocknames.txt | 1 + src/include/access/clog.h | 8 +++++- src/include/access/transam.h | 7 +++++ 10 files changed, 101 insertions(+), 17 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index dcb2d33..1c84ce5 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -1018,6 +1018,10 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser <entry>Waiting to read or update old snapshot control information.</entry> </row> <row> + <entry><literal>CLogTruncationLock</></entry> + <entry>Waiting to truncate the transaction log or waiting for transaction log truncation to finish.</entry> + </row> + <row> <entry><literal>clog</></entry> <entry>Waiting for I/O on a clog (transaction status) buffer.</entry> </row> diff --git a/src/backend/access/rmgrdesc/clogdesc.c b/src/backend/access/rmgrdesc/clogdesc.c index 352de48..ef268c5 100644 --- a/src/backend/access/rmgrdesc/clogdesc.c +++ b/src/backend/access/rmgrdesc/clogdesc.c @@ -23,12 +23,20 @@ clog_desc(StringInfo buf, XLogReaderState *record) char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - if (info == CLOG_ZEROPAGE || info == CLOG_TRUNCATE) + if (info == CLOG_ZEROPAGE) { int pageno; memcpy(&pageno, rec, sizeof(int)); - appendStringInfo(buf, "%d", pageno); + appendStringInfo(buf, "page %d", pageno); + } + else if (info == CLOG_TRUNCATE) + { + xl_clog_truncate xlrec; + + memcpy(&xlrec, rec, sizeof(xl_clog_truncate)); + appendStringInfo(buf, "page %d; oldestXact %u", + xlrec.pageno, xlrec.oldestXact); } } diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index 5b1d13d..2d33510 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -83,7 +83,8 @@ static SlruCtlData ClogCtlData; static int ZeroCLOGPage(int pageno, bool writeXlog); static bool CLOGPagePrecedes(int page1, int page2); static void WriteZeroPageXlogRec(int pageno); -static void WriteTruncateXlogRec(int pageno); +static void WriteTruncateXlogRec(int pageno, TransactionId oldestXact, + Oid oldestXidDb); static void TransactionIdSetPageStatus(TransactionId xid, int nsubxids, TransactionId *subxids, XidStatus status, XLogRecPtr lsn, int pageno); @@ -640,7 +641,7 @@ ExtendCLOG(TransactionId newestXact) * the XLOG flush unless we have confirmed that there is a removable segment. */ void -TruncateCLOG(TransactionId oldestXact) +TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid) { int cutoffPage; @@ -654,8 +655,26 @@ TruncateCLOG(TransactionId oldestXact) if (!SlruScanDirectory(ClogCtl, SlruScanDirCbReportPresence, &cutoffPage)) return; /* nothing to remove */ - /* Write XLOG record and flush XLOG to disk */ - WriteTruncateXlogRec(cutoffPage); + /* + * Advance oldestClogXid before truncating clog, so concurrent xact status + * lookups can ensure they don't attempt to access truncated-away clog. + * + * It's only necessary to do this if we will actually truncate away clog + * pages. + */ + AdvanceOldestClogXid(oldestXact); + + /* vac_truncate_clog already advanced oldestXid */ + Assert(TransactionIdPrecedesOrEquals(oldestXact, + ShmemVariableCache->oldestXid)); + + /* + * Write XLOG record and flush XLOG to disk. We record the oldest xid we're + * keeping information about here so we can ensure that it's always ahead + * of clog truncation in case we crash, and so a standby finds out the new + * valid xid before the next checkpoint. + */ + WriteTruncateXlogRec(cutoffPage, oldestXact, oldestxid_datoid); /* Now we can remove the old CLOG segment(s) */ SimpleLruTruncate(ClogCtl, cutoffPage); @@ -704,12 +723,17 @@ WriteZeroPageXlogRec(int pageno) * in TruncateCLOG(). */ static void -WriteTruncateXlogRec(int pageno) +WriteTruncateXlogRec(int pageno, TransactionId oldestXact, Oid oldestXactDb) { XLogRecPtr recptr; + xl_clog_truncate xlrec; + + xlrec.pageno = pageno; + xlrec.oldestXact = oldestXact; + xlrec.oldestXactDb = oldestXactDb; XLogBeginInsert(); - XLogRegisterData((char *) (&pageno), sizeof(int)); + XLogRegisterData((char *) (&xlrec), sizeof(xl_clog_truncate)); recptr = XLogInsert(RM_CLOG_ID, CLOG_TRUNCATE); XLogFlush(recptr); } @@ -742,17 +766,19 @@ clog_redo(XLogReaderState *record) } else if (info == CLOG_TRUNCATE) { - int pageno; + xl_clog_truncate xlrec; - memcpy(&pageno, XLogRecGetData(record), sizeof(int)); + memcpy(&xlrec, XLogRecGetData(record), sizeof(xl_clog_truncate)); /* * During XLOG replay, latest_page_number isn't set up yet; insert a * suitable value to bypass the sanity test in SimpleLruTruncate. */ - ClogCtl->shared->latest_page_number = pageno; + ClogCtl->shared->latest_page_number = xlrec.pageno; - SimpleLruTruncate(ClogCtl, pageno); + AdvanceOldestClogXid(xlrec.oldestXact); + + SimpleLruTruncate(ClogCtl, xlrec.pageno); } else elog(PANIC, "clog_redo: unknown op code %u", info); diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c index b91a259..562b53b 100644 --- a/src/backend/access/transam/transam.c +++ b/src/backend/access/transam/transam.c @@ -119,7 +119,7 @@ TransactionLogFetch(TransactionId transactionId) * True iff transaction associated with the identifier did commit. * * Note: - * Assumes transaction identifier is valid. + * Assumes transaction identifier is valid and exists in clog. */ bool /* true if given transaction committed */ TransactionIdDidCommit(TransactionId transactionId) @@ -175,7 +175,7 @@ TransactionIdDidCommit(TransactionId transactionId) * True iff transaction associated with the identifier did abort. * * Note: - * Assumes transaction identifier is valid. + * Assumes transaction identifier is valid and exists in clog. */ bool /* true if given transaction aborted */ TransactionIdDidAbort(TransactionId transactionId) diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 42fc351..5efbfbd 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -259,7 +259,28 @@ ReadNewTransactionId(void) } /* - * Determine the last safe XID to allocate given the currently oldest + * Advance the cluster-wide value for the oldest valid clog entry. + * + * We must acquire CLogTruncationLock to advance the oldestClogXid. It's not + * necessary to hold the lock during the actual clog truncation, only when we + * advance the limit, as code looking up arbitrary xids is required to hold + * CLogTruncationLock from when it tests oldestClogXid through to when it + * completes the clog lookup. + */ +void +AdvanceOldestClogXid(TransactionId oldest_datfrozenxid) +{ + LWLockAcquire(CLogTruncationLock, LW_EXCLUSIVE); + if (TransactionIdPrecedes(ShmemVariableCache->oldestClogXid, + oldest_datfrozenxid)) + { + ShmemVariableCache->oldestClogXid = oldest_datfrozenxid; + } + LWLockRelease(CLogTruncationLock); +} + +/* + * Determine the last safe XID to allocate using the currently oldest * datfrozenxid (ie, the oldest XID that might exist in any database * of our cluster), and the OID of the (or a) database with that value. */ diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 9480377..fbdff55 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -5016,6 +5016,7 @@ BootStrapXLOG(void) ShmemVariableCache->nextOid = checkPoint.nextOid; ShmemVariableCache->oidCount = 0; MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset); + AdvanceOldestClogXid(checkPoint.oldestXid); SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB); SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB, true); SetCommitTsLimit(InvalidTransactionId, InvalidTransactionId); @@ -6622,6 +6623,7 @@ StartupXLOG(void) ShmemVariableCache->nextOid = checkPoint.nextOid; ShmemVariableCache->oidCount = 0; MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset); + AdvanceOldestClogXid(checkPoint.oldestXid); SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB); SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB, true); SetCommitTsLimit(checkPoint.oldestCommitTsXid, @@ -8687,6 +8689,11 @@ CreateCheckPoint(int flags) /* * Get the other info we need for the checkpoint record. + * + * We don't need to save oldestClogXid in the checkpoint, it only matters + * for the short period in which clog is being truncated, and if we crash + * during that we'll redo the clog truncation and fix up oldestClogXid + * there. */ LWLockAcquire(XidGenLock, LW_SHARED); checkPoint.nextXid = ShmemVariableCache->nextXid; @@ -9616,6 +9623,10 @@ xlog_redo(XLogReaderState *record) MultiXactAdvanceOldest(checkPoint.oldestMulti, checkPoint.oldestMultiDB); + /* + * No need to set oldestClogXid here as well; it'll be set when we + * redo an xl_clog_truncate if it changed since initialization. + */ SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB); /* diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index ff633fa..c4a0f89 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1194,7 +1194,7 @@ vac_truncate_clog(TransactionId frozenXID, /* * Truncate CLOG, multixact and CommitTs to the oldest computed value. */ - TruncateCLOG(frozenXID); + TruncateCLOG(frozenXID, oldestxid_datoid); TruncateCommitTs(frozenXID); TruncateMultiXact(minMulti, minmulti_datoid); diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt index cd8b08f..e6025ec 100644 --- a/src/backend/storage/lmgr/lwlocknames.txt +++ b/src/backend/storage/lmgr/lwlocknames.txt @@ -49,3 +49,4 @@ MultiXactTruncationLock 41 OldSnapshotTimeMapLock 42 BackendRandomLock 43 LogicalRepWorkerLock 44 +CLogTruncationLock 45 diff --git a/src/include/access/clog.h b/src/include/access/clog.h index 2894bd5..60a9e11 100644 --- a/src/include/access/clog.h +++ b/src/include/access/clog.h @@ -28,6 +28,12 @@ typedef int XidStatus; #define TRANSACTION_STATUS_ABORTED 0x02 #define TRANSACTION_STATUS_SUB_COMMITTED 0x03 +typedef struct xl_clog_truncate +{ + int pageno; + TransactionId oldestXact; + Oid oldestXactDb; +} xl_clog_truncate; extern void TransactionIdSetTreeStatus(TransactionId xid, int nsubxids, TransactionId *subxids, XidStatus status, XLogRecPtr lsn); @@ -42,7 +48,7 @@ extern void TrimCLOG(void); extern void ShutdownCLOG(void); extern void CheckPointCLOG(void); extern void ExtendCLOG(TransactionId newestXact); -extern void TruncateCLOG(TransactionId oldestXact); +extern void TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid); /* XLOG stuff */ #define CLOG_ZEROPAGE 0x00 diff --git a/src/include/access/transam.h b/src/include/access/transam.h index 522c104..d25a2dd 100644 --- a/src/include/access/transam.h +++ b/src/include/access/transam.h @@ -134,6 +134,12 @@ typedef struct VariableCacheData */ TransactionId latestCompletedXid; /* newest XID that has committed or * aborted */ + + /* + * These fields are protected by CLogTruncationLock + */ + TransactionId oldestClogXid; /* oldest it's safe to look up in clog */ + } VariableCacheData; typedef VariableCacheData *VariableCache; @@ -173,6 +179,7 @@ extern TransactionId GetNewTransactionId(bool isSubXact); extern TransactionId ReadNewTransactionId(void); extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid); +extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid); extern bool ForceTransactionIdLimitUpdate(void); extern Oid GetNewObjectId(void); -- 2.5.5
From 2ba56472d01e3d9db03d21faed68829998dd4b76 Mon Sep 17 00:00:00 2001 From: Craig Ringer <cr...@2ndquadrant.com> Date: Mon, 23 Jan 2017 13:34:02 +0800 Subject: [PATCH 2/3] Introduce txid_status(bigint) to get status of an xact If an application loses its connection while a COMMIT request is in flight, the backend crashes mid-commit, etc, then an application may not be sure whether or not a commit completed successfully or was rolled back. While two-phase commit solves this it does so at a considerable overhead, so introduce a lighter alternative. txid_status(bigint) lets an application determine the status of a a commit based on an xid-with-epoch as returned by txid_current() or similar. Status may be committed, aborted, in-progress (including prepared xacts) or null if the xact is too old for its commit status to still be retained because it has passed the wrap-around epoch boundary. Applications must call txid_current() in their transactions to make much use of this since PostgreSQL does not automatically report an xid to the client when one is assigned. Introduces TransactionIdInRecentPast(...) for the use of other functions that need similar logic in future. Authors: Craig Ringer, Robert Haas --- doc/src/sgml/func.sgml | 27 +++++++++ src/backend/utils/adt/txid.c | 120 +++++++++++++++++++++++++++++++++++++ src/include/catalog/pg_proc.h | 2 + src/test/regress/expected/txid.out | 68 +++++++++++++++++++++ src/test/regress/sql/txid.sql | 38 ++++++++++++ 5 files changed, 255 insertions(+) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9408a25..c8639b8 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17523,6 +17523,10 @@ SELECT collation for ('foo' COLLATE "de_DE"); <primary>txid_visible_in_snapshot</primary> </indexterm> + <indexterm> + <primary>txid_status</primary> + </indexterm> + <para> The functions shown in <xref linkend="functions-txid-snapshot"> provide server transaction information in an exportable form. The main @@ -17573,6 +17577,11 @@ SELECT collation for ('foo' COLLATE "de_DE"); <entry><type>boolean</type></entry> <entry>is transaction ID visible in snapshot? (do not use with subtransaction ids)</entry> </row> + <row> + <entry><literal><function>txid_status(<parameter>bigint</parameter>)</function></literal></entry> + <entry><type>txid_status</type></entry> + <entry>report the status of the given xact - <literal>committed</literal>, <literal>aborted</literal>, <literal>in progress</literal>, or NULL if the xid is too old</entry> + </row> </tbody> </tgroup> </table> @@ -17643,6 +17652,24 @@ SELECT collation for ('foo' COLLATE "de_DE"); </para> <para> + <function>txid_status(bigint)</> reports the commit status of a recent + transaction. Applications may use it to determine whether a transaction + committed or aborted when the application and database server become + disconnected while a <literal>COMMIT</literal> is in progress. + The status of a transaction will be reported as either + <literal>in progress</>, + <literal>committed</>, or <literal>aborted</>, provided that the + transaction is recent enough that the system retains the commit status + of that transaction. If is old enough that no references to that + transaction survive in the system and the commit status information has + been discarded, this function will return NULL. Note that prepared + transactions are reported as <literal>in progress</>; applications must + check <link + linkend="view-pg-prepared-xacts"><literal>pg_prepared_xacts</></> if they + need to determine whether the xid is a prepared transaction. + </para> + + <para> The functions shown in <xref linkend="functions-commit-timestamp"> provide information about transactions that have been already committed. These functions mainly provide information about when the transactions diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c index 772d7c7..2b64a58 100644 --- a/src/backend/utils/adt/txid.c +++ b/src/backend/utils/adt/txid.c @@ -21,6 +21,7 @@ #include "postgres.h" +#include "access/clog.h" #include "access/transam.h" #include "access/xact.h" #include "access/xlog.h" @@ -28,6 +29,7 @@ #include "miscadmin.h" #include "libpq/pqformat.h" #include "postmaster/postmaster.h" +#include "storage/lwlock.h" #include "utils/builtins.h" #include "utils/memutils.h" #include "utils/snapmgr.h" @@ -93,6 +95,67 @@ load_xid_epoch(TxidEpoch *state) } /* + * 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 CLogTruncationLock 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(uint64 xid_with_epoch, TransactionId *extracted_xid) +{ + uint32 xid_epoch = (uint32) (xid_with_epoch >> 32); + TransactionId xid = (TransactionId) xid_with_epoch; + uint32 now_epoch; + TransactionId now_epoch_last_xid; + + GetNextXidAndEpoch(&now_epoch_last_xid, &now_epoch); + + if (extracted_xid != NULL) + *extracted_xid = xid; + + /* 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 (xid_epoch > now_epoch + || (xid_epoch == now_epoch && xid > now_epoch_last_xid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("transaction ID " UINT64_FORMAT " is in the future", + xid_with_epoch))); + + /* + * ShmemVariableCache->oldestClogXid is protected by CLogTruncationLock, + * 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(CLogTruncationLock)); + + /* + * 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_last_xid) + || TransactionIdPrecedes(xid, ShmemVariableCache->oldestClogXid)) + return false; + + return true; +} + +/* * do a TransactionId -> txid conversion for an XID near the given epoch */ static txid @@ -354,6 +417,9 @@ bad_format: * * Return the current toplevel transaction ID as TXID * If the current transaction does not have one, one is assigned. + * + * This value has the epoch as the high 32 bits and the 32-bit xid + * as the low 32 bits. */ Datum txid_current(PG_FUNCTION_ARGS) @@ -658,3 +724,57 @@ txid_snapshot_xip(PG_FUNCTION_ARGS) SRF_RETURN_DONE(fctx); } } + +/* + * Report the status of a recent transaction ID, or null for wrapped, + * truncated away or otherwise too old XIDs. + */ +Datum +txid_status(PG_FUNCTION_ARGS) +{ + const char *status; + uint64 xid_with_epoch = PG_GETARG_INT64(0); + TransactionId xid; + + /* + * We must protect against concurrent truncation of clog entries to avoid + * an I/O error on SLRU lookup. + */ + LWLockAcquire(CLogTruncationLock, LW_SHARED); + if (TransactionIdInRecentPast(xid_with_epoch, &xid)) + { + if (!TransactionIdIsValid(xid)) + { + LWLockRelease(XidGenLock); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("transaction ID " UINT64_FORMAT " is an invalid xid", + xid_with_epoch))); + } + + if (TransactionIdIsCurrentTransactionId(xid)) + status = gettext_noop("in progress"); + else if (TransactionIdDidCommit(xid)) + status = gettext_noop("committed"); + else if (TransactionIdDidAbort(xid)) + status = gettext_noop("aborted"); + else + + /* + * can't test TransactionIdIsInProgress here or we race with + * concurrent commit/abort. There's no point anyway, since it + * might then commit/abort just after we check. + */ + status = gettext_noop("in progress"); + } + else + { + status = NULL; + } + LWLockRelease(CLogTruncationLock); + + if (status == NULL) + PG_RETURN_NULL(); + else + PG_RETURN_TEXT_P(cstring_to_text(status)); +} diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 836d6ff..39bd295 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4975,6 +4975,8 @@ DATA(insert OID = 2947 ( txid_snapshot_xip PGNSP PGUID 12 1 50 0 0 f f f f t DESCR("get set of in-progress txids in snapshot"); DATA(insert OID = 2948 ( txid_visible_in_snapshot PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "20 2970" _null_ _null_ _null_ _null_ _null_ txid_visible_in_snapshot _null_ _null_ _null_ )); DESCR("is txid visible in snapshot?"); +DATA(insert OID = 3360 ( txid_status PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 25 "20" _null_ _null_ _null_ _null_ _null_ txid_status _null_ _null_ _null_ )); +DESCR("commit status of transaction"); /* record comparison using normal comparison rules */ DATA(insert OID = 2981 ( record_eq PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ _null_ record_eq _null_ _null_ _null_ )); diff --git a/src/test/regress/expected/txid.out b/src/test/regress/expected/txid.out index 802ccb9..015dae3 100644 --- a/src/test/regress/expected/txid.out +++ b/src/test/regress/expected/txid.out @@ -254,3 +254,71 @@ SELECT txid_current_if_assigned() IS NOT DISTINCT FROM BIGINT :'txid_current'; (1 row) COMMIT; +-- test xid status functions +BEGIN; +SELECT txid_current() AS committed \gset +COMMIT; +BEGIN; +SELECT txid_current() AS rolledback \gset +ROLLBACK; +BEGIN; +SELECT txid_current() AS inprogress \gset +SELECT txid_status(:committed) AS committed; + committed +----------- + committed +(1 row) + +SELECT txid_status(:rolledback) AS rolledback; + rolledback +------------ + aborted +(1 row) + +SELECT txid_status(:inprogress) AS inprogress; + inprogress +------------- + in progress +(1 row) + +SELECT txid_status(1); -- BootstrapTransactionId is always committed + txid_status +------------- + committed +(1 row) + +SELECT txid_status(2); -- FrozenTransactionId is always committed + txid_status +------------- + committed +(1 row) + +SELECT txid_status(3); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin + txid_status +------------- + +(1 row) + +COMMIT; +BEGIN; +CREATE FUNCTION test_future_xid_status(bigint) +RETURNS void +LANGUAGE plpgsql +AS +$$ +BEGIN + PERFORM txid_status($1); + RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected'; +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'Got expected error for xid in the future'; +END; +$$; +SELECT test_future_xid_status(:inprogress + 10000); +NOTICE: Got expected error for xid in the future + test_future_xid_status +------------------------ + +(1 row) + +ROLLBACK; diff --git a/src/test/regress/sql/txid.sql b/src/test/regress/sql/txid.sql index 4aefd9e..bd6decf 100644 --- a/src/test/regress/sql/txid.sql +++ b/src/test/regress/sql/txid.sql @@ -59,3 +59,41 @@ SELECT txid_current_if_assigned() IS NULL; SELECT txid_current() \gset SELECT txid_current_if_assigned() IS NOT DISTINCT FROM BIGINT :'txid_current'; COMMIT; + +-- test xid status functions +BEGIN; +SELECT txid_current() AS committed \gset +COMMIT; + +BEGIN; +SELECT txid_current() AS rolledback \gset +ROLLBACK; + +BEGIN; +SELECT txid_current() AS inprogress \gset + +SELECT txid_status(:committed) AS committed; +SELECT txid_status(:rolledback) AS rolledback; +SELECT txid_status(:inprogress) AS inprogress; +SELECT txid_status(1); -- BootstrapTransactionId is always committed +SELECT txid_status(2); -- FrozenTransactionId is always committed +SELECT txid_status(3); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin + +COMMIT; + +BEGIN; +CREATE FUNCTION test_future_xid_status(bigint) +RETURNS void +LANGUAGE plpgsql +AS +$$ +BEGIN + PERFORM txid_status($1); + RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected'; +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'Got expected error for xid in the future'; +END; +$$; +SELECT test_future_xid_status(:inprogress + 10000); +ROLLBACK; -- 2.5.5
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers