From 5154f34503981b2f96755b1b6da9b425bb20ca7f Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 05 Nov 2026 14:32:12 +0000
Subject: [PATCH] [#1269] Preserve replay order across duplicate replication deliveries (#1270)
---
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/PendingChange.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/PendingChange.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/PendingChange.java
index fa96a19..4a77a40 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/PendingChange.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/PendingChange.java
@@ -38,9 +38,20 @@
*/
private volatile UpdateMsg msg;
/**
+ * A remote delivery has been accepted and is waiting to be claimed from the replay
+ * queue. It has no owner yet, but replacing it would move it behind newer changes.
+ * Local pending changes do not use this flag.
+ */
+ @GuardedBy("RemotePendingChanges.pendingChangesLock")
+ private boolean queuedForReplay = true;
+ @GuardedBy("RemotePendingChanges.pendingChangesLock")
+ private boolean replayStarted;
+ @GuardedBy("RemotePendingChanges.pendingChangesLock")
+ private UpdateToReplay delivery;
+ /**
* The replay thread which owns this change: it is being replayed, or it waits for the
- * change it depends on. A remote change which no thread owns is one whose replay
- * failed and which the replication server is expected to deliver again.
+ * change it depends on. A remote change which no thread owns is either queued for
+ * replay or was released and may be taken over by another delivery.
* <p>
* The owner is kept rather than the bare fact that there is one, so that a change is
* given back by the thread it was handed to and by nobody else: a release which arrives
@@ -137,6 +148,43 @@
public void setMsg(LDAPUpdateMsg msg)
{
this.msg = msg;
+ queuedForReplay = true;
+ }
+
+ /**
+ * Returns whether a remote delivery is waiting to be claimed from the replay queue.
+ *
+ * @return {@code true} if the accepted delivery has not been dequeued or cancelled
+ */
+ public boolean isQueuedForReplay()
+ {
+ return queuedForReplay && !isDeliveryCancelled();
+ }
+
+ /** Installs a retry without changing this change's ordering or failure history. */
+ void setDelivery(UpdateToReplay delivery)
+ {
+ setMsg(delivery.getUpdateMessage());
+ this.delivery = delivery;
+ }
+
+ /** A failed queue hand-off can be cancelled without acquiring the pending lock. */
+ boolean isDeliveryCancelled()
+ {
+ return delivery != null && delivery.isCancelled();
+ }
+
+ /** Marks a remote delivery as dequeued while its replay claim is being established. */
+ public void markDequeued()
+ {
+ queuedForReplay = false;
+ replayStarted = true;
+ }
+
+ /** Keeps the in-progress monitor stable while a failed replay waits in the retry queue. */
+ boolean hasStartedReplay()
+ {
+ return replayStarted;
}
/**
--
Gitblit v1.10.0