From 9d7725370d82557e35ed802a6a7bc206c44b993b Mon Sep 17 00:00:00 2001
From: sin <sin@localhost>
Date: Tue, 10 Jul 2007 16:50:42 +0000
Subject: [PATCH] Bug# 1462:server deadlock on subtree delete Fix: Introduce a batch parameter to create the additional transactions for a new batch.

---
 opends/resource/schema/02-config.ldif                                        |    6 +
 opends/src/server/org/opends/server/backends/jeb/EntryContainer.java         |   94 +++++++++++++++++++++++++------
 opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml |   27 +++++++++
 opends/resource/config/config.ldif                                           |    1 
 4 files changed, 109 insertions(+), 19 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index e4dbc82..3fd0524 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -115,6 +115,7 @@
 ds-cfg-backend-mode: 700
 ds-cfg-backend-index-entry-limit: 4000
 ds-cfg-backend-subtree-delete-size-limit: 100000
+ds-cfg-backend-subtree-delete-batch-size: 5000
 ds-cfg-backend-preload-time-limit: 0 seconds
 ds-cfg-backend-import-temp-directory: importTmp
 ds-cfg-backend-import-buffer-size: 256 megabytes
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index e3fd83d..648087f 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -1279,6 +1279,10 @@
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.380 NAME 'ds-sync-conflict'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
   USAGE directoryOperation X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.381
+  NAME 'ds-cfg-backend-subtree-delete-batch-size'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
+  X-ORIGIN 'OpenDS Directory Server' )
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.382
   NAME 'ds-cfg-index-substring-length'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
@@ -1504,7 +1508,7 @@
   ds-cfg-backend-import-queue-size $ ds-cfg-backend-import-thread-count $
   ds-cfg-backend-entries-compressed $ ds-cfg-backend-deadlock-retry-limit $
   ds-cfg-backend-import-pass-size $ ds-cfg-backend-mode $
-  ds-cfg-database-cache-percent $
+  ds-cfg-database-cache-percent $ ds-cfg-backend-subtree-delete-batch-size $
   ds-cfg-database-cache-size $ ds-cfg-database-txn-no-sync $
   ds-cfg-database-txn-write-no-sync $ ds-cfg-database-run-cleaner $
   ds-cfg-database-cleaner-min-utilization $ ds-cfg-database-evictor-lru-only $
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml
index e6250ad..bce44f4 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml
+++ b/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml
@@ -469,6 +469,33 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+  <adm:property name="backend-subtree-delete-batch-size"
+    mandatory="false"
+    multi-valued="false">
+    <adm:synopsis>
+      Specifies the maximum number of entries that may be deleted from the
+      backend when using the subtree delete control within a single transaction.
+    </adm:synopsis>
+    <adm:description>
+      If a subtree delete operation targets a subtree with more than this
+      number of entries, then additional transactions are used to remove the
+      remaining entries in that subtree.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>5000</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:integer  lower-limit="0" />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.381</ldap:oid>
+        <ldap:name>ds-cfg-backend-subtree-delete-batch-size</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
   <adm:property name="database-cache-percent"
     mandatory="false"
     multi-valued="false">
diff --git a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java b/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
index d01b39e..47a80a4 100644
--- a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
+++ b/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -182,6 +182,8 @@
 
   private int subtreeDeleteSizeLimit;
 
+  private int subtreeDeleteBatchSize;
+
   private int indexEntryLimit;
 
   /**
@@ -215,6 +217,7 @@
     this.rootContainer = rootContainer;
     this.deadlockRetryLimit = config.getBackendDeadlockRetryLimit();
     this.subtreeDeleteSizeLimit = config.getBackendSubtreeDeleteSizeLimit();
+    this.subtreeDeleteBatchSize = config.getBackendSubtreeDeleteBatchSize();
     this.indexEntryLimit = config.getBackendIndexEntryLimit();
 
     // Instantiate the attribute indexes.
@@ -1678,24 +1681,33 @@
   {
     DeleteEntryTransaction operation =
         new DeleteEntryTransaction(entryDN, deleteOperation);
-
-    invokeTransactedOperation(operation);
-
-    if (operation.adminSizeLimitExceeded())
+    boolean isComplete = false;
+    while(!isComplete)
     {
-      String message = getMessage(MSGID_JEB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED,
+      invokeTransactedOperation(operation);
+
+      if (operation.adminSizeLimitExceeded())
+      {
+        String message = getMessage(
+                MSGID_JEB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED,
                                   operation.getDeletedEntryCount());
-      throw new DirectoryException(
+        throw new DirectoryException(
           ResultCode.ADMIN_LIMIT_EXCEEDED,
           message,
           MSGID_JEB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED);
-    }
-
-    String message = getMessage(MSGID_JEB_DELETED_ENTRY_COUNT,
+      }
+      if(operation.batchSizeExceeded())
+      {
+        operation.resetBatchSize();
+        continue;
+      }
+      isComplete = true;
+      String message = getMessage(MSGID_JEB_DELETED_ENTRY_COUNT,
                                 operation.getDeletedEntryCount());
-    StringBuilder errorMessage = new StringBuilder();
-    errorMessage.append(message);
-    deleteOperation.setErrorMessage(errorMessage);
+      StringBuilder errorMessage = new StringBuilder();
+      errorMessage.append(message);
+      deleteOperation.setErrorMessage(errorMessage);
+    }
   }
 
   /**
@@ -1930,7 +1942,7 @@
     private DeleteOperation deleteOperation;
 
     /**
-     * A list of the DNs of all entries deleted by this operation.
+     * A list of the DNs of all entries deleted by this operation in a batch.
      * The subtree delete control can cause multiple entries to be deleted.
      */
     private ArrayList<DN> deletedDNList;
