From c2f7a14064a1bcd170eb25cf8ff7ec57df132dbd Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 22 Sep 2026 10:29:51 +0000
Subject: [PATCH] [#1040] Leave the session to a total update when a configuration change asks for a restart (#1042)
---
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java | 54 ++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java
index 6ec3909..1467c35 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -6158,8 +6158,9 @@
changeConfig(configuration);
// Read assured + fractional configuration and each time reconnect if needed. A
- // domain which owns its session gets none of those reconnections.
- final boolean allowReconnection = !ownsItsSession();
+ // session which has an owner - the domain itself, or a total update into this
+ // replica - gets none of those reconnections.
+ final boolean allowReconnection = !sessionHasAnOwner();
readAssuredConfig(configuration, allowReconnection);
readFractionalConfig(configuration, allowReconnection);
solveConflictFlag = isSolveConflict(configuration);
@@ -6207,9 +6208,10 @@
}
/**
- * Whether the session of this domain has an owner other than the replay thread which
- * would restart it after a failed replay: the domain itself, when it is shutting down or
- * disabled ({@link #ownsItsSession()}), or a total update into this replica.
+ * Whether the session of this domain has an owner other than the thread which would
+ * restart it - a replay thread after a failed replay, or a configuration change for what
+ * it carries: the domain itself, when it is shutting down or disabled
+ * ({@link #ownsItsSession()}), or a total update into this replica.
* <p>
* The total update owns the session from the moment it is asked for, not from the
* moment its entries stream: the {@code InitializeTargetMsg} which answers the request
@@ -6223,6 +6225,15 @@
* Listed, it holds the ServerState back as well: a commit moves the state no further than
* the oldest uncommitted change, so the state in memory, and the one persisted from it,
* stop at the change until that restart.
+ * <p>
+ * A configuration change is refused while a total update runs - by the listener of the
+ * domain entry and by the one of its external changelog entry - so what reaches the
+ * domain all the same is a change accepted before the total update was asked for. The
+ * restart it asks for is refused on this predicate too, and the session the import
+ * starts when it ends reads the configuration stored meanwhile. Made through the server
+ * configuration, that restart would not end: it waits for the listener thread, which is
+ * the import, and the import waits for the lock of the configuration the change holds,
+ * to enable the backend back once the stream it was reading ends.
*/
private boolean sessionHasAnOwner()
{
@@ -6234,21 +6245,30 @@
{
synchronized (serviceStateLock)
{
- if (ownsItsSession())
+ if (sessionHasAnOwner())
{
/*
- * The domain is going away or is being imported into: a restart here would bring
- * a session, and the listener thread which goes with it, back up on a domain
- * whose ServerState is gone from memory. The session started when the domain is
- * enabled again reads the configuration this restart was asked for.
+ * The domain is going away or is disabled: a restart here would bring a session,
+ * and the listener thread which goes with it, back up on a domain whose
+ * ServerState is gone from memory. The session started when the domain is enabled
+ * again reads the configuration this restart was asked for.
+ *
+ * Or a total update into this replica is reading the session: the import streams
+ * over it, on the listener thread a restart would stop and wait for. Stopped, the
+ * broker ends the stream on the entries which had arrived; waited for, the listener
+ * thread ends the import and enables the backend back through the server
+ * configuration - whose lock a change made through it holds while it waits. The
+ * import starts the next session itself when it ends, from the state it loaded,
+ * and that session reads the configuration this restart was asked for.
*
* Recorded rather than passed over in silence: the configuration a restart was
* asked for is stored, and it is the session which is not brought up on it, so a
* change which reports plain success would have the administrator believe the
* domain is running on it already. A domain disabled for a total update comes up
- * on it when the total update ends; one which stays disabled - enable() gives up
- * when the data state it reads cannot be loaded, and nothing calls it again -
- * never does, and that is what the administrator is told to act on.
+ * on it when the total update ends, and so does one being imported into; one which
+ * stays disabled - enable() gives up when the data state it reads cannot be
+ * loaded, and nothing calls it again - never does, and that is what the
+ * administrator is told to act on.
*/
onSessionRestartSuppressed();
return;
@@ -6299,7 +6319,13 @@
public boolean isConfigurationChangeAcceptable(
ReplicationDomainCfg configuration, List<LocalizableMessage> unacceptableReasons)
{
- // Check that a import/export is not in progress
+ /*
+ * Check that a import/export is not in progress. The listener of the external
+ * changelog entry of this domain refuses its change for the same reason: what either
+ * change restarts the session for, an import into this replica is reading over that
+ * session. One which starts between this check and the change being applied meets
+ * the restart guard instead (see sessionHasAnOwner()).
+ */
if (ieRunning())
{
unacceptableReasons.add(
--
Gitblit v1.10.0