From 0b9c0f63f5c79e0a5d955011453bf415cb27e184 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 20 Aug 2026 09:17:16 +0000
Subject: [PATCH] Seek the primary key in the SQL Server upsert and retry a transaction conflict (#867)
---
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java | 83 ++++++++++++++++++++++++++++++-----------
1 files changed, 60 insertions(+), 23 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
index c258484..e9188ab 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
@@ -38,6 +38,8 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -199,21 +201,28 @@
{
final CryptoSuite cryptoSuite = newCryptoSuite(cfg.isConfidentialityEnabled());
final AttributeIndex index = newAttributeIndex(cfg, cryptoSuite);
+ final AtomicBoolean trusted = new AtomicBoolean();
storage.write(new WriteOperation()
{
@Override
public void run(WriteableTransaction txn) throws Exception
{
+ // The write may be replayed by the storage, and open() registers this index as a change listener of
+ // its configuration. close() removes every registration made for this index, so closing first leaves
+ // one listener behind rather than one per attempt; it is a no-op on the first attempt.
+ index.close();
index.open(txn, true);
- if (!index.isTrusted())
- {
- ccr.setAdminActionRequired(true);
- ccr.addMessage(NOTE_INDEX_ADD_REQUIRES_REBUILD.get(cfg.getAttribute().getNameOrOID()));
- }
+ trusted.set(index.isTrusted());
attrIndexMap.put(cfg.getAttribute(), index);
attrCryptoMap.put(cfg.getAttribute(), cryptoSuite);
}
});
+ if (!trusted.get())
+ {
+ // Reported outside the write, since a replayed attempt would otherwise repeat the message.
+ ccr.setAdminActionRequired(true);
+ ccr.addMessage(NOTE_INDEX_ADD_REQUIRES_REBUILD.get(cfg.getAttribute().getNameOrOID()));
+ }
}
catch(Exception e)
{
@@ -239,15 +248,23 @@
EntryContainer.this.lock();
try
{
- storage.write(new WriteOperation()
+ // The write may be replayed by the storage, so the maps are updated outside of it: left inside, the second
+ // attempt would find nothing to delete and commit an empty transaction, reporting success for work that
+ // did not happen. The index may already be gone, since applyConfigurationAdd can fail after the config
+ // entry was persisted but before the index reached the map.
+ final AttributeIndex index = attrIndexMap.remove(cfg.getAttribute());
+ attrCryptoMap.remove(cfg.getAttribute());
+ if (index != null)
{
- @Override
- public void run(WriteableTransaction txn) throws Exception
+ storage.write(new WriteOperation()
{
- attrIndexMap.remove(cfg.getAttribute()).closeAndDelete(txn);
- attrCryptoMap.remove(cfg.getAttribute());
- }
- });
+ @Override
+ public void run(WriteableTransaction txn) throws Exception
+ {
+ index.closeAndDelete(txn);
+ }
+ });
+ }
}
catch (Exception de)
{
@@ -283,21 +300,35 @@
final ConfigChangeResult ccr = new ConfigChangeResult();
try
{
+ final AtomicReference<VLVIndex> built = new AtomicReference<>();
+ final AtomicBoolean trusted = new AtomicBoolean();
storage.write(new WriteOperation()
{
@Override
public void run(WriteableTransaction txn) throws Exception
{
- VLVIndex vlvIndex = new VLVIndex(cfg, state, storage, EntryContainer.this, txn);
- vlvIndex.open(txn, true);
- if(!vlvIndex.isTrusted())
+ // The write may be replayed by the storage, and the VLVIndex constructor registers the new instance as
+ // a change listener of its configuration. Only the last instance reaches the map, so the one built by
+ // the previous attempt is closed here, which deregisters it: left registered it would never be closed
+ // again, and every later VLV configuration change would be applied once per attempt.
+ final VLVIndex previous = built.getAndSet(null);
+ if (previous != null)
{
- ccr.setAdminActionRequired(true);
- ccr.addMessage(NOTE_INDEX_ADD_REQUIRES_REBUILD.get(cfg.getName()));
+ previous.close();
}
+ VLVIndex vlvIndex = new VLVIndex(cfg, state, storage, EntryContainer.this, txn);
+ built.set(vlvIndex);
+ vlvIndex.open(txn, true);
+ trusted.set(vlvIndex.isTrusted());
vlvIndexMap.put(cfg.getName().toLowerCase(), vlvIndex);
}
});
+ if (!trusted.get())
+ {
+ // Reported outside the write, since a replayed attempt would otherwise repeat the message.
+ ccr.setAdminActionRequired(true);
+ ccr.addMessage(NOTE_INDEX_ADD_REQUIRES_REBUILD.get(cfg.getName()));
+ }
}
catch(Exception e)
{
@@ -321,14 +352,20 @@
EntryContainer.this.lock();
try
{
- storage.write(new WriteOperation()
+ // Removed outside the write for the reason given in the index delete listener above: the write may be
+ // replayed, and a replay must still have the deletion to perform.
+ final VLVIndex vlvIndex = vlvIndexMap.remove(cfg.getName().toLowerCase());
+ if (vlvIndex != null)
{
- @Override
- public void run(WriteableTransaction txn) throws Exception
+ storage.write(new WriteOperation()
{
- vlvIndexMap.remove(cfg.getName().toLowerCase()).closeAndDelete(txn);
- }
- });
+ @Override
+ public void run(WriteableTransaction txn) throws Exception
+ {
+ vlvIndex.closeAndDelete(txn);
+ }
+ });
+ }
}
catch (Exception e)
{
--
Gitblit v1.10.0