@@ -1941,6 +1953,18 @@
      */
     private boolean adminSizeLimitExceeded = false;
 
+
+    /**
+     * Indicates whether the subtree delete batch size has been exceeded.
+     */
+    private boolean batchSizeExceeded = false;
+
+
+    /**
+     * Indicates the count of deleted DNs in the Delete Operation.
+     */
+    private int countDeletedDN;
+
     /**
      * Create a new Delete Entry Transaction.
      * @param entryDN The entry or subtree to be deleted.
@@ -1963,12 +1987,31 @@
     }
 
     /**
+     * Determine whether the subtree delete batch size has been exceeded.
+     * @return true if the batch size has been exceeded.
+     */
+    public boolean batchSizeExceeded()
+    {
+      return batchSizeExceeded;
+    }
+
+    /**
+     * Resets the batchSizeExceeded parameter to reuse the object
+     * for multiple batches.
+     */
+    public void resetBatchSize()
+    {
+      batchSizeExceeded=false;
+      deletedDNList.clear();
+    }
+
+    /**
      * Get the number of entries deleted during the operation.
      * @return The number of entries deleted.
      */
     public int getDeletedEntryCount()
     {
-      return deletedDNList.size();
+      return countDeletedDN;
     }
 
     /**
@@ -1999,6 +2042,7 @@
 
       // Determine whether this is a subtree delete.
       int adminSizeLimit = subtreeDeleteSizeLimit;
+      int deleteBatchSize = subtreeDeleteBatchSize;
       boolean isSubtreeDelete = false;
       List<Control> controls = deleteOperation.getRequestControls();
       if (controls != null)
@@ -2082,12 +2126,19 @@
           }
 
           // Enforce any subtree delete size limit.
-          if (adminSizeLimit > 0 && deletedDNList.size() >= adminSizeLimit)
+          if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit)
           {
             adminSizeLimitExceeded = true;
             break;
           }
 
+          // Enforce any subtree delete batch size.
+          if (deleteBatchSize > 0 && deletedDNList.size() >= deleteBatchSize)
+          {
+            batchSizeExceeded = true;
+            break;
+          }
+
           /*
            * Delete this entry which by now must be a leaf because
            * we have been deleting from the bottom of the tree upwards.
@@ -2098,7 +2149,7 @@
                      txn, subordinateDN, entryID);
 
           deletedDNList.add(subordinateDN);
-
+          countDeletedDN++;
           status = cursor.getPrev(key, data, LockMode.DEFAULT);
         }
       }
@@ -2109,13 +2160,18 @@
 
       // Finally delete the target entry as it was not included
       // in the dn2id iteration.
-      if (!adminSizeLimitExceeded)
+      if (!adminSizeLimitExceeded && !batchSizeExceeded)
       {
         // Enforce any subtree delete size limit.
-        if (adminSizeLimit > 0 && deletedDNList.size() >= adminSizeLimit)
+        if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit)
         {
           adminSizeLimitExceeded = true;
         }
+        else if (deleteBatchSize > 0 &&
+                                      deletedDNList.size() >= deleteBatchSize)
+        {
+          batchSizeExceeded = true;
+        }
         else
         {
           boolean manageDsaIT;
@@ -2134,6 +2190,7 @@
           deleteTarget(manageDsaIT, id2cBuffered, id2sBuffered, txn, entryDN);
 
           deletedDNList.add(entryDN);
+          countDeletedDN++;
         }
       }
 
@@ -3762,6 +3819,7 @@
     this.config = cfg;
     this.deadlockRetryLimit = config.getBackendDeadlockRetryLimit();
     this.subtreeDeleteSizeLimit = config.getBackendSubtreeDeleteSizeLimit();
+    this.subtreeDeleteBatchSize = config.getBackendSubtreeDeleteBatchSize();
     this.indexEntryLimit = config.getBackendIndexEntryLimit();
     return new ConfigChangeResult(ResultCode.SUCCESS,
                                   adminActionRequired, messages);

--
Gitblit v1.10.0