From 92d88ca699cd8090a26b92cbe46789d2b848195f Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Mon, 07 Sep 2026 09:30:36 +0000
Subject: [PATCH] [#889] Keep a change the replay could not apply out of the ServerState (#892)
---
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/PendingChange.java | 84 +++++++++++++++++++++++++++++++++++++++++
1 files changed, 83 insertions(+), 1 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 38f0661..34f4c4f 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
@@ -13,6 +13,7 @@
*
* Copyright 2006-2008 Sun Microsystems, Inc.
* Portions copyright 2014-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.replication.plugin;
@@ -29,7 +30,29 @@
{
private final CSN csn;
private boolean committed;
- private UpdateMsg msg;
+ /**
+ * Written when the delivery which owns a remote change is taken over by the one which
+ * follows it, and read by the dependency checks without the pending changes lock.
+ */
+ private volatile UpdateMsg msg;
+ /**
+ * Whether a replay thread 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.
+ */
+ private boolean owned;
+ /**
+ * How many times in a row the replay of this change failed, and when the first of
+ * those failures happened - on a clock which only moves forward.
+ * <p>
+ * They live here, on the change which stays listed as the barrier holding the
+ * ServerState back, rather than in a map on the side: a bound on such a map would have
+ * a change evicted between two of its own failures and its give-up budget restarted,
+ * so a replica failing more changes than the bound would never give up on any of them
+ * (issue #889).
+ */
+ private int replayFailures;
+ private long firstReplayFailureTimeMs;
private final PluginOperation op;
/**
@@ -108,6 +131,65 @@
}
/**
+ * Returns whether a replay thread owns this change.
+ *
+ * @return {@code true} if a replay thread is replaying this change or waiting for the
+ * change it depends on
+ */
+ public boolean isOwned()
+ {
+ return owned;
+ }
+
+ /**
+ * Sets whether a replay thread owns this change.
+ *
+ * @param owned {@code true} when a replay thread takes the change over, {@code false}
+ * when its replay failed and the change must be delivered again
+ */
+ public void setOwned(boolean owned)
+ {
+ this.owned = owned;
+ }
+
+ /**
+ * Records that the replay of this change failed once more.
+ *
+ * @param nowMs
+ * when it failed, on a clock which only moves forward
+ */
+ public void recordReplayFailure(long nowMs)
+ {
+ if (replayFailures == 0)
+ {
+ firstReplayFailureTimeMs = nowMs;
+ }
+ replayFailures++;
+ }
+
+ /**
+ * Returns how many times in a row the replay of this change failed.
+ *
+ * @return the number of failures, 0 when its replay never failed
+ */
+ public int getReplayFailures()
+ {
+ return replayFailures;
+ }
+
+ /**
+ * Returns how long the replay of this change has been failing.
+ *
+ * @param nowMs
+ * the current time, on the clock {@link #recordReplayFailure(long)} was given
+ * @return the duration in milliseconds, 0 when its replay never failed
+ */
+ public long getReplayFailingForMs(long nowMs)
+ {
+ return replayFailures == 0 ? 0 : nowMs - firstReplayFailureTimeMs;
+ }
+
+ /**
* Get the operation associated to the PendingChange.
* @return the operation
*/
--
Gitblit v1.10.0