From fcf635c6a4436df96ac25e50f76d7e2c78e971b9 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Mon, 03 Sep 2007 19:53:23 +0000
Subject: [PATCH] Implement support for a maximum blocked write time limit in the LDAP connection handler, which can be used to terminate client connections if an attempt to write data to the client has been blocked for too long.  This will generally occur if the client has become unresponsive or there is a network outage.

---
 opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java |  266 ++++++++++++++++++++++++++++++++++------------------
 1 files changed, 172 insertions(+), 94 deletions(-)

diff --git a/opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java b/opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java
index ef52f6c..8800955 100644
--- a/opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java
+++ b/opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java
@@ -30,23 +30,27 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
 import java.nio.channels.SocketChannel;
+import java.util.Iterator;
 
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.ConnectionSecurityProvider;
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.DebugLogLevel;
 
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
 import static org.opends.messages.ExtensionMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.util.StaticUtils.*;
 
 
+
 /**
  * This class provides an implementation of a connection security provider that
  * does not actually provide any security for the communication process.  Any
@@ -62,7 +66,6 @@
 
 
 
-
   /**
    * The buffer size in bytes that will be used for data on this connection.
    */
@@ -92,7 +95,6 @@
   public NullConnectionSecurityProvider()
   {
     super();
-
   }
 
 
@@ -121,20 +123,9 @@
 
 
   /**
-   * Initializes this connection security provider using the information in the
-   * provided configuration entry.
-   *
-   * @param  configEntry  The entry that contains the configuration for this
-   *                      connection security provider.
-   *
-   * @throws  ConfigException  If the provided entry does not contain an
-   *                           acceptable configuration for this security
-   *                           provider.
-   *
-   * @throws  InitializationException  If a problem occurs during initialization
-   *                                   that is not related to the provided
-   *                                   configuration.
+   * {@inheritDoc}
    */
