From 739ea68b7c5946d80e640fde010221098fd83011 Mon Sep 17 00:00:00 2001
From: Maxim Thomas <maxim.thomas@gmail.com>
Date: Sat, 19 Sep 2026 14:17:51 +0000
Subject: [PATCH] [#962] Flag an admin action when a bounded storage write gives up on a configuration change (#994)

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java |   28 +++++++++++++++++++++++-----
 1 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
index 86ddbea..055fdd6 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
@@ -973,10 +973,6 @@
         ccr.addMessage(NOTE_INDEX_ADD_REQUIRES_REBUILD.get(addedIndex));
       }
 
-      config = newConfiguration;
-      indexingOptions = newIndexingOptions;
-      indexIdToIndexes = Collections.unmodifiableMap(newIndexIdToIndexes);
-
       // We get exclusive lock to ensure that no query is actually using the indexes that will be deleted.
       entryContainer.lock();
       try
@@ -992,6 +988,17 @@
             }
           }
         });
+        // Published once the deletion has committed, not before it: a write the storage gives up on
+        // leaves the trees of the removed indexes behind, and a map which no longer names them is a
+        // map through which nothing maintains them and nothing deletes them. The lock has drained
+        // every operation which enters through shared access, so the window in which the map names
+        // trees the write has just deleted lies inside it and no search sees it; published after the
+        // write rather than from within it, so that an operation the storage replays publishes once,
+        // from the attempt which committed. The added indexes are named only at the end of that
+        // window rather than before the deletion, which costs nothing: the write above has just
+        // asked for them to be rebuilt, so nothing may read them until it has been.
+        indexingOptions = newIndexingOptions;
+        indexIdToIndexes = Collections.unmodifiableMap(newIndexIdToIndexes);
       }
       finally
       {
@@ -1020,11 +1027,22 @@
       {
         updatedIndex.setIndexEntryLimit(newConfiguration.getIndexEntryLimit());
       }
+      // Published last: the entry limit this configuration declares reaches the indexes which stay
+      // only once the write which untrusts them has committed, so a configuration published before
+      // that would declare a limit those indexes do not hold yet.
+      config = newConfiguration;
     }
     catch (Exception e)
     {
+      // Logged as well as reported, for the reason given in the index delete listener of
+      // EntryContainer: what this index holds and what its configuration declares may no longer
+      // agree long after the session which asked for the change has ended.
+      final LocalizableMessage message = ERR_CONFIG_INDEX_CHANGE_FAILED.get(getAttributeType().getNameOrOID(),
+          entryContainer.getBaseDN(), StaticUtils.stackTraceToSingleLineString(e));
+      logger.error(message);
       ccr.setResultCode(DirectoryServer.getCoreConfigManager().getServerErrorResultCode());
-      ccr.addMessage(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(e)));
+      ccr.setAdminActionRequired(true);
+      ccr.addMessage(message);
     }
 
     return ccr;

--
Gitblit v1.10.0