/*-------------------------------------------------------------------------
*
* gtm_txn.c
- * Transaction handling
+ * Transaction handling on GTM
*
* Portions Copyright (c) 2012-2014, TransLattice, Inc.
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
- * $PostgreSQL$
+ * src/gtm/main/gtm_txn.c
+ *
+ *
+ * Functions in this file manage the main transaction array (GTMTransactions)
+ * and provide API to manage the contents - begin, commit and/or abort global
+ * transactions.
+ *
+ * The rest of this comment is a brief overview of the API. It's by no means
+ * exhaustive - you can find more details in comments at each function or
+ * in the code itself. But it should explain basic concepts and main functions
+ * of the GTM Transaction API.
+ *
+ * There are additional parts of the GTM, dealing with other types of objects
+ * (e.g. sequences or snapshots). Those are managed by functions in other
+ * files, and you will need to look into those files for description of that
+ * part of the API.
+ *
+ *
+ * Transaction Identifiers
+ * -----------------------
+ * There are several ways to identify a global transaction. Some identifiers
+ * are internal, while other are meant as an interface with users. There are
+ * four main types of identifiers in the code:
+ *
+ * 1) GTM_TransactionHandle (handle) : Index into the internal array of global
+ * transactions (GTMTransactions.gt_transactions_array), so the values are
+ * limited to interval [0, GTM_MAX_GLOBAL_TRANSACTIONS].
+ *
+ * 2) GlobalTransactionId (GXID) : Sequential ID (uint32), assigned by GTM
+ * to a transaction, just like PostgreSQL assigns XIDs to local transactions.
+ *
+ * 3) Global Transaction Identifier (GID) : Assigned to transactionss in 2PC
+ * transactions, visible to users.
+ *
+ * 4) Global Session ID : Not really a transaction identifier, but it's often
+ * necessary to lookup transaction assigned to a global session.
+ *
+ * One difference between the identifiers is in the cost of looking-up the
+ * transaction. Handles are very cheap, as all that's needed is simply
+ *
+ * GTMTransactions.gt_transactions_array[handle]
+ *
+ * All other identifiers may require walking through the currently opened
+ * transactions, which is more expensive. This is why the API references to
+ * transactions by handles in most places, and provides functions to convert
+ * the other identifiers to handles:
+ *
+ * - GTM_GXIDToHandle() : GXID -> handle
+ * - GTM_GlobalSessionIDToHandle() : session ID -> handle
+ * - GTM_GIDToHandle() : GID -> handle
+ *
+ * Conversion in the other direction is trivial, as the identifiers are
+ * stored as fields in GTM_TransactionInfo.
+ *
+ *
+ * Transaction Management
+ * ----------------------
+ * The basic transaction management commands (BEGIN/PREPARE/COMMIT/ABORT)
+ * are implemented in these eight methods:
+ *
+ * - GTM_BeginTransaction()
+ * - GTM_BeginTransactionMulti()
+ *
+ * - GTM_RollbackTransaction()
+ * - GTM_RollbackTransactionMulti()
+ *
+ * - GTM_CommitTransaction()
+ * - GTM_CommitTransactionMulti()
+ *
+ * - GTM_StartPreparedTransaction()
+ * - GTM_PrepareTransaction()
+ *
+ * The first three commands have two variants - the first one processes a
+ * single transaction (handle), while the "multi" variant operates on an
+ * array of handles. This is useful when processing commands grouped by
+ * GTM proxy nodes.
+ *
+ *
+ * Message Processing
+ * ------------------
+ * Most of the transaction management methods are declared as static, and
+ * are invoked from functions processing messages arriving from clients
+ * over the network. Names of all these meethods start with "Process", and
+ * in most cases it's quite clear which transaction management command is
+ * invoked by each function:
+ *
+ * - ProcessBeginTransactionCommand()
+ * - ProcessBeginTransactionGetGXIDCommand()
+ * - ProcessBeginTransactionGetGXIDAutovacuumCommand()
+ * - ProcessBeginTransactionGetGXIDCommandMulti()
+ *
+ * - ProcessRollbackTransactionCommand()
+ * - ProcessRollbackTransactionCommandMulti()
+ *
+ * - ProcessCommitTransactionCommand()
+ * - ProcessCommitTransactionCommandMulti()
+ * - ProcessCommitPreparedTransactionCommand()
+ *
+ * - ProcessPrepareTransactionCommand()
+ * - ProcessStartPreparedTransactionCommand()
+ *
+ * These function handle communication not only with the GTM clients (that
+ * is backends on datanodes/coordinators or proxies), but with a GTM standby
+ * nodes. They typically receive a message, execute the command locally
+ * and also forward it to the GTM standby node before responding to client.
+ *
+ * For some methods there are special variants with "Bkup" in the name:
+ *
+ * - ProcessBkupBeginTransactionCommand()
+ * - ProcessBkupBeginTransactionGetGXIDCommand()
+ * - ProcessBkupBeginTransactionGetGXIDAutovacuumCommand()
+ * - ProcessBkupBeginTransactionGetGXIDCommandMulti()
+ *
+ * Those are handling the commands on standby, in a slightly different way
+ * (e.g. without forwarding the messages to GTM standby nodes, etc.).
*
*-------------------------------------------------------------------------
*/
extern bool Backup_synchronously;
-#define GTM_CONTROL_VERSION 20160302
-
/* Local functions */
static bool GTM_SetDoVacuum(GTM_TransactionHandle handle);
-static void init_GTM_TransactionInfo(GTM_TransactionInfo *gtm_txninfo,
+static void GTM_TransactionInfo_Init(GTM_TransactionInfo *gtm_txninfo,
GTM_TransactionHandle txn,
GTM_IsolationLevel isolevel,
uint32 client_id,
GTMProxy_ConnID connid,
const char *global_sessionid,
bool readonly);
-static void clean_GTM_TransactionInfo(GTM_TransactionInfo *gtm_txninfo);
+static void GTM_TransactionInfo_Clean(GTM_TransactionInfo *gtm_txninfo);
static GTM_TransactionHandle GTM_GlobalSessionIDToHandle(
const char *global_sessionid);
static bool GTM_NeedXidRestoreUpdate(void);
-static int GTM_CommitTransaction(GTM_TransactionHandle txn,
- int waited_xid_count, GlobalTransactionId *waited_xids);
GlobalTransactionId ControlXid; /* last one written to control file */
GTM_Transactions GTMTransactions;
+/*
+ * GTM_InitTxnManager
+ * Initializes the internal data structures used by GTM.
+ *
+ * This only resets the data structures to "empty" state, initialized the
+ * locks protecting the structures. Restoring the last values from the GTM
+ * control file (written on shutdown) is handled elsewhere.
+ */
void
GTM_InitTxnManager(void)
{
/*
* XXX When GTM is stopped and restarted, it must start assinging GXIDs
- * greater than the previously assgined values. If it was a clean shutdown,
+ * greater than the previously assigned values. If it was a clean shutdown,
* the GTM can store the last assigned value at a known location on
* permanent storage and read it back when it's restarted. It will get
* trickier for GTM failures.
*
- * TODO We skip this part for the prototype.
+ * Restarts after a clean shutdown is handled by GTM_RestoreTxnInfo.
*/
GTMTransactions.gt_nextXid = FirstNormalGlobalTransactionId;
return;
}
-
/*
- * Given the GXID, find the corresponding transaction handle.
+ * GTM_GXIDToHandle_Internal
+ * Given the GXID, find handle of the corresponding global transaction.
+ *
+ * We simply walk the list of open transactions until we find a match.
+ *
+ * XXX I wonder if this might be an issue, as the search is linear and we
+ * may have up to 16k global transactions (by default). In that case we
+ * should change this to use a hash table (or so) to speed the lookup.
*/
static GTM_TransactionHandle
GTM_GXIDToHandle_Internal(GlobalTransactionId gxid, bool warn)
}
}
+/*
+ * GTM_GXIDToHandle
+ * Given the GXID, find handle of the corresponding global transaction.
+ *
+ * If the GXID is not found, returns InvalidTransactionHandle (and emits a
+ * warning).
+ */
GTM_TransactionHandle
GTM_GXIDToHandle(GlobalTransactionId gxid)
{
return GTM_GXIDToHandle_Internal(gxid, true);
}
+/*
+ * GTM_GlobalSessionIDToHandle
+ * Given ID of a global session, find ID of the global transaction.
+ *
+ * Returns InvalidTransactionHandle for empty session ID (NULL or '\0'),
+ * as well as for unknown session IDs.
+ *
+ * XXX Similarly to GTM_GXIDToHandle_Internal, the search is simply a loop
+ * over gt_open_transactions, so it might be causing performance issues.
+ * Especially as this is used in GTM_BeginTransactionMulti.
+ */
static GTM_TransactionHandle
GTM_GlobalSessionIDToHandle(const char *global_sessionid)
{
return InvalidTransactionHandle;
}
+/*
+ * GTM_IsGXIDInProgress
+ * Determines if a global transaction with a given GXID is still in progress.
+ *
+ * Returns true when the GXID is still in progress (exists in gt_open_transactions),
+ * false otherwise.
+ */
static bool
GTM_IsGXIDInProgress(GlobalTransactionId gxid)
{
InvalidTransactionHandle);
}
/*
- * Given the GID (for a prepared transaction), find the corresponding
- * transaction handle.
+ * GTM_GIDToHandle
+ * Find transaction handle for a given the GID (prepared transaction).
+ *
+ * XXX Similarly to GTM_GXIDToHandle_Internal the search is simply a loop
+ * over gt_open_transactions, so might be subject performance issues.
*/
static GTM_TransactionHandle
-GTM_GIDToHandle(char *gid)
+GTM_GIDToHandle(const char *gid)
{
gtm_ListCell *elem = NULL;
GTM_TransactionInfo *gtm_txninfo = NULL;
if (gtm_txninfo != NULL)
return gtm_txninfo->gti_handle;
- else
- return InvalidTransactionHandle;
+
+ /* Print warning for unknown global session IDs. */
+ ereport(WARNING,
+ (ERANGE, errmsg("No transaction handle for prepared transaction ID: '%s'",
+ gid)));
+
+ return InvalidTransactionHandle;
}
/*
- * Given the transaction handle, find the corresponding transaction info
- * structure
+ * GTM_HandleToTransactionInfo
+ * Given a transaction handle, find the transaction info structure.
+ *
+ * The transaction is expected to be still in use, so we emit a WARNING if
+ * that's not the case.
*
* Note: Since a transaction handle is just an index into the global array,
- * this function should be very quick. We should turn into an inline future for
- * fast path.
+ * this function should be very quick. We should turn into an inline future
+ * for fast path.
*/
GTM_TransactionInfo *
GTM_HandleToTransactionInfo(GTM_TransactionHandle handle)
/*
- * Remove the given transaction info structures from the global array. If the
- * calling thread does not have enough cached structures, we in fact keep the
- * structure in the global array and also add it to the list of cached
+ * GTM_RemoveTransInfoMulti
+ * Remove multiple transactions from the list of open global transactions.
+ *
+ * If the calling thread does not have enough cached structures, we in fact keep
+ * the structure in the global array and also add it to the list of cached
* structures for this thread. This ensures that the next transaction starting
* in this thread can quickly get a free slot in the array of transactions and
* also avoid repeated malloc/free of the structures.
*
- * Also compute the latestCompletedXid.
+ * Also updates the gt_latestCompletedXid.
+ *
+ * XXX We seem to be doing a new linear search for each transaction, which seems
+ * rather expensive. We could simply walk gt_open_transactions once and use
+ * gtm_list_delete_cell similarly to GTM_RemoveAllTransInfos.
*/
static void
GTM_RemoveTransInfoMulti(GTM_TransactionInfo *gtm_txninfo[], int txn_count)
GTMTransactions.gt_open_transactions = gtm_list_delete(GTMTransactions.gt_open_transactions, gtm_txninfo[ii]);
+ /*
+ * If this transaction is newer than the current gt_latestCompletedXid,
+ * then use the gti_gxid instead.
+ */
if (GlobalTransactionIdIsNormal(gtm_txninfo[ii]->gti_gxid) &&
GlobalTransactionIdFollowsOrEquals(gtm_txninfo[ii]->gti_gxid,
GTMTransactions.gt_latestCompletedXid))
gtm_txninfo[ii]->gti_handle);
/*
- * Now mark the transaction as aborted and mark the structure as not-in-use
+ * Do cleanup of objects (in particular sequences) modified by this
+ * transaction. What exactly happens depends on whether the transaction
+ * committed or aborted.
*/
- clean_GTM_TransactionInfo(gtm_txninfo[ii]);
+ GTM_TransactionInfo_Clean(gtm_txninfo[ii]);
}
GTM_RWLockRelease(>MTransactions.gt_TransArrayLock);
- return;
}
/*
- * Remove all transaction infos associated with the caller thread and the given
- * backend
+ * GTM_RemoveAllTransInfos
+ * Remove information about all transactions associated with a client/backend.
+ *
+ * Removes all transactions associated with a specified client/backend from
+ * the global transaction array (GTMTransactions.gt_open_transactions).
+ *
+ * Ignores transactions in GTM_TXN_PREPARED and GTM_TXN_PREPARE_IN_PROGRESS
+ * states - those must not be removed, and will be committed by a different
+ * thread (using a GID).
*
- * Also compute the latestCompletedXid.
+ * Also updates the gt_latestCompletedXid.
*/
void
GTM_RemoveAllTransInfos(uint32 client_id, int backend_id)
{
gtm_ListCell *cell, *prev;
+ elog(DEBUG1, "GTM_RemoveAllTransInfos: removing transactions for client %u backend %d",
+ client_id, backend_id);
+
/*
* Scan the global list of open transactions
*/
GTM_RWLockAcquire(>MTransactions.gt_TransArrayLock, GTM_LOCKMODE_WRITE);
+
prev = NULL;
cell = gtm_list_head(GTMTransactions.gt_open_transactions);
while (cell != NULL)
/*
* Now mark the transaction as aborted and mark the structure as not-in-use
*/
- clean_GTM_TransactionInfo(gtm_txninfo);
+ GTM_TransactionInfo_Clean(gtm_txninfo);
/* move to next cell in the list */
if (prev)
}
GTM_RWLockRelease(>MTransactions.gt_TransArrayLock);
- return;
}
/*
- * Get the latest client identifier issued to the currently open transactions.
+ * GTMGetLastClientIdentifier
+ * Get the latest client identifier assigned to currently open transactions.
+ *
* Remember this may not be the latest identifier issued by the old master, but
* we won't acknowledge client identifiers larger than what we are about to
- * compute. Any such identifiers will be overwritten the new identifier issued
- * by the new master
+ * compute. Any such identifiers will be overwritten new identifiers issued
+ * by the new master.
+ *
+ * XXX Another linear search over gt_open_transactions. Perhaps we could eliminate
+ * most of the searches by updating the value whenever we generate a higher value,
+ * and only doing the search when the client with the highest ID terminates.
+ *
+ * XXX What happens when the value wraps around, which is what GTM_CLIENT_ID_NEXT
+ * apparently does? If we ignore identifiers higher than the value, isn't that an
+ * issue?
*/
uint32
-GTMGetLastClientIdentifier(void)
+GTM_GetLastClientIdentifier(void)
{
gtm_ListCell *cell;
uint32 last_client_id = 0;
if (GTM_CLIENT_ID_GT(gtm_txninfo->gti_client_id, last_client_id))
last_client_id = gtm_txninfo->gti_client_id;
+
cell = gtm_lnext(cell);
}
GTM_RWLockRelease(>MTransactions.gt_TransArrayLock);
+
+ elog(DEBUG1, "GTMGetLastClientIdentifier: last client ID %u", last_client_id);
+
return last_client_id;
}
/*
- * Set that the transaction is doing vacuum
+ * GTM_SetDoVacuum
+ * Mark a given transaction (identified by a transaction handle) as VACUUM.
*
+ * Matters for GTM_GetTransactionSnapshot, which ignores lazy vacuums when
+ * building transaction snapshot
+ *
+ * Fails with an ERROR when the transaction handle does not exist.
*/
static bool
GTM_SetDoVacuum(GTM_TransactionHandle handle)
}
/*
- * Allocate the next XID for my new transaction
+ * GTM_GetGlobalTransactionIdMulti
+ * Allocate GXID for a list of transaction handles.
+ *
+ * The function accepts an array of transaction handles with txn_count elements,
+ * some of which may already have GXID assigned. Such handles (that already had
+ * GXID assigned) are skipped and we don't try to assign a new GXID to them.
+ *
+ * For we handles without a GXID, the function assigns a GXID, and tracks the
+ * handle to new_handles, so that the caller can easily identify which handles
+ * were modified.
*
- * The new XID is also stored into the transaction info structure of the given
- * transaction before returning.
+ * The output array 'gxids' should contain GXIDs for all handles (even those
+ * that had GXID assigned before calling this function).
+ *
+ * That means both 'gxids' and 'new_handles' should have space for at least
+ * txn_count elements, but 'new_handles' may use only some of the space.
+ *
+ * Input:
+ * handles - transactions to assing GXID to
+ * txn_count - number of handles in 'handles' array
+ *
+ * Output:
+ * gxids - array of newly assigned GXIDs
+ * new_handles - array of handles with newly assigned GXIDs
+ * new_txn_count - number of newly assigned GXIDs (and number of elements
+ * in new_handles)
*/
static bool
-GTM_GetGlobalTransactionIdMulti(GTM_TransactionHandle handle[], int txn_count,
- GlobalTransactionId gxid[], GTM_TransactionHandle new_handle[],
+GTM_GetGlobalTransactionIdMulti(GTM_TransactionHandle handles[], int txn_count,
+ GlobalTransactionId gxids[], GTM_TransactionHandle new_handles[],
int *new_txn_count)
{
GlobalTransactionId xid = InvalidGlobalTransactionId;
GTM_TransactionInfo *gtm_txninfo = NULL;
int ii;
+ int new_handles_count = 0;
bool save_control = false;
+ elog(DEBUG1, "GTM_GetGlobalTransactionIdMulti: generate GXIDs for %d transactions", txn_count);
+
+ /* gxids is required parameter (we always return the GXID) */
+ Assert(gxids != NULL);
+
+ /* either both new_handles and new_txn_count, or neiter of them */
+ Assert((new_handles && new_txn_count) || (!new_handles && !new_txn_count));
+
+ /* GTM standby can only receive GXID from the GTM master */
if (Recovery_IsStandby())
{
ereport(ERROR, (EINVAL, errmsg("GTM is running in STANDBY mode -- can not issue new transaction ids")));
return false;
}
- *new_txn_count = 0;
/*
- * Now advance the nextXid counter. This must not happen until after we
- * have successfully completed ExtendCLOG() --- if that routine fails, we
- * want the next incoming transaction to try it again. We cannot assign
- * more XIDs until there is CLOG space for them.
+ * Now generate a GXID to hadles that do now have a GXID assigned yet.
*/
for (ii = 0; ii < txn_count; ii++)
{
- gtm_txninfo = GTM_HandleToTransactionInfo(handle[ii]);
+ gtm_txninfo = GTM_HandleToTransactionInfo(handles[ii]);
Assert(gtm_txninfo);
if (GlobalTransactionIdIsValid(gtm_txninfo->gti_gxid))
{
- gxid[ii] = gtm_txninfo->gti_gxid;
+ gxids[ii] = gtm_txninfo->gti_gxid;
elog(DEBUG1, "GTM_TransactionInfo has XID already assgined - %s:%d",
- gtm_txninfo->gti_global_session_id, gxid[ii]);
+ gtm_txninfo->gti_global_session_id, gxids[ii]);
continue;
}
elog(DEBUG1, "Assigning new transaction ID = %s:%d",
gtm_txninfo->gti_global_session_id, xid);
- gxid[ii] = gtm_txninfo->gti_gxid = xid;
- new_handle[*new_txn_count] = gtm_txninfo->gti_handle;
- *new_txn_count = *new_txn_count + 1;
+
+ gxids[ii] = gtm_txninfo->gti_gxid = xid;
+
+ /* only return the new handles when requested */
+ if (new_handles)
+ new_handles[new_handles_count++] = gtm_txninfo->gti_handle;
}
- /* Periodically write the xid and sequence info out to the control file.
+ /*
+ * Periodically write the xid and sequence info out to the control file.
* Try and handle wrapping, too.
*/
if (GlobalTransactionIdIsValid(xid) &&
if (GTM_NeedXidRestoreUpdate())
GTM_SetNeedBackup();
+
GTM_RWLockRelease(>MTransactions.gt_XidGenLock);
/* save control info when not holding the XidGenLock */
if (save_control)
SaveControlInfo();
+ if (new_txn_count)
+ *new_txn_count = new_handles_count;
+
+ elog(DEBUG1, "GTM_GetGlobalTransactionIdMulti: assigned %d new GXIDs for %d handles",
+ new_handles_count, txn_count);
+
return true;
}
/*
- * Allocate the next XID for my new transaction
+ * GTM_GetGlobalTransactionId
+ * Allocate GXID for a new transaction.
*
- * The new XID is also stored into the transaction info structure of the given
- * transaction before returning.
+ * The new GXID is stored into the transaction info structure of the given
+ * transaction before returning (not just returned).
*/
GlobalTransactionId
GTM_GetGlobalTransactionId(GTM_TransactionHandle handle)
{
- GlobalTransactionId gxid;
- GTM_TransactionHandle new_handle;
- int new_count;
+ GlobalTransactionId gxid = InvalidGlobalTransactionId;
+
+ GTM_GetGlobalTransactionIdMulti(&handle, 1, &gxid, NULL, NULL);
+
+ elog(DEBUG1, "GTM_GetGlobalTransactionId: assigned new GXID %u", gxid);
+
+ Assert(GlobalTransactionIdIsValid(gxid));
- GTM_GetGlobalTransactionIdMulti(&handle, 1, &gxid, &new_handle,
- &new_count);
return gxid;
}
/*
- * Read nextXid but don't allocate it.
+ * GTM_ReadNewGlobalTransactionId
+ * Reads nextXid, but do not allocate it (advance to te next one).
*/
GlobalTransactionId
-ReadNewGlobalTransactionId(void)
+GTM_ReadNewGlobalTransactionId(void)
{
GlobalTransactionId xid;
}
/*
- * Set the nextXid.
+ * GTM_SetNextGlobalTransactionId
+ * Set the next global XID.
*
* The GXID is usually read from a control file and set when the GTM is
- * started. When the GTM is finally shutdown, the next to-be-assigned GXID is
- * stroed in the control file.
+ * started. When the GTM is finally shutdown, the next to-be-assigned GXID
+ * is stored in the control file.
*
- * XXX We don't yet handle any crash recovery. So if the GTM is no shutdown normally...
+ * The function also switches the GTM from 'starting' to 'running' state.
*
- * This is handled by gtm_backup.c. Anyway, because this function is to be called by
- * GTM_RestoreTransactionId() and the backup will be performed afterwords,
- * we don't care the new value of GTMTransactions.gt_nextXid here.
+ * This is handled by gtm_backup.c. Anyway, because this function is to be
+ * called by GTM_RestoreTransactionId() and the backup will be performed
+ * afterwards, we don't care the new value of GTMTransactions.gt_nextXid here
+ * (it may even be invalid or stale).
+ *
+ * XXX We don't yet handle any crash recovery. So if the GTM did not shutdown
+ * cleanly, it's not quite sure what'll happen.
*/
void
-SetNextGlobalTransactionId(GlobalTransactionId gxid)
+GTM_SetNextGlobalTransactionId(GlobalTransactionId gxid)
{
+ /* we should only be calling this during GTM startup */
+ Assert(GTMTransactions.gt_gtm_state == GTM_STARTING);
+
GTM_RWLockAcquire(>MTransactions.gt_XidGenLock, GTM_LOCKMODE_WRITE);
GTMTransactions.gt_nextXid = gxid;
GTMTransactions.gt_gtm_state = GTM_RUNNING;
GTM_RWLockRelease(>MTransactions.gt_XidGenLock);
+
return;
}
+/*
+ * GTM_SetControlXid
+ * Sets the control GXID.
+ */
void
-SetControlXid(GlobalTransactionId gxid)
+GTM_SetControlXid(GlobalTransactionId gxid)
{
+ elog(DEBUG1, "GTM_SetControlXid: setting control GXID %u", gxid);
ControlXid = gxid;
}
-/* Transaction Control */
+/*
+ * GTM_BeginTransactionMulti
+ * Starts transactions on provided global sessions, if needed.
+ *
+ * If there already is an open transaction on a global session, the existing
+ * transaction handle is reused.
+ *
+ * The transaction handles are initialized in the txns[] array, and the
+ * number of elements is returned (in general it will be equal to txn_count).
+ *
+ * Input:
+ * isolevel[] - requested isolation levels
+ * readonly[] - flags for read-only sessions
+ * global_sessionid[] - IDs of global sessions
+ * connid[] - IDs of proxy connections
+ * txn_count - number of sessions/transactions
+ *
+ * Output:
+ * txns[] - initialized transaction handles
+ *
+ * Returns number of transaction handles returned in txns[] array.
+ *
+ * The caller is responsible for ensuring the input/output arrays are
+ * correctly sized (all should have at least txn_count elements).
+ *
+ * XXX The transaction handles are allocated in TopMostMemoryContext.
+ */
static int
GTM_BeginTransactionMulti(GTM_IsolationLevel isolevel[],
bool readonly[],
MemoryContext oldContext;
int kk;
+ /* make sure we received all the required array paremeters */
+ Assert(isolevel && readonly && global_sessionid && txns && connid);
+
memset(gtm_txninfo, 0, sizeof (gtm_txninfo));
/*
GTM_TransactionHandle txn =
GTM_GlobalSessionIDToHandle(global_sessionid[kk]);
+ /*
+ * If tere already is a transaction open on the global session, reuse
+ * it and continue with the next one.
+ */
if (txn != InvalidTransactionHandle)
{
gtm_txninfo[kk] = GTM_HandleToTransactionInfo(txn);
}
/*
- * We had no cached slots. Now find a free slot in the transation array
- * and store the transaction info structure there
+ * We had no cached slots. Now find a free slot in the transaction array
+ * and store the new transaction info structure there.
+ *
+ * When looking for a new empty slot in the transactions array, we do not
+ * start at index 0 as the transaction are likely squashed there. Instead
+ * we track the ID of the last assigned slot (gt_lastslot), and start from
+ * that index. We do exactly GTM_MAX_GLOBAL_TRANSACTIONS steps, so we may
+ * walk the whole array in the worst case (everything is full).
+ *
+ * The assumptiion is that the "oldest" slots will be eventually freed, so
+ * when we get back to them (after about GTM_MAX_GLOBAL_TRANSACTIONS
+ * transaction), the slots will be free again.
+ *
+ * XXX This will degrade with many open global transactions, as the array
+ * gets "more full". In that case we could perhaps track the free slots
+ * in a freelist (similarly to gt_open_transactions), or something.
+ *
+ * XXX We could also track the number of assigned slots, to quickly detect
+ * when there are no free slots. But that seems unlikely.
*/
startslot = GTMTransactions.gt_lastslot + 1;
if (startslot >= GTM_MAX_GLOBAL_TRANSACTIONS)
break;
}
+ /*
+ * We got back to the starting point, and haven't found any free slot.
+ * That means we have reached GTM_MAX_GLOBAL_TRANSACTIONS.
+ */
if (ii == GTMTransactions.gt_lastslot)
{
GTM_RWLockRelease(>MTransactions.gt_TransArrayLock);
ereport(ERROR,
- (ERANGE, errmsg("Max transaction limit reached")));
+ (ERANGE, errmsg("Max global transactions limit reached (%d)",
+ GTM_MAX_GLOBAL_TRANSACTIONS)));
}
}
- init_GTM_TransactionInfo(gtm_txninfo[kk], ii, isolevel[kk],
+ GTM_TransactionInfo_Init(gtm_txninfo[kk], ii, isolevel[kk],
GetMyThreadInfo->thr_client_id, connid[kk],
global_sessionid[kk],
readonly[kk]);
+ /* remember which slot we used for the next loop */
GTMTransactions.gt_lastslot = ii;
txns[kk] = ii;
return txn_count;
}
-/* Transaction Control */
+/*
+ * GTM_BeginTransaction
+ * Starts transaction on provided global session.
+ *
+ * If there already is an open transaction on the global session, the
+ * existing transaction handle is reused.
+ *
+ * Input:
+ * isolevel - requested isolation level
+ * readonly - should the transaction be read-only
+ * global_sessionid - ID of theh global session
+ *
+ * Returns an initialized transaction handle.
+ *
+ * XXX The transaction handle is allocated in TopMostMemoryContext.
+ */
static GTM_TransactionHandle
GTM_BeginTransaction(GTM_IsolationLevel isolevel,
bool readonly,
GTMProxy_ConnID connid = -1;
GTM_BeginTransactionMulti(&isolevel, &readonly, &global_sessionid, &connid, 1, &txn);
+
return txn;
}
+/*
+ * GTM_TransactionInfo_Init
+ * Initialize info about a transaction and store it in global array.
+ */
static void
-init_GTM_TransactionInfo(GTM_TransactionInfo *gtm_txninfo,
+GTM_TransactionInfo_Init(GTM_TransactionInfo *gtm_txninfo,
GTM_TransactionHandle txn,
GTM_IsolationLevel isolevel,
uint32 client_id,
/*
- * Clean up the TransactionInfo slot and pfree all the palloc'ed memory,
- * except txid array of the snapshot, which is reused.
+ * GTM_TransactionInfo_Clean
+ * Mark a transaction slot as empty and release memory.
+ *
+ * Most of the cleanup is about dealing with sequences modified in the
+ * transaction, and what exactly needs to happen depends on whether the
+ * transaction is being committed or aborted.
+ *
+ * XXX We do not pfree the txid array of the snapshot, which may be referenced
+ * by by multiple transactions. But we should never really have more than
+ * GTM_MAX_GLOBAL_TRANSACTIONS of them (with 16k transactions, that's about
+ * 1GB of RAM).
+ *
+ * XXX Do we expect this being called only for transactions that are currently
+ * being aborted/committed, or in other states too (for example "starting")?
*/
static void
-clean_GTM_TransactionInfo(GTM_TransactionInfo *gtm_txninfo)
+GTM_TransactionInfo_Clean(GTM_TransactionInfo *gtm_txninfo)
{
gtm_ListCell *lc;
GTM_SeqRestoreAltered(gtm_lfirst(lc));
}
-
}
else if (gtm_txninfo->gti_state == GTM_TXN_COMMIT_IN_PROGRESS)
{
}
}
-
+/*
+ * GTM_BkupBeginTransactionMulti
+ * Open multiple transactions on provided global sessions.
+ *
+ * XXX I'm not sure why we need this when GTM_BeginTransactionMulti does the
+ * same thing (and it allocates everything in TopMostMemoryContext too).
+ * Maybe that we fail if some of the transactions fail to start?
+ */
static void
-GTM_BkupBeginTransactionMulti(GTM_IsolationLevel *isolevel,
- bool *readonly,
- const char **global_sessionid,
- uint32 *client_id,
- GTMProxy_ConnID *connid,
- int txn_count)
+GTM_BkupBeginTransactionMulti(GTM_IsolationLevel isolevel[],
+ bool readonly[],
+ const char *global_sessionid[],
+ uint32 client_id[],
+ GTMProxy_ConnID connid[],
+ int txn_count)
{
GTM_TransactionHandle txn[GTM_MAX_GLOBAL_TRANSACTIONS];
MemoryContext oldContext;
MemoryContextSwitchTo(oldContext);
}
+/*
+ * GTM_BkupBeginTransaction
+ * Starts transaction on provided global session.
+ *
+ * XXX I'm not sure why we need this when GTM_BeginTransaction does the
+ * same thing (and it allocates everything in TopMostMemoryContext too).
+ */
static void
GTM_BkupBeginTransaction(GTM_IsolationLevel isolevel,
bool readonly,
}
/*
- * Rollback multiple transactions in one go
+ * GTM_RollbackTransactionMulti
+ * Rollback multiple global transactions (handles) in one go.
+ *
+ * The function expects txn_count handles to be supplied in the txn[] array.
+ * We first mark all transactions as GTM_TXN_ABORT_IN_PROGRESS and then
+ * remove them.
+ *
+ * Rollback status for each of supplied transaction handle is returned in the
+ * status[] array (so it has to have space for at least txn_count elements).
+ * If a handle is not provided (it's NULL in txn[]), the matching status will
+ * be set to STATUS_ERROR.
+ *
+ * The function returns txn_count, that is the number of supplied handles.
*/
static int
GTM_RollbackTransactionMulti(GTM_TransactionHandle txn[], int txn_count, int status[])
GTM_TransactionInfo *gtm_txninfo[txn_count];
int ii;
+ ereport(DEBUG1,
+ (ERANGE, errmsg("GTM_RollbackTransactionMulti: rollbing back %d transactions",
+ txn_count)));
+
for (ii = 0; ii < txn_count; ii++)
{
gtm_txninfo[ii] = GTM_HandleToTransactionInfo(txn[ii]);
}
/*
- * Mark the transaction as being aborted
+ * Mark the transaction as being aborted. We need to acquire the lock
+ * on that transaction to do that.
*/
GTM_RWLockAcquire(>m_txninfo[ii]->gti_lock, GTM_LOCKMODE_WRITE);
gtm_txninfo[ii]->gti_state = GTM_TXN_ABORT_IN_PROGRESS;
GTM_RWLockRelease(>m_txninfo[ii]->gti_lock);
+
status[ii] = STATUS_OK;
}
}
/*
- * Rollback a transaction
+ * GTM_RollbackTransaction
+ * Rollback a single global transaction, identified by a handle.
*/
static int
GTM_RollbackTransaction(GTM_TransactionHandle txn)
}
/*
- * Commit multiple transactions in one go
+ * GTM_CommitTransactionMulti
+ * Commit multiple global transactions in one go.
+ *
+ * Commits txn_count transactions identified by handles passed in txn[] array,
+ * and returns the status for each of them in status[] array.
+ *
+ * It is also possible to provide an array of transactions that have to finish
+ * before txn[] transactions can be committed. If some of the transactions in
+ * waited_xids[] (with waited_xid_count elements) are still in progress, the
+ * transactions will not be committed and will be marked as delayed.
+ *
+ * Input:
+ * txn[] - array of transaction handles to commit
+ * txn_count - number of transaction handles in txn[]
+ * waited_xid_count - number of GIXDs in waited_xids[]
+ * waited_xids[] - GIXDs to wait for before the commit
+ *
+ * Output:
+ * status[] - outcome of the commit for each txn[] handle
+ *
+ * The function returns the number of successfully committed transactions
+ * (and removed from the global array).
+ *
+ * The status[] array contains the commit status for each txn[] element, i.e.
+ * txn_count elements. There are three possible values:
+ *
+ * - STATUS_OK transaction was committed (and removed)
+ * - STATUS_DELAYED commit is delayed due to in-progress transactions
+ * - STATUS_ERROR invalid (NULL) transaction handle
+ *
+ * XXX Do we need to repeat the loop over waited_xids for every transaction?
+ * Maybe we could check it once at the beginning. The only case why that might
+ * fail is probably when waited_xids[] and txn[] overlap, some of the GXIDs
+ * we're waiting for are also on the list of transactions to commit. But maybe
+ * that's not allowed, as such transaction would get delayed by itself.
*/
static int
GTM_CommitTransactionMulti(GTM_TransactionHandle txn[], int txn_count,
- int waited_xid_count, GlobalTransactionId *waited_xids,
+ int waited_xid_count, GlobalTransactionId waited_xids[],
int status[])
{
GTM_TransactionInfo *gtm_txninfo[txn_count];
gtm_txninfo[ii] = GTM_HandleToTransactionInfo(txn[ii]);
+ /* We should not be committing handles that are not initialized. */
if (gtm_txninfo[ii] == NULL)
{
+ elog(WARNING, "GTM_CommitTransactionMulti: can not commit non-initialized handle");
status[ii] = STATUS_ERROR;
continue;
}
/*
- * If any of the waited_xids is still running, we must delay commit for
- * this transaction till all such waited_xids are finished
+ * See if the current transaction depends on other transactions that are
+ * still running (possiby one of those we're currently committing?). In
+ * that case we have to delay commit of this transaction until after those
+ * transaction finish.
*/
for (jj = 0; jj < waited_xid_count; jj++)
{
}
}
+ /* We're waiting for in-progress transactions, so let's delay the commit. */
if (waited_xid_running)
{
+ elog(WARNING, "GTM_CommitTransactionMulti: delaying commit of handle %d",
+ gtm_txninfo[ii]->gti_gxid);
+
status[ii] = STATUS_DELAYED;
continue;
}
GTM_RWLockAcquire(>m_txninfo[ii]->gti_lock, GTM_LOCKMODE_WRITE);
gtm_txninfo[ii]->gti_state = GTM_TXN_COMMIT_IN_PROGRESS;
GTM_RWLockRelease(>m_txninfo[ii]->gti_lock);
+
status[ii] = STATUS_OK;
+ /* Keep track of transactions to remove from global array. */
remove_txninfo[remove_count++] = gtm_txninfo[ii];
}
+ /*
+ * Remove the transaction from the global array, but only those that we
+ * managed to switch to GTM_TXN_COMMIT_IN_PROGRESS state.
+ */
GTM_RemoveTransInfoMulti(remove_txninfo, remove_count);
return remove_count;
}
/*
- * Prepare a transaction
+ * GTM_CommitTransaction
+ * Commit a single global transaction handle.
+ *
+ * Similaarly to GTM_CommitTransactionMulti, it's possible to specify an array
+ * of GIXDs that should have completed before the transaction gets committed.
+ *
+ * Returns STATUS_OK (committed), STATUS_DELAYED (waiting by in-progress
+ * transactions) or STATUS_ERROR (txninfo for the handle not found).
+ */
+static int
+GTM_CommitTransaction(GTM_TransactionHandle txn, int waited_xid_count,
+ GlobalTransactionId *waited_xids)
+{
+ int status;
+ GTM_CommitTransactionMulti(&txn, 1, waited_xid_count, waited_xids, &status);
+ return status;
+}
+
+/*
+ * GTM_PrepareTransaction
+ * Prepare transaction for commit (in 2PC protocol).
+ *
+ * Prepare a transaction for commit, and returns STATUS_OK or STATUS_ERROR.
+ *
+ * XXX This should probably check the initial gti_state (at least by assert).
+ * I assume we can only see transactions in GTM_TXN_PREPARE_IN_PROGRESS.
*/
static int
GTM_PrepareTransaction(GTM_TransactionHandle txn)
{
+ int state;
GTM_TransactionInfo *gtm_txninfo = NULL;
gtm_txninfo = GTM_HandleToTransactionInfo(txn);
if (gtm_txninfo == NULL)
+ {
+ elog(WARNING, "GTM_PrepareTransaction: can't prepare transaction handle %d (txninfo is NULL)",
+ txn);
return STATUS_ERROR;
+ }
/*
* Mark the transaction as prepared
*/
GTM_RWLockAcquire(>m_txninfo->gti_lock, GTM_LOCKMODE_WRITE);
+ state = gtm_txninfo->gti_state;
gtm_txninfo->gti_state = GTM_TXN_PREPARED;
GTM_RWLockRelease(>m_txninfo->gti_lock);
- return STATUS_OK;
-}
+ /* The initial state should have been PREPARE_IN_PROGRESS. */
+ Assert(state == GTM_TXN_PREPARE_IN_PROGRESS);
-/*
- * Commit a transaction
- */
-static int
-GTM_CommitTransaction(GTM_TransactionHandle txn, int waited_xid_count,
- GlobalTransactionId *waited_xids)
-{
- int status;
- GTM_CommitTransactionMulti(&txn, 1, waited_xid_count, waited_xids, &status);
- return status;
+ return STATUS_OK;
}
/*
- * Prepare a transaction
+ * GTM_StartPreparedTransaction
+ * Start preparing a transaction (set GTM_TXN_PREPARE_IN_PROGRESS).
+ *
+ * Returns either STATUS_OK when the transaction was succesfully switched to
+ * GTM_TXN_PREPARE_IN_PROGRESS, or STATUS_ERROR when the state change fails
+ * for some reason (unknown transaction handle, duplicate GID).
*/
static int
GTM_StartPreparedTransaction(GTM_TransactionHandle txn,
GTM_TransactionInfo *gtm_txninfo = GTM_HandleToTransactionInfo(txn);
if (gtm_txninfo == NULL)
+ {
+ elog(WARNING, "GTM_StartPreparedTransaction: unknown handle %d", txn);
return STATUS_ERROR;
+ }
/*
* Check if given GID is already in use by another transaction.
*/
if (GTM_GIDToHandle(gid) != InvalidTransactionHandle)
+ {
+ elog(WARNING, "GTM_StartPreparedTransaction: GID %s already exists", gid);
return STATUS_ERROR;
-
- /*
- * Check if given GID is already in use by another transaction.
- */
- if (GTM_GIDToHandle(gid) != InvalidTransactionHandle)
- return STATUS_ERROR;
+ }
/*
* Mark the transaction as being prepared
return STATUS_OK;
}
+/*
+ * GTM_GetGIDData
+ * Returns gti_gxid and nodestring for a transaction handle.
+ *
+ * The nodestring (if available) is allocated in TopMostMemoryContext.
+ * If there is no matching transaction info (no open transaction for the
+ * handle), the rertur value is STATUS_ERROR.
+ *
+ * In case of success the return value is STATUS_OK.
+ */
static int
GTM_GetGIDData(GTM_TransactionHandle prepared_txn,
GlobalTransactionId *prepared_gxid,
GTM_TransactionInfo *gtm_txninfo = NULL;
MemoryContext oldContext;
+ Assert(prepared_gxid);
+
oldContext = MemoryContextSwitchTo(TopMostMemoryContext);
gtm_txninfo = GTM_HandleToTransactionInfo(prepared_txn);
return STATUS_OK;
}
+/*
+ * GTM_BkupBeginTransactionGetGXIDMulti
+ *
+ * XXX Not sure what this does.
+ */
+static void
+GTM_BkupBeginTransactionGetGXIDMulti(GlobalTransactionId *gxid,
+ GTM_IsolationLevel *isolevel,
+ bool *readonly,
+ const char **global_sessionid,
+ uint32 *client_id,
+ GTMProxy_ConnID *connid,
+ int txn_count)
+{
+ GTM_TransactionHandle txn[GTM_MAX_GLOBAL_TRANSACTIONS];
+ GTM_TransactionInfo *gtm_txninfo;
+ int ii;
+ int count;
+ MemoryContext oldContext;
+
+ bool save_control = false;
+ GlobalTransactionId xid = InvalidGlobalTransactionId;
+
+ oldContext = MemoryContextSwitchTo(TopMostMemoryContext);
+
+ count = GTM_BeginTransactionMulti(isolevel, readonly, global_sessionid,
+ connid, txn_count, txn);
+ if (count != txn_count)
+ ereport(ERROR,
+ (EINVAL,
+ errmsg("Failed to start %d new transactions", txn_count)));
+
+ elog(DEBUG2, "GTM_BkupBeginTransactionGetGXIDMulti - count %d", count);
+
+ //XCPTODO check oldContext = MemoryContextSwitchTo(TopMemoryContext);
+ GTM_RWLockAcquire(>MTransactions.gt_TransArrayLock, GTM_LOCKMODE_WRITE);
+
+ for (ii = 0; ii < txn_count; ii++)
+ {
+ gtm_txninfo = GTM_HandleToTransactionInfo(txn[ii]);
+ gtm_txninfo->gti_gxid = gxid[ii];
+ if (global_sessionid[ii])
+ strncpy(gtm_txninfo->gti_global_session_id, global_sessionid[ii],
+ GTM_MAX_SESSION_ID_LEN);
+
+ elog(DEBUG2, "GTM_BkupBeginTransactionGetGXIDMulti: xid(%u), handle(%u)",
+ gxid[ii], txn[ii]);
+
+ /*
+ * Advance next gxid -- because this is called at slave only, we don't care the restoration point
+ * here. Restoration point will be created at promotion.
+ */
+ if (GlobalTransactionIdPrecedesOrEquals(GTMTransactions.gt_nextXid, gxid[ii]))
+ GTMTransactions.gt_nextXid = gxid[ii] + 1;
+ if (!GlobalTransactionIdIsValid(GTMTransactions.gt_nextXid)) /* Handle wrap around too */
+ GTMTransactions.gt_nextXid = FirstNormalGlobalTransactionId;
+ xid = GTMTransactions.gt_nextXid;
+ }
+
+ /*
+ * Periodically write the xid and sequence info out to the control file.
+ * Try and handle wrapping, too.
+ */
+ if (GlobalTransactionIdIsValid(xid) &&
+ (xid - ControlXid > CONTROL_INTERVAL || xid < ControlXid))
+ {
+ save_control = true;
+ ControlXid = xid;
+ }
+
+ GTM_RWLockRelease(>MTransactions.gt_TransArrayLock);
+
+ /* save control info when not holding the XidGenLock */
+ if (save_control)
+ SaveControlInfo();
+
+ MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * GTM_BkupBeginTransactionGetGXID
+ *
+ * XXX Not sure what this does.
+ */
+static void
+GTM_BkupBeginTransactionGetGXID(GlobalTransactionId gxid,
+ GTM_IsolationLevel isolevel,
+ bool readonly,
+ const char *global_sessionid,
+ uint32 client_id)
+{
+ GTMProxy_ConnID connid = -1;
+
+ GTM_BkupBeginTransactionGetGXIDMulti(&gxid, &isolevel,
+ &readonly, &global_sessionid, &client_id, &connid, 1);
+}
+
/*
* Process MSG_TXN_BEGIN message
*/
return;
}
-static void
-GTM_BkupBeginTransactionGetGXIDMulti(GlobalTransactionId *gxid,
- GTM_IsolationLevel *isolevel,
- bool *readonly,
- const char **global_sessionid,
- uint32 *client_id,
- GTMProxy_ConnID *connid,
- int txn_count)
-{
- GTM_TransactionHandle txn[GTM_MAX_GLOBAL_TRANSACTIONS];
- GTM_TransactionInfo *gtm_txninfo;
- int ii;
- int count;
- MemoryContext oldContext;
-
- bool save_control = false;
- GlobalTransactionId xid = InvalidGlobalTransactionId;
-
- oldContext = MemoryContextSwitchTo(TopMostMemoryContext);
-
- count = GTM_BeginTransactionMulti(isolevel, readonly, global_sessionid,
- connid, txn_count, txn);
- if (count != txn_count)
- ereport(ERROR,
- (EINVAL,
- errmsg("Failed to start %d new transactions", txn_count)));
-
- elog(DEBUG2, "GTM_BkupBeginTransactionGetGXIDMulti - count %d", count);
-
- //XCPTODO check oldContext = MemoryContextSwitchTo(TopMemoryContext);
- GTM_RWLockAcquire(>MTransactions.gt_TransArrayLock, GTM_LOCKMODE_WRITE);
-
- for (ii = 0; ii < txn_count; ii++)
- {
- gtm_txninfo = GTM_HandleToTransactionInfo(txn[ii]);
- gtm_txninfo->gti_gxid = gxid[ii];
- if (global_sessionid[ii])
- strncpy(gtm_txninfo->gti_global_session_id, global_sessionid[ii],
- GTM_MAX_SESSION_ID_LEN);
-
- elog(DEBUG2, "GTM_BkupBeginTransactionGetGXIDMulti: xid(%u), handle(%u)",
- gxid[ii], txn[ii]);
-
- /*
- * Advance next gxid -- because this is called at slave only, we don't care the restoration point
- * here. Restoration point will be created at promotion.
- */
- if (GlobalTransactionIdPrecedesOrEquals(GTMTransactions.gt_nextXid, gxid[ii]))
- GTMTransactions.gt_nextXid = gxid[ii] + 1;
- if (!GlobalTransactionIdIsValid(GTMTransactions.gt_nextXid)) /* Handle wrap around too */
- GTMTransactions.gt_nextXid = FirstNormalGlobalTransactionId;
- xid = GTMTransactions.gt_nextXid;
- }
-
- /* Periodically write the xid and sequence info out to the control file.
- * Try and handle wrapping, too.
- */
- if (GlobalTransactionIdIsValid(xid) &&
- (xid - ControlXid > CONTROL_INTERVAL || xid < ControlXid))
- {
- save_control = true;
- ControlXid = xid;
- }
-
- GTM_RWLockRelease(>MTransactions.gt_TransArrayLock);
-
- /* save control info when not holding the XidGenLock */
- if (save_control)
- SaveControlInfo();
-
- MemoryContextSwitchTo(oldContext);
-}
-
-static void
-GTM_BkupBeginTransactionGetGXID(GlobalTransactionId gxid,
- GTM_IsolationLevel isolevel,
- bool readonly,
- const char *global_sessionid,
- uint32 client_id)
-{
- GTMProxy_ConnID connid = -1;
-
- GTM_BkupBeginTransactionGetGXIDMulti(&gxid, &isolevel,
- &readonly, &global_sessionid, &client_id, &connid, 1);
-}
-
/*
* Process MSG_BKUP_TXN_BEGIN_GETGXID message
*/
return(GlobalTransactionIdPrecedesOrEquals(GTMTransactions.gt_backedUpXid, GTMTransactions.gt_nextXid));
}
+/*
+ * GTM_RememberCreatedSequence
+ * Remember a sequence created by a given transaction (GXID).
+ *
+ * When creating a sequence in a transaction, we need to remember it so thah
+ * we can deal with it in case of commit/abort, or when it's later dropped in
+ * the same transaction.
+ *
+ * - If the transaction aborts, we simply remove it from the global structure
+ * (see GTM_SeqRemoveCreated).
+ *
+ * - If the sequence gets dropped in the same transaction (GXID), we can just
+ * remove it from the global structure and also stop tracking it in the
+ * transaction-specific list (see GTM_ForgetCreatedSequence).
+ *
+ * - If the transaction commits, just forget about this tracked sequence.
+ *
+ * See GTM_TransactionInfo_Clean for what happens with the tracked sequences
+ * in case of commit/abort of the global transaction.
+ */
void
-GTM_ForgetCreatedSequence(GlobalTransactionId gxid, void *seq)
+GTM_RememberCreatedSequence(GlobalTransactionId gxid, void *seq)
{
GTM_TransactionInfo *gtm_txninfo;
GTM_TransactionHandle txn = GTM_GXIDToHandle(gxid);
gtm_txninfo = GTM_HandleToTransactionInfo(txn);
gtm_txninfo->gti_created_seqs =
- gtm_list_delete(gtm_txninfo->gti_created_seqs, seq);
+ gtm_lappend(gtm_txninfo->gti_created_seqs, seq);
}
/*
- * Remember sequence created by transaction 'gxid'.
- *
- * This should be removed from the global data structure if the transaction
- * aborts (see GTM_SeqRemoveCreated). If the sequence is later dropped in the
- * same transaction, we remove it from the global structure as well as forget
- * tracking (see GTM_ForgetCreatedSequence). If the transaction commits, just
- * forget about this tracked sequence.
+ * GTM_ForgetCreatedSequence
+ * Stop tracking a sequence created in a given transaction (GXID).
*/
void
-GTM_RememberCreatedSequence(GlobalTransactionId gxid, void *seq)
+GTM_ForgetCreatedSequence(GlobalTransactionId gxid, void *seq)
{
GTM_TransactionInfo *gtm_txninfo;
GTM_TransactionHandle txn = GTM_GXIDToHandle(gxid);
gtm_txninfo = GTM_HandleToTransactionInfo(txn);
gtm_txninfo->gti_created_seqs =
- gtm_lappend(gtm_txninfo->gti_created_seqs, seq);
+ gtm_list_delete(gtm_txninfo->gti_created_seqs, seq);
}
+/*
+ * GTM_RememberDroppedSequence
+ * Remember that transaction GXID modified a given sequence.
+ *
+ * We need to track this, so that we can properly respond to commit/abort of
+ * the global transaction (and either undo or alter the sequence).
+ *
+ * See GTM_TransactionInfo_Clean for what happens with the tracked sequences
+ * in case of commit/abort of the global transaction.
+ */
void
GTM_RememberDroppedSequence(GlobalTransactionId gxid, void *seq)
{
gtm_lappend(gtm_txninfo->gti_dropped_seqs, seq);
}
+/*
+ * GTM_RememberDroppedSequence
+ * Remember that transaction GXID dropped a given sequence.
+ *
+ * We need to track this, so that we can properly respond to commit/abort of
+ * the global transaction (and either reinstate or definitely remove the
+ * sequence).
+ *
+ * See GTM_TransactionInfo_Clean for what happens with the tracked sequences
+ * in case of commit/abort of the global transaction.
+ */
void
GTM_RememberAlteredSequence(GlobalTransactionId gxid, void *seq)
{