From d9b412170996aa617f6303104b7fc5ea0892de0a Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Thu, 02 Jan 2014 15:47:14 +0000
Subject: [PATCH] OPENDJ-1090 ECL changenumbers get reset after a purge and server restart

---
 opendj-sdk/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDB.java                             |   41 +++++++++++++++++++++++++++++++----------
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDBTest.java |   15 +++++++++++++--
 2 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDB.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDB.java
index 166accf..8ae8161 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDB.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDB.java
@@ -22,7 +22,7 @@
  *
  *
  *      Copyright 2009-2010 Sun Microsystems, Inc.
- *      Portions Copyright 2011-2013 ForgeRock AS
+ *      Portions Copyright 2011-2014 ForgeRock AS
  */
 package org.opends.server.replication.server.changelog.je;
 
@@ -71,17 +71,25 @@
   private static int NO_KEY = 0;
 
   private DraftCNDB db;
-  /**
-   * FIXME Is this field that useful? {@link #getOldestChangeNumber()} does not
-   * even use it!
-   */
+  /** FIXME What is this field used for? */
   private volatile long oldestChangeNumber = NO_KEY;
   /**
-   * FIXME Is this field that useful? {@link #getNewestChangeNumber()} does not
-   * even use it!
+   * The newest changenumber stored in the DB. It is used to avoid trimming the
+   * record with the newest changenumber. The newest record in the changenumber
+   * index DB is used to persist the {@link #lastGeneratedChangeNumber} which is
+   * then retrieved on server startup.
    */
   private volatile long newestChangeNumber = NO_KEY;
-  /** The last generated value for the change number. */
+  /**
+   * The last generated value for the change number. It is kept separate from
+   * the {@link #newestChangeNumber} because there is an opportunity for a race
+   * condition between:
+   * <ol>
+   * <li>this atomic long being incremented for a new record ('recordB')</li>
+   * <li>the current newest record ('recordA') being trimmed from the DB</li>
+   * <li>'recordB' failing to be inserted in the DB</li>
+   * </ol>
+   */
   private final AtomicLong lastGeneratedChangeNumber;
   private DbMonitorProvider dbMonitor = new DbMonitorProvider();
   private final AtomicBoolean shutdown = new AtomicBoolean(false);
@@ -124,10 +132,10 @@
     final ChangeNumberIndexRecord oldestRecord = db.readFirstRecord();
     final ChangeNumberIndexRecord newestRecord = db.readLastRecord();
     oldestChangeNumber = getChangeNumber(oldestRecord);
-    newestChangeNumber = getChangeNumber(newestRecord);
+    final long newestCN = getChangeNumber(newestRecord);
+    newestChangeNumber = newestCN;
     // initialization of the lastGeneratedChangeNumber from the DB content
     // if DB is empty => last record does not exist => default to 0
-    long newestCN = (newestRecord != null) ? newestRecord.getChangeNumber() : 0;
     lastGeneratedChangeNumber = new AtomicLong(newestCN);
 
     // Monitoring registration
@@ -165,6 +173,7 @@
         new ChangeNumberIndexRecord(changeNumber, record.getPreviousCookie(),
             record.getBaseDN(), record.getCSN());
     db.addRecord(newRecord);
+    newestChangeNumber = changeNumber;
 
     if (debugEnabled())
       TRACER.debugInfo("In JEChangeNumberIndexDB.add, added: " + newRecord);
@@ -383,6 +392,18 @@
           }
 
           final ChangeNumberIndexRecord record = cursor.currentRecord();
+          if (record.getChangeNumber() != oldestChangeNumber)
+          {
+            oldestChangeNumber = record.getChangeNumber();
+          }
+          if (record.getChangeNumber() == newestChangeNumber)
+          {
+            // do not trim the newest record to avoid having the last generated
+            // changenumber dropping back to 0 if the server restarts
+            cursor.close();
+            return;
+          }
+
           if (baseDNToClear != null && baseDNToClear.equals(record.getBaseDN()))
           {
             cursor.delete();
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDBTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDBTest.java
index 72f04ae..e1446e4 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDBTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/JEChangeNumberIndexDBTest.java
@@ -22,7 +22,7 @@
  *
  *
  *      Copyright 2009-2010 Sun Microsystems, Inc.
- *      Portions Copyright 2011-2013 ForgeRock AS
+ *      Portions Copyright 2011-2014 ForgeRock AS
  */
 package org.opends.server.replication.server.changelog.je;
 
@@ -219,9 +219,20 @@
       cursor = cnIndexDB.getCursorFrom(cn3);
       assertCursorReadsInOrder(cursor, cn3);
 
-      cnIndexDB.clear();
+      // check only the last record is left
+      cnIndexDB.clear(null);
+      final ChangeNumberIndexRecord oldest = cnIndexDB.getOldestRecord();
+      final ChangeNumberIndexRecord newest = cnIndexDB.getNewestRecord();
+      assertEquals(oldest.getChangeNumber(), 3);
+      assertEquals(oldest.getChangeNumber(), newest.getChangeNumber());
+      assertEquals(oldest.getPreviousCookie(), newest.getPreviousCookie());
+      assertEquals(oldest.getBaseDN(), newest.getBaseDN());
+      assertEquals(oldest.getCSN(), newest.getCSN());
+      assertEquals(cnIndexDB.count(), 1);
+      assertFalse(cnIndexDB.isEmpty());
 
       // Check the db is cleared.
+      cnIndexDB.clear();
       assertNull(cnIndexDB.getOldestRecord());
       assertNull(cnIndexDB.getNewestRecord());
       assertEquals(cnIndexDB.count(), 0);

--
Gitblit v1.10.0