From d6a5316c7055055f2da0310748dc5a46dc777144 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 19 Aug 2026 08:37:49 +0000
Subject: [PATCH] [#870] Stop replaying a rolled back read in the PersistIt backend (#871)

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java              |   43 ++++++++++++++++++++++---------------------
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOperation.java |    5 +++++
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java       |    7 +++++--
 3 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java
index a4e6f97..ea23395 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java
@@ -598,29 +598,30 @@
     @Override
     public <T> T read(ReadOperation<T> operation) throws Exception
     {
+      /*
+       * A rolled back read is not replayed. Unlike WriteOperation, a ReadOperation is not required to be
+       * idempotent, and four of them are not: ExportJob has written entries to its LDIF stream, whose writer is
+       * opened once so that a replay appends rather than truncates; VerifyJob has accumulated its counters in
+       * instance fields that no attempt resets; and the two reads of BackendStat have printed records and
+       * appended to a map owned by their caller. Replaying corrupts their result rather than repairing it, so
+       * the failure goes to the caller, as it does in the JE, Cassandra and JDBC backends.
+       */
       final Transaction txn = db.getTransaction();
-      for (;;)
+      txn.begin();
+      try
       {
-        txn.begin();
-        try
-        {
-          final T result = operation.run(this);
-          txn.commit(commitPolicy);
-          return result;
-        }
-        catch (final RollbackException e)
-        {
-          // retry
-        }
-        catch (final Exception e)
-        {
-          txn.rollback();
-          throw e;
-        }
-        finally
-        {
-          txn.end();
-        }
+        final T result = operation.run(this);
+        txn.commit(commitPolicy);
+        return result;
+      }
+      catch (final Exception e)
+      {
+        txn.rollback();
+        throw e;
+      }
+      finally
+      {
+        txn.end();
       }
     }
 
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOperation.java
index 5f2e58b..891eeb1 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOperation.java
@@ -12,6 +12,7 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2014-2015 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.backends.pluggable.spi;
 
@@ -26,6 +27,10 @@
 {
   /**
    * Executes a read operation, and returns the read value.
+   * <p>
+   * Implementations need not be idempotent: unlike {@link WriteOperation}, a read operation is never replayed by
+   * {@link Storage#read(ReadOperation)}, so it is free to print, to write to a stream, or to accumulate state
+   * outside of the transaction.
    *
    * @param txn
    *          the read transaction where to execute the read operation
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java
index 179c073..cf04069 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java
@@ -12,6 +12,7 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2014-2015 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.backends.pluggable.spi;
 
@@ -56,8 +57,10 @@
   void open(AccessMode accessMode) throws Exception;
 
   /**
-   * Executes a read operation. In case of a read operation rollback, implementations must ensure
-   * the read operation is retried until it succeeds.
+   * Executes a read operation. In case of a read operation rollback, implementations must propagate the failure
+   * to the caller rather than replay the operation: unlike {@link WriteOperation}, a {@link ReadOperation} is not
+   * required to be idempotent, and several of them are not - they write to a stream, print, or accumulate state
+   * that a second attempt would double.
    *
    * @param <T>
    *          type of the value returned

--
Gitblit v1.10.0