From 69f502abd9219bf113a3b062d30068d699f79403 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 16 Jul 2026 05:16:04 +0000
Subject: [PATCH] [#735] Do not roll back a concurrently adopted generation ID on aborted handshake (#736)

---
 opendj-server-legacy/src/main/java/org/opends/server/replication/server/ServerHandler.java |   69 ++++++++++++++++++++++++++++++----
 1 files changed, 60 insertions(+), 9 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/server/ServerHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/server/ServerHandler.java
index 6d348dd..ccb3ebd 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/server/ServerHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/server/ServerHandler.java
@@ -148,8 +148,20 @@
   protected long generationId = -1;
   /** The generation id of the hosting RS. */
   protected long localGenerationId = -1;
-  /** The generation id before processing a new start handshake. */
-  protected long oldGenerationId = -1;
+  /**
+   * The domain generation id that this handler replaced during the start
+   * handshake, or -100 when this handler has not changed the domain generation
+   * id and there is nothing to roll back on {@link #abortStart(LocalizableMessage)}.
+   */
+  protected long oldGenerationId = -100;
+  /**
+   * The generation id this handler wrote into the domain during the start
+   * handshake, or -100 when it wrote none. Used by
+   * {@link #abortStart(LocalizableMessage)} to roll back only this handler's
+   * own change: a value concurrently set by another thread must not be
+   * overwritten with the stale {@link #oldGenerationId}.
+   */
+  private long generationIdSetOnStart = -100;
   /** Group id of this remote server. */
   protected byte groupId = -1;
   /** The SSL encryption after the negotiation with the peer. */
@@ -219,15 +231,54 @@
       localSession.close();
     }
 
-    releaseDomainLock();
-
-    // If generation id of domain was changed, set it back to old value
-    // We may have changed it as it was -1 and we received a value >0 from peer
-    // server and the last topo message sent may have failed being sent: in that
-    // case retrieve old value of generation id for replication server domain
+    // If this handler changed the domain generation id during the handshake,
+    // set it back to the old value. Only undo our own change: another thread
+    // may have legitimately changed the generation id in the meantime (e.g.
+    // adopted it from a peer topology message) and that value must not be
+    // overwritten with our stale snapshot. Do it BEFORE releasing the domain
+    // lock, so a handshake queued on the lock cannot read, advertise or arm on
+    // the doomed value in between.
     if (oldGenerationId != -100)
     {
-      replicationServerDomain.changeGenerationId(oldGenerationId);
+      replicationServerDomain.rollbackGenerationIdIfUnchanged(
+          generationIdSetOnStart, oldGenerationId);
+      oldGenerationId = -100;
+      generationIdSetOnStart = -100;
+    }
+
+    releaseDomainLock();
+  }
+
+  /**
+   * Changes the domain generation id during the start handshake, remembering
+   * the replaced value so that {@link #abortStart(LocalizableMessage)} can
+   * undo this handler's own change (and only it) if the handshake
+   * subsequently fails.
+   * <p>
+   * The change is applied only if the domain generation id still equals
+   * {@link #localGenerationId}, the value this handler based its decision on:
+   * a generation id concurrently adopted by a lock-free path (an update
+   * received from an already connected server) must not be stomped. This also
+   * makes a repeated call within one handshake a no-op, preserving the first
+   * arming's rollback point. Wire values below -1 are rejected — they can only
+   * come from a malformed peer and would collide with the -100 sentinel.
+   *
+   * @param generationId the generation id to set on the domain
+   */
+  protected void setDomainGenerationIdOnStart(long generationId)
+  {
+    if (generationId < -1)
+    {
+      return;
+    }
+    if (replicationServerDomain.changeGenerationIdIfUnchanged(
+        localGenerationId, generationId))
+    {
+      if (oldGenerationId == -100)
+      {
+        oldGenerationId = localGenerationId;
+      }
+      generationIdSetOnStart = generationId;
     }
   }
 

--
Gitblit v1.10.0