v1_amit-0001-Move-pending-conflict-log-state-out-of-share.txt
text/plain
Filename: v1_amit-0001-Move-pending-conflict-log-state-out-of-share.txt
Type: text/plain
Part: 0
From 773671f06bf3c2e577d0ad99d40a23b84cae9258 Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Wed, 24 Jun 2026 16:08:18 +0530
Subject: [PATCH v1_amit] Move pending conflict log state out of shared memory
The prepared conflict tuple and its error-context string are purely
worker-private data ferried across the apply error boundary, so keep them in a
process-local static struct in conflict.c instead of the shared LogicalRepWorker.
---
src/backend/replication/logical/conflict.c | 67 ++++++++++++++--------
src/backend/replication/logical/launcher.c | 1 -
src/include/replication/worker_internal.h | 10 ----
3 files changed, 43 insertions(+), 35 deletions(-)
diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c
index 983cdf94cf0..e927c152028 100644
--- a/src/backend/replication/logical/conflict.c
+++ b/src/backend/replication/logical/conflict.c
@@ -101,6 +101,25 @@ static const char *const ConflictTypeNames[] = {
[CT_MULTIPLE_UNIQUE_CONFLICTS] = "multiple_unique_conflicts"
};
+/*
+ * Worker-private state for a conflict that has been prepared for the conflict
+ * log table but not yet inserted. It carries the prepared tuple, and a
+ * description of the conflict used for error context, from
+ * prepare_conflict_log_tuple() across the apply error boundary to
+ * ProcessPendingConflictLogTuple()/InsertConflictLogTuple(). Both pointers
+ * reference memory allocated in ApplyContext.
+ *
+ * This is purely process-local state, so it lives here rather than in the
+ * shared LogicalRepWorker struct.
+ */
+typedef struct PendingConflictLogData
+{
+ HeapTuple tuple; /* prepared, not-yet-inserted conflict tuple */
+ char *errcontext_str; /* conflict description for error context */
+} PendingConflictLog;
+
+static PendingConflictLog pending_conflict_log = {0};
+
static int errcode_apply_conflict(ConflictType type);
static void errdetail_apply_conflict(EState *estate,
ResultRelInfo *relinfo,
@@ -427,15 +446,15 @@ ReportApplyConflict(EState *estate, ResultRelInfo *relinfo, int elevel,
* insertion path (ProcessPendingConflictLogTuple) does not retry
* this same failing insert.
*/
- if (MyLogicalRepWorker->conflict_log_tuple != NULL)
+ if (pending_conflict_log.tuple != NULL)
{
- heap_freetuple(MyLogicalRepWorker->conflict_log_tuple);
- MyLogicalRepWorker->conflict_log_tuple = NULL;
+ heap_freetuple(pending_conflict_log.tuple);
+ pending_conflict_log.tuple = NULL;
}
- if (MyLogicalRepWorker->conflict_log_errcontext != NULL)
+ if (pending_conflict_log.errcontext_str != NULL)
{
- pfree(MyLogicalRepWorker->conflict_log_errcontext);
- MyLogicalRepWorker->conflict_log_errcontext = NULL;
+ pfree(pending_conflict_log.errcontext_str);
+ pending_conflict_log.errcontext_str = NULL;
}
PG_RE_THROW();
}
@@ -468,7 +487,7 @@ ProcessPendingConflictLogTuple(void)
Relation conflictlogrel;
/* Nothing to do */
- if (MyLogicalRepWorker->conflict_log_tuple == NULL)
+ if (pending_conflict_log.tuple == NULL)
return;
/*
@@ -499,12 +518,12 @@ ProcessPendingConflictLogTuple(void)
* ReportApplyConflict(). Nothing more to do; just discard the prepared
* tuple and its context string.
*/
- heap_freetuple(MyLogicalRepWorker->conflict_log_tuple);
- MyLogicalRepWorker->conflict_log_tuple = NULL;
- if (MyLogicalRepWorker->conflict_log_errcontext)
+ heap_freetuple(pending_conflict_log.tuple);
+ pending_conflict_log.tuple = NULL;
+ if (pending_conflict_log.errcontext_str)
{
- pfree(MyLogicalRepWorker->conflict_log_errcontext);
- MyLogicalRepWorker->conflict_log_errcontext = NULL;
+ pfree(pending_conflict_log.errcontext_str);
+ pending_conflict_log.errcontext_str = NULL;
}
}
@@ -634,7 +653,7 @@ InsertConflictLogTuple(Relation conflictlogrel)
ErrorContextCallback errcallback;
/* A valid tuple must be prepared and stored in MyLogicalRepWorker. */
- Assert(MyLogicalRepWorker->conflict_log_tuple != NULL);
+ Assert(pending_conflict_log.tuple != NULL);
/*
* Set up an error context so that a failure to insert (e.g. the conflict
@@ -642,22 +661,22 @@ InsertConflictLogTuple(Relation conflictlogrel)
* identifying the conflict we were trying to log.
*/
errcallback.callback = conflict_log_insert_errcontext;
- errcallback.arg = MyLogicalRepWorker->conflict_log_errcontext;
+ errcallback.arg = pending_conflict_log.errcontext_str;
errcallback.previous = error_context_stack;
error_context_stack = &errcallback;
- heap_insert(conflictlogrel, MyLogicalRepWorker->conflict_log_tuple,
+ heap_insert(conflictlogrel, pending_conflict_log.tuple,
GetCurrentCommandId(true), HEAP_INSERT_NO_LOGICAL, NULL);
error_context_stack = errcallback.previous;
/* Free the conflict log tuple and its context string. */
- heap_freetuple(MyLogicalRepWorker->conflict_log_tuple);
- MyLogicalRepWorker->conflict_log_tuple = NULL;
- if (MyLogicalRepWorker->conflict_log_errcontext)
+ heap_freetuple(pending_conflict_log.tuple);
+ pending_conflict_log.tuple = NULL;
+ if (pending_conflict_log.errcontext_str)
{
- pfree(MyLogicalRepWorker->conflict_log_errcontext);
- MyLogicalRepWorker->conflict_log_errcontext = NULL;
+ pfree(pending_conflict_log.errcontext_str);
+ pending_conflict_log.errcontext_str = NULL;
}
}
@@ -1394,7 +1413,7 @@ build_local_conflicts_json_array(EState *estate, Relation rel,
*
* This routine prepares a tuple detailing a conflict encountered during
* logical replication. The prepared tuple will be stored in
- * MyLogicalRepWorker->conflict_log_tuple which should be inserted into the
+ * pending_conflict_log.tuple which should be inserted into the
* conflict log table by calling InsertConflictLogTuple.
*/
static void
@@ -1411,7 +1430,7 @@ prepare_conflict_log_tuple(EState *estate, Relation rel,
char *remote_origin = NULL;
MemoryContext oldctx;
- Assert(MyLogicalRepWorker->conflict_log_tuple == NULL);
+ Assert(pending_conflict_log.tuple == NULL);
/* Populate the values and nulls arrays. */
attno = 0;
@@ -1475,7 +1494,7 @@ prepare_conflict_log_tuple(EState *estate, Relation rel,
Assert(attno + 1 == NUM_CONFLICT_ATTRS);
oldctx = MemoryContextSwitchTo(ApplyContext);
- MyLogicalRepWorker->conflict_log_tuple =
+ pending_conflict_log.tuple =
heap_form_tuple(RelationGetDescr(conflictlogrel), values, nulls);
/*
@@ -1483,7 +1502,7 @@ prepare_conflict_log_tuple(EState *estate, Relation rel,
* the tuple into the conflict log table fails, the resulting error carries
* enough context to identify the conflict (see InsertConflictLogTuple).
*/
- MyLogicalRepWorker->conflict_log_errcontext =
+ pending_conflict_log.errcontext_str =
psprintf("while logging conflict \"%s\" detected on relation \"%s\"",
ConflictTypeNames[conflict_type],
RelationGetRelationName(rel));
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 05a30342f69..313e31ff2e3 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -487,7 +487,6 @@ retry:
worker->oldest_nonremovable_xid = retain_dead_tuples
? MyReplicationSlot->data.xmin
: InvalidTransactionId;
- worker->conflict_log_tuple = NULL;
worker->last_lsn = InvalidXLogRecPtr;
TIMESTAMP_NOBEGIN(worker->last_send_time);
TIMESTAMP_NOBEGIN(worker->last_recv_time);
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 79c90dddd89..00ad0d86a79 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -100,16 +100,6 @@ typedef struct LogicalRepWorker
*/
TransactionId oldest_nonremovable_xid;
- /* A conflict log tuple that is prepared but not yet inserted. */
- HeapTuple conflict_log_tuple;
-
- /*
- * Error-context string describing the conflict above, used to annotate any
- * error raised while inserting conflict_log_tuple into the conflict log
- * table. Allocated, like conflict_log_tuple, in ApplyContext.
- */
- char *conflict_log_errcontext;
-
/* Stats. */
XLogRecPtr last_lsn;
TimestampTz last_send_time;
--
2.54.0