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