Files
openGauss-server/contrib/gtm/main/gtm_snap.cpp
2020-06-30 17:38:27 +08:00

430 lines
16 KiB
C++

/* -------------------------------------------------------------------------
*
* 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(&GTMTransactions.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(&GTMTransactions.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;
}
}