From 18b64d8efe7da2095468d1937a379ba7d7083d27 Mon Sep 17 00:00:00 2001
From: Ludovic Poitou <ludovic.poitou@forgerock.com>
Date: Wed, 22 Dec 2010 12:06:31 +0000
Subject: [PATCH] Ensure that correct Grizzly MemoryManager is used for SASL and ASN1 filters.

---
 sdk/src/com/sun/opends/sdk/tools/ModRate.java                                         |   41 +-
 sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java                           |   12 
 sdk/src/com/sun/opends/sdk/tools/PerformanceRunner.java                               |  321 ++++++++++++++++----
 sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java                                 |    6 
 sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java                                 |    6 
 sdk/examples/org/opends/sdk/examples/server/store/Main.java                           |    6 
 sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java                                       |    8 
 sdk/src/com/sun/opends/sdk/tools/AuthRate.java                                        |  188 ++++++-----
 sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java                           |   12 
 sdk/src/com/sun/opends/sdk/tools/SearchRate.java                                      |   48 +-
 sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java                                 |    9 
 sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java |    4 
 sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java |    4 
 sdk/examples/org/opends/sdk/examples/server/proxy/Main.java                           |    6 
 sdk/src/com/sun/opends/sdk/tools/PerfToolTCPNIOTransportFactory.java                  |    5 
 sdk/src/org/opends/sdk/AVA.java                                                       |  183 +++++++----
 16 files changed, 558 insertions(+), 301 deletions(-)

diff --git a/sdk/examples/org/opends/sdk/examples/server/proxy/Main.java b/sdk/examples/org/opends/sdk/examples/server/proxy/Main.java
index 204b68e..4403021 100644
--- a/sdk/examples/org/opends/sdk/examples/server/proxy/Main.java
+++ b/sdk/examples/org/opends/sdk/examples/server/proxy/Main.java
@@ -35,11 +35,14 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import org.glassfish.grizzly.TransportFactory;
 import org.opends.sdk.*;
 import org.opends.sdk.controls.ProxiedAuthV2RequestControl;
 import org.opends.sdk.requests.*;
 import org.opends.sdk.responses.*;
 
+import com.sun.opends.sdk.tools.PerfToolTCPNIOTransportFactory;
+
 
 
 /**
@@ -602,6 +605,9 @@
       System.exit(1);
     }
 
+    // Use the same transport factory as the tools.
+    TransportFactory.setInstance(new PerfToolTCPNIOTransportFactory());
+
     // Parse command line arguments.
     final String localAddress = args[0];
     final int localPort = Integer.parseInt(args[1]);
diff --git a/sdk/examples/org/opends/sdk/examples/server/store/Main.java b/sdk/examples/org/opends/sdk/examples/server/store/Main.java
index cae70d8..e9f1b38 100644
--- a/sdk/examples/org/opends/sdk/examples/server/store/Main.java
+++ b/sdk/examples/org/opends/sdk/examples/server/store/Main.java
@@ -39,11 +39,14 @@
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+import org.glassfish.grizzly.TransportFactory;
 import org.opends.sdk.*;
 import org.opends.sdk.ldif.LDIFEntryReader;
 import org.opends.sdk.requests.*;
 import org.opends.sdk.responses.*;
 
+import com.sun.opends.sdk.tools.PerfToolTCPNIOTransportFactory;
+
 
 
 /**
@@ -488,6 +491,9 @@
       System.exit(1);
     }
 
+    // Use the same transport factory as the tools.
+    TransportFactory.setInstance(new PerfToolTCPNIOTransportFactory());
+
     // Parse command line arguments.
     final String localAddress = args[0];
     final int localPort = Integer.parseInt(args[1]);
diff --git a/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java b/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java
index 55d3c98..caf2f3f 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java
@@ -48,6 +48,8 @@
 import org.glassfish.grizzly.Buffer;
 import org.glassfish.grizzly.memory.BuffersBuffer;
 import org.glassfish.grizzly.memory.CompositeBuffer;
+import org.glassfish.grizzly.memory.MemoryManager;
+
 import com.sun.opends.sdk.util.StaticUtils;
 
 
@@ -226,13 +228,16 @@
    * @param maxElementSize
    *          The maximum BER element size, or <code>0</code> to indicate that
    *          there is no limit.
+   * @param memoryManager
+   *          The memory manager to use for buffering.
    */
-  ASN1BufferReader(final int maxElementSize)
+  ASN1BufferReader(final int maxElementSize,
+      final MemoryManager<?> memoryManager)
   {
     this.readLimiter = new RootSequenceLimiter();
     this.stringBuffer = new byte[MAX_STRING_BUFFER_SIZE];
     this.maxElementSize = maxElementSize;
-    this.buffer = BuffersBuffer.create();
+    this.buffer = BuffersBuffer.create(memoryManager);
   }
 
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java
index 9a8b55f..47b22bd 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java
@@ -171,7 +171,8 @@
               {
                 // The connection needs to be secured by the SASL
                 // mechanism.
-                ldapConnection.installFilter(new SASLFilter(l));
+                ldapConnection.installFilter(new SASLFilter(l, ctx
+                    .getConnection().getTransport().getMemoryManager()));
               }
             }
 
@@ -601,7 +602,8 @@
         .get(ctx.getConnection());
     if (asn1Reader == null)
     {
-      asn1Reader = new ASN1BufferReader(maxASN1ElementSize);
+      asn1Reader = new ASN1BufferReader(maxASN1ElementSize, ctx.getConnection()
+          .getTransport().getMemoryManager());
       LDAP_ASN1_READER_ATTR.set(ctx.getConnection(), asn1Reader);
     }
     asn1Reader.appendBytesRead(buffer);
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java
index 2017775..a4b9747 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java
@@ -315,7 +315,8 @@
     @Override
     public void startSASL(final ConnectionSecurityLayer bindContext)
     {
-      installFilter(connection, new SASLFilter(bindContext));
+      installFilter(connection, new SASLFilter(bindContext, connection
+          .getTransport().getMemoryManager()));
     }
 
 
@@ -1095,7 +1096,8 @@
         .get(ctx.getConnection());
     if (asn1Reader == null)
     {
-      asn1Reader = new ASN1BufferReader(maxASN1ElementSize);
+      asn1Reader = new ASN1BufferReader(maxASN1ElementSize, ctx.getConnection()
+          .getTransport().getMemoryManager());
       LDAP_ASN1_READER_ATTR.set(ctx.getConnection(), asn1Reader);
     }
     asn1Reader.appendBytesRead(buffer);
diff --git a/sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java b/sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java
index 5e817ba..fa518da 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java
@@ -49,20 +49,12 @@
   private final byte[] buffer = new byte[BUFFER_SIZE];
   private final ConnectionSecurityLayer bindContext;
 
