From d07bb318976a8e1c65b1c26b431b0909380ce473 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 17 Sep 2026 11:57:18 +0000
Subject: [PATCH] [#942] Warn once per interval that a change is being retried, not once per delivery (#982)
---
opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ReplayDuringImportTest.java | 71 +++++++++++++++++++++++++++++++++++
1 files changed, 71 insertions(+), 0 deletions(-)
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ReplayDuringImportTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ReplayDuringImportTest.java
index 3dc590f..0f0c15c 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ReplayDuringImportTest.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ReplayDuringImportTest.java
@@ -37,6 +37,7 @@
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.CSN;
import org.opends.server.replication.common.CSNGenerator;
+import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.replication.protocol.DoneMsg;
import org.opends.server.replication.protocol.EntryMsg;
import org.opends.server.replication.protocol.InitializeRequestMsg;
@@ -70,6 +71,10 @@
* The exporter is a broker of this test, so that the test says when the entries arrive: the
* change is replayed while the import is waiting for them - or, for the request, while the
* exporter is holding the answer.
+ * <p>
+ * The {@code timeOut} each case declares is what it is expected to take at the most; it is
+ * not what bounds it. {@code TestListener} sets the timeout of every test method from the
+ * {@code org.opends.test.timeout} property, ten minutes under Maven and none outside it.
*/
@SuppressWarnings("javadoc")
public class ReplayDuringImportTest extends ReplicationTestCase
@@ -326,6 +331,72 @@
}
/**
+ * A total update forgets the deliveries which were folded into no warning, along with
+ * the changes they were deliveries of (issue #942).
+ * <p>
+ * The changes listed as pending do not outlive the ServerState the import replaces, and
+ * the recovery from a failed replay goes with them - the session restart backoff, and the
+ * count the next warning about a change being asked for again says it stands for. The
+ * first warning over the imported data must not count the deliveries of a change which
+ * is not listed anymore.
+ * <p>
+ * Nothing sends a change of this test again - the exporter never had it - so the count
+ * is fed by two changes failing within one interval rather than by one change delivered
+ * twice: the first is warned about, the second is folded into no warning. The changes
+ * are deletes: a short circuit on the modifies would be tripped by the ServerState being
+ * saved to the base entry and by the import disabling the backend it replaces.
+ */
+ @Test(timeOut = 120_000)
+ public void aWarningAfterTheImportDoesNotCountTheDeliveriesBefore() throws Exception
+ {
+ final Entry warnedAbout = TestCaseUtils.addEntry(
+ "dn: cn=warnedAbout," + EXAMPLE_DN,
+ "objectClass: top",
+ "objectClass: person",
+ "cn: warnedAbout",
+ "sn: warnedAbout");
+ final Entry folded = TestCaseUtils.addEntry(
+ "dn: cn=folded," + EXAMPLE_DN,
+ "objectClass: top",
+ "objectClass: person",
+ "cn: folded",
+ "sn: folded");
+ final String warnedAboutUUID = getEntryUUID(warnedAbout.getName());
+ final String foldedUUID = getEntryUUID(folded.getName());
+ final String[] exported = exportedEntries();
+
+ ShortCircuitPlugin.registerShortCircuit(
+ OperationType.DELETE, "PreParse", ResultCode.UNAVAILABLE.intValue());
+ try
+ {
+ replayMsg(new DeleteMsg(warnedAbout.getName(), gen.newCSN(), warnedAboutUUID));
+ replayMsg(new DeleteMsg(folded.getName(), gen.newCSN(), foldedUUID));
+
+ startImportInto(exported.length);
+ finishImport(exported);
+
+ /*
+ * Only the timestamp of the throttle is put back, so that the failure over the
+ * imported data is warned about straight away: the count is the domain's to keep or
+ * to forget.
+ */
+ domain.resetReplayRetryWarningThrottle();
+ final CSN csn = gen.newCSN();
+ replayMsg(new DeleteMsg(DN.valueOf(IMPORTED_ENTRY_DN), csn, IMPORTED_ENTRY_UUID));
+ final List<String> warnings = errorLogRecordsOf(WARN_REPLAY_RETRYING_CHANGE.ordinal(), csn);
+ assertThat(warnings).as("the change which fails over the imported data must be warned about")
+ .isNotEmpty();
+ assertThat(warnings.get(0))
+ .as("the first warning after the import must not count the deliveries before it")
+ .contains(" 0 further deliveries");
+ }
+ finally
+ {
+ ShortCircuitPlugin.deregisterShortCircuit(OperationType.DELETE, "PreParse");
+ }
+ }
+
+ /**
* Has the exporter start a total update into this replica, and returns once the backend
* of the domain is deregistered for it: from then on the import is reading the session,
* and a change replayed here is replayed into no backend.
--
Gitblit v1.10.0