diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gtm/main/gtm_thread.c | 2 | ||||
-rw-r--r-- | src/gtm/main/gtm_txn.c | 943 | ||||
-rw-r--r-- | src/gtm/main/main.c | 6 | ||||
-rw-r--r-- | src/include/gtm/gtm_txn.h | 8 |
4 files changed, 735 insertions, 224 deletions
diff --git a/src/gtm/main/gtm_thread.c b/src/gtm/main/gtm_thread.c index 462a2a8517..151e61add0 100644 --- a/src/gtm/main/gtm_thread.c +++ b/src/gtm/main/gtm_thread.c @@ -432,7 +432,7 @@ void GTM_SetInitialAndNextClientIdentifierAtPromote(void) { GTM_RWLockAcquire(>MThreads->gt_lock, GTM_LOCKMODE_WRITE); - GTMThreads->gt_starting_client_id = GTMGetLastClientIdentifier(); + GTMThreads->gt_starting_client_id = GTM_GetLastClientIdentifier(); GTMThreads->gt_next_client_id = GTM_CLIENT_ID_NEXT(GTMThreads->gt_starting_client_id); GTM_RWLockRelease(>MThreads->gt_lock); diff --git a/src/gtm/main/gtm_txn.c b/src/gtm/main/gtm_txn.c index 1fde21aff8..62410e1466 100644 --- a/src/gtm/main/gtm_txn.c +++ b/src/gtm/main/gtm_txn.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * 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 @@ -10,7 +10,121 @@ * * * 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.). * *------------------------------------------------------------------------- */ @@ -32,27 +146,31 @@ 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) { @@ -69,12 +187,12 @@ 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; @@ -120,9 +238,15 @@ GTM_InitTxnManager(void) 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) @@ -157,12 +281,30 @@ 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) { @@ -185,6 +327,13 @@ 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) { @@ -192,11 +341,14 @@ 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; @@ -215,18 +367,26 @@ GTM_GIDToHandle(char *gid) 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) @@ -255,14 +415,20 @@ 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) @@ -282,6 +448,10 @@ 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)) @@ -292,30 +462,42 @@ GTM_RemoveTransInfoMulti(GTM_TransactionInfo *gtm_txninfo[], int txn_count) 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) @@ -347,7 +529,7 @@ GTM_RemoveAllTransInfos(uint32 client_id, int backend_id) /* * 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) @@ -363,18 +545,27 @@ GTM_RemoveAllTransInfos(uint32 client_id, int backend_id) } 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; @@ -391,16 +582,25 @@ GTMGetLastClientIdentifier(void) 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) @@ -415,21 +615,53 @@ 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"))); @@ -445,23 +677,19 @@ GTM_GetGlobalTransactionIdMulti(GTM_TransactionHandle handle[], int txn_count, 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; } @@ -501,12 +729,16 @@ GTM_GetGlobalTransactionIdMulti(GTM_TransactionHandle handle[], int txn_count, 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) && @@ -518,38 +750,49 @@ GTM_GetGlobalTransactionIdMulti(GTM_TransactionHandle handle[], int txn_count, 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; @@ -561,35 +804,75 @@ ReadNewGlobalTransactionId(void) } /* - * 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[], @@ -602,6 +885,9 @@ GTM_BeginTransactionMulti(GTM_IsolationLevel isolevel[], 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)); /* @@ -622,6 +908,10 @@ GTM_BeginTransactionMulti(GTM_IsolationLevel isolevel[], 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); @@ -633,8 +923,25 @@ GTM_BeginTransactionMulti(GTM_IsolationLevel isolevel[], } /* - * 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) @@ -650,19 +957,25 @@ GTM_BeginTransactionMulti(GTM_IsolationLevel isolevel[], 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; @@ -683,7 +996,22 @@ GTM_BeginTransactionMulti(GTM_IsolationLevel isolevel[], 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, @@ -693,11 +1021,16 @@ GTM_BeginTransaction(GTM_IsolationLevel isolevel, 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, @@ -750,11 +1083,23 @@ init_GTM_TransactionInfo(GTM_TransactionInfo *gtm_txninfo, /* - * 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; @@ -786,7 +1131,6 @@ clean_GTM_TransactionInfo(GTM_TransactionInfo *gtm_txninfo) GTM_SeqRestoreAltered(gtm_lfirst(lc)); } - } else if (gtm_txninfo->gti_state == GTM_TXN_COMMIT_IN_PROGRESS) { @@ -833,14 +1177,21 @@ clean_GTM_TransactionInfo(GTM_TransactionInfo *gtm_txninfo) } } - +/* + * 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; @@ -859,6 +1210,13 @@ GTM_BkupBeginTransactionMulti(GTM_IsolationLevel *isolevel, 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, @@ -873,7 +1231,19 @@ GTM_BkupBeginTransaction(GTM_IsolationLevel isolevel, } /* - * 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[]) @@ -881,6 +1251,10 @@ GTM_RollbackTransactionMulti(GTM_TransactionHandle txn[], int txn_count, int sta 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]); @@ -892,11 +1266,13 @@ GTM_RollbackTransactionMulti(GTM_TransactionHandle txn[], int txn_count, int sta } /* - * 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; } @@ -906,7 +1282,8 @@ GTM_RollbackTransactionMulti(GTM_TransactionHandle txn[], int txn_count, int sta } /* - * Rollback a transaction + * GTM_RollbackTransaction + * Rollback a single global transaction, identified by a handle. */ static int GTM_RollbackTransaction(GTM_TransactionHandle txn) @@ -917,11 +1294,45 @@ 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]; @@ -936,15 +1347,19 @@ GTM_CommitTransactionMulti(GTM_TransactionHandle txn[], int 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++) { @@ -957,8 +1372,12 @@ GTM_CommitTransactionMulti(GTM_TransactionHandle txn[], int txn_count, } } + /* 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; } @@ -969,53 +1388,86 @@ GTM_CommitTransactionMulti(GTM_TransactionHandle txn[], int txn_count, 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, @@ -1025,19 +1477,19 @@ 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 @@ -1060,6 +1512,16 @@ GTM_StartPreparedTransaction(GTM_TransactionHandle txn, 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, @@ -1068,6 +1530,8 @@ GTM_GetGIDData(GTM_TransactionHandle prepared_txn, GTM_TransactionInfo *gtm_txninfo = NULL; MemoryContext oldContext; + Assert(prepared_gxid); + oldContext = MemoryContextSwitchTo(TopMostMemoryContext); gtm_txninfo = GTM_HandleToTransactionInfo(prepared_txn); @@ -1091,6 +1555,103 @@ GTM_GetGIDData(GTM_TransactionHandle prepared_txn, } /* + * 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 */ void @@ -1287,92 +1848,6 @@ retry: 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 */ @@ -2690,8 +3165,28 @@ bool GTM_NeedXidRestoreUpdate(void) 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); @@ -2701,20 +3196,15 @@ GTM_ForgetCreatedSequence(GlobalTransactionId gxid, void *seq) 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); @@ -2724,9 +3214,19 @@ GTM_RememberCreatedSequence(GlobalTransactionId gxid, void *seq) 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) { @@ -2741,6 +3241,17 @@ 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) { diff --git a/src/gtm/main/main.c b/src/gtm/main/main.c index a518db392e..f9eeb1d788 100644 --- a/src/gtm/main/main.c +++ b/src/gtm/main/main.c @@ -2367,7 +2367,7 @@ GTM_RestoreTxnInfo(FILE *ctlf, GlobalTransactionId next_gxid, * Add in extra amount in case we had not gracefully stopped */ next_gxid = saved_gxid + CONTROL_INTERVAL; - SetControlXid(next_gxid); + GTM_SetControlXid(next_gxid); } else saved_gxid = next_gxid = InitialGXIDValue_Default; @@ -2391,7 +2391,7 @@ GTM_RestoreTxnInfo(FILE *ctlf, GlobalTransactionId next_gxid, GTMTransactions.gt_recent_global_xmin = next_gxid; } - SetNextGlobalTransactionId(next_gxid); + GTM_SetNextGlobalTransactionId(next_gxid); elog(LOG, "Restoring last GXID to %u\n", next_gxid); elog(LOG, "Restoring global xmin to %u\n", GTMTransactions.gt_recent_global_xmin); @@ -2413,7 +2413,7 @@ GTM_SaveTxnInfo(FILE *ctlf) GlobalTransactionId next_gxid; GlobalTransactionId global_xmin = GTMTransactions.gt_recent_global_xmin; - next_gxid = ReadNewGlobalTransactionId(); + next_gxid = GTM_ReadNewGlobalTransactionId(); elog(DEBUG1, "Saving transaction info - next_gxid: %u, global_xmin: %u", next_gxid, global_xmin); diff --git a/src/include/gtm/gtm_txn.h b/src/include/gtm/gtm_txn.h index c8b1fdc855..ee3d0a1f0b 100644 --- a/src/include/gtm/gtm_txn.h +++ b/src/include/gtm/gtm_txn.h @@ -32,9 +32,9 @@ struct GTM_RestoreContext; /* gtm/main/gtm_txn.c */ extern GlobalTransactionId GTM_GetGlobalTransactionId(GTM_TransactionHandle handle); -extern GlobalTransactionId ReadNewGlobalTransactionId(void); -extern void SetNextGlobalTransactionId(GlobalTransactionId gxid); -extern void SetControlXid(GlobalTransactionId gxid); +extern GlobalTransactionId GTM_ReadNewGlobalTransactionId(void); +extern void GTM_SetNextGlobalTransactionId(GlobalTransactionId gxid); +extern void GTM_SetControlXid(GlobalTransactionId gxid); extern void GTM_SetShuttingDown(void); /* for restoration point backup (gtm/main/gtm_backup.c) */ @@ -153,7 +153,7 @@ GTM_TransactionHandle GTM_GXIDToHandle(GlobalTransactionId gxid); /* Transaction Control */ void GTM_InitTxnManager(void); void GTM_RemoveAllTransInfos(uint32 client_id, int backend_id); -uint32 GTMGetLastClientIdentifier(void); +uint32 GTM_GetLastClientIdentifier(void); /* processing of messages in gtm_txn.c */ void ProcessBeginTransactionCommand(Port *myport, StringInfo message); |