-  private final MemoryManager<Buffer> memoryManager;
-
-
-
-  @SuppressWarnings("unchecked")
-  public SASLDecoderTransformer(final ConnectionSecurityLayer bindContext)
-  {
-    this(bindContext, TransportFactory.getInstance().getDefaultMemoryManager());
-  }
+  private final MemoryManager<?> memoryManager;
 
 
 
   public SASLDecoderTransformer(final ConnectionSecurityLayer bindContext,
-      final MemoryManager<Buffer> memoryManager)
+      final MemoryManager<?> memoryManager)
   {
     this.bindContext = bindContext;
     this.memoryManager = memoryManager;
diff --git a/sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java b/sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java
index 4878ab7..7c57777 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java
@@ -49,20 +49,12 @@
   private final byte[] buffer = new byte[BUFFER_SIZE];
   private final ConnectionSecurityLayer bindContext;
 
-  private final MemoryManager<Buffer> memoryManager;
-
-
-
-  @SuppressWarnings("unchecked")
-  public SASLEncoderTransformer(final ConnectionSecurityLayer bindContext)
-  {
-    this(bindContext, TransportFactory.getInstance().getDefaultMemoryManager());
-  }
+  private final MemoryManager<?> memoryManager;
 
 
 
   public SASLEncoderTransformer(final ConnectionSecurityLayer bindContext,
-      final MemoryManager<Buffer> memoryManager)
+      final MemoryManager<?> memoryManager)
   {
     this.bindContext = bindContext;
     this.memoryManager = memoryManager;
diff --git a/sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java b/sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java
index c08fa1c..17cf702 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java
@@ -33,6 +33,7 @@
 
 import org.glassfish.grizzly.Buffer;
 import org.glassfish.grizzly.filterchain.AbstractCodecFilter;
+import org.glassfish.grizzly.memory.MemoryManager;
 
 
 
@@ -41,9 +42,10 @@
  */
 final class SASLFilter extends AbstractCodecFilter<Buffer, Buffer>
 {
-  public SASLFilter(final ConnectionSecurityLayer bindContext)
+  public SASLFilter(final ConnectionSecurityLayer bindContext,
+      final MemoryManager<?> memoryManager)
   {
-    super(new SASLDecoderTransformer(bindContext), new SASLEncoderTransformer(
-        bindContext));
+    super(new SASLDecoderTransformer(bindContext, memoryManager),
+        new SASLEncoderTransformer(bindContext, memoryManager));
   }
 }
diff --git a/sdk/src/com/sun/opends/sdk/tools/AuthRate.java b/sdk/src/com/sun/opends/sdk/tools/AuthRate.java
index e8b8f99..fa50545 100644
--- a/sdk/src/com/sun/opends/sdk/tools/AuthRate.java
+++ b/sdk/src/com/sun/opends/sdk/tools/AuthRate.java
@@ -27,13 +27,11 @@
 
 package com.sun.opends.sdk.tools;
 
-import com.sun.opends.sdk.util.RecursiveFutureResult;
 
-import org.glassfish.grizzly.TransportFactory;
-import org.opends.sdk.*;
-import org.opends.sdk.requests.*;
-import org.opends.sdk.responses.BindResult;
-import org.opends.sdk.responses.SearchResultEntry;
+
+import static com.sun.opends.sdk.messages.Messages.*;
+import static com.sun.opends.sdk.tools.ToolConstants.*;
+import static com.sun.opends.sdk.tools.Utils.filterExitCode;
 
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -44,28 +42,31 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
-import static com.sun.opends.sdk.messages.Messages.*;
-import static com.sun.opends.sdk.tools.ToolConstants.*;
-import static com.sun.opends.sdk.tools.Utils.filterExitCode;
+import org.glassfish.grizzly.TransportFactory;
+import org.opends.sdk.*;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.SearchResultEntry;
+
+import com.sun.opends.sdk.util.RecursiveFutureResult;
+
+
 
 /**
- * A load generation tool that can be used to load a Directory Server with
- * Bind requests using one or more LDAP connections.
+ * A load generation tool that can be used to load a Directory Server with Bind
+ * requests using one or more LDAP connections.
  */
 public final class AuthRate extends ConsoleApplication
 {
   private final class BindPerformanceRunner extends PerformanceRunner
   {
-    private final AtomicLong searchWaitRecentTime = new AtomicLong();
-    private final AtomicInteger invalidCredRecentCount = new AtomicInteger();
-
     private final class BindStatsThread extends StatsThread
     {
       private final String[] extraColumn;
 
 
 
-      private BindStatsThread(boolean extraFieldRequired)
+      private BindStatsThread(final boolean extraFieldRequired)
       {
         super(extraFieldRequired ? new String[] { "bind time %" }
             : new String[0]);
@@ -93,15 +94,16 @@
     private final class BindUpdateStatsResultHandler extends
         UpdateStatsResultHandler<BindResult>
     {
-      private BindUpdateStatsResultHandler(long startTime)
+      private BindUpdateStatsResultHandler(final long startTime,
+          final AsynchronousConnection connection, final ConnectionWorker worker)
       {
-        super(startTime);
+        super(startTime, connection, worker);
       }
 
 
 
       @Override
-      public void handleErrorResult(ErrorResultException error)
+      public void handleErrorResult(final ErrorResultException error)
       {
         super.handleErrorResult(error);
 
@@ -112,7 +114,9 @@
       }
     }
 
-    private final class BindWorkerThread extends WorkerThread
+
+
+    private final class BindWorkerThread extends ConnectionWorker
     {
       private SearchRequest sr;
       private BindRequest br;
@@ -122,6 +126,7 @@
       private final ThreadLocal<Random> rng = new ThreadLocal<Random>()
       {
 
+        @Override
         protected Random initialValue()
         {
           return new Random();
@@ -130,6 +135,7 @@
       };
 
 
+
       private BindWorkerThread(final AsynchronousConnection connection,
           final ConnectionFactory connectionFactory)
       {
@@ -146,14 +152,14 @@
         if (dataSources != null)
         {
           data = DataSource.generateData(dataSources, data);
-          if(data.length == dataSources.length)
+          if (data.length == dataSources.length)
           {
-            Object[] newData = new Object[data.length + 1];
+            final Object[] newData = new Object[data.length + 1];
             System.arraycopy(data, 0, newData, 0, data.length);
             data = newData;
           }
         }
-        if(filter != null && baseDN != null)
+        if (filter != null && baseDN != null)
         {
           if (sr == null)
           {
@@ -163,8 +169,8 @@
             }
             else
             {
-              sr = Requests.newSearchRequest(String.format(baseDN, data), scope,
-                  String.format(filter, data), attributes);
+              sr = Requests.newSearchRequest(String.format(baseDN, data),
+                  scope, String.format(filter, data), attributes);
             }
             sr.setDereferenceAliasesPolicy(dereferencesAliasesPolicy);
           }
@@ -174,32 +180,32 @@
             sr.setName(String.format(baseDN, data));
           }
 
-          RecursiveFutureResult<SearchResultEntry, BindResult> future =
-              new RecursiveFutureResult<SearchResultEntry, BindResult>(
-                  new BindUpdateStatsResultHandler(startTime))
+          final RecursiveFutureResult<SearchResultEntry, BindResult> future =
+            new RecursiveFutureResult<SearchResultEntry, BindResult>(
+              new BindUpdateStatsResultHandler(startTime, connection, this))
+          {
+            @Override
+            protected FutureResult<? extends BindResult> chainResult(
+                final SearchResultEntry innerResult,
+                final ResultHandler<? super BindResult> resultHandler)
+                throws ErrorResultException
+            {
+              searchWaitRecentTime.getAndAdd(System.nanoTime() - startTime);
+              if (data == null)
               {
-                @Override
-                protected FutureResult<? extends BindResult> chainResult(
-                    SearchResultEntry innerResult,
-                    ResultHandler<? super BindResult> resultHandler)
-                    throws ErrorResultException
-                {
-                  searchWaitRecentTime.getAndAdd(System.nanoTime() - startTime);
-                  if(data == null)
-                  {
-                    data = new Object[1];
-                  }
-                  data[data.length-1] = innerResult.getName().toString();
-                  return performBind(connection, data, resultHandler);
-                }
-              };
+                data = new Object[1];
+              }
+              data[data.length - 1] = innerResult.getName().toString();
+              return performBind(connection, data, resultHandler);
+            }
+          };
           connection.searchSingleEntry(sr, future);
           return future;
         }
         else
         {
           return performBind(connection, data,
-              new BindUpdateStatsResultHandler(startTime));
+              new BindUpdateStatsResultHandler(startTime, connection, this));
         }
       }
 
@@ -229,13 +235,13 @@
 
         if (bindRequest instanceof SimpleBindRequest)
         {
-          SimpleBindRequest o = (SimpleBindRequest) bindRequest;
+          final SimpleBindRequest o = (SimpleBindRequest) bindRequest;
           if (br == null)
           {
             br = Requests.copyOfSimpleBindRequest(o);
           }
 
-          SimpleBindRequest sbr = (SimpleBindRequest) br;
+          final SimpleBindRequest sbr = (SimpleBindRequest) br;
           if (data != null && o.getName() != null)
           {
             sbr.setName(String.format(o.getName(), data));
@@ -251,13 +257,13 @@
         }
         else if (bindRequest instanceof DigestMD5SASLBindRequest)
         {
-          DigestMD5SASLBindRequest o = (DigestMD5SASLBindRequest) bindRequest;
+          final DigestMD5SASLBindRequest o = (DigestMD5SASLBindRequest) bindRequest;
           if (br == null)
           {
             br = Requests.copyOfDigestMD5SASLBindRequest(o);
           }
 
-          DigestMD5SASLBindRequest sbr = (DigestMD5SASLBindRequest) br;
+          final DigestMD5SASLBindRequest sbr = (DigestMD5SASLBindRequest) br;
           if (data != null)
           {
             if (o.getAuthenticationID() != null)
@@ -281,13 +287,13 @@
         }
         else if (bindRequest instanceof CRAMMD5SASLBindRequest)
         {
-          CRAMMD5SASLBindRequest o = (CRAMMD5SASLBindRequest) bindRequest;
+          final CRAMMD5SASLBindRequest o = (CRAMMD5SASLBindRequest) bindRequest;
           if (br == null)
           {
             br = Requests.copyOfCRAMMD5SASLBindRequest(o);
           }
 
-          CRAMMD5SASLBindRequest sbr = (CRAMMD5SASLBindRequest) br;
+          final CRAMMD5SASLBindRequest sbr = (CRAMMD5SASLBindRequest) br;
           if (data != null && o.getAuthenticationID() != null)
           {
             sbr.setAuthenticationID(String.format(o.getAuthenticationID(), data));
@@ -303,13 +309,13 @@
         }
         else if (bindRequest instanceof GSSAPISASLBindRequest)
         {
-          GSSAPISASLBindRequest o = (GSSAPISASLBindRequest) bindRequest;
+          final GSSAPISASLBindRequest o = (GSSAPISASLBindRequest) bindRequest;
           if (br == null)
           {
             br = Requests.copyOfGSSAPISASLBindRequest(o);
           }
 
-          GSSAPISASLBindRequest sbr = (GSSAPISASLBindRequest) br;
+          final GSSAPISASLBindRequest sbr = (GSSAPISASLBindRequest) br;
           if (data != null)
           {
             if (o.getAuthenticationID() != null)
@@ -333,13 +339,13 @@
         }
         else if (bindRequest instanceof ExternalSASLBindRequest)
         {
-          ExternalSASLBindRequest o = (ExternalSASLBindRequest) bindRequest;
+          final ExternalSASLBindRequest o = (ExternalSASLBindRequest) bindRequest;
           if (br == null)
           {
             br = Requests.copyOfExternalSASLBindRequest(o);
           }
 
-          ExternalSASLBindRequest sbr = (ExternalSASLBindRequest) br;
+          final ExternalSASLBindRequest sbr = (ExternalSASLBindRequest) br;
           if (data != null && o.getAuthorizationID() != null)
           {
             sbr.setAuthorizationID(String.format(o.getAuthorizationID(), data));
@@ -347,13 +353,13 @@
         }
         else if (bindRequest instanceof PlainSASLBindRequest)
         {
-          PlainSASLBindRequest o = (PlainSASLBindRequest) bindRequest;
+          final PlainSASLBindRequest o = (PlainSASLBindRequest) bindRequest;
           if (br == null)
           {
             br = Requests.copyOfPlainSASLBindRequest(o);
           }
 
-          PlainSASLBindRequest sbr = (PlainSASLBindRequest) br;
+          final PlainSASLBindRequest sbr = (PlainSASLBindRequest) br;
           if (data != null)
           {
             if (o.getAuthenticationID() != null)
@@ -382,6 +388,10 @@
 
 
 
+    private final AtomicLong searchWaitRecentTime = new AtomicLong();
+
+    private final AtomicInteger invalidCredRecentCount = new AtomicInteger();
+
     private String filter;
 
     private String baseDN;
@@ -407,20 +417,24 @@
 
 
     @Override
-    StatsThread newStatsThread()
+    ConnectionWorker newConnectionWorker(
+        final AsynchronousConnection connection,
+        final ConnectionFactory connectionFactory)
     {
-      return new BindStatsThread(filter != null && baseDN != null);
+      return new BindWorkerThread(connection, connectionFactory);
     }
 
 
 
     @Override
-    WorkerThread newWorkerThread(final AsynchronousConnection connection,
-        final ConnectionFactory connectionFactory)
+    StatsThread newStatsThread()
     {
-      return new BindWorkerThread(connection, connectionFactory);
+      return new BindStatsThread(filter != null && baseDN != null);
     }
   }
+
+
+
   /**
    * The main method for AuthRate tool.
    *
@@ -485,7 +499,6 @@
 
 
 
-
   private AuthRate(final InputStream in, final OutputStream out,
       final OutputStream err)
   {
@@ -583,10 +596,10 @@
   {
     // Create the command-line argument parser for use with this
     // program.
-    final LocalizableMessage toolDescription =
-        INFO_AUTHRATE_TOOL_DESCRIPTION.get();
-    final ArgumentParser argParser = new ArgumentParser(AuthRate.class
-        .getName(), toolDescription, false, true, 0, 0,
+    final LocalizableMessage toolDescription = INFO_AUTHRATE_TOOL_DESCRIPTION
+        .get();
+    final ArgumentParser argParser = new ArgumentParser(
+        AuthRate.class.getName(), toolDescription, false, true, 0, 0,
         "[filter format string] [attributes ...]");
 
     ConnectionFactoryProvider connectionFactoryProvider;
@@ -604,8 +617,7 @@
     try
     {
       TransportFactory.setInstance(new PerfToolTCPNIOTransportFactory());
-      connectionFactoryProvider =
-        new ConnectionFactoryProvider(argParser, this);
+      connectionFactoryProvider = new ConnectionFactoryProvider(argParser, this);
       runner = new BindPerformanceRunner(argParser, this);
 
       propertiesFileArgument = new StringArgument("propertiesFilePath", null,
@@ -627,34 +639,35 @@
       argParser.setUsageArgument(showUsage, getOutputStream());
 
       baseDN = new StringArgument("baseDN", OPTION_SHORT_BASEDN,
-          OPTION_LONG_BASEDN, false, false, true, INFO_BASEDN_PLACEHOLDER.get(),
-          null, null, INFO_SEARCHRATE_TOOL_DESCRIPTION_BASEDN.get());
+          OPTION_LONG_BASEDN, false, false, true,
+          INFO_BASEDN_PLACEHOLDER.get(), null, null,
+          INFO_SEARCHRATE_TOOL_DESCRIPTION_BASEDN.get());
       baseDN.setPropertyName(OPTION_LONG_BASEDN);
       argParser.addArgument(baseDN);
 
       searchScope = new MultiChoiceArgument<SearchScope>("searchScope", 's',
           "searchScope", false, true, INFO_SEARCH_SCOPE_PLACEHOLDER.get(),
-          SearchScope.values(), false, INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE
-              .get());
+          SearchScope.values(), false,
+          INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE.get());
       searchScope.setPropertyName("searchScope");
       searchScope.setDefaultValue(SearchScope.WHOLE_SUBTREE);
       argParser.addArgument(searchScope);
 
       dereferencePolicy = new MultiChoiceArgument<DereferenceAliasesPolicy>(
           "derefpolicy", 'a', "dereferencePolicy", false, true,
-          INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(), DereferenceAliasesPolicy
-              .values(), false, INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY
-              .get());
+          INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(),
+          DereferenceAliasesPolicy.values(), false,
+          INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY.get());
       dereferencePolicy.setPropertyName("dereferencePolicy");
       dereferencePolicy.setDefaultValue(DereferenceAliasesPolicy.NEVER);
       argParser.addArgument(dereferencePolicy);
 
       invalidCredPercent = new IntegerArgument("invalidPassword", 'I',
-        "invalidPassword", false, false, true, LocalizableMessage
-            .raw("{invalidPassword}"), 0, null, true, 0, true, 100,
-        LocalizableMessage
-            .raw("Percent of bind operations with simulated " +
-            "invalid password"));
+          "invalidPassword", false, false, true,
+          LocalizableMessage.raw("{invalidPassword}"), 0, null, true, 0, true,
+          100,
+          LocalizableMessage.raw("Percent of bind operations with simulated "
+              + "invalid password"));
       invalidCredPercent.setPropertyName("invalidPassword");
       argParser.addArgument(invalidCredPercent);
 
@@ -688,15 +701,15 @@
         return 0;
       }
 
-      connectionFactory =
-          connectionFactoryProvider.getConnectionFactory();
+      connectionFactory = connectionFactoryProvider.getConnectionFactory();
       runner.validate();
 
       runner.bindRequest = connectionFactoryProvider.getBindRequest();
-      if(runner.bindRequest == null)
+      if (runner.bindRequest == null)
       {
-        throw new ArgumentException(LocalizableMessage.raw(
-            "Authentication information must be provided to use this tool"));
+        throw new ArgumentException(
+            LocalizableMessage
+                .raw("Authentication information must be provided to use this tool"));
       }
     }
     catch (final ArgumentException ae)
@@ -739,11 +752,11 @@
 
     // Try it out to make sure the format string and data sources
     // match.
-    final Object[] data = DataSource.generateData(runner.getDataSources(),
-        null);
+    final Object[] data = DataSource
+        .generateData(runner.getDataSources(), null);
     try
     {
-      if(runner.baseDN != null && runner.filter != null)
+      if (runner.baseDN != null && runner.filter != null)
       {
         String.format(runner.filter, data);
         String.format(runner.baseDN, data);
@@ -759,4 +772,3 @@
     return runner.run(connectionFactory);
   }
 }
-
diff --git a/sdk/src/com/sun/opends/sdk/tools/ModRate.java b/sdk/src/com/sun/opends/sdk/tools/ModRate.java
index 360f490..664da15 100644
--- a/sdk/src/com/sun/opends/sdk/tools/ModRate.java
+++ b/sdk/src/com/sun/opends/sdk/tools/ModRate.java
@@ -52,7 +52,7 @@
 {
   private static final class ModifyPerformanceRunner extends PerformanceRunner
   {
-    private final class ModifyWorkerThread extends WorkerThread
+    private final class ModifyWorkerThread extends ConnectionWorker
     {
       private ModifyRequest mr;
       private Object[] data;
@@ -70,15 +70,15 @@
       @Override
       public FutureResult<?> performOperation(
           final AsynchronousConnection connection,
-          final DataSource[] dataSources, long startTime)
+          final DataSource[] dataSources, final long startTime)
       {
         if (dataSources != null)
         {
           data = DataSource.generateData(dataSources, data);
         }
         mr = newModifyRequest(data);
-        return connection.modify(mr,
-            new UpdateStatsResultHandler<Result>(startTime));
+        return connection.modify(mr, new UpdateStatsResultHandler<Result>(
+            startTime, connection, this));
       }
 
 
@@ -109,9 +109,9 @@
           colonPos = formattedString.indexOf(':');
           if (colonPos > 0)
           {
-            mr.addModification(ModificationType.REPLACE, formattedString
-                .substring(0, colonPos), formattedString
-                .substring(colonPos + 1));
+            mr.addModification(ModificationType.REPLACE,
+                formattedString.substring(0, colonPos),
+                formattedString.substring(colonPos + 1));
           }
         }
         return mr;
@@ -135,18 +135,19 @@
 
 
     @Override
-    StatsThread newStatsThread()
+    ConnectionWorker newConnectionWorker(
+        final AsynchronousConnection connection,
+        final ConnectionFactory connectionFactory)
     {
-      return new StatsThread(new String[0]);
+      return new ModifyWorkerThread(connection, connectionFactory);
     }
 
 
 
     @Override
-    WorkerThread newWorkerThread(final AsynchronousConnection connection,
-        final ConnectionFactory connectionFactory)
+    StatsThread newStatsThread()
     {
-      return new ModifyWorkerThread(connection, connectionFactory);
+      return new StatsThread(new String[0]);
     }
   }
 
@@ -313,8 +314,8 @@
   {
     // Create the command-line argument parser for use with this
     // program.
-    final LocalizableMessage toolDescription =
-        INFO_MODRATE_TOOL_DESCRIPTION.get();
+    final LocalizableMessage toolDescription = INFO_MODRATE_TOOL_DESCRIPTION
+        .get();
     final ArgumentParser argParser = new ArgumentParser(
         ModRate.class.getName(), toolDescription, false, true, 1, 0,
         "[(attribute:value format string) ...]");
@@ -330,8 +331,7 @@
     try
     {
       TransportFactory.setInstance(new PerfToolTCPNIOTransportFactory());
-      connectionFactoryProvider =
-          new ConnectionFactoryProvider(argParser, this);
+      connectionFactoryProvider = new ConnectionFactoryProvider(argParser, this);
       runner = new ModifyPerformanceRunner(argParser, this);
       propertiesFileArgument = new StringArgument("propertiesFilePath", null,
           OPTION_LONG_PROP_FILE_PATH, false, false, true,
@@ -347,8 +347,9 @@
       argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
 
       baseDN = new StringArgument("targetDN", OPTION_SHORT_BASEDN,
-          OPTION_LONG_TARGETDN, true, false, true, INFO_TARGETDN_PLACEHOLDER.get(),
-          null, null, INFO_MODRATE_TOOL_DESCRIPTION_TARGETDN.get());
+          OPTION_LONG_TARGETDN, true, false, true,
+          INFO_TARGETDN_PLACEHOLDER.get(), null, null,
+          INFO_MODRATE_TOOL_DESCRIPTION_TARGETDN.get());
       baseDN.setPropertyName(OPTION_LONG_BASEDN);
       argParser.addArgument(baseDN);
 
@@ -387,8 +388,8 @@
         return 0;
       }
 
-      connectionFactory =
-          connectionFactoryProvider.getAuthenticatedConnectionFactory();
+      connectionFactory = connectionFactoryProvider
+          .getAuthenticatedConnectionFactory();
       runner.validate();
     }
     catch (final ArgumentException ae)
diff --git a/sdk/src/com/sun/opends/sdk/tools/PerfToolTCPNIOTransportFactory.java b/sdk/src/com/sun/opends/sdk/tools/PerfToolTCPNIOTransportFactory.java
index 1f6a1ec..c0452ce 100644
--- a/sdk/src/com/sun/opends/sdk/tools/PerfToolTCPNIOTransportFactory.java
+++ b/sdk/src/com/sun/opends/sdk/tools/PerfToolTCPNIOTransportFactory.java
@@ -46,7 +46,8 @@
 /**
  * The TCPNIOTransportFactory which performance tools will use.
  */
-final class PerfToolTCPNIOTransportFactory extends DefaultNIOTransportFactory
+public final class PerfToolTCPNIOTransportFactory extends
+    DefaultNIOTransportFactory
 {
   private int selectors;
 
@@ -115,7 +116,7 @@
     }
     final String selectorsStr = System
         .getProperty("org.opends.sdk.ldap.transport.selectors");
-    if (threadsStr != null)
+    if (selectorsStr != null)
     {
       selectors = Integer.parseInt(selectorsStr);
     }
diff --git a/sdk/src/com/sun/opends/sdk/tools/PerformanceRunner.java b/sdk/src/com/sun/opends/sdk/tools/PerformanceRunner.java
index b4c5300..9f4ab43 100644
--- a/sdk/src/com/sun/opends/sdk/tools/PerformanceRunner.java
+++ b/sdk/src/com/sun/opends/sdk/tools/PerformanceRunner.java
@@ -33,16 +33,18 @@
 import java.lang.management.GarbageCollectorMXBean;
 import java.lang.management.ManagementFactory;
 import java.util.*;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
-import com.sun.opends.sdk.util.StaticUtils;
 import org.opends.sdk.*;
+import org.opends.sdk.responses.BindResult;
 import org.opends.sdk.responses.ExtendedResult;
 import org.opends.sdk.responses.Result;
 
 import com.sun.opends.sdk.tools.AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection;
+import com.sun.opends.sdk.util.StaticUtils;
 
 
 
@@ -51,6 +53,170 @@
  */
 abstract class PerformanceRunner implements ConnectionEventListener
 {
+  abstract class ConnectionWorker
+  {
+    private final AtomicInteger operationsInFlight = new AtomicInteger();
+
+    private volatile int count;
+
+    private final AsynchronousConnection staticConnection;
+
+    private final ConnectionFactory connectionFactory;
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+
+
+
+    ConnectionWorker(final AsynchronousConnection staticConnection,
+        final ConnectionFactory connectionFactory)
+    {
+      this.staticConnection = staticConnection;
+      this.connectionFactory = connectionFactory;
+    }
+
+
+
+    public void operationCompleted(final AsynchronousConnection connection)
+    {
+      if (operationsInFlight.decrementAndGet() == 0
+          && this.staticConnection == null)
+      {
+        connection.close();
+      }
+      startWork();
+    }
+
+
+
+    public abstract FutureResult<?> performOperation(
+        final AsynchronousConnection connection,
+        final DataSource[] dataSources, final long startTime);
+
+
+
+    public void startWork()
+    {
+      if (!stopRequested && !(maxIterations > 0 && count >= maxIterations))
+      {
+        if (this.staticConnection == null)
+        {
+          connectionFactory
+              .getAsynchronousConnection(new ResultHandler<AsynchronousConnection>()
+              {
+                public void handleErrorResult(final ErrorResultException e)
+                {
+                  app.println(LocalizableMessage.raw(e.getResult()
+                      .getDiagnosticMessage()));
+                  if (e.getCause() != null && app.isVerbose())
+                  {
+                    e.getCause().printStackTrace(app.getErrorStream());
+                  }
+                  stopRequested = true;
+                }
+
+
+
+                public void handleResult(final AsynchronousConnection result)
+                {
+                  doWork(result);
+                }
+              });
+        }
+        else
+        {
+          if (!noRebind
+              && this.staticConnection instanceof AuthenticatedAsynchronousConnection)
+          {
+            final AuthenticatedAsynchronousConnection ac =
+              (AuthenticatedAsynchronousConnection) this.staticConnection;
+            ac.rebind(new ResultHandler<BindResult>()
+            {
+              public void handleErrorResult(final ErrorResultException e)
+              {
+                app.println(LocalizableMessage.raw(e.getResult().toString()));
+                if (e.getCause() != null && app.isVerbose())
+                {
+                  e.getCause().printStackTrace(app.getErrorStream());
+                }
+                stopRequested = true;
+              }
+
+
+
+              public void handleResult(final BindResult result)
+              {
+                doWork(staticConnection);
+              }
+            });
+          }
+          else
+          {
+            doWork(staticConnection);
+          }
+        }
+      }
+      else
+      {
+        latch.countDown();
+      }
+    }
+
+
+
+    public void waitFor() throws InterruptedException
+    {
+      latch.await();
+    }
+
+
+
+    private void doWork(final AsynchronousConnection connection)
+    {
+      long start;
+      double sleepTimeInMS = 0;
+      final int opsToPerform = isAsync ? numConcurrentTasks : numConcurrentTasks
+          - operationsInFlight.get();
+      for (int i = 0; i < opsToPerform; i++)
+      {
+        if (maxIterations > 0 && count >= maxIterations)
+        {
+          break;
+        }
+        start = System.nanoTime();
+        performOperation(connection, dataSources.get(), start);
+        operationRecentCount.getAndIncrement();
+        operationsInFlight.getAndIncrement();
+        count++;
+
+        if (targetThroughput > 0)
+        {
+          try
+          {
+            if (sleepTimeInMS > 1)
+            {
+              Thread.sleep((long) Math.floor(sleepTimeInMS));
+            }
+          }
+          catch (final InterruptedException e)
+          {
+            continue;
+          }
+
+          sleepTimeInMS += targetTimeInMS
+              - ((System.nanoTime() - start) / 1000000.0);
+          if (sleepTimeInMS < -60000)
+          {
+            // If we fall behind by 60 seconds, just forget about
+            // catching up
+            sleepTimeInMS = -60000;
+          }
+        }
+      }
+    }
+  }
+
+
+
   /**
    * Statistics thread base implementation.
    */
@@ -263,8 +429,8 @@
         if (resultCount > 0)
         {
           strings[2] = String.format("%.3f",
-              (waitTime - (gcDuration - lastGCDuration))
-                  / (double) resultCount / 1000000.0);
+              (waitTime - (gcDuration - lastGCDuration)) / (double) resultCount
+                  / 1000000.0);
         }
         else
         {
@@ -370,7 +536,7 @@
         {
           // Script-friendly.
           app.getOutputStream().print(averageDuration);
-          for (String s : strings)
+          for (final String s : strings)
           {
             app.getOutputStream().print(",");
             app.getOutputStream().print(s);
@@ -399,12 +565,17 @@
   class UpdateStatsResultHandler<S extends Result> implements ResultHandler<S>
   {
     private final long startTime;
+    private final AsynchronousConnection connection;
+    private final ConnectionWorker worker;
 
 
 
-    UpdateStatsResultHandler(final long startTime)
+    UpdateStatsResultHandler(final long startTime,
+        final AsynchronousConnection connection, final ConnectionWorker worker)
     {
       this.startTime = startTime;
+      this.connection = connection;
+      this.worker = worker;
     }
 
 
@@ -418,6 +589,8 @@
       {
         app.println(LocalizableMessage.raw(error.getResult().toString()));
       }
+
+      worker.operationCompleted(connection);
     }
 
 
@@ -426,6 +599,7 @@
     {
       successRecentCount.getAndIncrement();
       updateStats();
+      worker.operationCompleted(connection);
     }
 
 
@@ -487,8 +661,7 @@
       AsynchronousConnection connection;
 
       final double targetTimeInMS =
-        (1.0 / (targetThroughput /
-            (double) (numThreads * numConnections))) * 1000.0;
+        (1.0 / (targetThroughput / (double) (numConcurrentTasks * numConnections))) * 1000.0;
       double sleepTimeInMS = 0;
       long start;
       while (!stopRequested && !(maxIterations > 0 && count >= maxIterations))
@@ -797,20 +970,20 @@
 
   private final AtomicLong waitRecentTime = new AtomicLong();
 
-  private final AtomicReference<ReversableArray> eTimeBuffer =
-    new AtomicReference<ReversableArray>(new ReversableArray(100000));
+  private final AtomicReference<ReversableArray> eTimeBuffer = new AtomicReference<ReversableArray>(
+      new ReversableArray(100000));
 
   private final ConsoleApplication app;
 
   private DataSource[] dataSourcePrototypes;
 
   // Thread local copies of the data sources
-  private final ThreadLocal<DataSource[]> dataSources =
-    new ThreadLocal<DataSource[]>()
+  private final ThreadLocal<DataSource[]> dataSources = new ThreadLocal<DataSource[]>()
   {
     /**
      * {@inheritDoc}
      */
+    @Override
     protected DataSource[] initialValue()
     {
       final DataSource[] prototypes = getDataSources();
@@ -827,7 +1000,7 @@
 
   private volatile boolean stopRequested;
 
-  private int numThreads;
+  private int numConcurrentTasks;
 
   private int numConnections;
 
@@ -841,7 +1014,9 @@
 
   private int statsInterval;
 
-  private final IntegerArgument numThreadsArgument;
+  private double targetTimeInMS;
+
+  private final IntegerArgument numConcurrentTasksArgument;
 
   private final IntegerArgument maxIterationsArgument;
 
@@ -864,52 +1039,52 @@
 
 
   PerformanceRunner(final ArgumentParser argParser,
-                    final ConsoleApplication app,
-                    boolean neverRebind, boolean neverAsynchronous,
-                    boolean alwaysSingleThreaded)
+      final ConsoleApplication app, final boolean neverRebind,
+      final boolean neverAsynchronous, final boolean alwaysSingleThreaded)
       throws ArgumentException
   {
     this.app = app;
-    numThreadsArgument = new IntegerArgument("numThreads", 't', "numThreads",
-        false, false, true, LocalizableMessage.raw("{numThreads}"), 1, null,
-        true, 1, false, 0, LocalizableMessage
-            .raw("Number of worker threads per connection"));
-    numThreadsArgument.setPropertyName("numThreads");
-    if(!alwaysSingleThreaded)
+    numConcurrentTasksArgument = new IntegerArgument("numConcurrentTasks", 't',
+        "numConcurrentTasks", false, false, true,
+        LocalizableMessage.raw("{numConcurrentTasks}"), 1, null, true, 1,
+        false, 0,
+        LocalizableMessage.raw("Number of concurrent tasks per connection"));
+    numConcurrentTasksArgument.setPropertyName("numConcurrentTasks");
+    if (!alwaysSingleThreaded)
     {
-      argParser.addArgument(numThreadsArgument);
+      argParser.addArgument(numConcurrentTasksArgument);
     }
     else
     {
-      numThreadsArgument.addValue("1");
+      numConcurrentTasksArgument.addValue("1");
     }
 
     numConnectionsArgument = new IntegerArgument("numConnections", 'c',
-        "numConnections", false, false, true, LocalizableMessage
-            .raw("{numConnections}"), 1, null, true, 1, false, 0,
+        "numConnections", false, false, true,
+        LocalizableMessage.raw("{numConnections}"), 1, null, true, 1, false, 0,
         LocalizableMessage.raw("Number of connections"));
     numConnectionsArgument.setPropertyName("numConnections");
     argParser.addArgument(numConnectionsArgument);
 
     maxIterationsArgument = new IntegerArgument("maxIterations", 'm',
-        "maxIterations", false, false, true, LocalizableMessage
-            .raw("{maxIterations}"), 0, null, LocalizableMessage
-            .raw("Max iterations, 0 for unlimited"));
+        "maxIterations", false, false, true,
+        LocalizableMessage.raw("{maxIterations}"), 0, null,
+        LocalizableMessage.raw("Max iterations, 0 for unlimited"));
     maxIterationsArgument.setPropertyName("maxIterations");
     argParser.addArgument(maxIterationsArgument);
 
     statsIntervalArgument = new IntegerArgument("statInterval", 'i',
-        "statInterval", false, false, true, LocalizableMessage
-            .raw("{statInterval}"), 5, null, true, 1, false, 0,
+        "statInterval", false, false, true,
+        LocalizableMessage.raw("{statInterval}"), 5, null, true, 1, false, 0,
         LocalizableMessage
             .raw("Display results each specified number of seconds"));
     statsIntervalArgument.setPropertyName("statInterval");
     argParser.addArgument(statsIntervalArgument);
 
     targetThroughputArgument = new IntegerArgument("targetThroughput", 'M',
-        "targetThroughput", false, false, true, LocalizableMessage
-            .raw("{targetThroughput}"), 0, null, LocalizableMessage
-            .raw("Target average throughput to achieve"));
+        "targetThroughput", false, false, true,
+        LocalizableMessage.raw("{targetThroughput}"), 0, null,
+        LocalizableMessage.raw("Target average throughput to achieve"));
     targetThroughputArgument.setPropertyName("targetThroughput");
     argParser.addArgument(targetThroughputArgument);
 
@@ -929,7 +1104,7 @@
     noRebindArgument = new BooleanArgument("noRebind", 'F', "noRebind",
         LocalizableMessage.raw("Keep connections open and don't rebind"));
     noRebindArgument.setPropertyName("noRebind");
-    if(!neverRebind)
+    if (!neverRebind)
     {
       argParser.addArgument(noRebindArgument);
     }
@@ -939,24 +1114,30 @@
     }
 
     asyncArgument = new BooleanArgument("asynchronous", 'A', "asynchronous",
-        LocalizableMessage.raw("Use asynchronous mode and don't " +
-            "wait for results before sending the next request"));
+        LocalizableMessage.raw("Use asynchronous mode and don't "
+            + "wait for results before sending the next request"));
     asyncArgument.setPropertyName("asynchronous");
-    if(!neverAsynchronous)
+    if (!neverAsynchronous)
     {
       argParser.addArgument(asyncArgument);
     }
 
-    arguments = new StringArgument("argument", 'g', "argument", false, true,
-        true, LocalizableMessage.raw("{generator function or static string}"),
-        null, null,
-        LocalizableMessage.raw("Argument used to evaluate the Java " +
-            "style format strings in program parameters (ie. Base DN, " +
-            "Search Filter). The set of all arguments provided form the " +
-            "the argument list in order. Besides static string " +
-            "arguments, they can be generated per iteration with the " +
-            "following functions: " + StaticUtils.EOL +
-            DataSource.getUsage()));
+    arguments = new StringArgument(
+        "argument",
+        'g',
+        "argument",
+        false,
+        true,
+        true,
+        LocalizableMessage.raw("{generator function or static string}"),
+        null,
+        null,
+        LocalizableMessage.raw("Argument used to evaluate the Java "
+            + "style format strings in program parameters (ie. Base DN, "
+            + "Search Filter). The set of all arguments provided form the "
+            + "the argument list in order. Besides static string "
+            + "arguments, they can be generated per iteration with the "
+            + "following functions: " + StaticUtils.EOL + DataSource.getUsage()));
     argParser.addArgument(arguments);
   }
 
@@ -986,8 +1167,7 @@
 
 
 
-  public void handleUnsolicitedNotification(
-      final ExtendedResult notification)
+  public void handleUnsolicitedNotification(final ExtendedResult notification)
   {
     // Ignore
   }
@@ -997,20 +1177,19 @@
   public final void validate() throws ArgumentException
   {
     numConnections = numConnectionsArgument.getIntValue();
-    numThreads = numThreadsArgument.getIntValue();
-    maxIterations = maxIterationsArgument.getIntValue() /
-        numConnections / numThreads;
+    numConcurrentTasks = numConcurrentTasksArgument.getIntValue();
+    maxIterations = maxIterationsArgument.getIntValue() / numConnections;
     statsInterval = statsIntervalArgument.getIntValue() * 1000;
     targetThroughput = targetThroughputArgument.getIntValue();
 
     isAsync = asyncArgument.isPresent();
     noRebind = noRebindArgument.isPresent();
 
-    if (!noRebindArgument.isPresent() && this.numThreads > 1)
+    if (!noRebindArgument.isPresent() && this.numConcurrentTasks > 1)
     {
       throw new ArgumentException(LocalizableMessage.raw("--"
           + noRebindArgument.getLongIdentifier() + " must be used if --"
-          + numThreadsArgument.getLongIdentifier() + " is > 1"));
+          + numConcurrentTasksArgument.getLongIdentifier() + " is > 1"));
     }
 
     if (!noRebindArgument.isPresent() && asyncArgument.isPresent())
@@ -1021,6 +1200,9 @@
     }
 
     dataSourcePrototypes = DataSource.parse(arguments.getValues());
+
+    targetTimeInMS =
+      (1.0 / (targetThroughput / (double) (numConcurrentTasks * numConnections))) * 1000.0;
   }
 
 
@@ -1037,22 +1219,22 @@
 
 
 
+  abstract ConnectionWorker newConnectionWorker(
+      final AsynchronousConnection connection,
+      final ConnectionFactory connectionFactory);
+
+
+
   abstract StatsThread newStatsThread();
 
 
 
-  abstract WorkerThread newWorkerThread(AsynchronousConnection connection,
-      ConnectionFactory connectionFactory);
-
-
-
   final int run(final ConnectionFactory connectionFactory)
   {
-    final List<Thread> threads = new ArrayList<Thread>();
+    final List<ConnectionWorker> workers = new ArrayList<ConnectionWorker>();
     final List<AsynchronousConnection> connections = new ArrayList<AsynchronousConnection>();
 
     AsynchronousConnection connection = null;
-    Thread thread;
     try
     {
       for (int i = 0; i < numConnections; i++)
@@ -1063,21 +1245,18 @@
           connection.addConnectionEventListener(this);
           connections.add(connection);
         }
-        for (int j = 0; j < numThreads; j++)
-        {
-          thread = newWorkerThread(connection, connectionFactory);
-
-          threads.add(thread);
-          thread.start();
-        }
+        final ConnectionWorker worker = newConnectionWorker(connection,
+            connectionFactory);
+        workers.add(worker);
+        worker.startWork();
       }
 
       final Thread statsThread = newStatsThread();
       statsThread.start();
 
-      for (final Thread t : threads)
+      for (final ConnectionWorker w : workers)
       {
-        t.join();
+        w.waitFor();
       }
       stopRequested = true;
       statsThread.join();
diff --git a/sdk/src/com/sun/opends/sdk/tools/SearchRate.java b/sdk/src/com/sun/opends/sdk/tools/SearchRate.java
index 332f510..8ee12d6 100644
--- a/sdk/src/com/sun/opends/sdk/tools/SearchRate.java
+++ b/sdk/src/com/sun/opends/sdk/tools/SearchRate.java
@@ -61,13 +61,15 @@
     private final class SearchStatsHandler extends
         UpdateStatsResultHandler<Result> implements SearchResultHandler
     {
-      private SearchStatsHandler(final long startTime)
+      private SearchStatsHandler(final long startTime,
+          final AsynchronousConnection connection, final ConnectionWorker worker)
       {
-        super(startTime);
+        super(startTime, connection, worker);
       }
 
 
 
+      @Override
       public boolean handleEntry(final SearchResultEntry entry)
       {
         entryRecentCount.getAndIncrement();
@@ -76,6 +78,7 @@
 
 
 
+      @Override
       public boolean handleReference(final SearchResultReference reference)
       {
         return true;
@@ -117,7 +120,7 @@
 
 
 
-    private final class SearchWorkerThread extends WorkerThread
+    private final class SearchWorkerThread extends ConnectionWorker
     {
       private SearchRequest sr;
 
@@ -158,7 +161,8 @@
           sr.setFilter(String.format(filter, data));
           sr.setName(String.format(baseDN, data));
         }
-        return connection.search(sr, new SearchStatsHandler(startTime));
+        return connection.search(sr, new SearchStatsHandler(startTime,
+            connection, this));
       }
     }
 
@@ -185,18 +189,19 @@
 
 
     @Override
-    StatsThread newStatsThread()
+    ConnectionWorker newConnectionWorker(
+        final AsynchronousConnection connection,
+        final ConnectionFactory connectionFactory)
     {
-      return new SearchStatsThread();
+      return new SearchWorkerThread(connection, connectionFactory);
     }
 
 
 
     @Override
-    WorkerThread newWorkerThread(final AsynchronousConnection connection,
-        final ConnectionFactory connectionFactory)
+    StatsThread newStatsThread()
     {
-      return new SearchWorkerThread(connection, connectionFactory);
+      return new SearchStatsThread();
     }
   }
 
@@ -365,10 +370,10 @@
   {
     // Create the command-line argument parser for use with this
     // program.
-    final LocalizableMessage toolDescription =
-        INFO_SEARCHRATE_TOOL_DESCRIPTION.get();
-    final ArgumentParser argParser = new ArgumentParser(SearchRate.class
-        .getName(), toolDescription, false, true, 1, 0,
+    final LocalizableMessage toolDescription = INFO_SEARCHRATE_TOOL_DESCRIPTION
+        .get();
+    final ArgumentParser argParser = new ArgumentParser(
+        SearchRate.class.getName(), toolDescription, false, true, 1, 0,
         "[filter format string] [attributes ...]");
 
     ConnectionFactoryProvider connectionFactoryProvider;
@@ -385,8 +390,7 @@
     try
     {
       TransportFactory.setInstance(new PerfToolTCPNIOTransportFactory());
-      connectionFactoryProvider =
-          new ConnectionFactoryProvider(argParser, this);
+      connectionFactoryProvider = new ConnectionFactoryProvider(argParser, this);
       runner = new SearchPerformanceRunner(argParser, this);
 
       propertiesFileArgument = new StringArgument("propertiesFilePath", null,
@@ -415,17 +419,17 @@
 
       searchScope = new MultiChoiceArgument<SearchScope>("searchScope", 's',
           "searchScope", false, true, INFO_SEARCH_SCOPE_PLACEHOLDER.get(),
-          SearchScope.values(), false, INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE
-              .get());
+          SearchScope.values(), false,
+          INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE.get());
       searchScope.setPropertyName("searchScope");
       searchScope.setDefaultValue(SearchScope.WHOLE_SUBTREE);
       argParser.addArgument(searchScope);
 
       dereferencePolicy = new MultiChoiceArgument<DereferenceAliasesPolicy>(
           "derefpolicy", 'a', "dereferencePolicy", false, true,
-          INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(), DereferenceAliasesPolicy
-              .values(), false, INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY
-              .get());
+          INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(),
+          DereferenceAliasesPolicy.values(), false,
+          INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY.get());
       dereferencePolicy.setPropertyName("dereferencePolicy");
       dereferencePolicy.setDefaultValue(DereferenceAliasesPolicy.NEVER);
       argParser.addArgument(dereferencePolicy);
@@ -460,8 +464,8 @@
         return 0;
       }
 
-      connectionFactory =
-          connectionFactoryProvider.getAuthenticatedConnectionFactory();
+      connectionFactory = connectionFactoryProvider
+          .getAuthenticatedConnectionFactory();
       runner.validate();
     }
     catch (final ArgumentException ae)
diff --git a/sdk/src/org/opends/sdk/AVA.java b/sdk/src/org/opends/sdk/AVA.java
index 6ed0388..70c5784 100644
--- a/sdk/src/org/opends/sdk/AVA.java
+++ b/sdk/src/org/opends/sdk/AVA.java
@@ -32,6 +32,8 @@
 import static com.sun.opends.sdk.messages.Messages.*;
 import static com.sun.opends.sdk.util.StaticUtils.*;
 
+import java.util.Comparator;
+
 import org.opends.sdk.schema.*;
 
 import com.sun.opends.sdk.util.StaticUtils;
@@ -105,8 +107,8 @@
     }
     catch (final UnknownSchemaElementException e)
     {
-      final LocalizableMessage message = ERR_RDN_TYPE_NOT_FOUND.get(ava, e
-          .getMessageObject());
+      final LocalizableMessage message = ERR_RDN_TYPE_NOT_FOUND.get(ava,
+          e.getMessageObject());
       throw new LocalizedIllegalArgumentException(message);
     }
   }
@@ -138,8 +140,8 @@
     char c;
     if ((c = reader.read()) != '=')
     {
-      final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(reader
-          .getString(), attribute.getNameOrOID(), c);
+      final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(
+          reader.getString(), attribute.getNameOrOID(), c);
       throw new LocalizedIllegalArgumentException(message);
     }
 
@@ -665,6 +667,12 @@
 
   private final ByteString attributeValue;
 
+  // Cached normalized value using equality matching rule.
+  private ByteString equalityNormalizedAttributeValue = null;
+
+  // Cached normalized value using ordering matching rule.
+  private ByteString orderingNormalizedAttributeValue = null;
+
 
 
   /**
@@ -676,8 +684,8 @@
    * @param attributeValue
    *          The attribute value.
    * @throws NullPointerException
-   *           If {@code attributeType} or {@code attributeValue} was {@code
-   *           null}.
+   *           If {@code attributeType} or {@code attributeValue} was
+   *           {@code null}.
    */
   public AVA(final AttributeType attributeType, final ByteString attributeValue)
       throws NullPointerException
@@ -704,8 +712,8 @@
    * @throws UnknownSchemaElementException
    *           If {@code attributeType} was not found in the default schema.
    * @throws NullPointerException
-   *           If {@code attributeType} or {@code attributeValue} was {@code
-   *           null}.
+   *           If {@code attributeType} or {@code attributeValue} was
+   *           {@code null}.
    */
   public AVA(final String attributeType, final Object attributeValue)
       throws UnknownSchemaElementException, NullPointerException
@@ -722,55 +730,27 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public int compareTo(final AVA ava)
   {
-    int result = attributeType.compareTo(ava.attributeType);
+    final int result = attributeType.compareTo(ava.attributeType);
     if (result != 0)
     {
       return result > 0 ? 1 : -1;
     }
-    final ByteString normalizedValue = getNormalizeValue();
+
+    final ByteString normalizedValue = getOrderingNormalizedValue();
+    final ByteString otherNormalizedValue = ava.getOrderingNormalizedValue();
     final MatchingRule rule = attributeType.getOrderingMatchingRule();
-    try
+    if (rule != null)
     {
-      if (rule != null)
-      {
-        // Check equality assertion first.
-        final Assertion lteAssertion = rule
-            .getLessOrEqualAssertion(ava.attributeValue);
-        final ConditionResult lteResult = lteAssertion.matches(normalizedValue);
-        final Assertion gteAssertion = rule
-            .getGreaterOrEqualAssertion(ava.attributeValue);
-        final ConditionResult gteResult = gteAssertion.matches(normalizedValue);
-
-        if (lteResult.equals(gteResult))
-        {
-          // it is equal to the assertion value.
-          return 0;
-        }
-        else if (lteResult == ConditionResult.TRUE)
-        {
-          return -1;
-        }
-        else
-        {
-          return 1;
-        }
-      }
+      final Comparator<ByteSequence> comparator = rule.comparator();
+      return comparator.compare(normalizedValue, otherNormalizedValue);
     }
-    catch (final DecodeException de)
+    else
     {
-      // use the bytestring comparison as default.
+      return normalizedValue.compareTo(otherNormalizedValue);
     }
-
-    if (result == 0)
-    {
-      final ByteString nv1 = normalizedValue;
-      final ByteString nv2 = ava.getNormalizeValue();
-      result = nv1.compareTo(nv2);
-    }
-
-    return result;
   }
 
 
@@ -787,7 +767,26 @@
     }
     else if (obj instanceof AVA)
     {
-      return compareTo((AVA) obj) == 0;
+      final AVA ava = (AVA) obj;
+
+      if (!attributeType.equals(ava.attributeType))
+      {
+        return false;
+      }
+
+      final ByteString normalizedValue = getEqualityNormalizedValue();
+      final ByteString otherNormalizedValue = ava.getEqualityNormalizedValue();
+      final MatchingRule rule = attributeType.getEqualityMatchingRule();
+      if (rule != null)
+      {
+        final Comparator<ByteSequence> comparator = rule.comparator();
+        return comparator.compare(normalizedValue, otherNormalizedValue) != 0 ? false
+            : true;
+      }
+      else
+      {
+        return normalizedValue.equals(otherNormalizedValue);
+      }
     }
     else
     {
@@ -827,7 +826,8 @@
   @Override
   public int hashCode()
   {
-    return attributeType.hashCode() * 31 + getNormalizeValue().hashCode();
+    return attributeType.hashCode() * 31
+        + getEqualityNormalizedValue().hashCode();
   }
 
 
@@ -844,25 +844,6 @@
 
 
 
-  private ByteString getNormalizeValue()
-  {
-    final MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
-    if (matchingRule != null)
-    {
-      try
-      {
-        return matchingRule.normalizeAttributeValue(attributeValue);
-      }
-      catch (final DecodeException de)
-      {
-        // Ignore - we'll drop back to the user provided value.
-      }
-    }
-    return attributeValue;
-  }
-
-
-
   StringBuilder toString(final StringBuilder builder)
   {
     if (!attributeType.getNames().iterator().hasNext())
@@ -925,4 +906,72 @@
     }
     return builder;
   }
+
+
+
+  private ByteString getEqualityNormalizedValue()
+  {
+    final ByteString normalizedValue = equalityNormalizedAttributeValue;
+
+    if (normalizedValue != null)
+    {
+      return normalizedValue;
+    }
+
+    final MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
+    if (matchingRule != null)
+    {
+      try
+      {
+        equalityNormalizedAttributeValue = matchingRule
+            .normalizeAttributeValue(attributeValue);
+      }
+      catch (final DecodeException de)
+      {
+        // Unable to normalize, so default to byte-wise comparison.
+        equalityNormalizedAttributeValue = attributeValue;
+      }
+    }
+    else
+    {
+      // No matching rule, so default to byte-wise comparison.
+      equalityNormalizedAttributeValue = attributeValue;
+    }
+
+    return equalityNormalizedAttributeValue;
+  }
+
+
+
+  private ByteString getOrderingNormalizedValue()
+  {
+    final ByteString normalizedValue = orderingNormalizedAttributeValue;
+
+    if (normalizedValue != null)
+    {
+      return normalizedValue;
+    }
+
+    final MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
+    if (matchingRule != null)
+    {
+      try
+      {
+        orderingNormalizedAttributeValue = matchingRule
+            .normalizeAttributeValue(attributeValue);
+      }
+      catch (final DecodeException de)
+      {
+        // Unable to normalize, so default to equality matching.
+        orderingNormalizedAttributeValue = getEqualityNormalizedValue();
+      }
+    }
+    else
+    {
+      // No matching rule, so default to equality matching.
+      orderingNormalizedAttributeValue = getEqualityNormalizedValue();
+    }
+
+    return orderingNormalizedAttributeValue;
+  }
 }
diff --git a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java
index 68bfa1a..5c33709 100644
--- a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java
@@ -35,6 +35,7 @@
 import org.opends.sdk.asn1.ASN1Reader;
 import org.opends.sdk.asn1.ASN1ReaderTestCase;
 
+import org.glassfish.grizzly.TransportFactory;
 import org.glassfish.grizzly.memory.ByteBufferWrapper;
 
 
@@ -49,7 +50,8 @@
       throws IOException
   {
     final ByteBufferWrapper buffer = new ByteBufferWrapper(ByteBuffer.wrap(b));
-    final ASN1BufferReader reader = new ASN1BufferReader(maxElementSize);
+    final ASN1BufferReader reader = new ASN1BufferReader(maxElementSize,
+        TransportFactory.getInstance().getDefaultMemoryManager());
     reader.appendBytesRead(buffer);
     return reader;
   }
diff --git a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java
index f1d4cf2..18125d4 100644
--- a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java
@@ -38,6 +38,7 @@
 import org.opends.sdk.asn1.ASN1WriterTestCase;
 
 import org.glassfish.grizzly.Buffer;
+import org.glassfish.grizzly.TransportFactory;
 import org.glassfish.grizzly.memory.ByteBufferWrapper;
 
 
@@ -68,7 +69,8 @@
       throws DecodeException, IOException
   {
     final ByteBufferWrapper buffer = new ByteBufferWrapper(ByteBuffer.wrap(encodedBytes));
-    final ASN1BufferReader reader = new ASN1BufferReader(0);
+    final ASN1BufferReader reader = new ASN1BufferReader(0, TransportFactory
+        .getInstance().getDefaultMemoryManager());
     reader.appendBytesRead(buffer);
     return reader;
   }

--
Gitblit v1.10.0