From 1af0a1247d3e2d503a53723dd1d844c90c43ccc6 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Sat, 19 Sep 2026 08:56:47 +0000
Subject: [PATCH] [#993] Register an entry container's configuration listeners only once it has opened (#999)

---
 opendj-server-legacy/src/test/java/org/opends/server/backends/pdb/PDBStorageTest.java |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 120 insertions(+), 0 deletions(-)

diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pdb/PDBStorageTest.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pdb/PDBStorageTest.java
index f633849..9bcbbb2 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/pdb/PDBStorageTest.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pdb/PDBStorageTest.java
@@ -22,6 +22,7 @@
 import static org.opends.server.util.StaticUtils.*;
 import static org.forgerock.opendj.ldap.ByteString.*;
 
+import java.io.File;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.forgerock.opendj.config.server.ConfigException;
@@ -32,10 +33,12 @@
 import org.opends.server.backends.pluggable.spi.AccessMode;
 import org.opends.server.backends.pluggable.spi.ReadOperation;
 import org.opends.server.backends.pluggable.spi.ReadableTransaction;
+import org.opends.server.backends.pluggable.spi.StorageInUseException;
 import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
 import org.opends.server.backends.pluggable.spi.TreeName;
 import org.opends.server.backends.pluggable.spi.WriteOperation;
 import org.opends.server.backends.pluggable.spi.WriteableTransaction;
+import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.MemoryQuota;
 import org.opends.server.core.ServerContext;
 import org.opends.server.extensions.DiskSpaceMonitor;
@@ -422,6 +425,123 @@
     assertThat(grown).as("the last attempts still sleep within the first attempt's bound").isGreaterThan(500);
   }
 
+  /**
+   * An open which fails gives back what it took before it failed: the memory it reserved for the
+   * cache, and the listener the constructor registered on the backend configuration. Nothing else
+   * will - a root container does not close a storage which did not open - and a backend whose
+   * volume another storage holds is enabled again and again, each attempt draining one cache size.
+   */
+  @Test
+  public void aStorageWhoseOpenFailedGivesBackWhatItTook() throws Exception
+  {
+    final PDBBackendCfg cfg = createBackendCfg();
+    // Over the volume the storage of setUp() holds: what a second attempt to enable the backend meets.
+    final PDBStorage second = new PDBStorage(cfg, serverContext);
+    final MemoryQuota quota = serverContext.getMemoryQuota();
+    final long availableBefore = quota.getAvailableMemory();
+    try
+    {
+      second.open(AccessMode.READ_WRITE);
+      fail("the storage was expected not to open over a volume another storage holds");
+    }
+    catch (StorageInUseException expected)
+    {
+      // What the lock on the volume file does.
+    }
+
+    assertThat(quota.getAvailableMemory()).isEqualTo(availableBefore);
+    verify(cfg).removePDBChangeListener(second);
+  }
+
+  /**
+   * A storage whose open failed has given everything back already, so closing it afterwards takes
+   * nothing more - {@code BackendImpl.importLDIF} closes the storage of its root container however
+   * the import ended - and does not fail on what the open never got to.
+   */
+  @Test
+  public void closingAStorageWhoseOpenFailedTakesNothingMore() throws Exception
+  {
+    final PDBStorage second = new PDBStorage(createBackendCfg(), serverContext);
+    final MemoryQuota quota = serverContext.getMemoryQuota();
+    final long availableBefore = quota.getAvailableMemory();
+    try
+    {
+      second.open(AccessMode.READ_WRITE);
+      fail("the storage was expected not to open over a volume another storage holds");
+    }
+    catch (StorageInUseException expected)
+    {
+      // What the lock on the volume file does.
+    }
+
+    second.close();
+
+    assertThat(quota.getAvailableMemory()).isEqualTo(availableBefore);
+  }
+
+  /**
+   * A storage which is open refuses to open again before it takes anything, and what it holds is
+   * left as it is: the refusal is a guard against a programming error, not a failed open with
+   * something to give back.
+   */
+  @Test
+  public void openingAnOpenStorageIsRefusedAndTakesNothing() throws Exception
+  {
+    createTree();
+    final MemoryQuota quota = serverContext.getMemoryQuota();
+    final long availableBefore = quota.getAvailableMemory();
+    try
+    {
+      storage.open(AccessMode.READ_WRITE);
+      fail("a storage which is open was expected to refuse to open again");
+    }
+    catch (IllegalStateException expected)
+    {
+      // The guard against a double open.
+    }
+
+    assertThat(quota.getAvailableMemory()).isEqualTo(availableBefore);
+    // Still open: a read reaches the database.
+    assertThat(read("missing")).isNull();
+  }
+
+  /**
+   * An open which fails once the database is open gives the database back with the rest: the
+   * volume, or no later open of the backend can take it, and the monitor the open registered. The
+   * disk monitor is the one thing past the database open that a test can refuse.
+   */
+  @Test
+  public void aStorageWhoseOpenFailedAfterItsDatabaseOpenedGivesTheDatabaseBack() throws Exception
+  {
+    // The volume of setUp() is given up first: held, it fails the open before the database is built.
+    closeAndRemove(storage);
+    final DiskSpaceMonitor refusing = mock(DiskSpaceMonitor.class);
+    doThrow(new IllegalStateException("the directory cannot be monitored"))
+        .when(refusing).registerMonitoredDirectory(anyString(), any(File.class), anyLong(), anyLong(), any());
+    when(serverContext.getDiskSpaceMonitor()).thenReturn(refusing);
+    final PDBBackendCfg cfg = createBackendCfg();
+    final PDBStorage second = new PDBStorage(cfg, serverContext);
+    final MemoryQuota quota = serverContext.getMemoryQuota();
+    final long availableBefore = quota.getAvailableMemory();
+    try
+    {
+      second.open(AccessMode.READ_WRITE);
+      fail("the storage was expected not to open when its directory cannot be monitored");
+    }
+    catch (IllegalStateException expected)
+    {
+      // What the failure past the database open does.
+    }
+
+    assertThat(quota.getAvailableMemory()).isEqualTo(availableBefore);
+    verify(cfg).removePDBChangeListener(second);
+    assertThat(DirectoryServer.getMonitorProviders()).doesNotContainKey("pdbstoragetest pdb database");
+    // The volume was given back: a storage over the same directory opens.
+    when(serverContext.getDiskSpaceMonitor()).thenReturn(mock(DiskSpaceMonitor.class));
+    storage = new PDBStorage(createBackendCfg(), serverContext);
+    storage.open(AccessMode.READ_WRITE);
+  }
+
   private void createTree() throws Exception
   {
     storage.write(new WriteOperation()

--
Gitblit v1.10.0