/* ------------------------------------------------------------------------- * * gtm_snap.c * Snapshot handling on GTM * * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 2010-2012 Postgres-XC Development Group * * * IDENTIFICATION * $PostgreSQL$ * * ------------------------------------------------------------------------- */ #include "gtm/assert.h" #include "gtm/elog.h" #include "gtm/gtm.h" #include "gtm/gtm_client.h" #include "gtm/gtm_standby.h" #include "gtm/stringinfo.h" #include "gtm/libpq.h" #include "gtm/libpq-int.h" #include "gtm/pqformat.h" /* * Get snapshot for the given transactions. If this is the first call in the * transaction, a fresh snapshot is taken and returned back. For a serializable * transaction, repeated calls to the function will return the same snapshot. * For a read-committed transaction, fresh snapshot is taken every time and * returned to the caller. * * The returned snapshot includes xmin (lowest still-running xact ID), * xmax (highest completed xact ID + 1), and a list of running xact IDs * in the range xmin <= xid < xmax. It is used as follows: * All xact IDs < xmin are considered finished. * All xact IDs >= xmax are considered still running. * For an xact ID xmin <= xid < xmax, consult list to see whether * it is considered running or not. * This ensures that the set of transactions seen as "running" by the * current xact will not change after it takes the snapshot. * * All running top-level XIDs are included in the snapshot. * * We also update the following global variables: * RecentGlobalXmin: the global xmin (oldest TransactionXmin across all * running transactions * * Note: this function should probably not be called with an argument that's * not statically allocated (see xip allocation below). */ GTM_Snapshot GTM_GetTransactionSnapshot(GTM_TransactionHandle handle[], int txn_count, int* status) { GlobalTransactionId xmin; GlobalTransactionId xmax; GlobalTransactionId globalxmin; int count = 0; gtm_ListCell* elem = NULL; int ii; errno_t rc = 0; /* * Instead of allocating memory for a snapshot, we use the snapshot of the * first transaction in the given array. The same snapshot will later be * copied to other transaction info structures. */ GTM_TransactionInfo* mygtm_txninfo = NULL; GTM_Snapshot snapshot = NULL; rc = memset_s(status, sizeof(int) * txn_count, 0, sizeof(int) * txn_count); securec_check(rc, "\0", "\0"); for (ii = 0; ii < txn_count; ii++) { mygtm_txninfo = GTM_HandleToTransactionInfo(handle[ii]); /* * If the transaction does not exist, just mark the status field with * a STATUS_ERROR code */ if (mygtm_txninfo == NULL) status[ii] = STATUS_ERROR; else if (snapshot == NULL) snapshot = &mygtm_txninfo->gti_current_snapshot; } /* * If no valid transaction exists in the array, send an error message back. * Otherwise, we should still get the snapshot and send it back. The * invalid transaction ids are marked separately in the status array. */ if (snapshot == NULL) return NULL; Assert(snapshot != NULL); if (snapshot->sn_xip == NULL) { /* * First call for this snapshot */ snapshot->sn_xip = (GlobalTransactionId*)palloc(GTM_MAX_GLOBAL_TRANSACTIONS * sizeof(GlobalTransactionId)); if (snapshot->sn_xip == NULL) ereport(ERROR, (ENOMEM, errmsg("out of memory"))); } /* * It is sufficient to get shared lock on ProcArrayLock, even if we are * going to set MyProc->xmin. */ GTM_RWLockAcquire(>MTransactions.gt_TransArrayLock, GTM_LOCKMODE_READ); /* xmax is always latestCompletedXid + 1 */ xmax = GTMTransactions.gt_latestCompletedXid; Assert(GlobalTransactionIdIsNormal(xmax)); GlobalTransactionIdAdvance(xmax); /* initialize xmin calculation with xmax */ globalxmin = xmin = xmax; /* * Spin over transaction list checking xid, xmin, and subxids. The goal is to * gather all active xids and find the lowest xmin */ gtm_foreach(elem, GTMTransactions.gt_open_transactions) { volatile GTM_TransactionInfo* gtm_txninfo = (GTM_TransactionInfo*)gtm_lfirst(elem); GlobalTransactionId xid; /* Don't take into account LAZY VACUUMs */ if (gtm_txninfo->gti_vacuum) continue; /* Update globalxmin to be the smallest valid xmin */ xid = gtm_txninfo->gti_xmin; /* fetch just once */ if (GlobalTransactionIdIsNormal(xid) && GlobalTransactionIdPrecedes(xid, globalxmin)) globalxmin = xid; /* Fetch xid just once - see GetNewTransactionId */ xid = gtm_txninfo->gti_gxid; /* * If the transaction has been assigned an xid < xmax we add it to the * snapshot, and update xmin if necessary. There's no need to store * XIDs >= xmax, since we'll treat them as running anyway. We don't * bother to examine their subxids either. * * We don't include our own XID (if any) in the snapshot, but we must * include it into xmin. */ if (GlobalTransactionIdIsNormal(xid)) { /* * Unlike Postgres, we include the GXID of the current transaction * as well in the snapshot. This is necessary because the same * snapshot is shared by multiple backends through GTM proxy and * the GXID will vary for each backend. * * XXX We should confirm that this does not have any adverse effect * on the MVCC visibility and check if any changes are related to * the MVCC checks because of the change */ if (GlobalTransactionIdFollowsOrEquals(xid, xmax)) continue; if (GlobalTransactionIdPrecedes(xid, xmin)) xmin = xid; snapshot->sn_xip[count++] = xid; } } /* * Update globalxmin to include actual process xids. This is a slightly * different way of computing it than GetOldestXmin uses, but should give * the same result. */ if (GlobalTransactionIdPrecedes(xmin, globalxmin)) globalxmin = xmin; GTMTransactions.gt_recent_global_xmin = globalxmin; snapshot->sn_xmin = xmin; snapshot->sn_xmax = xmax; snapshot->sn_xcnt = count; snapshot->sn_recent_global_xmin = globalxmin; /* * Now, before the proc array lock is released, set the xmin in the txninfo * structures of all the transactions. */ for (ii = 0; ii < txn_count; ii++) { GTM_Snapshot mysnap = NULL; /* * We have already gone through all the transaction handles above and * marked the invalid handles with STATUS_ERROR */ if (status[ii] == STATUS_ERROR) continue; mygtm_txninfo = GTM_HandleToTransactionInfo(handle[ii]); mysnap = &mygtm_txninfo->gti_current_snapshot; if (GTM_IsTransSerializable(mygtm_txninfo)) { if ((mygtm_txninfo->gti_snapshot_set) && (txn_count > 1)) elog(ERROR, "Grouped snapshot can only include first snapshot in Serializable transaction"); if (!mygtm_txninfo->gti_snapshot_set) { /* * For the first transaction in the array, the snapshot is * already set. */ if (snapshot != mysnap) { if (mysnap->sn_xip == NULL) { /* * First call for this snapshot */ mysnap->sn_xip = (GlobalTransactionId*)palloc(GTM_MAX_GLOBAL_TRANSACTIONS * sizeof(GlobalTransactionId)); if (mysnap->sn_xip == NULL) ereport(ERROR, (ENOMEM, errmsg("out of memory"))); } mysnap->sn_xmin = snapshot->sn_xmin; mysnap->sn_xmax = snapshot->sn_xmax; mysnap->sn_xcnt = snapshot->sn_xcnt; mysnap->sn_recent_global_xmin = snapshot->sn_recent_global_xmin; rc = memcpy_s(mysnap->sn_xip, sizeof(GlobalTransactionId) * snapshot->sn_xcnt, snapshot->sn_xip, sizeof(GlobalTransactionId) * snapshot->sn_xcnt); securec_check(rc, "\0", "\0"); } mygtm_txninfo->gti_snapshot_set = true; } } else if (snapshot != mysnap) { if (mysnap->sn_xip == NULL) { /* * First call for this snapshot */ mysnap->sn_xip = (GlobalTransactionId*)palloc(GTM_MAX_GLOBAL_TRANSACTIONS * sizeof(GlobalTransactionId)); if (mysnap->sn_xip == NULL) ereport(ERROR, (ENOMEM, errmsg("out of memory"))); } mysnap->sn_xmin = snapshot->sn_xmin; mysnap->sn_xmax = snapshot->sn_xmax; mysnap->sn_xcnt = snapshot->sn_xcnt; mysnap->sn_recent_global_xmin = snapshot->sn_recent_global_xmin; rc = memcpy_s(mysnap->sn_xip, sizeof(GlobalTransactionId) * snapshot->sn_xcnt, snapshot->sn_xip, sizeof(GlobalTransactionId) * snapshot->sn_xcnt); securec_check(rc, "\0", "\0"); } if ((mygtm_txninfo != NULL) && (!GlobalTransactionIdIsValid(mygtm_txninfo->gti_xmin))) mygtm_txninfo->gti_xmin = xmin; } GTM_RWLockRelease(>MTransactions.gt_TransArrayLock); elog(DEBUG1, "GTM_GetTransactionSnapshot: (%u:%u:%u:%u)", snapshot->sn_xmin, snapshot->sn_xmax, snapshot->sn_xcnt, snapshot->sn_recent_global_xmin); return snapshot; } /* * Process MSG_SNAPSHOT_GET command */ void ProcessGetSnapshotCommand(Port* myport, StringInfo message, bool get_gxid) { StringInfoData buf; GTM_TransactionHandle txn; GlobalTransactionId gxid; int isgxid = 0; GTM_Snapshot snapshot; MemoryContext oldContext; int status; int txn_count = 1; errno_t rc = 0; /* * Here we consume a byte which is a boolean to determine if snapshot can * be grouped or not. This is used only by GTM-Proxy and it is useless for GTM * so consume data. */ pq_getmsgbyte(message); isgxid = pq_getmsgbyte(message); if (isgxid) { const char* data = NULL; Assert(!get_gxid); data = pq_getmsgbytes(message, sizeof(gxid)); if (data == NULL) ereport(ERROR, (EPROTO, errmsg("Message does not contain valid GXID"))); rc = memcpy_s(&gxid, sizeof(gxid), data, sizeof(gxid)); securec_check(rc, "\0", "\0"); elog(DEBUG1, "Received transaction ID %d for snapshot obtention", gxid); txn = GTM_GXIDToHandle(gxid); } else { const char* data = pq_getmsgbytes(message, sizeof(txn)); if (data == NULL) ereport(ERROR, (EPROTO, errmsg("Message does not contain valid Transaction Handle"))); rc = memcpy_s(&txn, sizeof(txn), data, sizeof(txn)); securec_check(rc, "\0", "\0"); } pq_getmsgend(message); if (get_gxid) { Assert(!isgxid); gxid = GTM_GetGlobalTransactionId(txn); if (gxid == InvalidGlobalTransactionId) ereport(ERROR, (EINVAL, errmsg("Failed to get a new transaction id"))); } oldContext = MemoryContextSwitchTo(TopMostMemoryContext); /* * Get a fresh snapshot */ if ((snapshot = GTM_GetTransactionSnapshot(&txn, 1, &status)) == NULL) ereport(ERROR, (EINVAL, errmsg("Failed to get a snapshot"))); MemoryContextSwitchTo(oldContext); pq_beginmessage(&buf, 'S'); pq_sendint(&buf, get_gxid ? SNAPSHOT_GXID_GET_RESULT : SNAPSHOT_GET_RESULT, 4); if (myport->remote_type == GTM_NODE_GTM_PROXY) { GTM_ProxyMsgHeader proxyhdr; proxyhdr.ph_conid = myport->conn_id; pq_sendbytes(&buf, (char*)&proxyhdr, sizeof(GTM_ProxyMsgHeader)); } pq_sendbytes(&buf, (char*)&gxid, sizeof(GlobalTransactionId)); pq_sendbytes(&buf, (char*)&txn_count, sizeof(txn_count)); pq_sendbytes(&buf, (char*)&status, sizeof(int) * txn_count); pq_sendbytes(&buf, (char*)&snapshot->sn_xmin, sizeof(GlobalTransactionId)); pq_sendbytes(&buf, (char*)&snapshot->sn_xmax, sizeof(GlobalTransactionId)); pq_sendbytes(&buf, (char*)&snapshot->sn_recent_global_xmin, sizeof(GlobalTransactionId)); pq_sendint(&buf, snapshot->sn_xcnt, sizeof(int)); pq_sendbytes(&buf, (char*)snapshot->sn_xip, sizeof(GlobalTransactionId) * snapshot->sn_xcnt); pq_endmessage(myport, &buf); if (myport->remote_type != GTM_NODE_GTM_PROXY) pq_flush(myport); return; } /* * Process MSG_SNAPSHOT_GET_MULTI command */ void ProcessGetSnapshotCommandMulti(Port* myport, StringInfo message) { StringInfoData buf; GTM_TransactionHandle txn[GTM_MAX_GLOBAL_TRANSACTIONS]; GlobalTransactionId gxid[GTM_MAX_GLOBAL_TRANSACTIONS]; int isgxid[GTM_MAX_GLOBAL_TRANSACTIONS]; GTM_Snapshot snapshot; MemoryContext oldContext; int txn_count; int ii; int status[GTM_MAX_GLOBAL_TRANSACTIONS]; errno_t rc = 0; txn_count = pq_getmsgint(message, sizeof(int)); for (ii = 0; ii < txn_count; ii++) { isgxid[ii] = pq_getmsgbyte(message); if (isgxid[ii]) { const char* data = pq_getmsgbytes(message, sizeof(gxid[ii])); if (data == NULL) ereport(ERROR, (EPROTO, errmsg("Message does not contain valid GXID"))); rc = memcpy_s(&gxid[ii], sizeof(gxid[ii]), data, sizeof(gxid[ii])); securec_check(rc, "\0", "\0"); txn[ii] = GTM_GXIDToHandle(gxid[ii]); } else { const char* data = pq_getmsgbytes(message, sizeof(txn[ii])); if (data == NULL) ereport(ERROR, (EPROTO, errmsg("Message does not contain valid Transaction Handle"))); rc = memcpy_s(&txn[ii], sizeof(txn[ii]), data, sizeof(txn[ii])); securec_check(rc, "\0", "\0"); } } pq_getmsgend(message); oldContext = MemoryContextSwitchTo(TopMostMemoryContext); /* * Get a fresh snapshot */ if ((snapshot = GTM_GetTransactionSnapshot(txn, txn_count, status)) == NULL) ereport(ERROR, (EINVAL, errmsg("Failed to get a snapshot"))); MemoryContextSwitchTo(oldContext); pq_beginmessage(&buf, 'S'); pq_sendint(&buf, SNAPSHOT_GET_MULTI_RESULT, 4); if (myport->remote_type == GTM_NODE_GTM_PROXY) { GTM_ProxyMsgHeader proxyhdr; proxyhdr.ph_conid = myport->conn_id; pq_sendbytes(&buf, (char*)&proxyhdr, sizeof(GTM_ProxyMsgHeader)); } pq_sendbytes(&buf, (char*)&txn_count, sizeof(txn_count)); pq_sendbytes(&buf, (char*)status, sizeof(int) * txn_count); pq_sendbytes(&buf, (char*)&snapshot->sn_xmin, sizeof(GlobalTransactionId)); pq_sendbytes(&buf, (char*)&snapshot->sn_xmax, sizeof(GlobalTransactionId)); pq_sendbytes(&buf, (char*)&snapshot->sn_recent_global_xmin, sizeof(GlobalTransactionId)); pq_sendint(&buf, snapshot->sn_xcnt, sizeof(int)); pq_sendbytes(&buf, (char*)snapshot->sn_xip, sizeof(GlobalTransactionId) * snapshot->sn_xcnt); pq_endmessage(myport, &buf); if (myport->remote_type != GTM_NODE_GTM_PROXY) pq_flush(myport); return; } /* * Free the snapshot data. The snapshot itself is not freed though */ void GTM_FreeSnapshotData(GTM_Snapshot snapshot) { if (snapshot == NULL) return; if (snapshot->sn_xip != NULL) { Assert(snapshot->sn_xcnt); pfree(snapshot->sn_xip); snapshot->sn_xip = NULL; } }