+  @Override()
   public void initializeConnectionSecurityProvider(ConfigEntry configEntry)
          throws ConfigException, InitializationException
   {
@@ -146,9 +137,9 @@
 
 
   /**
-   * Performs any finalization that may be necessary for this connection
-   * security provider.
+   * {@inheritDoc}
    */
+  @Override()
   public void finalizeConnectionSecurityProvider()
   {
     // No implementation is required.
@@ -157,10 +148,9 @@
 
 
   /**
-   * Retrieves the name used to identify this security mechanism.
-   *
-   * @return  The name used to identify this security mechanism.
+   * {@inheritDoc}
    */
+  @Override()
   public String getSecurityMechanismName()
   {
     return "NULL";
@@ -169,13 +159,9 @@
 
 
   /**
-   * Indicates whether client connections using this connection security
-   * provider should be considered secure.
-   *
-   * @return  <CODE>true</CODE> if client connections using this connection
-   *          security provider should be considered secure, or
-   *          <CODE>false</CODE> if not.
+   * {@inheritDoc}
    */
+  @Override()
   public boolean isSecure()
   {
     // This is not a secure provider.
@@ -185,21 +171,9 @@
 
 
   /**
-   * Creates a new instance of this connection security provider that will be
-   * used to encode and decode all communication on the provided client
-   * connection.
-   *
-   * @param  clientConnection  The client connection with which this security
-   *                           provider will be associated.
-   * @param  socketChannel     The socket channel that may be used to
-   *                           communicate with the client.
-   *
-   * @return  The created connection security provider instance.
-   *
-   * @throws  DirectoryException  If a problem occurs while creating a new
-   *                              instance of this security provider for the
-   *                              given client connection.
+   * {@inheritDoc}
    */
+  @Override()
   public ConnectionSecurityProvider newInstance(ClientConnection
                                                       clientConnection,
                                                 SocketChannel socketChannel)
@@ -212,20 +186,9 @@
 
 
   /**
-   * Indicates that the associated client connection is being closed and that
-   * this security provider should perform any necessary processing to deal with
-   * that.  If it is indicated that the connection is still valid, then the
-   * security provider may attempt to communicate with the client to perform a
-   * graceful shutdown.
-   *
-   * @param  connectionValid  Indicates whether the Directory Server believes
-   *                          that the client connection is still valid and may
-   *                          be used for communication with the client.  Note
-   *                          that this may be inaccurate, or that the state of
-   *                          the connection may change during the course of
-   *                          this method, so the security provider must be able
-   *                          to handle failures if they arise.
+   * {@inheritDoc}
    */
+  @Override()
   public void disconnect(boolean connectionValid)
   {
     // No implementation is required.
@@ -234,13 +197,9 @@
 
 
   /**
-   * Retrieves the size in bytes that the client should use for the byte buffer
-   * meant to hold clear-text data read from or to be written to the client.
-   *
-   * @return  The size in bytes that the client should use for the byte buffer
-   *          meant to hold clear-text data read from or to be written to the
-   *          client.
+   * {@inheritDoc}
    */
+  @Override()
   public int getClearBufferSize()
   {
     return BUFFER_SIZE;
@@ -249,13 +208,9 @@
 
 
   /**
-   * Retrieves the size in bytes that the client should use for the byte buffer
-   * meant to hold encoded data read from or to be written to the client.
-   *
-   * @return  The size in bytes that the client should use for the byte buffer
-   *          meant to hold encoded data read from or to be written to the
-   *          client.
+   * {@inheritDoc}
    */
+  @Override()
   public int getEncodedBufferSize()
   {
     return BUFFER_SIZE;
@@ -264,18 +219,9 @@
 
 
   /**
-   * Reads data from a client connection, performing any necessary negotiation
-   * in the process.  Whenever any clear-text data has been obtained, then the
-   * connection security provider should make that available to the client by
-   * calling the <CODE>ClientConnection.processDataRead</CODE> method.
-   *
-   * @return  <CODE>true</CODE> if all the data in the provided buffer was
-   *          processed and the client connection can remain established, or
-   *          <CODE>false</CODE> if a decoding error occurred and requests from
-   *          this client should no longer be processed.  Note that if this
-   *          method does return <CODE>false</CODE>, then it must have already
-   *          disconnected the client.
+   * {@inheritDoc}
    */
+  @Override()
   public boolean readData()
   {
     clearBuffer.clear();
@@ -342,23 +288,9 @@
 
 
   /**
-   * Writes the data contained in the provided clear-text buffer to the client,
-   * performing any necessary encoding in the process.  It must be capable of
-   * dealing with input buffers that are larger than the value returned by the
-   * <CODE>getClearBufferSize</CODE> method.  When this method returns, the
-   * provided buffer should be in its original state with regard to the position
-   * and limit.
-   *
-   * @param  clearData  The buffer containing the clear-text data to write to
-   *                    the client.
-   *
-   * @return  <CODE>true</CODE> if all the data in the provided buffer was
-   *          written to the client and the connection may remain established,
-   *          or <CODE>false</CODE> if a problem occurred and the client
-   *          connection is no longer valid.  Note that if this method does
-   *          return <CODE>false</CODE>, then it must have already disconnected
-   *          the client.
+   * {@inheritDoc}
    */
+  @Override()
   public boolean writeData(ByteBuffer clearData)
   {
     int position = clearData.position();
@@ -376,6 +308,14 @@
                                       null);
           return false;
         }
+        else if (bytesWritten == 0)
+        {
+          // This can happen if the server can't send data to the client (e.g.,
+          // because the client is blocked or there is a network problem.  In
+          // that case, then use a selector to perform the write, timing out and
+          // terminating the client connection if necessary.
+          return writeWithTimeout(clientConnection, socketChannel, clearData);
+        }
       }
 
       return true;
@@ -411,5 +351,143 @@
       clearData.limit(limit);
     }
   }
+
+
+
+  /**
+   * Writes the contents of the provided buffer to the client, terminating the
+   * connection if the write is unsuccessful for too long (e.g., if the client
+   * is unresponsive or there is a network problem).  If possible, it will
+   * attempt to use the selector returned by the
+   * {@code ClientConnection.getWriteSelector} method, but it is capable of
+   * working even if that method returns {@code null}.
+   * <BR><BR>
+   * Note that this method has been written in a generic manner so that other
+   * connection security providers can use it to send data to the client,
+   * provided that the given buffer contains the appropriate pre-encoded
+   * information.
+   * <BR><BR>
+   * Also note that the original position and limit values will not be
+   * preserved, so if that is important to the caller, then it should record
+   * them before calling this method and restore them after it returns.
+   *
+   * @param  clientConnection  The client connection to which the data is to be
+   *                           written.
+   * @param  socketChannel     The socket channel over which to write the data.
+   * @param  buffer            The data to be written to the client.
+   *
+   * @return  <CODE>true</CODE> if all the data in the provided buffer was
+   *          written to the client and the connection may remain established,
+   *          or <CODE>false</CODE> if a problem occurred and the client
+   *          connection is no longer valid.  Note that if this method does
+   *          return <CODE>false</CODE>, then it must have already disconnected
+   *          the client.
+   *
+   * @throws  IOException  If a problem occurs while attempting to write data
+   *                       to the client.  The caller will be responsible for
+   *                       catching this and terminating the client connection.
+   */
+  public static boolean writeWithTimeout(ClientConnection clientConnection,
+                                         SocketChannel socketChannel,
+                                         ByteBuffer buffer)
+         throws IOException
+  {
+    long startTime = System.currentTimeMillis();
+    long waitTime  = clientConnection.getMaxBlockedWriteTimeLimit();
+    if (waitTime <= 0)
+    {
+      // We won't support an infinite time limit, so fall back to using
+      // five minutes, which is a very long timeout given that we're
+      // blocking a worker thread.
+      waitTime = 300000L;
+    }
+
+    long stopTime = startTime + waitTime;
+
+
+    Selector selector = clientConnection.getWriteSelector();
+    if (selector == null)
+    {
+      // The client connection does not provide a selector, so we'll fall back
+      // to a more inefficient way that will work without a selector.
+      while (buffer.hasRemaining() && (System.currentTimeMillis() < stopTime))
+      {
+        if (socketChannel.write(buffer) < 0)
+        {
+          // The client connection has been closed.  Disconnect and return.
+          clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT, false,
+                                      null);
+          return false;
+        }
+      }
+
+      if (buffer.hasRemaining())
+      {
+        // If we've gotten here, then the write timed out.  Terminate the client
+        // connection.
+        clientConnection.disconnect(DisconnectReason.IO_TIMEOUT, false, null);
+        return false;
+      }
+
+      return true;
+    }
+
+
+    // Register with the selector for handling write operations.
+    SelectionKey key = socketChannel.register(selector, SelectionKey.OP_WRITE);
+
+    try
+    {
+      selector.select(waitTime);
+      while (buffer.hasRemaining())
+      {
+        long currentTime = System.currentTimeMillis();
+        if (currentTime >= stopTime)
+        {
+          // We've been blocked for too long.  Terminate the client connection.
+          clientConnection.disconnect(DisconnectReason.IO_TIMEOUT, false, null);
+          return false;
+        }
+        else
+        {
+          waitTime = stopTime - currentTime;
+        }
+
+        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
+        while (iterator.hasNext())
+        {
+          SelectionKey k = iterator.next();
+          if (k.isWritable())
+          {
+            int bytesWritten = socketChannel.write(buffer);
+            if (bytesWritten < 0)
+            {
+              // The client connection has been closed.  Disconnect and return.
+              clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT,
+                                          false, null);
+              return false;
+            }
+
+            iterator.remove();
+          }
+        }
+
+        if (buffer.hasRemaining())
+        {
+          selector.select(waitTime);
+        }
+      }
+
+      return true;
+    }
+    finally
+    {
+      if (key.isValid())
+      {
+        key.cancel();
+        selector.selectNow();
+      }
+    }
+  }
 }
 

--
Gitblit v1.10.0