syncSnapshots.1.diff
text/x-patch
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c3e232..d7d2fbe 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** FOR EACH ROW EXECUTE PROCEDURE suppress_
*** 15012,15015 ****
--- 15012,15089 ----
<xref linkend="SQL-CREATETRIGGER">.
</para>
</sect1>
+
+ <sect1 id="functions-snapshotsync">
+ <title>Snapshot Synchronization Functions</title>
+
+ <indexterm>
+ <primary>pg_export_snapshot</primary>
+ </indexterm>
+
+ <para>
+ <productname>PostgreSQL</> allows different sessions to synchronize their
+ snapshots. A database snapshot determines which data is visible to
+ the client that is using this snapshot. Synchronized snapshots are necessary when
+ two clients need to see the same content in the database. If these two clients
+ just connected to the database and opened their transactions, then they could
+ never be sure that there was no data modification right between both
+ connections.
+ </para>
+ <para>
+ As a solution, <productname>PostgreSQL</> offers the function
+ <function>pg_export_snapshot</> which saves the snapshot internally and
+ from then on until the end of the saving transaction, the snapshot can be
+ used on a <xref linkend="sql-begin"> (or <xref
+ linkend="sql-start-transaction">) command to open a second transaction with the
+ exact same snapshot. Now both transactions are guaranteed to see the exact same
+ data even though they might have connected at different times.
+ </para>
+ <para>
+ Note that a snapshot can only be used to start a new transaction as long
+ as the transaction that originally saved it is held open. Also note that even
+ after the synchronization both clients still run their own independent
+ transactions. As a consequence, even though synchronized with respect to
+ reading pre-existing data, both transactions won't be able to see each other's
+ uncommitted data.
+ </para>
+ <table id="functions-snapshot-synchronization">
+ <title>Snapshot Synchronization Functions</title>
+ <tgroup cols="3">
+ <thead>
+ <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <literal><function>pg_export_snapshot()</function></literal>
+ </entry>
+ <entry><type>text</type></entry>
+ <entry>Save the snapshot and return its identifier</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The function <function>pg_export_snapshot</> does not take an argument
+ and returns the snapshot's identifier as <type>text</type> data. Internally the
+ function will save the snapshot data to a file so that it can be retrieved
+ from a different backend process later on. Note that as soon as the
+ transaction ends, any saved snapshots become invalid and their
+ identifiers cannot be used to start other transactions anymore. If the function
+ has been executed, the transaction cannot be prepared anymore with <xref
+ linkend="sql-prepare-transaction">.
+ </para>
+ <programlisting>
+ SELECT pg_export_snapshot();
+
+ pg_export_snapshot
+ --------------------
+ 000003A1-1
+ (1 row)
+ </programlisting>
+ </sect1>
</chapter>
+
diff --git a/doc/src/sgml/ref/begin.sgml b/doc/src/sgml/ref/begin.sgml
index acd8232..d5fb728 100644
*** a/doc/src/sgml/ref/begin.sgml
--- b/doc/src/sgml/ref/begin.sgml
*************** BEGIN [ WORK | TRANSACTION ] [ <replacea
*** 28,33 ****
--- 28,34 ----
ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
READ WRITE | READ ONLY
[ NOT ] DEFERRABLE
+ ( SNAPSHOT = snapshot_id )
</synopsis>
</refsynopsisdiv>
*************** BEGIN [ WORK | TRANSACTION ] [ <replacea
*** 78,83 ****
--- 79,100 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>BEGIN ... (SNAPSHOT = snapshot_id )</literal></term>
+ <listitem>
+ <para>
+ Use the <literal>(SNAPSHOT = snapshot_id)</literal> parameter to start a
+ new transaction with the same snapshot as an already running transaction.
+ A call to <literal>pg_export_snapshot</literal> (see <xref
+ linkend="functions-snapshotsync">) returns a snapshot id which must be
+ passed to the <literal>BEGIN</literal> command to create a second
+ transaction running with the same snapshot. You also need to make the
+ transaction <literal>ISOLATION LEVEL SERIALIZABLE</literal> or
+ <literal>ISOLATION LEVEL REPEATABLE READ</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
*************** BEGIN [ WORK | TRANSACTION ] [ <replacea
*** 123,128 ****
--- 140,167 ----
<programlisting>
BEGIN;
</programlisting></para>
+
+ <para>
+ To begin a new transaction block with the same snapshot as an already
+ existing transaction, first export the snapshot from the existing
+ transaction. This will return the snapshot id:
+
+ <programlisting>
+ # SELECT pg_export_snapshot();
+ pg_export_snapshot
+ --------------------
+ 000003A1-1
+ (1 row)
+ </programlisting>
+
+ Then reference this snapshot id on the BEGIN TRANSACTION command:
+
+ <programlisting>
+ BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ (SNAPSHOT = '000003A1-1');
+ </programlisting>
+ </para>
+
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/start_transaction.sgml b/doc/src/sgml/ref/start_transaction.sgml
index f25a3e9..0576faf 100644
*** a/doc/src/sgml/ref/start_transaction.sgml
--- b/doc/src/sgml/ref/start_transaction.sgml
*************** START TRANSACTION [ <replaceable class="
*** 28,33 ****
--- 28,34 ----
ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
READ WRITE | READ ONLY
[ NOT ] DEFERRABLE
+ ( SNAPSHOT = snapshot_id )
</synopsis>
</refsynopsisdiv>
*************** START TRANSACTION [ <replaceable class="
*** 46,53 ****
<title>Parameters</title>
<para>
! Refer to <xref linkend="sql-set-transaction"> for information on the meaning
! of the parameters to this statement.
</para>
</refsect1>
--- 47,54 ----
<title>Parameters</title>
<para>
! Refer to <xref linkend="sql-set-transaction"> and <xref linkend="sql-begin">
! for information on the meaning of the parameters to this statement.
</para>
</refsect1>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e8821f7..e372c80 100644
*** a/src/backend/access/transam/xact.c
--- b/src/backend/access/transam/xact.c
*************** CommitTransaction(void)
*** 1855,1860 ****
--- 1855,1866 ----
*/
PreCommit_Notify();
+ /*
+ * Cleans up exported snapshots (this needs to happen before we update
+ * our MyProc entry).
+ */
+ PreCommit_Snapshot();
+
/* Prevent cancel/die interrupt while cleaning up */
HOLD_INTERRUPTS();
*************** PrepareTransaction(void)
*** 2073,2078 ****
--- 2079,2089 ----
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot PREPARE a transaction that has operated on temporary tables")));
+ if (exportedSnapshots)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot PREPARE a transaction that has exported snapshots")));
+
/* Prevent cancel/die interrupt while cleaning up */
HOLD_INTERRUPTS();
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 11035e6..611ac81 100644
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
***************
*** 58,63 ****
--- 58,64 ----
#include "utils/guc.h"
#include "utils/ps_status.h"
#include "utils/relmapper.h"
+ #include "utils/snapmgr.h"
#include "pg_trace.h"
*************** StartupXLOG(void)
*** 6368,6373 ****
--- 6369,6379 ----
CheckRequiredParameterValues();
/*
+ * We can delete any saved transaction snapshots that still exist
+ */
+ DeleteAllExportedSnapshotFiles();
+
+ /*
* We're in recovery, so unlogged relations relations may be trashed
* and must be reset. This should be done BEFORE allowing Hot Standby
* connections, so that read-only backends don't try to read whatever
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e9f3896..8bdf096 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** transaction_mode_item:
*** 7252,7257 ****
--- 7252,7260 ----
| NOT DEFERRABLE
{ $$ = makeDefElem("transaction_deferrable",
makeIntConst(FALSE, @1)); }
+ | '(' ColId '=' Sconst ')'
+ { $$ = makeDefElem($2,
+ makeStringConst($4, @4)); }
;
/* Syntax with commas is SQL-spec, without commas is Postgres historical */
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index e7593fa..8b98e4e 100644
*** a/src/backend/storage/ipc/procarray.c
--- b/src/backend/storage/ipc/procarray.c
*************** ProcArrayShmemSize(void)
*** 167,175 ****
{
Size size;
- /* Size of the ProcArray structure itself */
- #define PROCARRAY_MAXPROCS (MaxBackends + max_prepared_xacts)
-
size = offsetof(ProcArrayStruct, procs);
size = add_size(size, mul_size(sizeof(PGPROC *), PROCARRAY_MAXPROCS));
--- 167,172 ----
*************** ProcArrayShmemSize(void)
*** 180,193 ****
* TransactionIdIsInProgress() and GetRunningTransactionData(). All of the
* main structures created in those functions must be identically sized,
* since we may at times copy the whole of the data structures around. We
! * refer to this size as TOTAL_MAX_CACHED_SUBXIDS.
*
* Ideally we'd only create this structure if we were actually doing hot
* standby in the current run, but we don't know that yet at the time
* shared memory is being set up.
*/
- #define TOTAL_MAX_CACHED_SUBXIDS \
- ((PGPROC_MAX_CACHED_SUBXIDS + 1) * PROCARRAY_MAXPROCS)
if (EnableHotStandby)
{
--- 177,188 ----
* TransactionIdIsInProgress() and GetRunningTransactionData(). All of the
* main structures created in those functions must be identically sized,
* since we may at times copy the whole of the data structures around. We
! * refer to this size as TOTAL_MAX_CACHED_SUBXIDS, defined in procarray.h.
*
* Ideally we'd only create this structure if we were actually doing hot
* standby in the current run, but we don't know that yet at the time
* shared memory is being set up.
*/
if (EnableHotStandby)
{
*************** GetOldestXmin(bool allDbs, bool ignoreVa
*** 1145,1151 ****
* not statically allocated (see xip allocation below).
*/
Snapshot
! GetSnapshotData(Snapshot snapshot)
{
ProcArrayStruct *arrayP = procArray;
TransactionId xmin;
--- 1140,1146 ----
* not statically allocated (see xip allocation below).
*/
Snapshot
! GetSnapshotData(Snapshot snapshot, Snapshot stemplate)
{
ProcArrayStruct *arrayP = procArray;
TransactionId xmin;
*************** GetSnapshotData(Snapshot snapshot)
*** 1159,1164 ****
--- 1154,1182 ----
Assert(snapshot != NULL);
/*
+ * We only get a valid snapshot in stemplate if the snapshot
+ * synchronization feature used. In that case we just need to copy the
+ * values that we get onto the snapshot we return.
+ * Note that in this case we always duplicate an existing snapshot, that is
+ * currently held by another active transaction. That's why we do not need
+ * to update any { RecentGlobalXmin, RecentXmin, globalxmin }.
+ */
+ if (stemplate != InvalidSnapshot)
+ {
+ /*
+ * 'stemplate' is only read and its values are copied onto 'snapshot'.
+ */
+ CopySnapshotOnto(stemplate, snapshot);
+
+ /*
+ * We can use the result of the copy except for that this snapshot
+ * should look like new and not copied.
+ */
+ snapshot->copied = false;
+ return snapshot;
+ }
+
+ /*
* Allocating space for maxProcs xids is usually overkill; numProcs would
* be sufficient. But it seems better to do the malloc while not holding
* the lock, so we can't look at numProcs. Likewise, we allocate much
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 8e7a7f0..cca0bef 100644
*** a/src/backend/storage/lmgr/predicate.c
--- b/src/backend/storage/lmgr/predicate.c
*************** static void OldSerXidSetActiveSerXmin(Tr
*** 416,423 ****
static uint32 predicatelock_hash(const void *key, Size keysize);
static void SummarizeOldestCommittedSxact(void);
! static Snapshot GetSafeSnapshot(Snapshot snapshot);
! static Snapshot RegisterSerializableTransactionInt(Snapshot snapshot);
static bool PredicateLockExists(const PREDICATELOCKTARGETTAG *targettag);
static bool GetParentPredicateLockTag(const PREDICATELOCKTARGETTAG *tag,
PREDICATELOCKTARGETTAG *parent);
--- 416,423 ----
static uint32 predicatelock_hash(const void *key, Size keysize);
static void SummarizeOldestCommittedSxact(void);
! static Snapshot GetSafeSnapshot(Snapshot snapshot, Snapshot stemplate);
! static Snapshot RegisterSerializableTransactionInt(Snapshot snapshot, Snapshot stemplate);
static bool PredicateLockExists(const PREDICATELOCKTARGETTAG *targettag);
static bool GetParentPredicateLockTag(const PREDICATELOCKTARGETTAG *tag,
PREDICATELOCKTARGETTAG *parent);
*************** SummarizeOldestCommittedSxact(void)
*** 1487,1493 ****
* one of them could possibly create a conflict.
*/
static Snapshot
! GetSafeSnapshot(Snapshot origSnapshot)
{
Snapshot snapshot;
--- 1487,1493 ----
* one of them could possibly create a conflict.
*/
static Snapshot
! GetSafeSnapshot(Snapshot origSnapshot, Snapshot stemplate)
{
Snapshot snapshot;
*************** GetSafeSnapshot(Snapshot origSnapshot)
*** 1501,1507 ****
* caller passed to us. It returns a copy of that snapshot and
* registers it on TopTransactionResourceOwner.
*/
! snapshot = RegisterSerializableTransactionInt(origSnapshot);
if (MySerializableXact == InvalidSerializableXact)
return snapshot; /* no concurrent r/w xacts; it's safe */
--- 1501,1507 ----
* caller passed to us. It returns a copy of that snapshot and
* registers it on TopTransactionResourceOwner.
*/
! snapshot = RegisterSerializableTransactionInt(origSnapshot, stemplate);
if (MySerializableXact == InvalidSerializableXact)
return snapshot; /* no concurrent r/w xacts; it's safe */
*************** GetSafeSnapshot(Snapshot origSnapshot)
*** 1554,1560 ****
* It should be current for this process and be contained in PredXact.
*/
Snapshot
! RegisterSerializableTransaction(Snapshot snapshot)
{
Assert(IsolationIsSerializable());
--- 1554,1560 ----
* It should be current for this process and be contained in PredXact.
*/
Snapshot
! RegisterSerializableTransaction(Snapshot snapshot, Snapshot stemplate)
{
Assert(IsolationIsSerializable());
*************** RegisterSerializableTransaction(Snapshot
*** 1564,1576 ****
* thereby avoid all SSI overhead once it's running..
*/
if (XactReadOnly && XactDeferrable)
! return GetSafeSnapshot(snapshot);
! return RegisterSerializableTransactionInt(snapshot);
}
static Snapshot
! RegisterSerializableTransactionInt(Snapshot snapshot)
{
PGPROC *proc;
VirtualTransactionId vxid;
--- 1564,1576 ----
* thereby avoid all SSI overhead once it's running..
*/
if (XactReadOnly && XactDeferrable)
! return GetSafeSnapshot(snapshot, stemplate);
! return RegisterSerializableTransactionInt(snapshot, stemplate);
}
static Snapshot
! RegisterSerializableTransactionInt(Snapshot snapshot, Snapshot stemplate)
{
PGPROC *proc;
VirtualTransactionId vxid;
*************** RegisterSerializableTransactionInt(Snaps
*** 1608,1614 ****
} while (!sxact);
/* Get and register a snapshot */
! snapshot = GetSnapshotData(snapshot);
snapshot = RegisterSnapshotOnOwner(snapshot, TopTransactionResourceOwner);
/*
--- 1608,1614 ----
} while (!sxact);
/* Get and register a snapshot */
! snapshot = GetSnapshotData(snapshot, stemplate);
snapshot = RegisterSnapshotOnOwner(snapshot, TopTransactionResourceOwner);
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 0749227..8dbcc40 100644
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 58,63 ****
--- 58,64 ----
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/guc.h"
+ #include "utils/snapmgr.h"
#include "utils/syscache.h"
*************** standard_ProcessUtility(Node *parsetree,
*** 375,380 ****
--- 376,382 ----
case TRANS_STMT_START:
{
ListCell *lc;
+ char *snapshotId = NULL;
BeginTransactionBlock();
foreach(lc, stmt->options)
*************** standard_ProcessUtility(Node *parsetree,
*** 393,398 ****
--- 395,411 ----
SetPGVariable("transaction_deferrable",
list_make1(item->arg),
true);
+ else if (strcmp(item->defname, "snapshot") == 0)
+ /*
+ * Only save the snapshot's id for now, so that we
+ * process any other modifiers first.
+ */
+ snapshotId = ((A_Const *) item->arg)->val.val.str;
+ }
+ if (snapshotId)
+ {
+ if (!ImportSnapshot(snapshotId))
+ elog(ERROR, "Could not import the requested snapshot");
}
}
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6670997..6c583bb 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** ExecSetVariableStmt(VariableSetStmt *stm
*** 6115,6120 ****
--- 6115,6125 ----
else if (strcmp(item->defname, "transaction_deferrable") == 0)
SetPGVariable("transaction_deferrable",
list_make1(item->arg), stmt->is_local);
+ else if (strcmp(item->defname, "snapshot") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set a snapshot with SET TRANSACTION"),
+ errhint("use BEGIN/START TRANSACTION instead")));
else
elog(ERROR, "unexpected SET TRANSACTION element: %s",
item->defname);
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index ef66466..52ded72 100644
*** a/src/backend/utils/time/snapmgr.c
--- b/src/backend/utils/time/snapmgr.c
***************
*** 25,36 ****
*/
#include "postgres.h"
#include "access/transam.h"
#include "access/xact.h"
#include "storage/predicate.h"
#include "storage/proc.h"
#include "storage/procarray.h"
! #include "utils/memutils.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/snapmgr.h"
--- 25,42 ----
*/
#include "postgres.h"
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+
#include "access/transam.h"
#include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/fd.h"
#include "storage/predicate.h"
#include "storage/proc.h"
#include "storage/procarray.h"
! #include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/snapmgr.h"
*************** static Snapshot CopySnapshot(Snapshot sn
*** 109,126 ****
static void FreeSnapshot(Snapshot snapshot);
static void SnapshotResetXmin(void);
/*
! * GetTransactionSnapshot
* Get the appropriate snapshot for a new query in a transaction.
*
! * Note that the return value may point at static storage that will be modified
! * by future calls and by CommandCounterIncrement(). Callers should call
! * RegisterSnapshot or PushActiveSnapshot on the returned snap if it is to be
! * used very long.
*/
! Snapshot
! GetTransactionSnapshot(void)
{
/* First call in transaction? */
if (!FirstSnapshotSet)
--- 115,138 ----
static void FreeSnapshot(Snapshot snapshot);
static void SnapshotResetXmin(void);
+ /* What we need for exporting snapshots */
+ #define SNAPSHOT_EXPORT_DIR "pg_snapshots"
+ #define XactExportFilePath(path, xid, num, suffix) \
+ snprintf(path, MAXPGPATH, SNAPSHOT_EXPORT_DIR "/%08X-%d%s", xid, num, suffix)
+
+ List *exportedSnapshots = NIL;
/*
! * GetTransactionSnapshotFromTemplate
* Get the appropriate snapshot for a new query in a transaction.
*
! * A template snapshot is passed for the synchronized snapshots feature.
! * In that case we want to have a snapshot back that has the template's
! * values. We just pass it along and the lower level functions take care
! * of it.
*/
! static Snapshot
! GetTransactionSnapshotFromTemplate(Snapshot stemplate)
{
/* First call in transaction? */
if (!FirstSnapshotSet)
*************** GetTransactionSnapshot(void)
*** 135,151 ****
if (IsolationUsesXactSnapshot())
{
if (IsolationIsSerializable())
! CurrentSnapshot = RegisterSerializableTransaction(&CurrentSnapshotData);
else
{
! CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
CurrentSnapshot = RegisterSnapshotOnOwner(CurrentSnapshot,
TopTransactionResourceOwner);
}
registered_xact_snapshot = true;
}
else
! CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
FirstSnapshotSet = true;
return CurrentSnapshot;
--- 147,171 ----
if (IsolationUsesXactSnapshot())
{
if (IsolationIsSerializable())
! CurrentSnapshot = RegisterSerializableTransaction(&CurrentSnapshotData,
! stemplate);
else
{
! CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData, stemplate);
CurrentSnapshot = RegisterSnapshotOnOwner(CurrentSnapshot,
TopTransactionResourceOwner);
}
registered_xact_snapshot = true;
}
else
! {
! /*
! * template is only used for the synchronized snapshot feature. Which in
! * turn is only allowed for IsolationUsesXactSnapshot() == true transactions
! */
! Assert(stemplate == InvalidSnapshot);
! CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData, InvalidSnapshot);
! }
FirstSnapshotSet = true;
return CurrentSnapshot;
*************** GetTransactionSnapshot(void)
*** 154,165 ****
if (IsolationUsesXactSnapshot())
return CurrentSnapshot;
! CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
return CurrentSnapshot;
}
/*
* GetLatestSnapshot
* Get a snapshot that is up-to-date as of the current instant,
* even if we are executing in transaction-snapshot mode.
--- 174,206 ----
if (IsolationUsesXactSnapshot())
return CurrentSnapshot;
! /* see comment above */
! Assert(stemplate == InvalidSnapshot);
! CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData, InvalidSnapshot);
return CurrentSnapshot;
}
/*
+ * GetTransactionSnapshot
+ * Get the appropriate snapshot for a new query in a transaction.
+ *
+ * This is the public interface for anything different than the snapshot
+ * synchronization feature.
+ *
+ * Note that the return value may point at static storage that will be modified
+ * by future calls and by CommandCounterIncrement(). Callers should call
+ * RegisterSnapshot or PushActiveSnapshot on the returned snap if it is to be
+ * used very long.
+ */
+ Snapshot
+ GetTransactionSnapshot(void)
+ {
+ return GetTransactionSnapshotFromTemplate(InvalidSnapshot);
+ }
+
+
+ /*
* GetLatestSnapshot
* Get a snapshot that is up-to-date as of the current instant,
* even if we are executing in transaction-snapshot mode.
*************** GetLatestSnapshot(void)
*** 171,177 ****
if (!FirstSnapshotSet)
elog(ERROR, "no snapshot has been set");
! SecondarySnapshot = GetSnapshotData(&SecondarySnapshotData);
return SecondarySnapshot;
}
--- 212,218 ----
if (!FirstSnapshotSet)
elog(ERROR, "no snapshot has been set");
! SecondarySnapshot = GetSnapshotData(&SecondarySnapshotData, InvalidSnapshot);
return SecondarySnapshot;
}
*************** SnapshotSetCommandId(CommandId curcid)
*** 193,235 ****
}
/*
! * CopySnapshot
! * Copy the given snapshot.
*
! * The copy is palloc'd in TopTransactionContext and has initial refcounts set
! * to 0. The returned snapshot has the copied flag set.
*/
! static Snapshot
! CopySnapshot(Snapshot snapshot)
{
- Snapshot newsnap;
Size subxipoff;
- Size size;
-
- Assert(snapshot != InvalidSnapshot);
! /* We allocate any XID arrays needed in the same palloc block. */
! size = subxipoff = sizeof(SnapshotData) +
! snapshot->xcnt * sizeof(TransactionId);
! if (snapshot->subxcnt > 0)
! size += snapshot->subxcnt * sizeof(TransactionId);
!
! newsnap = (Snapshot) MemoryContextAlloc(TopTransactionContext, size);
! memcpy(newsnap, snapshot, sizeof(SnapshotData));
! newsnap->regd_count = 0;
! newsnap->active_count = 0;
! newsnap->copied = true;
/* setup XID array */
if (snapshot->xcnt > 0)
{
! newsnap->xip = (TransactionId *) (newsnap + 1);
! memcpy(newsnap->xip, snapshot->xip,
snapshot->xcnt * sizeof(TransactionId));
}
else
! newsnap->xip = NULL;
/*
* Setup subXID array. Don't bother to copy it if it had overflowed,
--- 234,266 ----
}
/*
! * CopySnapshotOnto
! * Copy the given snapshot onto an already sufficiently allocated other
! * snapshot.
*
! * Return the modified snapshot (onto).
*/
! Snapshot
! CopySnapshotOnto(Snapshot snapshot, Snapshot onto)
{
Size subxipoff;
! subxipoff = sizeof(SnapshotData) + snapshot->xcnt * sizeof(TransactionId);
! memcpy(onto, snapshot, sizeof(SnapshotData));
! onto->regd_count = 0;
! onto->active_count = 0;
! onto->copied = true;
/* setup XID array */
if (snapshot->xcnt > 0)
{
! onto->xip = (TransactionId *) (onto + 1);
! memcpy(onto->xip, snapshot->xip,
snapshot->xcnt * sizeof(TransactionId));
}
else
! onto->xip = NULL;
/*
* Setup subXID array. Don't bother to copy it if it had overflowed,
*************** CopySnapshot(Snapshot snapshot)
*** 240,253 ****
if (snapshot->subxcnt > 0 &&
(!snapshot->suboverflowed || snapshot->takenDuringRecovery))
{
! newsnap->subxip = (TransactionId *) ((char *) newsnap + subxipoff);
! memcpy(newsnap->subxip, snapshot->subxip,
snapshot->subxcnt * sizeof(TransactionId));
}
else
! newsnap->subxip = NULL;
! return newsnap;
}
/*
--- 271,309 ----
if (snapshot->subxcnt > 0 &&
(!snapshot->suboverflowed || snapshot->takenDuringRecovery))
{
! onto->subxip = (TransactionId *) ((char *) onto + subxipoff);
! memcpy(onto->subxip, snapshot->subxip,
snapshot->subxcnt * sizeof(TransactionId));
}
else
! onto->subxip = NULL;
! return onto;
! }
!
! /*
! * CopySnapshot
! * Copy the given snapshot.
! *
! * The copy is palloc'd in TopTransactionContext and has initial refcounts set
! * to 0. The returned snapshot has the copied flag set.
! */
! static Snapshot
! CopySnapshot(Snapshot snapshot)
! {
! Snapshot newsnap;
! Size size;
!
! Assert(snapshot != InvalidSnapshot);
!
! /* We allocate any XID arrays needed in the same palloc block. */
! size = sizeof(SnapshotData) + snapshot->xcnt * sizeof(TransactionId);
! if (snapshot->subxcnt > 0)
! size += snapshot->subxcnt * sizeof(TransactionId);
!
! newsnap = (Snapshot) MemoryContextAlloc(TopTransactionContext, size);
!
! return CopySnapshotOnto(snapshot, newsnap);
}
/*
*************** AtEOXact_Snapshot(bool isCommit)
*** 577,579 ****
--- 633,1021 ----
FirstSnapshotSet = false;
registered_xact_snapshot = false;
}
+
+ /*
+ * PreCommit_Snapshot
+ * Cleans up exported snapshots (this needs to happen before we update
+ * our MyProc entry, hence it is in PreCommit).
+ */
+ void
+ PreCommit_Snapshot(void)
+ {
+ ListCell *snapshot;
+ int i;
+ char buf[MAXPGPATH];
+
+ if (exportedSnapshots == NIL)
+ return;
+
+ Assert(list_length(exportedSnapshots) > 0);
+ Assert(TransactionIdIsValid(GetTopTransactionIdIfAny()));
+
+ for(i = 1; i <= list_length(exportedSnapshots); i++)
+ {
+ XactExportFilePath(buf, GetTopTransactionId(), i, "");
+ unlink(buf);
+ }
+
+ foreach(snapshot, exportedSnapshots)
+ UnregisterSnapshotFromOwner(lfirst(snapshot), TopTransactionResourceOwner);
+
+ exportedSnapshots = NIL;
+ }
+
+ /*
+ * DeleteAllExportedSnapshotFiles
+ * Cleans up any files that have been left behind by a crashed backend
+ * that had exported snapshots before it died.
+ */
+ void
+ DeleteAllExportedSnapshotFiles(void)
+ {
+ char buf[MAXPGPATH];
+ DIR *s_dir;
+ struct dirent *s_de;
+
+ if (!(s_dir = AllocateDir(SNAPSHOT_EXPORT_DIR)))
+ {
+ /*
+ * We really should have that directory in a sane cluster setup. But
+ * then again if we don't it's not fatal enough to make it FATAL.
+ */
+ elog(WARNING,
+ "could not open directory \"%s\": %m",
+ SNAPSHOT_EXPORT_DIR);
+ return;
+ }
+
+ while ((s_de = ReadDir(s_dir, SNAPSHOT_EXPORT_DIR)) != NULL)
+ {
+ if (strcmp(s_de->d_name, ".") == 0 ||
+ strcmp(s_de->d_name, "..") == 0)
+ continue;
+
+ snprintf(buf, MAXPGPATH, SNAPSHOT_EXPORT_DIR "/%s", s_de->d_name);
+ unlink(buf);
+ }
+ FreeDir(s_dir);
+ }
+
+ /*
+ * ExportSnapshot
+ * Export the snapshot to a file so that other backends can import the same
+ * snapshot.
+ * Returns the token (the file name) that can be used to import this
+ * snapshot.
+ */
+ static char *
+ ExportSnapshot(Snapshot snapshot)
+ {
+ #define SNAPSHOT_APPEND(x, y) (appendStringInfo(&buf, (x), (y)))
+ TransactionId *children, topXid;
+ FILE *f;
+ int i;
+ int nchildren;
+ MemoryContext oldcxt;
+ char path[MAXPGPATH];
+ char pathtmp[MAXPGPATH];
+ StringInfoData buf;
+
+ Assert(IsTransactionState());
+
+ /*
+ * This will also assign a transaction id if we do not yet have one.
+ */
+ topXid = GetTopTransactionId();
+
+ Assert(TransactionIdIsValid(GetTopTransactionIdIfAny()));
+
+ /*
+ * We cannot export a snapshot from a subtransaction because in a
+ * subtransaction we don't see our open subxip values in the snapshot so
+ * they would be missing in the backend applying it.
+ */
+ if (GetCurrentTransactionNestLevel() != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+ errmsg("cannot export a snapshot from a subtransaction")));
+
+ /*
+ * We do however see our already committed subxip values and add them to
+ * the subxip array.
+ */
+ nchildren = xactGetCommittedChildren(&children);
+
+ initStringInfo(&buf);
+
+ /* Write up all the data that we return */
+ SNAPSHOT_APPEND("xid:%d ", topXid);
+ SNAPSHOT_APPEND("xmi:%d ", snapshot->xmin);
+ SNAPSHOT_APPEND("xma:%d ", snapshot->xmax);
+ /* Include our own transaction ID into the count. */
+ SNAPSHOT_APPEND("xcnt:%d ", snapshot->xcnt + 1);
+ for (i = 0; i < snapshot->xcnt; i++)
+ SNAPSHOT_APPEND("xip:%d ", snapshot->xip[i]);
+ /*
+ * Finally add our own XID, since by definition we will still be running
+ * when the other transaction takes over the snapshot.
+ */
+ SNAPSHOT_APPEND("xip:%d ", topXid);
+ if (snapshot->suboverflowed || snapshot->subxcnt + nchildren > TOTAL_MAX_CACHED_SUBXIDS)
+ SNAPSHOT_APPEND("sof:%d ", 1);
+ else
+ {
+ SNAPSHOT_APPEND("sxcnt:%d ", snapshot->subxcnt + nchildren);
+ for (i = 0; i < snapshot->subxcnt; i++)
+ SNAPSHOT_APPEND("sxp:%d ", snapshot->subxip[i]);
+ /* Add already committed subtransactions. */
+ for (i = 0; i < nchildren; i++)
+ SNAPSHOT_APPEND("sxp:%d ", children[i]);
+ }
+
+ /*
+ * buf ends with a trailing space but we leave it in for simplicity. The
+ * parsing routines also depend on it.
+ */
+
+ /* Register the snapshot and add it to the list of exported snapshots */
+ snapshot = RegisterSnapshotOnOwner(snapshot, TopTransactionResourceOwner);
+
+ oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+ exportedSnapshots = lappend(exportedSnapshots, snapshot);
+ MemoryContextSwitchTo(oldcxt);
+
+ XactExportFilePath(pathtmp, topXid, list_length(exportedSnapshots), ".tmp");
+ if (!(f = AllocateFile(pathtmp, PG_BINARY_W)))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", pathtmp)));
+
+ if (fwrite(buf.data, buf.len, 1, f) != 1)
+ /* Aborting the transaction will also call FreeFile() */
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", pathtmp)));
+
+ if (FreeFile(f))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", pathtmp)));
+
+ /*
+ * Now that we have written everything into a .tmp file we rename the file
+ * and remove the .tmp suffix. Our filename is predictable and we're
+ * paranoid enough to not let us read a partially written file (we can't
+ * read a .tmp file because this would fail the valid characters check in
+ * ImportSnapshot).
+ */
+ XactExportFilePath(path, topXid, list_length(exportedSnapshots), "");
+
+ if (rename(pathtmp, path) < 0)
+ ereport(WARNING,
+ (errcode_for_file_access(),
+ errmsg("could not rename file \"%s\" to \"%s\": %m",
+ pathtmp, path)));
+
+ /*
+ * The basename of the file is what we return from pg_export_snapshot().
+ * It's already in path in a textual format and we know that the path
+ * starts with SNAPSHOT_EXPORT_DIR. Skip over the prefix and over the
+ * slash and pstrdup it to not return a local variable.
+ */
+ return pstrdup(path + strlen(SNAPSHOT_EXPORT_DIR) + 1);
+ #undef SNAPSHOT_APPEND
+ }
+
+ /*
+ * Poor man's type independent parser. We only use it in the three functions
+ * below so there's no need to get ambitious about putting extra (x) around the
+ * arguments.
+ */
+ #define SNAPSHOT_PARSE(valFunc, inFunc, type, strpp, prfx, notfound) \
+ do { \
+ char *n, *p = strstr(*strpp, prfx); \
+ type v; \
+ \
+ if (!p) \
+ return notfound; \
+ p += strlen(prfx); \
+ n = strchr(p, ' '); \
+ if (!n) \
+ return notfound; \
+ *n = '\0'; \
+ v = valFunc(DirectFunctionCall1(inFunc, CStringGetDatum(p))); \
+ *strpp = n + 1; \
+ return v; \
+ } while (0);
+
+ static int
+ parseIntFromText(char **s, const char *prefix)
+ {
+ SNAPSHOT_PARSE(DatumGetInt32, int4in, int, s, prefix, 0);
+ }
+
+ static bool
+ parseBoolFromText(char **s, const char *prefix)
+ {
+ SNAPSHOT_PARSE(DatumGetInt32, int4in, bool, s, prefix, false);
+ }
+
+ static TransactionId
+ parseXactFromText(char **s, const char *prefix)
+ {
+ SNAPSHOT_PARSE(DatumGetTransactionId, xidin, TransactionId,
+ s, prefix, InvalidTransactionId);
+ }
+
+ #undef SNAPSHOT_PARSE
+
+ /*
+ * ImportSnapshot
+ * Import a previously exported snapshot. We expect that whatever we get
+ * is a filename in SNAPSHOT_EXPORT_DIR. Load the snapshot from that file.
+ * This is called from "START TRANSACTION (SNAPSHOT = 'foo')" so we always
+ * start fresh from zero with respect to the transaction state that we
+ * work on. Returns true on success and false on failure.
+ */
+ bool
+ ImportSnapshot(char *idstr)
+ {
+ char path[MAXPGPATH];
+ FILE *f;
+ int i;
+ char *s;
+ struct stat stat_buf;
+ int sxcnt, xcnt;
+ TransactionId xid, origXid, myXid;
+ SnapshotData snapshot = {HeapTupleSatisfiesMVCC};
+
+ /*
+ * If we were in read committed mode then the next query would execute with a
+ * new snapshot thus making this function call quite useless.
+ */
+ if (!IsolationUsesXactSnapshot())
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("A snapshot importing transaction must have ISOLATION "
+ "LEVEL SERIALIZABLE or ISOLATION LEVEL REPEATABLE READ")));
+
+ /* We're lucky to always start off from a pretty clean state */
+ Assert(IsTransactionState());
+ Assert(GetCurrentTransactionNestLevel() == 1);
+ Assert(GetTopTransactionIdIfAny() == InvalidTransactionId);
+ Assert(CurrentSnapshot == NULL);
+ Assert(SecondarySnapshot == NULL);
+
+ /* verify the identifier, only 0-9,A-F and a hyphen are allowed... */
+ s = idstr;
+ while (*s)
+ {
+ if (!isdigit(*s) && !(*s >= 'A' && *s <= 'F') && *s != '-')
+ return false;
+ s++;
+ }
+
+ /*
+ * Assign a transaction id. We only do this to detect a possible
+ * transaction id wraparound which is somewhere between unlikely
+ * and impossible...
+ */
+ myXid = GetTopTransactionId();
+
+ snprintf(path, MAXPGPATH, SNAPSHOT_EXPORT_DIR "/%s", idstr);
+
+ /* get the size of the file so that we know how much memory we need */
+ if (stat(path, &stat_buf) != 0)
+ return false;
+
+ if (!(f = AllocateFile(path, PG_BINARY_R)))
+ return false;
+
+ s = palloc(stat_buf.st_size + 1);
+ if (fread(s, stat_buf.st_size, 1, f) != 1)
+ return false;
+
+ s[stat_buf.st_size] = '\0';
+
+ FreeFile(f);
+
+ origXid = parseXactFromText(&s, "xid:");
+
+ snapshot.xmin = parseXactFromText(&s, "xmi:");
+ Assert(snapshot.xmin != InvalidTransactionId);
+ snapshot.xmax = parseXactFromText(&s, "xma:");
+ Assert(snapshot.xmax != InvalidTransactionId);
+
+ xcnt = parseIntFromText(&s, "xcnt:");
+ /*
+ * This snapshot only serves as a template, there is no need for it to have
+ * maxProcs entries, so let's make it just as large as we need it.
+ */
+ snapshot.xip = palloc(xcnt * sizeof(TransactionId));
+
+ i = 0;
+ while ((xid = parseXactFromText(&s, "xip:")) != InvalidTransactionId)
+ snapshot.xip[i++] = xid;
+ snapshot.xcnt = i;
+ Assert(snapshot.xcnt == xcnt);
+
+ /*
+ * We only write "sof:1" if the snapshot overflowed. If not, then there is
+ * no "sof:x" entry at all and parseBoolFromText() will return false.
+ */
+ snapshot.suboverflowed = parseBoolFromText(&s, "sof:");
+
+ if (!snapshot.suboverflowed)
+ {
+ sxcnt = parseIntFromText(&s, "sxcnt:");
+ snapshot.subxip = palloc(sxcnt * sizeof(TransactionId));
+
+ i = 0;
+ while ((xid = parseXactFromText(&s, "sxp:")) != InvalidTransactionId)
+ snapshot.subxip[i++] = xid;
+ snapshot.subxcnt = i;
+ Assert(snapshot.subxcnt == sxcnt);
+ } else {
+ snapshot.subxip = NULL;
+ snapshot.subxcnt = 0;
+ }
+
+ /* complete the snapshot data structure */
+ snapshot.curcid = 0;
+ snapshot.takenDuringRecovery = RecoveryInProgress();
+
+ /*
+ * Note that MyProc->xmin can go backwards here. However this is safe
+ * because the xmin we set here is the same as in the backend's proc->xmin
+ * whose snapshot we are copying. At this very moment, anybody computing a
+ * minimum will calculate at least this xmin as the overall xmin with or
+ * without us setting MyProc->xmin to this value.
+ */
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+ MyProc->xmin = snapshot.xmin;
+ LWLockRelease(ProcArrayLock);
+
+ /* bail out if the original transaction is not running anymore... */
+ if (!TransactionIdIsInProgress(origXid) || TransactionIdPrecedes(myXid, origXid))
+ return false;
+
+ /*
+ * Install the snapshot as if we got it through GetTransactionSnapshot().
+ * This will set up CurrentSnapshot and also set up the predicate locks for a
+ * serializable transaction.
+ */
+ GetTransactionSnapshotFromTemplate(&snapshot);
+ return true;
+ }
+
+ Datum
+ pg_export_snapshot(PG_FUNCTION_ARGS)
+ {
+ char *snapshotData;
+
+ RequireTransactionChain(true, "pg_export_snapshot()");
+
+ snapshotData = ExportSnapshot(GetTransactionSnapshot());
+ PG_RETURN_TEXT_P(cstring_to_text(snapshotData));
+ }
+
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 399c734..c1b80da 100644
*** a/src/bin/initdb/initdb.c
--- b/src/bin/initdb/initdb.c
*************** main(int argc, char *argv[])
*** 2598,2603 ****
--- 2598,2604 ----
"pg_serial",
"pg_subtrans",
"pg_twophase",
+ "pg_snapshots",
"pg_multixact/members",
"pg_multixact/offsets",
"base",
diff --git a/src/include/access/twophase.h b/src/include/access/twophase.h
index 799bf8b..bd2ae80 100644
*** a/src/include/access/twophase.h
--- b/src/include/access/twophase.h
***************
*** 25,33 ****
*/
typedef struct GlobalTransactionData *GlobalTransaction;
- /* GUC variable */
- extern int max_prepared_xacts;
-
extern Size TwoPhaseShmemSize(void);
extern void TwoPhaseShmemInit(void);
--- 25,30 ----
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 96f43fe..a4e0387 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2171 ( pg_cancel_backe
*** 2853,2858 ****
--- 2853,2860 ----
DESCR("cancel a server process' current query");
DATA(insert OID = 2096 ( pg_terminate_backend PGNSP PGUID 12 1 0 0 0 f f f t f v 1 0 16 "23" _null_ _null_ _null_ _null_ pg_terminate_backend _null_ _null_ _null_ ));
DESCR("terminate a server process");
+ DATA(insert OID = 3122 ( pg_export_snapshot PGNSP PGUID 12 1 0 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_export_snapshot _null_ _null_ _null_ ));
+ DESCR("export a snapshot");
DATA(insert OID = 2172 ( pg_start_backup PGNSP PGUID 12 1 0 0 0 f f f t f v 2 0 25 "25 16" _null_ _null_ _null_ _null_ pg_start_backup _null_ _null_ _null_ ));
DESCR("prepare for taking an online backup");
DATA(insert OID = 2173 ( pg_stop_backup PGNSP PGUID 12 1 0 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_stop_backup _null_ _null_ _null_ ));
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 9d19417..15326cf 100644
*** a/src/include/miscadmin.h
--- b/src/include/miscadmin.h
*************** extern PGDLLIMPORT int NBuffers;
*** 134,139 ****
--- 134,142 ----
extern int MaxBackends;
extern int MaxConnections;
+ /* GUC variable */
+ extern int max_prepared_xacts;
+
extern PGDLLIMPORT int MyProcPid;
extern PGDLLIMPORT pg_time_t MyStartTime;
extern PGDLLIMPORT struct Port *MyProcPort;
diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h
index 5ddbc1d..f4f0303 100644
*** a/src/include/storage/predicate.h
--- b/src/include/storage/predicate.h
*************** extern void CheckPointPredicate(void);
*** 42,48 ****
extern bool PageIsPredicateLocked(Relation relation, BlockNumber blkno);
/* predicate lock maintenance */
! extern Snapshot RegisterSerializableTransaction(Snapshot snapshot);
extern void RegisterPredicateLockingXid(TransactionId xid);
extern void PredicateLockRelation(Relation relation, Snapshot snapshot);
extern void PredicateLockPage(Relation relation, BlockNumber blkno, Snapshot snapshot);
--- 42,48 ----
extern bool PageIsPredicateLocked(Relation relation, BlockNumber blkno);
/* predicate lock maintenance */
! extern Snapshot RegisterSerializableTransaction(Snapshot snapshot, Snapshot stemplate);
extern void RegisterPredicateLockingXid(TransactionId xid);
extern void PredicateLockRelation(Relation relation, Snapshot snapshot);
extern void PredicateLockPage(Relation relation, BlockNumber blkno, Snapshot snapshot);
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index 3c20fc4..a2440e4 100644
*** a/src/include/storage/procarray.h
--- b/src/include/storage/procarray.h
*************** extern void ExpireOldKnownAssignedTransa
*** 41,47 ****
extern RunningTransactions GetRunningTransactionData(void);
! extern Snapshot GetSnapshotData(Snapshot snapshot);
extern bool TransactionIdIsInProgress(TransactionId xid);
extern bool TransactionIdIsActive(TransactionId xid);
--- 41,47 ----
extern RunningTransactions GetRunningTransactionData(void);
! extern Snapshot GetSnapshotData(Snapshot snapshot, Snapshot stemplate);
extern bool TransactionIdIsInProgress(TransactionId xid);
extern bool TransactionIdIsActive(TransactionId xid);
*************** extern void XidCacheRemoveRunningXids(Tr
*** 71,74 ****
--- 71,80 ----
int nxids, const TransactionId *xids,
TransactionId latestXid);
+ /* Size of the ProcArray structure itself */
+ #define PROCARRAY_MAXPROCS (MaxBackends + max_prepared_xacts)
+
+ #define TOTAL_MAX_CACHED_SUBXIDS \
+ ((PGPROC_MAX_CACHED_SUBXIDS + 1) * PROCARRAY_MAXPROCS)
+
#endif /* PROCARRAY_H */
diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h
index a7e7d3d..47f62ab 100644
*** a/src/include/utils/snapmgr.h
--- b/src/include/utils/snapmgr.h
*************** extern TransactionId TransactionXmin;
*** 23,28 ****
--- 23,30 ----
extern TransactionId RecentXmin;
extern TransactionId RecentGlobalXmin;
+ extern List *exportedSnapshots;
+
extern Snapshot GetTransactionSnapshot(void);
extern Snapshot GetLatestSnapshot(void);
extern void SnapshotSetCommandId(CommandId curcid);
*************** extern void UpdateActiveSnapshotCommandI
*** 33,47 ****
--- 35,55 ----
extern void PopActiveSnapshot(void);
extern Snapshot GetActiveSnapshot(void);
extern bool ActiveSnapshotSet(void);
+ extern Snapshot CopySnapshotOnto(Snapshot onto, Snapshot snapshot);
extern Snapshot RegisterSnapshot(Snapshot snapshot);
extern void UnregisterSnapshot(Snapshot snapshot);
extern Snapshot RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner);
extern void UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner);
+ extern void PreCommit_Snapshot(void);
extern void AtSubCommit_Snapshot(int level);
extern void AtSubAbort_Snapshot(int level);
extern void AtEarlyCommit_Snapshot(void);
extern void AtEOXact_Snapshot(bool isCommit);
+ extern Datum pg_export_snapshot(PG_FUNCTION_ARGS);
+ extern bool ImportSnapshot(char *idstr);
+ extern void DeleteAllExportedSnapshotFiles(void);
+
#endif /* SNAPMGR_H */