From add86d3c7c047215f886c51e2b29a4c7f1e86c0c Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 03 Sep 2026 08:21:14 +0000
Subject: [PATCH] [#877] Bound a statement of the JDBC backend by the class of the work it belongs to (#882)
---
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ShardedCounter.java | 55 +++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 49 insertions(+), 6 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ShardedCounter.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ShardedCounter.java
index efde5df..9e5769d 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ShardedCounter.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ShardedCounter.java
@@ -12,6 +12,7 @@
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2015 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.backends.pluggable;
@@ -72,16 +73,30 @@
super(name);
}
- SequentialCursor<ByteString, Void> openCursor(ReadableTransaction txn)
+ /**
+ * Walks this counter whole, which {@code verify-index} does and no client operation does: there
+ * is no overload of this method that would take the bound of an operation by accident. Reading a
+ * single counter is another matter - {@link #getCount} opens a cursor of its own, and one of a
+ * client operation unless the caller says otherwise.
+ *
+ * @see ReadableTransaction#openBulkCursor(TreeName)
+ */
+ SequentialCursor<ByteString, Void> openBulkCursor(ReadableTransaction txn)
+ {
+ return uniqueKeys(txn.openBulkCursor(getName()));
+ }
+
+ private SequentialCursor<ByteString, Void> uniqueKeys(Cursor<ByteString, ByteString> cursor)
{
return new UniqueKeysCursor<>(transformKeysAndValues(
- txn.openCursor(getName()), TO_KEY,
+ cursor, TO_KEY,
CursorTransformer.<ByteString, ByteString, Void> constant(null)));
}
- private Cursor<ByteString, Long> openCursor0(ReadableTransaction txn)
+ private Cursor<ByteString, Long> openCursor0(ReadableTransaction txn, boolean partOfAWholeTreeWalk)
{
- return transformKeysAndValues(txn.openCursor(getName()), TO_KEY, TO_LONG);
+ return transformKeysAndValues(
+ partOfAWholeTreeWalk ? txn.openBulkCursor(getName()) : txn.openCursor(getName()), TO_KEY, TO_LONG);
}
void addCount(final WriteableTransaction txn, ByteSequence key, final long delta)
@@ -107,8 +122,35 @@
long getCount(final ReadableTransaction txn, ByteSequence key)
{
+ return getCount(txn, key, false);
+ }
+
+ /**
+ * The same read, told which kind of work it is part of. A client operation reads a counter of its
+ * own and takes the bound of one - {@code numSubordinates} of a search
+ * ({@code EntryContainer.getNumberOfChildren}), the entry count of a VLV index a search is paging
+ * through ({@code VLVIndex.getEntryCount}) - while {@code verify-index} reads one per DN of the
+ * tree it is walking, with nobody waiting on it: bounding those as client operations is what #877
+ * exists to stop, and on the JDBC backend it aborted a verify of a backend large enough.
+ * <p>
+ * The third caller is {@code ID2ChildrenCount.getTotalCount}, which is read both ways and is told
+ * which it is by its own caller: a verify sizes its progress report with it, {@code cn=monitor}
+ * and the searches of {@code GroupManager} and {@code SubentryManager} read it for a client. A
+ * delete and a modify DN reach neither form - they go through {@link #removeCount}, which is a
+ * client operation by construction.
+ *
+ * @param txn storage transaction
+ * @param key the counter to read
+ * @param partOfAWholeTreeWalk whether this read belongs to a walk of a whole tree rather than to
+ * a client operation
+ * @return Value of the counter. 0 if no counter is associated yet.
+ * @see ReadableTransaction#openBulkCursor(TreeName)
+ */
+ long getCount(final ReadableTransaction txn, ByteSequence key, boolean partOfAWholeTreeWalk)
+ {
long counterValue = 0;
- try (final SequentialCursor<ByteString, Long> cursor = new ShardCursor(openCursor0(txn), key))
+ try (final SequentialCursor<ByteString, Long> cursor =
+ new ShardCursor(openCursor0(txn, partOfAWholeTreeWalk), key))
{
while (cursor.next())
{
@@ -121,7 +163,8 @@
long removeCount(final WriteableTransaction txn, ByteSequence key)
{
long counterValue = 0;
- try (final SequentialCursor<ByteString, Long> cursor = new ShardCursor(openCursor0(txn), key))
+ // a removal is always a client operation: an entry is being deleted or moved
+ try (final SequentialCursor<ByteString, Long> cursor = new ShardCursor(openCursor0(txn, false), key))
{
// Iterate over and remove all the thread local shards
while (cursor.next())
--
Gitblit v1.10.0