#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "pgxc/barrier.h"
#include "replication/logical.h"
#include "replication/logicallauncher.h"
#include "replication/origin.h"
s->topGlobalTransansactionId = s->transactionId;
if (IS_PGXC_LOCAL_COORDINATOR)
{
+ /*
+ * First ensure that there is no CREATE BARRIER request in-progress and
+ * also block any further request until we finish the 2PC.
+ *
+ * The lock gets automatically released when the transaction ends.
+ * Since we ensure that the local transaction is finished only after
+ * the 2PC is run completely on the remote nodes, this seems
+ * sufficient. The lock also gets released if an error occurs this
+ * point onwards.
+ */
+ BarrierLockAcquireForXact();
+
XactLocalNodePrepared = false;
if (savePrepareGID)
{
* ereport and we will run error recovery as part of AbortTransaction
*/
PreCommit_Remote(savePrepareGID, saveNodeString, XactLocalNodePrepared);
+
/*
* Now that all the remote nodes have successfully prepared and
* commited, commit the local transaction as well. Remember, any errors
* before this point would have been reported via ereport. The fact
* that we are here shows that the transaction has been committed
- * successfully on the remote nodes
+ * successfully on the remote nodes.
*/
if (XactLocalNodePrepared)
{
#include "pgxc/pgxc.h"
#include "nodes/nodes.h"
#include "pgxc/pgxcnode.h"
-#include "storage/lwlock.h"
+#include "storage/lock.h"
#include "tcop/dest.h"
static const char *generate_barrier_id(const char *id);
static void ExecuteBarrier(const char *id);
static void EndBarrier(PGXCNodeAllHandles *handles, const char *id);
+/*
+ * Use some random values to uniquely identify the barrier lock.
+ *
+ * XXX The chances of this conflicting with anything real is so small that it
+ * doesn't seem worth doing anything special to either detect or avoid
+ * conflicts.
+ */
+#define BarrierLockTagMagic1 1696412986
+#define BarrierLockTagMagic2 155831266
+#define BarrierLockTagMagic3 227185880
+#define BarrierLockTagMagic4 21676
+
+/*
+ * This is same as an advisory lock, but we use DEFAULT_LOCKMETHOD to ensure
+ * that the lock is released in case of ereport.
+ */
+#define SET_BARRIER_LOCKTAG(locktag) \
+ ((locktag).locktag_field1 = BarrierLockTagMagic1, \
+ (locktag).locktag_field2 = BarrierLockTagMagic2, \
+ (locktag).locktag_field3 = BarrierLockTagMagic3, \
+ (locktag).locktag_field4 = BarrierLockTagMagic4, \
+ (locktag).locktag_type = LOCKTAG_ADVISORY, \
+ (locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
+
+/*
+ * Acquire the BarrierLock while generating a BARRIER. This locks out all other
+ * readers-writers. The lock is only obtained in a transaction boundary
+ * since we don't the callers to cross that boundary.
+ */
+void
+BarrierLockAcquireForBarrier(void)
+{
+ LOCKTAG barrierlock;
+
+ SET_BARRIER_LOCKTAG(barrierlock);
+ LockAcquire(&barrierlock, AccessExclusiveLock, false, false);
+}
+
+/*
+ * Acquire the BarrierLock while starting a 2PC. The lock is obtained in a
+ * Share mode to ensure that multiple backends can run 2PC in parallel, but
+ * conflict with any ongoing CREATE BARRIER request.
+ *
+ * It's enough to obtain the lock in a transaction boundary because even though
+ * the local transaction may be prepared while running 2PC, the lock is not
+ * released until the prepared transaction is actually finished. That happens
+ * only after the 2PC is finished on all the remote nodes. So that seems enough
+ * to block in-flight 2PC while generating a BARRIER.
+ */
+void
+BarrierLockAcquireForXact(void)
+{
+ LOCKTAG barrierlock;
+
+ SET_BARRIER_LOCKTAG(barrierlock);
+ LockAcquire(&barrierlock, ShareLock, false, false);
+}
+/*
+ * Acquire the BarrierLock in read mode.
+ */
+void
+BarrierLockReleaseForBarrier(void)
+{
+ LOCKTAG barrierlock;
+
+ SET_BARRIER_LOCKTAG(barrierlock);
+ LockRelease(&barrierlock, AccessExclusiveLock, false);
+}
+
+/*
+ * Currently unused since the lock gets auto released once the transaction
+ * ends.
+ */
+void
+BarrierLockReleaseForXact(void)
+{
+ LOCKTAG barrierlock;
+
+ SET_BARRIER_LOCKTAG(barrierlock);
+ LockRelease(&barrierlock, ShareLock, false);
+}
+
/*
* Prepare ourselves for an incoming BARRIER. We must disable all new 2PC
* commits and let the ongoing commits to finish. We then remember the
* new 2PC and there can not be any 2PC in-progress. This technique would
* rely on assumption that an exclusive lock requester is not starved by
* share lock requesters.
- *
- * Note: To ensure that the 2PC are not blocked for a long time, we should
- * set a timeout. The lock should be release after the timeout and the
- * barrier should be canceled.
*/
void
ProcessCreateBarrierPrepare(const char *id)
{
- StringInfoData buf;
+ StringInfoData buf;
if (!IS_PGXC_REMOTE_COORDINATOR)
ereport(ERROR,
errmsg("The CREATE BARRIER PREPARE message is expected to "
"arrive at a Coordinator from another Coordinator")));
- LWLockAcquire(BarrierLock, LW_EXCLUSIVE);
+ /* Acquire the BarrierLock */
+ BarrierLockAcquireForBarrier();
pq_beginmessage(&buf, 'b');
pq_sendstring(&buf, id);
pq_endmessage(&buf);
pq_flush();
-
- /*
- * TODO Start a timer to terminate the pending barrier after a specified
- * timeout
- */
}
/*
void
ProcessCreateBarrierEnd(const char *id)
{
- StringInfoData buf;
+ StringInfoData buf;
if (!IS_PGXC_REMOTE_COORDINATOR)
ereport(ERROR,
errmsg("The CREATE BARRIER END message is expected to "
"arrive at a Coordinator from another Coordinator")));
- LWLockRelease(BarrierLock);
+ /* Release the lock. */
+ BarrierLockReleaseForBarrier();
pq_beginmessage(&buf, 'b');
pq_sendstring(&buf, id);
pq_endmessage(&buf);
pq_flush();
-
- /*
- * TODO Stop the timer
- */
}
/*
for (conn = 0; conn < count; conn++)
{
PGXCNodeHandle *handle;
+ ResponseCombiner combiner;
+
+ InitResponseCombiner(&combiner, 1, COMBINE_TYPE_NONE);
if (conn < conn_handles->co_conn_count)
handle = conn_handles->coord_handles[conn];
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("Failed to receive response from the remote side")));
- if (handle_response(handle, NULL) != RESPONSE_BARRIER_OK)
+ if (handle_response(handle, &combiner) != RESPONSE_BARRIER_OK)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("CREATE BARRIER PREPARE command failed "
elog(DEBUG2, "Preparing Coordinators for BARRIER");
/*
- * Send a CREATE BARRIER PREPARE message to all the Coordinators. We should
- * send an asynchronous request so that we can disable local commits and
- * then wait for the remote Coordinators to finish the work
+ * Send a CREATE BARRIER PREPARE message to all the Coordinators. We send
+ * an asynchronous request so that we can disable local commits and then
+ * wait for the remote Coordinators to finish the work
*/
coord_handles = SendBarrierPrepareRequest(GetAllCoordNodes(), id);
/*
- * Disable local commits
+ * Now disable 2PC originating at this coordinator.
*/
- LWLockAcquire(BarrierLock, LW_EXCLUSIVE);
+ BarrierLockAcquireForBarrier();
elog(DEBUG2, "Disabled 2PC commits originating at the driving Coordinator");
- /*
- * TODO Start a timer to cancel the barrier request in case of a timeout
- */
-
/*
* Local in-flight commits are now over. Check status of the remote
- * Coordinators
+ * Coordinators.
*/
CheckBarrierCommandStatus(coord_handles, id, "PREPARE");
EndBarrier(PGXCNodeAllHandles *prepared_handles, const char *id)
{
/* Resume 2PC locally */
- LWLockRelease(BarrierLock);
+ BarrierLockReleaseForBarrier();
+ /* and also on the remote coordinators. */
SendBarrierEndRequest(prepared_handles, id);
CheckBarrierCommandStatus(prepared_handles, id, "END");