You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@hc.apache.org by "Lorenzo Moretti (JIRA)" <ji...@apache.org> on 2008/01/17 16:23:34 UTC

[jira] Created: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
-------------------------------------------------------------------------------------------------------------------------------

                 Key: HTTPCORE-140
                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
             Project: HttpComponents Core
          Issue Type: Bug
          Components: HttpCore NIO
    Affects Versions: 4.0-alpha6
         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
            Reporter: Lorenzo Moretti
            Priority: Minor


When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.

I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
I am not sure however if this is the right way to fix the issue.

At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
1) The socket timeout is set to 2 seconds. 
2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.

Following is the console output (comments below) for a given execution:

=======
Request started at 09:47:07.007
Connection open: [/127.0.0.1:58694] (31578843)
Connection timed out: [closed] (31578843)
Connection closed: [closed] (31578843)
Response content
-><-
Request completed (4100 ms)



Request started at 09:47:11.011
Connection open: [/127.0.0.1:58695] (367156)
Incoming entity content (bytes): 5
Serving file /home/lorenzo/bla.html
Connection closed: [closed] (367156)
Response content
->HTTP/1.1 200 OK
Date: Thu, 17 Jan 2008 14:47:11 GMT
Server: Jakarta-HttpComponents-NIO/1.1
Content-Length: 4
Content-Type: text/html
Connection: Close

bla
<-
Request completed (137 ms)
=======

The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").

The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.

=======
"Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0xadca3968> (a java.lang.Object)
	at java.lang.Object.wait(Object.java:485)
	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
	- locked <0xadca3968> (a java.lang.Object)
	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
	- locked <0xadca3968> (a java.lang.Object)
	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
=======

The source code:

=======
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
import org.apache.http.nio.NHttpConnection;
import org.apache.http.nio.NHttpServerConnection;
import org.apache.http.nio.protocol.EventListener;
import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.ListeningIOReactor;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.util.EntityUtils;
import org.apache.http.util.concurrent.Executor;

public class TestExecutorTermination
{
    /**
     * This value is used to configure the HttpParams and the delay in posting the 
     * HttpRequest entity content.
     */
    static private final int SOCKET_TIMEOUT = 2000;
    
    static private final String DOC_ROOT = "/home/lorenzo/";
    
    /**
     * HTTP POST request sent to the server. It has an incomplete content. The remaining character
     * will be sent to the server after a delay, to simulate a slow producer.
     * 
     *  The document bla.html must be present in the doc root.
     */
    static private final String REQUEST = 
        "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
    
    /**
     * User-friendly time to math the time an HTTP request is issued and the corresponding
     * executor thread (the time is in the thread name and will be visible using a
     * thread dump). 
     */
    static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
    
    /**
     * Start an HTTP server, perform a first request that creates an executor thread that
     * hangs, then perform a second successful request.
     */
    static public void main( String[] args ) throws Exception
    {
        Thread.currentThread().setName( "RequestProducer" );
        
        // Start NHttpServer on a separate thread
        Thread server = new Thread( "server" )
        {
            public void run()
            {
                try
                {
                    NHttpServer.main( new String[] { DOC_ROOT } );
                }
                catch ( Throwable t )
                {
                    t.printStackTrace();
                }
            }
        };
        server.start();
        
        // Wait for the server to be up (hopefully)
        Thread.sleep( 1000 );
        
        hangExecutorThread();
        System.out.println( "\n\n" );
        successfulExecutorTermination();
    }
    
    /**
     * Connect to the server, but send the whole entity before a socket timeout occurs
     */
    static private void successfulExecutorTermination() throws Exception
    {
        queryHttpServer( SOCKET_TIMEOUT - 1900 );
    }

    /**
     * Connect to the server, but send the whole entity after a socket timeout 
     * has closed the connection
     */
    static private void hangExecutorThread() throws Exception
    {
        queryHttpServer( SOCKET_TIMEOUT * 2 );
    }

    /**
     * Will start to write a post request to the HTTP server, start a thread
     * awaiting for the response, and then will finish writing the request to the
     * server after a delay.
     * 
     * The file bla.html exists in the doc root and contains the string "bla"
     * 
     * It seems that if the delay is longer than the configured socket timeout,
     * the executor thread will never terminate (hangs inside
     * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
     * the socket timeout, then all content is sent to the server and the executor thread
     * terminates properly.
     * 
     * @param contentDelay
     *            millis before the entity content is completely sent to the
     *            HTTP server.
     */
    static private void queryHttpServer( int contentDelay ) throws Exception
    {
        Date start = new Date();
        System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
        
        // start the producer, but send only 4 of the 5 expected bytes of the entity content
        final Socket socket = new Socket( "localhost", 8080 );
        socket.getOutputStream().write( REQUEST.getBytes() );
        
        // start the consumer on a different thread
        Thread client = new Thread( "ResponseConsumer" )
        {
            public void run()
            {
                try
                {
                    System.out.println( "Response content\n" + 
                                        "->" + getResponse( socket.getInputStream() ) + "<-" );
                }
                catch ( Throwable t )
                {
                    t.printStackTrace();
                }
            }
        };
        client.start();
   
        // simulate a slow producer: the end of the content is sent after contentDelay
        Thread.sleep( contentDelay );
        socket.getOutputStream().write( "5".getBytes() );
        
        // wait for the consumer to finish
        client.join();
        socket.close();
        
        // console feedback to know when client.join() finishes
        System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
    }
    
    static private String getResponse( InputStream is ) throws IOException
    {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int b;
        while ( ( b = is.read() ) != -1 )
        {
            bos.write( b );
        }
        
        return new String( bos.toByteArray() );
    }
    
    /**
     * This executor starts a thread for the execution of the command.
     * It is used by the ThrottlingHttpServiceHandler instance.
     */
    static private final class SimpleExecutor implements Executor
    {
        public void execute( final Runnable command )
        {
            new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
            {
                public void run()
                {
                    try
                    {
                        command.run();
                    }
                    catch ( Throwable t )
                    {
                        t.printStackTrace();
                    }
                }
            }.start();
        }    
    }
    
    /**
     * NHttpServer taken from the HttpCore example directory with the following
     * changes: 
     * 1) Socket timeout set to 2 seconds. 
     * 2) BufferingHttpServiceHandler
     *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
     *    (defined above) to execute incoming requests.
     * 3) EventLogger prints the connection hash code to easily identify connection instances 
     *    when closed.
     */
    static private class NHttpServer {

        public static void main(String[] args) throws Exception {
            if (args.length < 1) {
                System.err.println("Please specify document root directory");
                System.exit(1);
            }
            HttpParams params = new BasicHttpParams(null);
            params
                .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
                .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
                .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
                .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
                .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");

            BasicHttpProcessor httpproc = new BasicHttpProcessor();
            httpproc.addInterceptor(new ResponseDate());
            httpproc.addInterceptor(new ResponseServer());
            httpproc.addInterceptor(new ResponseContent());
            httpproc.addInterceptor(new ResponseConnControl());
            
            /*
            BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
                    httpproc,
                    new DefaultHttpResponseFactory(),
                    new DefaultConnectionReuseStrategy(),
                    params);*/
            
            ThrottlingHttpServiceHandler handler = 
                new ThrottlingHttpServiceHandler( httpproc,
                                                  new DefaultHttpResponseFactory(),
                                                  new DefaultConnectionReuseStrategy(),
                                                  new SimpleExecutor(),
                                                  params )
            {
                public void closed(final NHttpServerConnection conn) 
                {
                    //shutdownConnection( conn, null ); // un-comment this line for the workaround 
                    super.closed( conn );
                }
            };
            
            // Set up request handlers
            HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
            reqistry.register("*", new HttpFileHandler(args[0]));
            
            handler.setHandlerResolver(reqistry);
            
            // Provide an event logger
            handler.setEventListener(new EventLogger());
            
            IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
            ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
            try {
                ioReactor.listen(new InetSocketAddress(8080));
                ioReactor.execute(ioEventDispatch);
            } catch (InterruptedIOException ex) {
                System.err.println("Interrupted");
            } catch (IOException e) {
                System.err.println("I/O error: " + e.getMessage());
            }
            System.out.println("Shutdown");
        }

        static class HttpFileHandler implements HttpRequestHandler  {
            
            private final String docRoot;
            
            public HttpFileHandler(final String docRoot) {
                super();
                this.docRoot = docRoot;
            }
            
            public void handle(
                    final HttpRequest request, 
                    final HttpResponse response,
                    final HttpContext context) throws HttpException, IOException {

                String method = request.getRequestLine().getMethod().toUpperCase();
                if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
                    throw new MethodNotSupportedException(method + " method not supported"); 
                }

                if (request instanceof HttpEntityEnclosingRequest) {
                    HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
                    byte[] entityContent = EntityUtils.toByteArray(entity);
                    System.out.println("Incoming entity content (bytes): " + entityContent.length);
                }
                
                String target = request.getRequestLine().getUri();
                final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
                if (!file.exists()) {

                    response.setStatusCode(HttpStatus.SC_NOT_FOUND);
                    EntityTemplate body = new EntityTemplate(new ContentProducer() {
                        
                        public void writeTo(final OutputStream outstream) throws IOException {
                            OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
                            writer.write("<html><body><h1>");
                            writer.write("File ");
                            writer.write(file.getPath());
                            writer.write(" not found");
                            writer.write("</h1></body></html>");
                            writer.flush();
                        }
                        
                    });
                    body.setContentType("text/html; charset=UTF-8");
                    response.setEntity(body);
                    System.out.println("File " + file.getPath() + " not found");
                    
                } else if (!file.canRead() || file.isDirectory()) {
                    
                    response.setStatusCode(HttpStatus.SC_FORBIDDEN);
                    EntityTemplate body = new EntityTemplate(new ContentProducer() {
                        
                        public void writeTo(final OutputStream outstream) throws IOException {
                            OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
                            writer.write("<html><body><h1>");
                            writer.write("Access denied");
                            writer.write("</h1></body></html>");
                            writer.flush();
                        }
                        
                    });
                    body.setContentType("text/html; charset=UTF-8");
                    response.setEntity(body);
                    System.out.println("Cannot read file " + file.getPath());
                    
                } else {
                    
                    response.setStatusCode(HttpStatus.SC_OK);
                    FileEntity body = new FileEntity(file, "text/html");
                    response.setEntity(body);
                    System.out.println("Serving file " + file.getPath());
                    
                }
            }
            
        }
        
        static class EventLogger implements EventListener {

            public void connectionOpen(final NHttpConnection conn) {
                System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
            }

            public void connectionTimeout(final NHttpConnection conn) {
                System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
            }

            public void connectionClosed(final NHttpConnection conn) {
                System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
            }

            public void fatalIOException(final IOException ex, final NHttpConnection conn) {
                System.err.println("I/O error: " + ex.getMessage());
            }

            public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
                System.err.println("HTTP error: " + ex.getMessage());
            }
            
        }
    }
}
=======

Thanks for your time,

-- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org


[jira] Commented: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

Posted by "Lorenzo Moretti (JIRA)" <ji...@apache.org>.
    [ https://issues.apache.org/jira/browse/HTTPCORE-140?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12560030#action_12560030 ] 

Lorenzo Moretti commented on HTTPCORE-140:
------------------------------------------

Oleg,

I am perfectly fine with a post-BETA1 investigation. If more (or different) examples are needed do not hesitate to ask.

As a side note, the BufferingHttpServiceHandler does not suffer from the same issue since the processing of a HttpEntityEnclosingRequest is done by BufferingHttpServiceHandler.inputReady() only when the whole content is received: no waiting for data can occur.

Thanks again for your time,

-- Lorenzo

> When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
> -------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCORE-140
>                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
>             Project: HttpComponents Core
>          Issue Type: Bug
>          Components: HttpCore NIO
>    Affects Versions: 4.0-alpha6
>         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
>            Reporter: Lorenzo Moretti
>            Priority: Minor
>             Fix For: 4.0-rc1
>
>         Attachments: TestExecutorTermination.java
>
>
> When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.
> I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
> I am not sure however if this is the right way to fix the issue.
> At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
> 1) The socket timeout is set to 2 seconds. 
> 2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
> 3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.
> Following is the console output (comments below) for a given execution:
> =======
> Request started at 09:47:07.007
> Connection open: [/127.0.0.1:58694] (31578843)
> Connection timed out: [closed] (31578843)
> Connection closed: [closed] (31578843)
> Response content
> -><-
> Request completed (4100 ms)
> Request started at 09:47:11.011
> Connection open: [/127.0.0.1:58695] (367156)
> Incoming entity content (bytes): 5
> Serving file /home/lorenzo/bla.html
> Connection closed: [closed] (367156)
> Response content
> ->HTTP/1.1 200 OK
> Date: Thu, 17 Jan 2008 14:47:11 GMT
> Server: Jakarta-HttpComponents-NIO/1.1
> Content-Length: 4
> Content-Type: text/html
> Connection: Close
> bla
> <-
> Request completed (137 ms)
> =======
> The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
> The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").
> The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.
> =======
> "Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
>    java.lang.Thread.State: WAITING (on object monitor)
> 	at java.lang.Object.wait(Native Method)
> 	- waiting on <0xadca3968> (a java.lang.Object)
> 	at java.lang.Object.wait(Object.java:485)
> 	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
> 	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
> 	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
> 	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
> =======
> The source code:
> =======
> import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.io.OutputStreamWriter;
> import java.net.InetSocketAddress;
> import java.net.Socket;
> import java.net.URLDecoder;
> import java.text.DateFormat;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.HttpStatus;
> import org.apache.http.MethodNotSupportedException;
> import org.apache.http.entity.ContentProducer;
> import org.apache.http.entity.EntityTemplate;
> import org.apache.http.entity.FileEntity;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.DefaultHttpResponseFactory;
> import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
> import org.apache.http.nio.NHttpConnection;
> import org.apache.http.nio.NHttpServerConnection;
> import org.apache.http.nio.protocol.EventListener;
> import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.reactor.ListeningIOReactor;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.HttpRequestHandler;
> import org.apache.http.protocol.HttpRequestHandlerRegistry;
> import org.apache.http.protocol.ResponseConnControl;
> import org.apache.http.protocol.ResponseContent;
> import org.apache.http.protocol.ResponseDate;
> import org.apache.http.protocol.ResponseServer;
> import org.apache.http.util.EntityUtils;
> import org.apache.http.util.concurrent.Executor;
> public class TestExecutorTermination
> {
>     /**
>      * This value is used to configure the HttpParams and the delay in posting the 
>      * HttpRequest entity content.
>      */
>     static private final int SOCKET_TIMEOUT = 2000;
>     
>     static private final String DOC_ROOT = "/home/lorenzo/";
>     
>     /**
>      * HTTP POST request sent to the server. It has an incomplete content. The remaining character
>      * will be sent to the server after a delay, to simulate a slow producer.
>      * 
>      *  The document bla.html must be present in the doc root.
>      */
>     static private final String REQUEST = 
>         "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
>     
>     /**
>      * User-friendly time to math the time an HTTP request is issued and the corresponding
>      * executor thread (the time is in the thread name and will be visible using a
>      * thread dump). 
>      */
>     static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
>     
>     /**
>      * Start an HTTP server, perform a first request that creates an executor thread that
>      * hangs, then perform a second successful request.
>      */
>     static public void main( String[] args ) throws Exception
>     {
>         Thread.currentThread().setName( "RequestProducer" );
>         
>         // Start NHttpServer on a separate thread
>         Thread server = new Thread( "server" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     NHttpServer.main( new String[] { DOC_ROOT } );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         server.start();
>         
>         // Wait for the server to be up (hopefully)
>         Thread.sleep( 1000 );
>         
>         hangExecutorThread();
>         System.out.println( "\n\n" );
>         successfulExecutorTermination();
>     }
>     
>     /**
>      * Connect to the server, but send the whole entity before a socket timeout occurs
>      */
>     static private void successfulExecutorTermination() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT - 1900 );
>     }
>     /**
>      * Connect to the server, but send the whole entity after a socket timeout 
>      * has closed the connection
>      */
>     static private void hangExecutorThread() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT * 2 );
>     }
>     /**
>      * Will start to write a post request to the HTTP server, start a thread
>      * awaiting for the response, and then will finish writing the request to the
>      * server after a delay.
>      * 
>      * The file bla.html exists in the doc root and contains the string "bla"
>      * 
>      * It seems that if the delay is longer than the configured socket timeout,
>      * the executor thread will never terminate (hangs inside
>      * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
>      * the socket timeout, then all content is sent to the server and the executor thread
>      * terminates properly.
>      * 
>      * @param contentDelay
>      *            millis before the entity content is completely sent to the
>      *            HTTP server.
>      */
>     static private void queryHttpServer( int contentDelay ) throws Exception
>     {
>         Date start = new Date();
>         System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
>         
>         // start the producer, but send only 4 of the 5 expected bytes of the entity content
>         final Socket socket = new Socket( "localhost", 8080 );
>         socket.getOutputStream().write( REQUEST.getBytes() );
>         
>         // start the consumer on a different thread
>         Thread client = new Thread( "ResponseConsumer" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     System.out.println( "Response content\n" + 
>                                         "->" + getResponse( socket.getInputStream() ) + "<-" );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         client.start();
>    
>         // simulate a slow producer: the end of the content is sent after contentDelay
>         Thread.sleep( contentDelay );
>         socket.getOutputStream().write( "5".getBytes() );
>         
>         // wait for the consumer to finish
>         client.join();
>         socket.close();
>         
>         // console feedback to know when client.join() finishes
>         System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
>     }
>     
>     static private String getResponse( InputStream is ) throws IOException
>     {
>         ByteArrayOutputStream bos = new ByteArrayOutputStream();
>         int b;
>         while ( ( b = is.read() ) != -1 )
>         {
>             bos.write( b );
>         }
>         
>         return new String( bos.toByteArray() );
>     }
>     
>     /**
>      * This executor starts a thread for the execution of the command.
>      * It is used by the ThrottlingHttpServiceHandler instance.
>      */
>     static private final class SimpleExecutor implements Executor
>     {
>         public void execute( final Runnable command )
>         {
>             new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
>             {
>                 public void run()
>                 {
>                     try
>                     {
>                         command.run();
>                     }
>                     catch ( Throwable t )
>                     {
>                         t.printStackTrace();
>                     }
>                 }
>             }.start();
>         }    
>     }
>     
>     /**
>      * NHttpServer taken from the HttpCore example directory with the following
>      * changes: 
>      * 1) Socket timeout set to 2 seconds. 
>      * 2) BufferingHttpServiceHandler
>      *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
>      *    (defined above) to execute incoming requests.
>      * 3) EventLogger prints the connection hash code to easily identify connection instances 
>      *    when closed.
>      */
>     static private class NHttpServer {
>         public static void main(String[] args) throws Exception {
>             if (args.length < 1) {
>                 System.err.println("Please specify document root directory");
>                 System.exit(1);
>             }
>             HttpParams params = new BasicHttpParams(null);
>             params
>                 .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
>                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
>                 .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
>                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
>                 .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");
>             BasicHttpProcessor httpproc = new BasicHttpProcessor();
>             httpproc.addInterceptor(new ResponseDate());
>             httpproc.addInterceptor(new ResponseServer());
>             httpproc.addInterceptor(new ResponseContent());
>             httpproc.addInterceptor(new ResponseConnControl());
>             
>             /*
>             BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
>                     httpproc,
>                     new DefaultHttpResponseFactory(),
>                     new DefaultConnectionReuseStrategy(),
>                     params);*/
>             
>             ThrottlingHttpServiceHandler handler = 
>                 new ThrottlingHttpServiceHandler( httpproc,
>                                                   new DefaultHttpResponseFactory(),
>                                                   new DefaultConnectionReuseStrategy(),
>                                                   new SimpleExecutor(),
>                                                   params )
>             {
>                 public void closed(final NHttpServerConnection conn) 
>                 {
>                     //shutdownConnection( conn, null ); // un-comment this line for the workaround 
>                     super.closed( conn );
>                 }
>             };
>             
>             // Set up request handlers
>             HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
>             reqistry.register("*", new HttpFileHandler(args[0]));
>             
>             handler.setHandlerResolver(reqistry);
>             
>             // Provide an event logger
>             handler.setEventListener(new EventLogger());
>             
>             IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
>             ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
>             try {
>                 ioReactor.listen(new InetSocketAddress(8080));
>                 ioReactor.execute(ioEventDispatch);
>             } catch (InterruptedIOException ex) {
>                 System.err.println("Interrupted");
>             } catch (IOException e) {
>                 System.err.println("I/O error: " + e.getMessage());
>             }
>             System.out.println("Shutdown");
>         }
>         static class HttpFileHandler implements HttpRequestHandler  {
>             
>             private final String docRoot;
>             
>             public HttpFileHandler(final String docRoot) {
>                 super();
>                 this.docRoot = docRoot;
>             }
>             
>             public void handle(
>                     final HttpRequest request, 
>                     final HttpResponse response,
>                     final HttpContext context) throws HttpException, IOException {
>                 String method = request.getRequestLine().getMethod().toUpperCase();
>                 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
>                     throw new MethodNotSupportedException(method + " method not supported"); 
>                 }
>                 if (request instanceof HttpEntityEnclosingRequest) {
>                     HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
>                     byte[] entityContent = EntityUtils.toByteArray(entity);
>                     System.out.println("Incoming entity content (bytes): " + entityContent.length);
>                 }
>                 
>                 String target = request.getRequestLine().getUri();
>                 final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
>                 if (!file.exists()) {
>                     response.setStatusCode(HttpStatus.SC_NOT_FOUND);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("File ");
>                             writer.write(file.getPath());
>                             writer.write(" not found");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("File " + file.getPath() + " not found");
>                     
>                 } else if (!file.canRead() || file.isDirectory()) {
>                     
>                     response.setStatusCode(HttpStatus.SC_FORBIDDEN);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("Access denied");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("Cannot read file " + file.getPath());
>                     
>                 } else {
>                     
>                     response.setStatusCode(HttpStatus.SC_OK);
>                     FileEntity body = new FileEntity(file, "text/html");
>                     response.setEntity(body);
>                     System.out.println("Serving file " + file.getPath());
>                     
>                 }
>             }
>             
>         }
>         
>         static class EventLogger implements EventListener {
>             public void connectionOpen(final NHttpConnection conn) {
>                 System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionTimeout(final NHttpConnection conn) {
>                 System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionClosed(final NHttpConnection conn) {
>                 System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void fatalIOException(final IOException ex, final NHttpConnection conn) {
>                 System.err.println("I/O error: " + ex.getMessage());
>             }
>             public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
>                 System.err.println("HTTP error: " + ex.getMessage());
>             }
>             
>         }
>     }
> }
> =======
> Thanks for your time,
> -- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org


[jira] Resolved: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

Posted by "Oleg Kalnichevski (JIRA)" <ji...@apache.org>.
     [ https://issues.apache.org/jira/browse/HTTPCORE-140?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Oleg Kalnichevski resolved HTTPCORE-140.
----------------------------------------

       Resolution: Fixed
    Fix Version/s:     (was: 4.0-rc1)
                   4.0-beta2

Lorenzo,

I committed a small change to the throttling service handler that appears to have fixed the problem for me. Please review / re-test. Also added the test case you wrote to the official test suite. Many thanks for contributing a test case for a complex problem. It is really appreciated. 

I'll also do my best to bring the test coverage of the throttling protocol handlers on a par with that of buffering ones.

Oleg  

> When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
> -------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCORE-140
>                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
>             Project: HttpComponents Core
>          Issue Type: Bug
>          Components: HttpCore NIO
>    Affects Versions: 4.0-alpha6
>         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
>            Reporter: Lorenzo Moretti
>            Priority: Minor
>             Fix For: 4.0-beta2
>
>         Attachments: TestExecutorTermination.java, TestNIOHttp.java
>
>
> When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.
> I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
> I am not sure however if this is the right way to fix the issue.
> At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
> 1) The socket timeout is set to 2 seconds. 
> 2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
> 3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.
> Following is the console output (comments below) for a given execution:
> =======
> Request started at 09:47:07.007
> Connection open: [/127.0.0.1:58694] (31578843)
> Connection timed out: [closed] (31578843)
> Connection closed: [closed] (31578843)
> Response content
> -><-
> Request completed (4100 ms)
> Request started at 09:47:11.011
> Connection open: [/127.0.0.1:58695] (367156)
> Incoming entity content (bytes): 5
> Serving file /home/lorenzo/bla.html
> Connection closed: [closed] (367156)
> Response content
> ->HTTP/1.1 200 OK
> Date: Thu, 17 Jan 2008 14:47:11 GMT
> Server: Jakarta-HttpComponents-NIO/1.1
> Content-Length: 4
> Content-Type: text/html
> Connection: Close
> bla
> <-
> Request completed (137 ms)
> =======
> The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
> The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").
> The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.
> =======
> "Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
>    java.lang.Thread.State: WAITING (on object monitor)
> 	at java.lang.Object.wait(Native Method)
> 	- waiting on <0xadca3968> (a java.lang.Object)
> 	at java.lang.Object.wait(Object.java:485)
> 	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
> 	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
> 	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
> 	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
> =======
> The source code:
> =======
> import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.io.OutputStreamWriter;
> import java.net.InetSocketAddress;
> import java.net.Socket;
> import java.net.URLDecoder;
> import java.text.DateFormat;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.HttpStatus;
> import org.apache.http.MethodNotSupportedException;
> import org.apache.http.entity.ContentProducer;
> import org.apache.http.entity.EntityTemplate;
> import org.apache.http.entity.FileEntity;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.DefaultHttpResponseFactory;
> import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
> import org.apache.http.nio.NHttpConnection;
> import org.apache.http.nio.NHttpServerConnection;
> import org.apache.http.nio.protocol.EventListener;
> import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.reactor.ListeningIOReactor;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.HttpRequestHandler;
> import org.apache.http.protocol.HttpRequestHandlerRegistry;
> import org.apache.http.protocol.ResponseConnControl;
> import org.apache.http.protocol.ResponseContent;
> import org.apache.http.protocol.ResponseDate;
> import org.apache.http.protocol.ResponseServer;
> import org.apache.http.util.EntityUtils;
> import org.apache.http.util.concurrent.Executor;
> public class TestExecutorTermination
> {
>     /**
>      * This value is used to configure the HttpParams and the delay in posting the 
>      * HttpRequest entity content.
>      */
>     static private final int SOCKET_TIMEOUT = 2000;
>     
>     static private final String DOC_ROOT = "/home/lorenzo/";
>     
>     /**
>      * HTTP POST request sent to the server. It has an incomplete content. The remaining character
>      * will be sent to the server after a delay, to simulate a slow producer.
>      * 
>      *  The document bla.html must be present in the doc root.
>      */
>     static private final String REQUEST = 
>         "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
>     
>     /**
>      * User-friendly time to math the time an HTTP request is issued and the corresponding
>      * executor thread (the time is in the thread name and will be visible using a
>      * thread dump). 
>      */
>     static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
>     
>     /**
>      * Start an HTTP server, perform a first request that creates an executor thread that
>      * hangs, then perform a second successful request.
>      */
>     static public void main( String[] args ) throws Exception
>     {
>         Thread.currentThread().setName( "RequestProducer" );
>         
>         // Start NHttpServer on a separate thread
>         Thread server = new Thread( "server" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     NHttpServer.main( new String[] { DOC_ROOT } );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         server.start();
>         
>         // Wait for the server to be up (hopefully)
>         Thread.sleep( 1000 );
>         
>         hangExecutorThread();
>         System.out.println( "\n\n" );
>         successfulExecutorTermination();
>     }
>     
>     /**
>      * Connect to the server, but send the whole entity before a socket timeout occurs
>      */
>     static private void successfulExecutorTermination() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT - 1900 );
>     }
>     /**
>      * Connect to the server, but send the whole entity after a socket timeout 
>      * has closed the connection
>      */
>     static private void hangExecutorThread() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT * 2 );
>     }
>     /**
>      * Will start to write a post request to the HTTP server, start a thread
>      * awaiting for the response, and then will finish writing the request to the
>      * server after a delay.
>      * 
>      * The file bla.html exists in the doc root and contains the string "bla"
>      * 
>      * It seems that if the delay is longer than the configured socket timeout,
>      * the executor thread will never terminate (hangs inside
>      * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
>      * the socket timeout, then all content is sent to the server and the executor thread
>      * terminates properly.
>      * 
>      * @param contentDelay
>      *            millis before the entity content is completely sent to the
>      *            HTTP server.
>      */
>     static private void queryHttpServer( int contentDelay ) throws Exception
>     {
>         Date start = new Date();
>         System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
>         
>         // start the producer, but send only 4 of the 5 expected bytes of the entity content
>         final Socket socket = new Socket( "localhost", 8080 );
>         socket.getOutputStream().write( REQUEST.getBytes() );
>         
>         // start the consumer on a different thread
>         Thread client = new Thread( "ResponseConsumer" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     System.out.println( "Response content\n" + 
>                                         "->" + getResponse( socket.getInputStream() ) + "<-" );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         client.start();
>    
>         // simulate a slow producer: the end of the content is sent after contentDelay
>         Thread.sleep( contentDelay );
>         socket.getOutputStream().write( "5".getBytes() );
>         
>         // wait for the consumer to finish
>         client.join();
>         socket.close();
>         
>         // console feedback to know when client.join() finishes
>         System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
>     }
>     
>     static private String getResponse( InputStream is ) throws IOException
>     {
>         ByteArrayOutputStream bos = new ByteArrayOutputStream();
>         int b;
>         while ( ( b = is.read() ) != -1 )
>         {
>             bos.write( b );
>         }
>         
>         return new String( bos.toByteArray() );
>     }
>     
>     /**
>      * This executor starts a thread for the execution of the command.
>      * It is used by the ThrottlingHttpServiceHandler instance.
>      */
>     static private final class SimpleExecutor implements Executor
>     {
>         public void execute( final Runnable command )
>         {
>             new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
>             {
>                 public void run()
>                 {
>                     try
>                     {
>                         command.run();
>                     }
>                     catch ( Throwable t )
>                     {
>                         t.printStackTrace();
>                     }
>                 }
>             }.start();
>         }    
>     }
>     
>     /**
>      * NHttpServer taken from the HttpCore example directory with the following
>      * changes: 
>      * 1) Socket timeout set to 2 seconds. 
>      * 2) BufferingHttpServiceHandler
>      *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
>      *    (defined above) to execute incoming requests.
>      * 3) EventLogger prints the connection hash code to easily identify connection instances 
>      *    when closed.
>      */
>     static private class NHttpServer {
>         public static void main(String[] args) throws Exception {
>             if (args.length < 1) {
>                 System.err.println("Please specify document root directory");
>                 System.exit(1);
>             }
>             HttpParams params = new BasicHttpParams(null);
>             params
>                 .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
>                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
>                 .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
>                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
>                 .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");
>             BasicHttpProcessor httpproc = new BasicHttpProcessor();
>             httpproc.addInterceptor(new ResponseDate());
>             httpproc.addInterceptor(new ResponseServer());
>             httpproc.addInterceptor(new ResponseContent());
>             httpproc.addInterceptor(new ResponseConnControl());
>             
>             /*
>             BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
>                     httpproc,
>                     new DefaultHttpResponseFactory(),
>                     new DefaultConnectionReuseStrategy(),
>                     params);*/
>             
>             ThrottlingHttpServiceHandler handler = 
>                 new ThrottlingHttpServiceHandler( httpproc,
>                                                   new DefaultHttpResponseFactory(),
>                                                   new DefaultConnectionReuseStrategy(),
>                                                   new SimpleExecutor(),
>                                                   params )
>             {
>                 public void closed(final NHttpServerConnection conn) 
>                 {
>                     //shutdownConnection( conn, null ); // un-comment this line for the workaround 
>                     super.closed( conn );
>                 }
>             };
>             
>             // Set up request handlers
>             HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
>             reqistry.register("*", new HttpFileHandler(args[0]));
>             
>             handler.setHandlerResolver(reqistry);
>             
>             // Provide an event logger
>             handler.setEventListener(new EventLogger());
>             
>             IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
>             ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
>             try {
>                 ioReactor.listen(new InetSocketAddress(8080));
>                 ioReactor.execute(ioEventDispatch);
>             } catch (InterruptedIOException ex) {
>                 System.err.println("Interrupted");
>             } catch (IOException e) {
>                 System.err.println("I/O error: " + e.getMessage());
>             }
>             System.out.println("Shutdown");
>         }
>         static class HttpFileHandler implements HttpRequestHandler  {
>             
>             private final String docRoot;
>             
>             public HttpFileHandler(final String docRoot) {
>                 super();
>                 this.docRoot = docRoot;
>             }
>             
>             public void handle(
>                     final HttpRequest request, 
>                     final HttpResponse response,
>                     final HttpContext context) throws HttpException, IOException {
>                 String method = request.getRequestLine().getMethod().toUpperCase();
>                 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
>                     throw new MethodNotSupportedException(method + " method not supported"); 
>                 }
>                 if (request instanceof HttpEntityEnclosingRequest) {
>                     HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
>                     byte[] entityContent = EntityUtils.toByteArray(entity);
>                     System.out.println("Incoming entity content (bytes): " + entityContent.length);
>                 }
>                 
>                 String target = request.getRequestLine().getUri();
>                 final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
>                 if (!file.exists()) {
>                     response.setStatusCode(HttpStatus.SC_NOT_FOUND);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("File ");
>                             writer.write(file.getPath());
>                             writer.write(" not found");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("File " + file.getPath() + " not found");
>                     
>                 } else if (!file.canRead() || file.isDirectory()) {
>                     
>                     response.setStatusCode(HttpStatus.SC_FORBIDDEN);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("Access denied");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("Cannot read file " + file.getPath());
>                     
>                 } else {
>                     
>                     response.setStatusCode(HttpStatus.SC_OK);
>                     FileEntity body = new FileEntity(file, "text/html");
>                     response.setEntity(body);
>                     System.out.println("Serving file " + file.getPath());
>                     
>                 }
>             }
>             
>         }
>         
>         static class EventLogger implements EventListener {
>             public void connectionOpen(final NHttpConnection conn) {
>                 System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionTimeout(final NHttpConnection conn) {
>                 System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionClosed(final NHttpConnection conn) {
>                 System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void fatalIOException(final IOException ex, final NHttpConnection conn) {
>                 System.err.println("I/O error: " + ex.getMessage());
>             }
>             public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
>                 System.err.println("HTTP error: " + ex.getMessage());
>             }
>             
>         }
>     }
> }
> =======
> Thanks for your time,
> -- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org


[jira] Commented: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

Posted by "Lorenzo Moretti (JIRA)" <ji...@apache.org>.
    [ https://issues.apache.org/jira/browse/HTTPCORE-140?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12567048#action_12567048 ] 

Lorenzo Moretti commented on HTTPCORE-140:
------------------------------------------

Oleg,

I apologize for the unbelievably late reply.

I have tested the trunk version of httpcore-nio and the request Executor shuts down properly. I confirm that the issue is solved on my end.

Thanks for your time,

-- Lorenzo

> When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
> -------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCORE-140
>                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
>             Project: HttpComponents Core
>          Issue Type: Bug
>          Components: HttpCore NIO
>    Affects Versions: 4.0-alpha6
>         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
>            Reporter: Lorenzo Moretti
>            Priority: Minor
>             Fix For: 4.0-beta2
>
>         Attachments: TestExecutorTermination.java, TestNIOHttp.java
>
>
> When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.
> I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
> I am not sure however if this is the right way to fix the issue.
> At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
> 1) The socket timeout is set to 2 seconds. 
> 2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
> 3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.
> Following is the console output (comments below) for a given execution:
> =======
> Request started at 09:47:07.007
> Connection open: [/127.0.0.1:58694] (31578843)
> Connection timed out: [closed] (31578843)
> Connection closed: [closed] (31578843)
> Response content
> -><-
> Request completed (4100 ms)
> Request started at 09:47:11.011
> Connection open: [/127.0.0.1:58695] (367156)
> Incoming entity content (bytes): 5
> Serving file /home/lorenzo/bla.html
> Connection closed: [closed] (367156)
> Response content
> ->HTTP/1.1 200 OK
> Date: Thu, 17 Jan 2008 14:47:11 GMT
> Server: Jakarta-HttpComponents-NIO/1.1
> Content-Length: 4
> Content-Type: text/html
> Connection: Close
> bla
> <-
> Request completed (137 ms)
> =======
> The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
> The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").
> The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.
> =======
> "Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
>    java.lang.Thread.State: WAITING (on object monitor)
> 	at java.lang.Object.wait(Native Method)
> 	- waiting on <0xadca3968> (a java.lang.Object)
> 	at java.lang.Object.wait(Object.java:485)
> 	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
> 	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
> 	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
> 	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
> =======
> The source code:
> =======
> import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.io.OutputStreamWriter;
> import java.net.InetSocketAddress;
> import java.net.Socket;
> import java.net.URLDecoder;
> import java.text.DateFormat;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.HttpStatus;
> import org.apache.http.MethodNotSupportedException;
> import org.apache.http.entity.ContentProducer;
> import org.apache.http.entity.EntityTemplate;
> import org.apache.http.entity.FileEntity;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.DefaultHttpResponseFactory;
> import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
> import org.apache.http.nio.NHttpConnection;
> import org.apache.http.nio.NHttpServerConnection;
> import org.apache.http.nio.protocol.EventListener;
> import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.reactor.ListeningIOReactor;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.HttpRequestHandler;
> import org.apache.http.protocol.HttpRequestHandlerRegistry;
> import org.apache.http.protocol.ResponseConnControl;
> import org.apache.http.protocol.ResponseContent;
> import org.apache.http.protocol.ResponseDate;
> import org.apache.http.protocol.ResponseServer;
> import org.apache.http.util.EntityUtils;
> import org.apache.http.util.concurrent.Executor;
> public class TestExecutorTermination
> {
>     /**
>      * This value is used to configure the HttpParams and the delay in posting the 
>      * HttpRequest entity content.
>      */
>     static private final int SOCKET_TIMEOUT = 2000;
>     
>     static private final String DOC_ROOT = "/home/lorenzo/";
>     
>     /**
>      * HTTP POST request sent to the server. It has an incomplete content. The remaining character
>      * will be sent to the server after a delay, to simulate a slow producer.
>      * 
>      *  The document bla.html must be present in the doc root.
>      */
>     static private final String REQUEST = 
>         "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
>     
>     /**
>      * User-friendly time to math the time an HTTP request is issued and the corresponding
>      * executor thread (the time is in the thread name and will be visible using a
>      * thread dump). 
>      */
>     static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
>     
>     /**
>      * Start an HTTP server, perform a first request that creates an executor thread that
>      * hangs, then perform a second successful request.
>      */
>     static public void main( String[] args ) throws Exception
>     {
>         Thread.currentThread().setName( "RequestProducer" );
>         
>         // Start NHttpServer on a separate thread
>         Thread server = new Thread( "server" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     NHttpServer.main( new String[] { DOC_ROOT } );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         server.start();
>         
>         // Wait for the server to be up (hopefully)
>         Thread.sleep( 1000 );
>         
>         hangExecutorThread();
>         System.out.println( "\n\n" );
>         successfulExecutorTermination();
>     }
>     
>     /**
>      * Connect to the server, but send the whole entity before a socket timeout occurs
>      */
>     static private void successfulExecutorTermination() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT - 1900 );
>     }
>     /**
>      * Connect to the server, but send the whole entity after a socket timeout 
>      * has closed the connection
>      */
>     static private void hangExecutorThread() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT * 2 );
>     }
>     /**
>      * Will start to write a post request to the HTTP server, start a thread
>      * awaiting for the response, and then will finish writing the request to the
>      * server after a delay.
>      * 
>      * The file bla.html exists in the doc root and contains the string "bla"
>      * 
>      * It seems that if the delay is longer than the configured socket timeout,
>      * the executor thread will never terminate (hangs inside
>      * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
>      * the socket timeout, then all content is sent to the server and the executor thread
>      * terminates properly.
>      * 
>      * @param contentDelay
>      *            millis before the entity content is completely sent to the
>      *            HTTP server.
>      */
>     static private void queryHttpServer( int contentDelay ) throws Exception
>     {
>         Date start = new Date();
>         System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
>         
>         // start the producer, but send only 4 of the 5 expected bytes of the entity content
>         final Socket socket = new Socket( "localhost", 8080 );
>         socket.getOutputStream().write( REQUEST.getBytes() );
>         
>         // start the consumer on a different thread
>         Thread client = new Thread( "ResponseConsumer" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     System.out.println( "Response content\n" + 
>                                         "->" + getResponse( socket.getInputStream() ) + "<-" );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         client.start();
>    
>         // simulate a slow producer: the end of the content is sent after contentDelay
>         Thread.sleep( contentDelay );
>         socket.getOutputStream().write( "5".getBytes() );
>         
>         // wait for the consumer to finish
>         client.join();
>         socket.close();
>         
>         // console feedback to know when client.join() finishes
>         System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
>     }
>     
>     static private String getResponse( InputStream is ) throws IOException
>     {
>         ByteArrayOutputStream bos = new ByteArrayOutputStream();
>         int b;
>         while ( ( b = is.read() ) != -1 )
>         {
>             bos.write( b );
>         }
>         
>         return new String( bos.toByteArray() );
>     }
>     
>     /**
>      * This executor starts a thread for the execution of the command.
>      * It is used by the ThrottlingHttpServiceHandler instance.
>      */
>     static private final class SimpleExecutor implements Executor
>     {
>         public void execute( final Runnable command )
>         {
>             new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
>             {
>                 public void run()
>                 {
>                     try
>                     {
>                         command.run();
>                     }
>                     catch ( Throwable t )
>                     {
>                         t.printStackTrace();
>                     }
>                 }
>             }.start();
>         }    
>     }
>     
>     /**
>      * NHttpServer taken from the HttpCore example directory with the following
>      * changes: 
>      * 1) Socket timeout set to 2 seconds. 
>      * 2) BufferingHttpServiceHandler
>      *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
>      *    (defined above) to execute incoming requests.
>      * 3) EventLogger prints the connection hash code to easily identify connection instances 
>      *    when closed.
>      */
>     static private class NHttpServer {
>         public static void main(String[] args) throws Exception {
>             if (args.length < 1) {
>                 System.err.println("Please specify document root directory");
>                 System.exit(1);
>             }
>             HttpParams params = new BasicHttpParams(null);
>             params
>                 .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
>                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
>                 .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
>                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
>                 .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");
>             BasicHttpProcessor httpproc = new BasicHttpProcessor();
>             httpproc.addInterceptor(new ResponseDate());
>             httpproc.addInterceptor(new ResponseServer());
>             httpproc.addInterceptor(new ResponseContent());
>             httpproc.addInterceptor(new ResponseConnControl());
>             
>             /*
>             BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
>                     httpproc,
>                     new DefaultHttpResponseFactory(),
>                     new DefaultConnectionReuseStrategy(),
>                     params);*/
>             
>             ThrottlingHttpServiceHandler handler = 
>                 new ThrottlingHttpServiceHandler( httpproc,
>                                                   new DefaultHttpResponseFactory(),
>                                                   new DefaultConnectionReuseStrategy(),
>                                                   new SimpleExecutor(),
>                                                   params )
>             {
>                 public void closed(final NHttpServerConnection conn) 
>                 {
>                     //shutdownConnection( conn, null ); // un-comment this line for the workaround 
>                     super.closed( conn );
>                 }
>             };
>             
>             // Set up request handlers
>             HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
>             reqistry.register("*", new HttpFileHandler(args[0]));
>             
>             handler.setHandlerResolver(reqistry);
>             
>             // Provide an event logger
>             handler.setEventListener(new EventLogger());
>             
>             IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
>             ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
>             try {
>                 ioReactor.listen(new InetSocketAddress(8080));
>                 ioReactor.execute(ioEventDispatch);
>             } catch (InterruptedIOException ex) {
>                 System.err.println("Interrupted");
>             } catch (IOException e) {
>                 System.err.println("I/O error: " + e.getMessage());
>             }
>             System.out.println("Shutdown");
>         }
>         static class HttpFileHandler implements HttpRequestHandler  {
>             
>             private final String docRoot;
>             
>             public HttpFileHandler(final String docRoot) {
>                 super();
>                 this.docRoot = docRoot;
>             }
>             
>             public void handle(
>                     final HttpRequest request, 
>                     final HttpResponse response,
>                     final HttpContext context) throws HttpException, IOException {
>                 String method = request.getRequestLine().getMethod().toUpperCase();
>                 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
>                     throw new MethodNotSupportedException(method + " method not supported"); 
>                 }
>                 if (request instanceof HttpEntityEnclosingRequest) {
>                     HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
>                     byte[] entityContent = EntityUtils.toByteArray(entity);
>                     System.out.println("Incoming entity content (bytes): " + entityContent.length);
>                 }
>                 
>                 String target = request.getRequestLine().getUri();
>                 final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
>                 if (!file.exists()) {
>                     response.setStatusCode(HttpStatus.SC_NOT_FOUND);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("File ");
>                             writer.write(file.getPath());
>                             writer.write(" not found");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("File " + file.getPath() + " not found");
>                     
>                 } else if (!file.canRead() || file.isDirectory()) {
>                     
>                     response.setStatusCode(HttpStatus.SC_FORBIDDEN);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("Access denied");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("Cannot read file " + file.getPath());
>                     
>                 } else {
>                     
>                     response.setStatusCode(HttpStatus.SC_OK);
>                     FileEntity body = new FileEntity(file, "text/html");
>                     response.setEntity(body);
>                     System.out.println("Serving file " + file.getPath());
>                     
>                 }
>             }
>             
>         }
>         
>         static class EventLogger implements EventListener {
>             public void connectionOpen(final NHttpConnection conn) {
>                 System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionTimeout(final NHttpConnection conn) {
>                 System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionClosed(final NHttpConnection conn) {
>                 System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void fatalIOException(final IOException ex, final NHttpConnection conn) {
>                 System.err.println("I/O error: " + ex.getMessage());
>             }
>             public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
>                 System.err.println("HTTP error: " + ex.getMessage());
>             }
>             
>         }
>     }
> }
> =======
> Thanks for your time,
> -- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org


[jira] Commented: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

Posted by "Lorenzo Moretti (JIRA)" <ji...@apache.org>.
    [ https://issues.apache.org/jira/browse/HTTPCORE-140?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12562530#action_12562530 ] 

Lorenzo Moretti commented on HTTPCORE-140:
------------------------------------------

Oleg,

I will work on the JUnit test for the issue and adapt the code to version beta 1 that I have just downloaded. I will attach the test case to this slip.

-- Lorenzo

> When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
> -------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCORE-140
>                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
>             Project: HttpComponents Core
>          Issue Type: Bug
>          Components: HttpCore NIO
>    Affects Versions: 4.0-alpha6
>         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
>            Reporter: Lorenzo Moretti
>            Priority: Minor
>             Fix For: 4.0-rc1
>
>         Attachments: TestExecutorTermination.java
>
>
> When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.
> I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
> I am not sure however if this is the right way to fix the issue.
> At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
> 1) The socket timeout is set to 2 seconds. 
> 2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
> 3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.
> Following is the console output (comments below) for a given execution:
> =======
> Request started at 09:47:07.007
> Connection open: [/127.0.0.1:58694] (31578843)
> Connection timed out: [closed] (31578843)
> Connection closed: [closed] (31578843)
> Response content
> -><-
> Request completed (4100 ms)
> Request started at 09:47:11.011
> Connection open: [/127.0.0.1:58695] (367156)
> Incoming entity content (bytes): 5
> Serving file /home/lorenzo/bla.html
> Connection closed: [closed] (367156)
> Response content
> ->HTTP/1.1 200 OK
> Date: Thu, 17 Jan 2008 14:47:11 GMT
> Server: Jakarta-HttpComponents-NIO/1.1
> Content-Length: 4
> Content-Type: text/html
> Connection: Close
> bla
> <-
> Request completed (137 ms)
> =======
> The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
> The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").
> The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.
> =======
> "Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
>    java.lang.Thread.State: WAITING (on object monitor)
> 	at java.lang.Object.wait(Native Method)
> 	- waiting on <0xadca3968> (a java.lang.Object)
> 	at java.lang.Object.wait(Object.java:485)
> 	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
> 	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
> 	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
> 	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
> =======
> The source code:
> =======
> import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.io.OutputStreamWriter;
> import java.net.InetSocketAddress;
> import java.net.Socket;
> import java.net.URLDecoder;
> import java.text.DateFormat;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.HttpStatus;
> import org.apache.http.MethodNotSupportedException;
> import org.apache.http.entity.ContentProducer;
> import org.apache.http.entity.EntityTemplate;
> import org.apache.http.entity.FileEntity;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.DefaultHttpResponseFactory;
> import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
> import org.apache.http.nio.NHttpConnection;
> import org.apache.http.nio.NHttpServerConnection;
> import org.apache.http.nio.protocol.EventListener;
> import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.reactor.ListeningIOReactor;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.HttpRequestHandler;
> import org.apache.http.protocol.HttpRequestHandlerRegistry;
> import org.apache.http.protocol.ResponseConnControl;
> import org.apache.http.protocol.ResponseContent;
> import org.apache.http.protocol.ResponseDate;
> import org.apache.http.protocol.ResponseServer;
> import org.apache.http.util.EntityUtils;
> import org.apache.http.util.concurrent.Executor;
> public class TestExecutorTermination
> {
>     /**
>      * This value is used to configure the HttpParams and the delay in posting the 
>      * HttpRequest entity content.
>      */
>     static private final int SOCKET_TIMEOUT = 2000;
>     
>     static private final String DOC_ROOT = "/home/lorenzo/";
>     
>     /**
>      * HTTP POST request sent to the server. It has an incomplete content. The remaining character
>      * will be sent to the server after a delay, to simulate a slow producer.
>      * 
>      *  The document bla.html must be present in the doc root.
>      */
>     static private final String REQUEST = 
>         "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
>     
>     /**
>      * User-friendly time to math the time an HTTP request is issued and the corresponding
>      * executor thread (the time is in the thread name and will be visible using a
>      * thread dump). 
>      */
>     static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
>     
>     /**
>      * Start an HTTP server, perform a first request that creates an executor thread that
>      * hangs, then perform a second successful request.
>      */
>     static public void main( String[] args ) throws Exception
>     {
>         Thread.currentThread().setName( "RequestProducer" );
>         
>         // Start NHttpServer on a separate thread
>         Thread server = new Thread( "server" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     NHttpServer.main( new String[] { DOC_ROOT } );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         server.start();
>         
>         // Wait for the server to be up (hopefully)
>         Thread.sleep( 1000 );
>         
>         hangExecutorThread();
>         System.out.println( "\n\n" );
>         successfulExecutorTermination();
>     }
>     
>     /**
>      * Connect to the server, but send the whole entity before a socket timeout occurs
>      */
>     static private void successfulExecutorTermination() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT - 1900 );
>     }
>     /**
>      * Connect to the server, but send the whole entity after a socket timeout 
>      * has closed the connection
>      */
>     static private void hangExecutorThread() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT * 2 );
>     }
>     /**
>      * Will start to write a post request to the HTTP server, start a thread
>      * awaiting for the response, and then will finish writing the request to the
>      * server after a delay.
>      * 
>      * The file bla.html exists in the doc root and contains the string "bla"
>      * 
>      * It seems that if the delay is longer than the configured socket timeout,
>      * the executor thread will never terminate (hangs inside
>      * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
>      * the socket timeout, then all content is sent to the server and the executor thread
>      * terminates properly.
>      * 
>      * @param contentDelay
>      *            millis before the entity content is completely sent to the
>      *            HTTP server.
>      */
>     static private void queryHttpServer( int contentDelay ) throws Exception
>     {
>         Date start = new Date();
>         System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
>         
>         // start the producer, but send only 4 of the 5 expected bytes of the entity content
>         final Socket socket = new Socket( "localhost", 8080 );
>         socket.getOutputStream().write( REQUEST.getBytes() );
>         
>         // start the consumer on a different thread
>         Thread client = new Thread( "ResponseConsumer" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     System.out.println( "Response content\n" + 
>                                         "->" + getResponse( socket.getInputStream() ) + "<-" );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         client.start();
>    
>         // simulate a slow producer: the end of the content is sent after contentDelay
>         Thread.sleep( contentDelay );
>         socket.getOutputStream().write( "5".getBytes() );
>         
>         // wait for the consumer to finish
>         client.join();
>         socket.close();
>         
>         // console feedback to know when client.join() finishes
>         System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
>     }
>     
>     static private String getResponse( InputStream is ) throws IOException
>     {
>         ByteArrayOutputStream bos = new ByteArrayOutputStream();
>         int b;
>         while ( ( b = is.read() ) != -1 )
>         {
>             bos.write( b );
>         }
>         
>         return new String( bos.toByteArray() );
>     }
>     
>     /**
>      * This executor starts a thread for the execution of the command.
>      * It is used by the ThrottlingHttpServiceHandler instance.
>      */
>     static private final class SimpleExecutor implements Executor
>     {
>         public void execute( final Runnable command )
>         {
>             new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
>             {
>                 public void run()
>                 {
>                     try
>                     {
>                         command.run();
>                     }
>                     catch ( Throwable t )
>                     {
>                         t.printStackTrace();
>                     }
>                 }
>             }.start();
>         }    
>     }
>     
>     /**
>      * NHttpServer taken from the HttpCore example directory with the following
>      * changes: 
>      * 1) Socket timeout set to 2 seconds. 
>      * 2) BufferingHttpServiceHandler
>      *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
>      *    (defined above) to execute incoming requests.
>      * 3) EventLogger prints the connection hash code to easily identify connection instances 
>      *    when closed.
>      */
>     static private class NHttpServer {
>         public static void main(String[] args) throws Exception {
>             if (args.length < 1) {
>                 System.err.println("Please specify document root directory");
>                 System.exit(1);
>             }
>             HttpParams params = new BasicHttpParams(null);
>             params
>                 .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
>                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
>                 .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
>                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
>                 .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");
>             BasicHttpProcessor httpproc = new BasicHttpProcessor();
>             httpproc.addInterceptor(new ResponseDate());
>             httpproc.addInterceptor(new ResponseServer());
>             httpproc.addInterceptor(new ResponseContent());
>             httpproc.addInterceptor(new ResponseConnControl());
>             
>             /*
>             BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
>                     httpproc,
>                     new DefaultHttpResponseFactory(),
>                     new DefaultConnectionReuseStrategy(),
>                     params);*/
>             
>             ThrottlingHttpServiceHandler handler = 
>                 new ThrottlingHttpServiceHandler( httpproc,
>                                                   new DefaultHttpResponseFactory(),
>                                                   new DefaultConnectionReuseStrategy(),
>                                                   new SimpleExecutor(),
>                                                   params )
>             {
>                 public void closed(final NHttpServerConnection conn) 
>                 {
>                     //shutdownConnection( conn, null ); // un-comment this line for the workaround 
>                     super.closed( conn );
>                 }
>             };
>             
>             // Set up request handlers
>             HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
>             reqistry.register("*", new HttpFileHandler(args[0]));
>             
>             handler.setHandlerResolver(reqistry);
>             
>             // Provide an event logger
>             handler.setEventListener(new EventLogger());
>             
>             IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
>             ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
>             try {
>                 ioReactor.listen(new InetSocketAddress(8080));
>                 ioReactor.execute(ioEventDispatch);
>             } catch (InterruptedIOException ex) {
>                 System.err.println("Interrupted");
>             } catch (IOException e) {
>                 System.err.println("I/O error: " + e.getMessage());
>             }
>             System.out.println("Shutdown");
>         }
>         static class HttpFileHandler implements HttpRequestHandler  {
>             
>             private final String docRoot;
>             
>             public HttpFileHandler(final String docRoot) {
>                 super();
>                 this.docRoot = docRoot;
>             }
>             
>             public void handle(
>                     final HttpRequest request, 
>                     final HttpResponse response,
>                     final HttpContext context) throws HttpException, IOException {
>                 String method = request.getRequestLine().getMethod().toUpperCase();
>                 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
>                     throw new MethodNotSupportedException(method + " method not supported"); 
>                 }
>                 if (request instanceof HttpEntityEnclosingRequest) {
>                     HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
>                     byte[] entityContent = EntityUtils.toByteArray(entity);
>                     System.out.println("Incoming entity content (bytes): " + entityContent.length);
>                 }
>                 
>                 String target = request.getRequestLine().getUri();
>                 final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
>                 if (!file.exists()) {
>                     response.setStatusCode(HttpStatus.SC_NOT_FOUND);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("File ");
>                             writer.write(file.getPath());
>                             writer.write(" not found");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("File " + file.getPath() + " not found");
>                     
>                 } else if (!file.canRead() || file.isDirectory()) {
>                     
>                     response.setStatusCode(HttpStatus.SC_FORBIDDEN);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("Access denied");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("Cannot read file " + file.getPath());
>                     
>                 } else {
>                     
>                     response.setStatusCode(HttpStatus.SC_OK);
>                     FileEntity body = new FileEntity(file, "text/html");
>                     response.setEntity(body);
>                     System.out.println("Serving file " + file.getPath());
>                     
>                 }
>             }
>             
>         }
>         
>         static class EventLogger implements EventListener {
>             public void connectionOpen(final NHttpConnection conn) {
>                 System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionTimeout(final NHttpConnection conn) {
>                 System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionClosed(final NHttpConnection conn) {
>                 System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void fatalIOException(final IOException ex, final NHttpConnection conn) {
>                 System.err.println("I/O error: " + ex.getMessage());
>             }
>             public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
>                 System.err.println("HTTP error: " + ex.getMessage());
>             }
>             
>         }
>     }
> }
> =======
> Thanks for your time,
> -- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org


[jira] Updated: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

Posted by "Lorenzo Moretti (JIRA)" <ji...@apache.org>.
     [ https://issues.apache.org/jira/browse/HTTPCORE-140?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Lorenzo Moretti updated HTTPCORE-140:
-------------------------------------

    Attachment: TestExecutorTermination.java

I did not realize I was authorized to attach files to an issue. This is the same code present in the initial comment.

> When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
> -------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCORE-140
>                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
>             Project: HttpComponents Core
>          Issue Type: Bug
>          Components: HttpCore NIO
>    Affects Versions: 4.0-alpha6
>         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
>            Reporter: Lorenzo Moretti
>            Priority: Minor
>         Attachments: TestExecutorTermination.java
>
>
> When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.
> I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
> I am not sure however if this is the right way to fix the issue.
> At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
> 1) The socket timeout is set to 2 seconds. 
> 2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
> 3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.
> Following is the console output (comments below) for a given execution:
> =======
> Request started at 09:47:07.007
> Connection open: [/127.0.0.1:58694] (31578843)
> Connection timed out: [closed] (31578843)
> Connection closed: [closed] (31578843)
> Response content
> -><-
> Request completed (4100 ms)
> Request started at 09:47:11.011
> Connection open: [/127.0.0.1:58695] (367156)
> Incoming entity content (bytes): 5
> Serving file /home/lorenzo/bla.html
> Connection closed: [closed] (367156)
> Response content
> ->HTTP/1.1 200 OK
> Date: Thu, 17 Jan 2008 14:47:11 GMT
> Server: Jakarta-HttpComponents-NIO/1.1
> Content-Length: 4
> Content-Type: text/html
> Connection: Close
> bla
> <-
> Request completed (137 ms)
> =======
> The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
> The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").
> The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.
> =======
> "Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
>    java.lang.Thread.State: WAITING (on object monitor)
> 	at java.lang.Object.wait(Native Method)
> 	- waiting on <0xadca3968> (a java.lang.Object)
> 	at java.lang.Object.wait(Object.java:485)
> 	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
> 	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
> 	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
> 	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
> =======
> The source code:
> =======
> import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.io.OutputStreamWriter;
> import java.net.InetSocketAddress;
> import java.net.Socket;
> import java.net.URLDecoder;
> import java.text.DateFormat;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.HttpStatus;
> import org.apache.http.MethodNotSupportedException;
> import org.apache.http.entity.ContentProducer;
> import org.apache.http.entity.EntityTemplate;
> import org.apache.http.entity.FileEntity;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.DefaultHttpResponseFactory;
> import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
> import org.apache.http.nio.NHttpConnection;
> import org.apache.http.nio.NHttpServerConnection;
> import org.apache.http.nio.protocol.EventListener;
> import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.reactor.ListeningIOReactor;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.HttpRequestHandler;
> import org.apache.http.protocol.HttpRequestHandlerRegistry;
> import org.apache.http.protocol.ResponseConnControl;
> import org.apache.http.protocol.ResponseContent;
> import org.apache.http.protocol.ResponseDate;
> import org.apache.http.protocol.ResponseServer;
> import org.apache.http.util.EntityUtils;
> import org.apache.http.util.concurrent.Executor;
> public class TestExecutorTermination
> {
>     /**
>      * This value is used to configure the HttpParams and the delay in posting the 
>      * HttpRequest entity content.
>      */
>     static private final int SOCKET_TIMEOUT = 2000;
>     
>     static private final String DOC_ROOT = "/home/lorenzo/";
>     
>     /**
>      * HTTP POST request sent to the server. It has an incomplete content. The remaining character
>      * will be sent to the server after a delay, to simulate a slow producer.
>      * 
>      *  The document bla.html must be present in the doc root.
>      */
>     static private final String REQUEST = 
>         "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
>     
>     /**
>      * User-friendly time to math the time an HTTP request is issued and the corresponding
>      * executor thread (the time is in the thread name and will be visible using a
>      * thread dump). 
>      */
>     static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
>     
>     /**
>      * Start an HTTP server, perform a first request that creates an executor thread that
>      * hangs, then perform a second successful request.
>      */
>     static public void main( String[] args ) throws Exception
>     {
>         Thread.currentThread().setName( "RequestProducer" );
>         
>         // Start NHttpServer on a separate thread
>         Thread server = new Thread( "server" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     NHttpServer.main( new String[] { DOC_ROOT } );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         server.start();
>         
>         // Wait for the server to be up (hopefully)
>         Thread.sleep( 1000 );
>         
>         hangExecutorThread();
>         System.out.println( "\n\n" );
>         successfulExecutorTermination();
>     }
>     
>     /**
>      * Connect to the server, but send the whole entity before a socket timeout occurs
>      */
>     static private void successfulExecutorTermination() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT - 1900 );
>     }
>     /**
>      * Connect to the server, but send the whole entity after a socket timeout 
>      * has closed the connection
>      */
>     static private void hangExecutorThread() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT * 2 );
>     }
>     /**
>      * Will start to write a post request to the HTTP server, start a thread
>      * awaiting for the response, and then will finish writing the request to the
>      * server after a delay.
>      * 
>      * The file bla.html exists in the doc root and contains the string "bla"
>      * 
>      * It seems that if the delay is longer than the configured socket timeout,
>      * the executor thread will never terminate (hangs inside
>      * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
>      * the socket timeout, then all content is sent to the server and the executor thread
>      * terminates properly.
>      * 
>      * @param contentDelay
>      *            millis before the entity content is completely sent to the
>      *            HTTP server.
>      */
>     static private void queryHttpServer( int contentDelay ) throws Exception
>     {
>         Date start = new Date();
>         System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
>         
>         // start the producer, but send only 4 of the 5 expected bytes of the entity content
>         final Socket socket = new Socket( "localhost", 8080 );
>         socket.getOutputStream().write( REQUEST.getBytes() );
>         
>         // start the consumer on a different thread
>         Thread client = new Thread( "ResponseConsumer" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     System.out.println( "Response content\n" + 
>                                         "->" + getResponse( socket.getInputStream() ) + "<-" );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         client.start();
>    
>         // simulate a slow producer: the end of the content is sent after contentDelay
>         Thread.sleep( contentDelay );
>         socket.getOutputStream().write( "5".getBytes() );
>         
>         // wait for the consumer to finish
>         client.join();
>         socket.close();
>         
>         // console feedback to know when client.join() finishes
>         System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
>     }
>     
>     static private String getResponse( InputStream is ) throws IOException
>     {
>         ByteArrayOutputStream bos = new ByteArrayOutputStream();
>         int b;
>         while ( ( b = is.read() ) != -1 )
>         {
>             bos.write( b );
>         }
>         
>         return new String( bos.toByteArray() );
>     }
>     
>     /**
>      * This executor starts a thread for the execution of the command.
>      * It is used by the ThrottlingHttpServiceHandler instance.
>      */
>     static private final class SimpleExecutor implements Executor
>     {
>         public void execute( final Runnable command )
>         {
>             new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
>             {
>                 public void run()
>                 {
>                     try
>                     {
>                         command.run();
>                     }
>                     catch ( Throwable t )
>                     {
>                         t.printStackTrace();
>                     }
>                 }
>             }.start();
>         }    
>     }
>     
>     /**
>      * NHttpServer taken from the HttpCore example directory with the following
>      * changes: 
>      * 1) Socket timeout set to 2 seconds. 
>      * 2) BufferingHttpServiceHandler
>      *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
>      *    (defined above) to execute incoming requests.
>      * 3) EventLogger prints the connection hash code to easily identify connection instances 
>      *    when closed.
>      */
>     static private class NHttpServer {
>         public static void main(String[] args) throws Exception {
>             if (args.length < 1) {
>                 System.err.println("Please specify document root directory");
>                 System.exit(1);
>             }
>             HttpParams params = new BasicHttpParams(null);
>             params
>                 .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
>                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
>                 .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
>                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
>                 .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");
>             BasicHttpProcessor httpproc = new BasicHttpProcessor();
>             httpproc.addInterceptor(new ResponseDate());
>             httpproc.addInterceptor(new ResponseServer());
>             httpproc.addInterceptor(new ResponseContent());
>             httpproc.addInterceptor(new ResponseConnControl());
>             
>             /*
>             BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
>                     httpproc,
>                     new DefaultHttpResponseFactory(),
>                     new DefaultConnectionReuseStrategy(),
>                     params);*/
>             
>             ThrottlingHttpServiceHandler handler = 
>                 new ThrottlingHttpServiceHandler( httpproc,
>                                                   new DefaultHttpResponseFactory(),
>                                                   new DefaultConnectionReuseStrategy(),
>                                                   new SimpleExecutor(),
>                                                   params )
>             {
>                 public void closed(final NHttpServerConnection conn) 
>                 {
>                     //shutdownConnection( conn, null ); // un-comment this line for the workaround 
>                     super.closed( conn );
>                 }
>             };
>             
>             // Set up request handlers
>             HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
>             reqistry.register("*", new HttpFileHandler(args[0]));
>             
>             handler.setHandlerResolver(reqistry);
>             
>             // Provide an event logger
>             handler.setEventListener(new EventLogger());
>             
>             IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
>             ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
>             try {
>                 ioReactor.listen(new InetSocketAddress(8080));
>                 ioReactor.execute(ioEventDispatch);
>             } catch (InterruptedIOException ex) {
>                 System.err.println("Interrupted");
>             } catch (IOException e) {
>                 System.err.println("I/O error: " + e.getMessage());
>             }
>             System.out.println("Shutdown");
>         }
>         static class HttpFileHandler implements HttpRequestHandler  {
>             
>             private final String docRoot;
>             
>             public HttpFileHandler(final String docRoot) {
>                 super();
>                 this.docRoot = docRoot;
>             }
>             
>             public void handle(
>                     final HttpRequest request, 
>                     final HttpResponse response,
>                     final HttpContext context) throws HttpException, IOException {
>                 String method = request.getRequestLine().getMethod().toUpperCase();
>                 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
>                     throw new MethodNotSupportedException(method + " method not supported"); 
>                 }
>                 if (request instanceof HttpEntityEnclosingRequest) {
>                     HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
>                     byte[] entityContent = EntityUtils.toByteArray(entity);
>                     System.out.println("Incoming entity content (bytes): " + entityContent.length);
>                 }
>                 
>                 String target = request.getRequestLine().getUri();
>                 final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
>                 if (!file.exists()) {
>                     response.setStatusCode(HttpStatus.SC_NOT_FOUND);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("File ");
>                             writer.write(file.getPath());
>                             writer.write(" not found");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("File " + file.getPath() + " not found");
>                     
>                 } else if (!file.canRead() || file.isDirectory()) {
>                     
>                     response.setStatusCode(HttpStatus.SC_FORBIDDEN);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("Access denied");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("Cannot read file " + file.getPath());
>                     
>                 } else {
>                     
>                     response.setStatusCode(HttpStatus.SC_OK);
>                     FileEntity body = new FileEntity(file, "text/html");
>                     response.setEntity(body);
>                     System.out.println("Serving file " + file.getPath());
>                     
>                 }
>             }
>             
>         }
>         
>         static class EventLogger implements EventListener {
>             public void connectionOpen(final NHttpConnection conn) {
>                 System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionTimeout(final NHttpConnection conn) {
>                 System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionClosed(final NHttpConnection conn) {
>                 System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void fatalIOException(final IOException ex, final NHttpConnection conn) {
>                 System.err.println("I/O error: " + ex.getMessage());
>             }
>             public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
>                 System.err.println("HTTP error: " + ex.getMessage());
>             }
>             
>         }
>     }
> }
> =======
> Thanks for your time,
> -- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org


[jira] Updated: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

Posted by "Oleg Kalnichevski (JIRA)" <ji...@apache.org>.
     [ https://issues.apache.org/jira/browse/HTTPCORE-140?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Oleg Kalnichevski updated HTTPCORE-140:
---------------------------------------

    Fix Version/s: 4.0-rc1

Lorenzo,

I am very glad you are experimenting with the throttling versions of the protocol handlers. They do need more testing and polishing. 

I'll look into this issue immediately after the BETA1 release since it does not seem to require changes to the public API.

Oleg  

> When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
> -------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCORE-140
>                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
>             Project: HttpComponents Core
>          Issue Type: Bug
>          Components: HttpCore NIO
>    Affects Versions: 4.0-alpha6
>         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
>            Reporter: Lorenzo Moretti
>            Priority: Minor
>             Fix For: 4.0-rc1
>
>         Attachments: TestExecutorTermination.java
>
>
> When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.
> I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
> I am not sure however if this is the right way to fix the issue.
> At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
> 1) The socket timeout is set to 2 seconds. 
> 2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
> 3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.
> Following is the console output (comments below) for a given execution:
> =======
> Request started at 09:47:07.007
> Connection open: [/127.0.0.1:58694] (31578843)
> Connection timed out: [closed] (31578843)
> Connection closed: [closed] (31578843)
> Response content
> -><-
> Request completed (4100 ms)
> Request started at 09:47:11.011
> Connection open: [/127.0.0.1:58695] (367156)
> Incoming entity content (bytes): 5
> Serving file /home/lorenzo/bla.html
> Connection closed: [closed] (367156)
> Response content
> ->HTTP/1.1 200 OK
> Date: Thu, 17 Jan 2008 14:47:11 GMT
> Server: Jakarta-HttpComponents-NIO/1.1
> Content-Length: 4
> Content-Type: text/html
> Connection: Close
> bla
> <-
> Request completed (137 ms)
> =======
> The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
> The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").
> The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.
> =======
> "Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
>    java.lang.Thread.State: WAITING (on object monitor)
> 	at java.lang.Object.wait(Native Method)
> 	- waiting on <0xadca3968> (a java.lang.Object)
> 	at java.lang.Object.wait(Object.java:485)
> 	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
> 	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
> 	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
> 	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
> =======
> The source code:
> =======
> import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.io.OutputStreamWriter;
> import java.net.InetSocketAddress;
> import java.net.Socket;
> import java.net.URLDecoder;
> import java.text.DateFormat;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.HttpStatus;
> import org.apache.http.MethodNotSupportedException;
> import org.apache.http.entity.ContentProducer;
> import org.apache.http.entity.EntityTemplate;
> import org.apache.http.entity.FileEntity;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.DefaultHttpResponseFactory;
> import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
> import org.apache.http.nio.NHttpConnection;
> import org.apache.http.nio.NHttpServerConnection;
> import org.apache.http.nio.protocol.EventListener;
> import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.reactor.ListeningIOReactor;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.HttpRequestHandler;
> import org.apache.http.protocol.HttpRequestHandlerRegistry;
> import org.apache.http.protocol.ResponseConnControl;
> import org.apache.http.protocol.ResponseContent;
> import org.apache.http.protocol.ResponseDate;
> import org.apache.http.protocol.ResponseServer;
> import org.apache.http.util.EntityUtils;
> import org.apache.http.util.concurrent.Executor;
> public class TestExecutorTermination
> {
>     /**
>      * This value is used to configure the HttpParams and the delay in posting the 
>      * HttpRequest entity content.
>      */
>     static private final int SOCKET_TIMEOUT = 2000;
>     
>     static private final String DOC_ROOT = "/home/lorenzo/";
>     
>     /**
>      * HTTP POST request sent to the server. It has an incomplete content. The remaining character
>      * will be sent to the server after a delay, to simulate a slow producer.
>      * 
>      *  The document bla.html must be present in the doc root.
>      */
>     static private final String REQUEST = 
>         "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
>     
>     /**
>      * User-friendly time to math the time an HTTP request is issued and the corresponding
>      * executor thread (the time is in the thread name and will be visible using a
>      * thread dump). 
>      */
>     static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
>     
>     /**
>      * Start an HTTP server, perform a first request that creates an executor thread that
>      * hangs, then perform a second successful request.
>      */
>     static public void main( String[] args ) throws Exception
>     {
>         Thread.currentThread().setName( "RequestProducer" );
>         
>         // Start NHttpServer on a separate thread
>         Thread server = new Thread( "server" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     NHttpServer.main( new String[] { DOC_ROOT } );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         server.start();
>         
>         // Wait for the server to be up (hopefully)
>         Thread.sleep( 1000 );
>         
>         hangExecutorThread();
>         System.out.println( "\n\n" );
>         successfulExecutorTermination();
>     }
>     
>     /**
>      * Connect to the server, but send the whole entity before a socket timeout occurs
>      */
>     static private void successfulExecutorTermination() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT - 1900 );
>     }
>     /**
>      * Connect to the server, but send the whole entity after a socket timeout 
>      * has closed the connection
>      */
>     static private void hangExecutorThread() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT * 2 );
>     }
>     /**
>      * Will start to write a post request to the HTTP server, start a thread
>      * awaiting for the response, and then will finish writing the request to the
>      * server after a delay.
>      * 
>      * The file bla.html exists in the doc root and contains the string "bla"
>      * 
>      * It seems that if the delay is longer than the configured socket timeout,
>      * the executor thread will never terminate (hangs inside
>      * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
>      * the socket timeout, then all content is sent to the server and the executor thread
>      * terminates properly.
>      * 
>      * @param contentDelay
>      *            millis before the entity content is completely sent to the
>      *            HTTP server.
>      */
>     static private void queryHttpServer( int contentDelay ) throws Exception
>     {
>         Date start = new Date();
>         System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
>         
>         // start the producer, but send only 4 of the 5 expected bytes of the entity content
>         final Socket socket = new Socket( "localhost", 8080 );
>         socket.getOutputStream().write( REQUEST.getBytes() );
>         
>         // start the consumer on a different thread
>         Thread client = new Thread( "ResponseConsumer" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     System.out.println( "Response content\n" + 
>                                         "->" + getResponse( socket.getInputStream() ) + "<-" );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         client.start();
>    
>         // simulate a slow producer: the end of the content is sent after contentDelay
>         Thread.sleep( contentDelay );
>         socket.getOutputStream().write( "5".getBytes() );
>         
>         // wait for the consumer to finish
>         client.join();
>         socket.close();
>         
>         // console feedback to know when client.join() finishes
>         System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
>     }
>     
>     static private String getResponse( InputStream is ) throws IOException
>     {
>         ByteArrayOutputStream bos = new ByteArrayOutputStream();
>         int b;
>         while ( ( b = is.read() ) != -1 )
>         {
>             bos.write( b );
>         }
>         
>         return new String( bos.toByteArray() );
>     }
>     
>     /**
>      * This executor starts a thread for the execution of the command.
>      * It is used by the ThrottlingHttpServiceHandler instance.
>      */
>     static private final class SimpleExecutor implements Executor
>     {
>         public void execute( final Runnable command )
>         {
>             new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
>             {
>                 public void run()
>                 {
>                     try
>                     {
>                         command.run();
>                     }
>                     catch ( Throwable t )
>                     {
>                         t.printStackTrace();
>                     }
>                 }
>             }.start();
>         }    
>     }
>     
>     /**
>      * NHttpServer taken from the HttpCore example directory with the following
>      * changes: 
>      * 1) Socket timeout set to 2 seconds. 
>      * 2) BufferingHttpServiceHandler
>      *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
>      *    (defined above) to execute incoming requests.
>      * 3) EventLogger prints the connection hash code to easily identify connection instances 
>      *    when closed.
>      */
>     static private class NHttpServer {
>         public static void main(String[] args) throws Exception {
>             if (args.length < 1) {
>                 System.err.println("Please specify document root directory");
>                 System.exit(1);
>             }
>             HttpParams params = new BasicHttpParams(null);
>             params
>                 .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
>                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
>                 .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
>                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
>                 .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");
>             BasicHttpProcessor httpproc = new BasicHttpProcessor();
>             httpproc.addInterceptor(new ResponseDate());
>             httpproc.addInterceptor(new ResponseServer());
>             httpproc.addInterceptor(new ResponseContent());
>             httpproc.addInterceptor(new ResponseConnControl());
>             
>             /*
>             BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
>                     httpproc,
>                     new DefaultHttpResponseFactory(),
>                     new DefaultConnectionReuseStrategy(),
>                     params);*/
>             
>             ThrottlingHttpServiceHandler handler = 
>                 new ThrottlingHttpServiceHandler( httpproc,
>                                                   new DefaultHttpResponseFactory(),
>                                                   new DefaultConnectionReuseStrategy(),
>                                                   new SimpleExecutor(),
>                                                   params )
>             {
>                 public void closed(final NHttpServerConnection conn) 
>                 {
>                     //shutdownConnection( conn, null ); // un-comment this line for the workaround 
>                     super.closed( conn );
>                 }
>             };
>             
>             // Set up request handlers
>             HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
>             reqistry.register("*", new HttpFileHandler(args[0]));
>             
>             handler.setHandlerResolver(reqistry);
>             
>             // Provide an event logger
>             handler.setEventListener(new EventLogger());
>             
>             IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
>             ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
>             try {
>                 ioReactor.listen(new InetSocketAddress(8080));
>                 ioReactor.execute(ioEventDispatch);
>             } catch (InterruptedIOException ex) {
>                 System.err.println("Interrupted");
>             } catch (IOException e) {
>                 System.err.println("I/O error: " + e.getMessage());
>             }
>             System.out.println("Shutdown");
>         }
>         static class HttpFileHandler implements HttpRequestHandler  {
>             
>             private final String docRoot;
>             
>             public HttpFileHandler(final String docRoot) {
>                 super();
>                 this.docRoot = docRoot;
>             }
>             
>             public void handle(
>                     final HttpRequest request, 
>                     final HttpResponse response,
>                     final HttpContext context) throws HttpException, IOException {
>                 String method = request.getRequestLine().getMethod().toUpperCase();
>                 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
>                     throw new MethodNotSupportedException(method + " method not supported"); 
>                 }
>                 if (request instanceof HttpEntityEnclosingRequest) {
>                     HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
>                     byte[] entityContent = EntityUtils.toByteArray(entity);
>                     System.out.println("Incoming entity content (bytes): " + entityContent.length);
>                 }
>                 
>                 String target = request.getRequestLine().getUri();
>                 final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
>                 if (!file.exists()) {
>                     response.setStatusCode(HttpStatus.SC_NOT_FOUND);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("File ");
>                             writer.write(file.getPath());
>                             writer.write(" not found");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("File " + file.getPath() + " not found");
>                     
>                 } else if (!file.canRead() || file.isDirectory()) {
>                     
>                     response.setStatusCode(HttpStatus.SC_FORBIDDEN);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("Access denied");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("Cannot read file " + file.getPath());
>                     
>                 } else {
>                     
>                     response.setStatusCode(HttpStatus.SC_OK);
>                     FileEntity body = new FileEntity(file, "text/html");
>                     response.setEntity(body);
>                     System.out.println("Serving file " + file.getPath());
>                     
>                 }
>             }
>             
>         }
>         
>         static class EventLogger implements EventListener {
>             public void connectionOpen(final NHttpConnection conn) {
>                 System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionTimeout(final NHttpConnection conn) {
>                 System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionClosed(final NHttpConnection conn) {
>                 System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void fatalIOException(final IOException ex, final NHttpConnection conn) {
>                 System.err.println("I/O error: " + ex.getMessage());
>             }
>             public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
>                 System.err.println("HTTP error: " + ex.getMessage());
>             }
>             
>         }
>     }
> }
> =======
> Thanks for your time,
> -- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org


[jira] Updated: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

Posted by "Lorenzo Moretti (JIRA)" <ji...@apache.org>.
     [ https://issues.apache.org/jira/browse/HTTPCORE-140?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Lorenzo Moretti updated HTTPCORE-140:
-------------------------------------

    Attachment: TestNIOHttp.java

Oleg,

I have added to the beta 1 version of TestNIOHttp.java the test testExecutorTermination. As it is the test will fail, but I have included the workaround used in the previous code sample, mainly for debugging purposes. You will probably devise a different fix.


Overview of the test:

* The server reads the first byte of the request entity content and then set the response status to OK.
* The client will send a POST request claiming its entity contains 1 byte, but will never send that byte.


Assertions:

* The client has sent a request and has finalized its context within a reasonable amount of time (5 seconds). The time limit does not need to be enforced, it is only there to avoid that the whole test suite hangs forever:
assertTrue(clientExpectations.get(CLIENT_FINALIZED));
* The server-side executor has terminated the command it was supposed to terminate within a reasonable amount of time:
assertTrue(serverExpectations.get(COMMAND_FINISHED));
* Server-side handling of the request generated an exception:
assertNotNull(handlerException.getCause());


Code changes:

* Extracted constant DEFAULT_SERVER_SO_TIMEOUT (5 seconds) since it is referenced in the new test.
* Added overload of createHttpServiceHandler() to create both a BufferingHttpServiceHandler or a ThrottlingHttpServiceHandler with a passed-in Executor instance.
* Since I used Eclipse the imports might not be ordered like they used to be. I tried to be consistent in coding style but I might have missed some spots.


Comments on testExecutorTermination:

* The request entity is an InputStreamEntity that is fed by a pipe. I used the pipe initially to test that the server handled the request properly under normal conditions. The pipe must be closed right away otherwise the client will not post any request: it will remain blocked (on InputStreamEntity.writeTo(OutputStream) line 91) waiting for the promised byte.
* Testing the class of the server-side exception is not necessary.
* I used the ConnectingIOReactor instead of the plain socket of the previous code sample in order to be consistent with the rest of the suite, however I am not familiar with it so it might not be setup properly.


Needless to say, you can modify the test as you see fit. Let me know if you need something else.

-- Lorenzo

> When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
> -------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCORE-140
>                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
>             Project: HttpComponents Core
>          Issue Type: Bug
>          Components: HttpCore NIO
>    Affects Versions: 4.0-alpha6
>         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
>            Reporter: Lorenzo Moretti
>            Priority: Minor
>             Fix For: 4.0-rc1
>
>         Attachments: TestExecutorTermination.java, TestNIOHttp.java
>
>
> When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.
> I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
> I am not sure however if this is the right way to fix the issue.
> At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
> 1) The socket timeout is set to 2 seconds. 
> 2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
> 3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.
> Following is the console output (comments below) for a given execution:
> =======
> Request started at 09:47:07.007
> Connection open: [/127.0.0.1:58694] (31578843)
> Connection timed out: [closed] (31578843)
> Connection closed: [closed] (31578843)
> Response content
> -><-
> Request completed (4100 ms)
> Request started at 09:47:11.011
> Connection open: [/127.0.0.1:58695] (367156)
> Incoming entity content (bytes): 5
> Serving file /home/lorenzo/bla.html
> Connection closed: [closed] (367156)
> Response content
> ->HTTP/1.1 200 OK
> Date: Thu, 17 Jan 2008 14:47:11 GMT
> Server: Jakarta-HttpComponents-NIO/1.1
> Content-Length: 4
> Content-Type: text/html
> Connection: Close
> bla
> <-
> Request completed (137 ms)
> =======
> The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
> The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").
> The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.
> =======
> "Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
>    java.lang.Thread.State: WAITING (on object monitor)
> 	at java.lang.Object.wait(Native Method)
> 	- waiting on <0xadca3968> (a java.lang.Object)
> 	at java.lang.Object.wait(Object.java:485)
> 	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
> 	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
> 	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
> 	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
> =======
> The source code:
> =======
> import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.io.OutputStreamWriter;
> import java.net.InetSocketAddress;
> import java.net.Socket;
> import java.net.URLDecoder;
> import java.text.DateFormat;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.HttpStatus;
> import org.apache.http.MethodNotSupportedException;
> import org.apache.http.entity.ContentProducer;
> import org.apache.http.entity.EntityTemplate;
> import org.apache.http.entity.FileEntity;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.DefaultHttpResponseFactory;
> import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
> import org.apache.http.nio.NHttpConnection;
> import org.apache.http.nio.NHttpServerConnection;
> import org.apache.http.nio.protocol.EventListener;
> import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.reactor.ListeningIOReactor;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.HttpRequestHandler;
> import org.apache.http.protocol.HttpRequestHandlerRegistry;
> import org.apache.http.protocol.ResponseConnControl;
> import org.apache.http.protocol.ResponseContent;
> import org.apache.http.protocol.ResponseDate;
> import org.apache.http.protocol.ResponseServer;
> import org.apache.http.util.EntityUtils;
> import org.apache.http.util.concurrent.Executor;
> public class TestExecutorTermination
> {
>     /**
>      * This value is used to configure the HttpParams and the delay in posting the 
>      * HttpRequest entity content.
>      */
>     static private final int SOCKET_TIMEOUT = 2000;
>     
>     static private final String DOC_ROOT = "/home/lorenzo/";
>     
>     /**
>      * HTTP POST request sent to the server. It has an incomplete content. The remaining character
>      * will be sent to the server after a delay, to simulate a slow producer.
>      * 
>      *  The document bla.html must be present in the doc root.
>      */
>     static private final String REQUEST = 
>         "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
>     
>     /**
>      * User-friendly time to math the time an HTTP request is issued and the corresponding
>      * executor thread (the time is in the thread name and will be visible using a
>      * thread dump). 
>      */
>     static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
>     
>     /**
>      * Start an HTTP server, perform a first request that creates an executor thread that
>      * hangs, then perform a second successful request.
>      */
>     static public void main( String[] args ) throws Exception
>     {
>         Thread.currentThread().setName( "RequestProducer" );
>         
>         // Start NHttpServer on a separate thread
>         Thread server = new Thread( "server" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     NHttpServer.main( new String[] { DOC_ROOT } );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         server.start();
>         
>         // Wait for the server to be up (hopefully)
>         Thread.sleep( 1000 );
>         
>         hangExecutorThread();
>         System.out.println( "\n\n" );
>         successfulExecutorTermination();
>     }
>     
>     /**
>      * Connect to the server, but send the whole entity before a socket timeout occurs
>      */
>     static private void successfulExecutorTermination() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT - 1900 );
>     }
>     /**
>      * Connect to the server, but send the whole entity after a socket timeout 
>      * has closed the connection
>      */
>     static private void hangExecutorThread() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT * 2 );
>     }
>     /**
>      * Will start to write a post request to the HTTP server, start a thread
>      * awaiting for the response, and then will finish writing the request to the
>      * server after a delay.
>      * 
>      * The file bla.html exists in the doc root and contains the string "bla"
>      * 
>      * It seems that if the delay is longer than the configured socket timeout,
>      * the executor thread will never terminate (hangs inside
>      * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
>      * the socket timeout, then all content is sent to the server and the executor thread
>      * terminates properly.
>      * 
>      * @param contentDelay
>      *            millis before the entity content is completely sent to the
>      *            HTTP server.
>      */
>     static private void queryHttpServer( int contentDelay ) throws Exception
>     {
>         Date start = new Date();
>         System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
>         
>         // start the producer, but send only 4 of the 5 expected bytes of the entity content
>         final Socket socket = new Socket( "localhost", 8080 );
>         socket.getOutputStream().write( REQUEST.getBytes() );
>         
>         // start the consumer on a different thread
>         Thread client = new Thread( "ResponseConsumer" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     System.out.println( "Response content\n" + 
>                                         "->" + getResponse( socket.getInputStream() ) + "<-" );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         client.start();
>    
>         // simulate a slow producer: the end of the content is sent after contentDelay
>         Thread.sleep( contentDelay );
>         socket.getOutputStream().write( "5".getBytes() );
>         
>         // wait for the consumer to finish
>         client.join();
>         socket.close();
>         
>         // console feedback to know when client.join() finishes
>         System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
>     }
>     
>     static private String getResponse( InputStream is ) throws IOException
>     {
>         ByteArrayOutputStream bos = new ByteArrayOutputStream();
>         int b;
>         while ( ( b = is.read() ) != -1 )
>         {
>             bos.write( b );
>         }
>         
>         return new String( bos.toByteArray() );
>     }
>     
>     /**
>      * This executor starts a thread for the execution of the command.
>      * It is used by the ThrottlingHttpServiceHandler instance.
>      */
>     static private final class SimpleExecutor implements Executor
>     {
>         public void execute( final Runnable command )
>         {
>             new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
>             {
>                 public void run()
>                 {
>                     try
>                     {
>                         command.run();
>                     }
>                     catch ( Throwable t )
>                     {
>                         t.printStackTrace();
>                     }
>                 }
>             }.start();
>         }    
>     }
>     
>     /**
>      * NHttpServer taken from the HttpCore example directory with the following
>      * changes: 
>      * 1) Socket timeout set to 2 seconds. 
>      * 2) BufferingHttpServiceHandler
>      *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
>      *    (defined above) to execute incoming requests.
>      * 3) EventLogger prints the connection hash code to easily identify connection instances 
>      *    when closed.
>      */
>     static private class NHttpServer {
>         public static void main(String[] args) throws Exception {
>             if (args.length < 1) {
>                 System.err.println("Please specify document root directory");
>                 System.exit(1);
>             }
>             HttpParams params = new BasicHttpParams(null);
>             params
>                 .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
>                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
>                 .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
>                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
>                 .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");
>             BasicHttpProcessor httpproc = new BasicHttpProcessor();
>             httpproc.addInterceptor(new ResponseDate());
>             httpproc.addInterceptor(new ResponseServer());
>             httpproc.addInterceptor(new ResponseContent());
>             httpproc.addInterceptor(new ResponseConnControl());
>             
>             /*
>             BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
>                     httpproc,
>                     new DefaultHttpResponseFactory(),
>                     new DefaultConnectionReuseStrategy(),
>                     params);*/
>             
>             ThrottlingHttpServiceHandler handler = 
>                 new ThrottlingHttpServiceHandler( httpproc,
>                                                   new DefaultHttpResponseFactory(),
>                                                   new DefaultConnectionReuseStrategy(),
>                                                   new SimpleExecutor(),
>                                                   params )
>             {
>                 public void closed(final NHttpServerConnection conn) 
>                 {
>                     //shutdownConnection( conn, null ); // un-comment this line for the workaround 
>                     super.closed( conn );
>                 }
>             };
>             
>             // Set up request handlers
>             HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
>             reqistry.register("*", new HttpFileHandler(args[0]));
>             
>             handler.setHandlerResolver(reqistry);
>             
>             // Provide an event logger
>             handler.setEventListener(new EventLogger());
>             
>             IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
>             ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
>             try {
>                 ioReactor.listen(new InetSocketAddress(8080));
>                 ioReactor.execute(ioEventDispatch);
>             } catch (InterruptedIOException ex) {
>                 System.err.println("Interrupted");
>             } catch (IOException e) {
>                 System.err.println("I/O error: " + e.getMessage());
>             }
>             System.out.println("Shutdown");
>         }
>         static class HttpFileHandler implements HttpRequestHandler  {
>             
>             private final String docRoot;
>             
>             public HttpFileHandler(final String docRoot) {
>                 super();
>                 this.docRoot = docRoot;
>             }
>             
>             public void handle(
>                     final HttpRequest request, 
>                     final HttpResponse response,
>                     final HttpContext context) throws HttpException, IOException {
>                 String method = request.getRequestLine().getMethod().toUpperCase();
>                 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
>                     throw new MethodNotSupportedException(method + " method not supported"); 
>                 }
>                 if (request instanceof HttpEntityEnclosingRequest) {
>                     HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
>                     byte[] entityContent = EntityUtils.toByteArray(entity);
>                     System.out.println("Incoming entity content (bytes): " + entityContent.length);
>                 }
>                 
>                 String target = request.getRequestLine().getUri();
>                 final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
>                 if (!file.exists()) {
>                     response.setStatusCode(HttpStatus.SC_NOT_FOUND);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("File ");
>                             writer.write(file.getPath());
>                             writer.write(" not found");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("File " + file.getPath() + " not found");
>                     
>                 } else if (!file.canRead() || file.isDirectory()) {
>                     
>                     response.setStatusCode(HttpStatus.SC_FORBIDDEN);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("Access denied");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("Cannot read file " + file.getPath());
>                     
>                 } else {
>                     
>                     response.setStatusCode(HttpStatus.SC_OK);
>                     FileEntity body = new FileEntity(file, "text/html");
>                     response.setEntity(body);
>                     System.out.println("Serving file " + file.getPath());
>                     
>                 }
>             }
>             
>         }
>         
>         static class EventLogger implements EventListener {
>             public void connectionOpen(final NHttpConnection conn) {
>                 System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionTimeout(final NHttpConnection conn) {
>                 System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionClosed(final NHttpConnection conn) {
>                 System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void fatalIOException(final IOException ex, final NHttpConnection conn) {
>                 System.err.println("I/O error: " + ex.getMessage());
>             }
>             public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
>                 System.err.println("HTTP error: " + ex.getMessage());
>             }
>             
>         }
>     }
> }
> =======
> Thanks for your time,
> -- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org


[jira] Commented: (HTTPCORE-140) When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()

Posted by "Oleg Kalnichevski (JIRA)" <ji...@apache.org>.
    [ https://issues.apache.org/jira/browse/HTTPCORE-140?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12562527#action_12562527 ] 

Oleg Kalnichevski commented on HTTPCORE-140:
--------------------------------------------

Lorenzo,

I can reproduce the problem using the test application, which already makes 95% of the solution. Would it be a big deal for you, though, to make a JUnit out of it? Something along the line of this one:

http://hc.apache.org/httpcomponents-core/httpcore-nio/xref-test/org/apache/http/nio/protocol/TestNIOHttp.html

Oleg 

> When using a ThrottlingHttpServiceHandler, a connection timeout does not relinquish the lock in SharedInputBuffer.waitForData()
> -------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCORE-140
>                 URL: https://issues.apache.org/jira/browse/HTTPCORE-140
>             Project: HttpComponents Core
>          Issue Type: Bug
>          Components: HttpCore NIO
>    Affects Versions: 4.0-alpha6
>         Environment: Linux RHEL 4, jdk 1.6.0_02-b05.
>            Reporter: Lorenzo Moretti
>            Priority: Minor
>             Fix For: 4.0-rc1
>
>         Attachments: TestExecutorTermination.java
>
>
> When the ThrottlingHttpServiceHandler.requestReceived() is invoked, the request handling can be passed to a worker thread through an implementation of org.apache.http.util.concurrent.Executor before the whole entity content (if any) is received by the server. If the worker thread tries to read the request entity content through HttpEntityEnclosingRequest.getEntity().getContent().read() before the whole content is received, then it waits for data using SharedInputBuffer.waitForData(). If the connection is closed through a socket timeout, waitForData() does not return, and the worker thread does not terminate.
> I have set the severity to minor because there is a simple workaround: invoking ThrottlingHttpServiceHandler.shutdownConnection(conn, null) when ThrottlingHttpServiceHandler.closed() is called (an anonymous class that inherits from ThrottlingHttpServiceHandler works fine): shutdownConnection() invokes ServerConnState.shutdown() which in turn shuts down the SharedInputBuffer, interrupting the wait.
> I am not sure however if this is the right way to fix the issue.
> At the bottom is the source code for a simple application to demonstrate the issue. The code uses a copy of the NHttpServer found in the examples directory with a few modifications:
> 1) The socket timeout is set to 2 seconds. 
> 2) The BufferingHttpServiceHandler was replaced with a ThrottlingHttpServiceHandler that spawns a thread through its executor to handle every incoming request.
> 3) The EventLogger prints the connection hash code in parenthesis to easily identify connection instances when closed.
> Following is the console output (comments below) for a given execution:
> =======
> Request started at 09:47:07.007
> Connection open: [/127.0.0.1:58694] (31578843)
> Connection timed out: [closed] (31578843)
> Connection closed: [closed] (31578843)
> Response content
> -><-
> Request completed (4100 ms)
> Request started at 09:47:11.011
> Connection open: [/127.0.0.1:58695] (367156)
> Incoming entity content (bytes): 5
> Serving file /home/lorenzo/bla.html
> Connection closed: [closed] (367156)
> Response content
> ->HTTP/1.1 200 OK
> Date: Thu, 17 Jan 2008 14:47:11 GMT
> Server: Jakarta-HttpComponents-NIO/1.1
> Content-Length: 4
> Content-Type: text/html
> Connection: Close
> bla
> <-
> Request completed (137 ms)
> =======
> The first request (09:47:07.007) is too slow to send the entity content to the server. The server times out the connection, and the response consumer does not get anything back from the server (text between '->' and '<-' is empty), which is expected.
> The second request (09:47:11.011) shows that the server is still able to function perfectly well: the entity content is sent in a reasonable time span, and file /bla.html is served back to the client (the file contains the string "bla").
> The thread dump (taken at 09:47:25) shows the executor thread for the 09:47:07.007 request still waiting for data (the reactor thread and the dispatch thread are polling events normally, I have ommitted their dump). The executor for the 09:47:11.011 request has terminated.
> =======
> "Executor_09:47:07.007" prio=10 tid=0x083c5000 nid=0x59e5 in Object.wait() [0xad498000..0xad498ec0]
>    java.lang.Thread.State: WAITING (on object monitor)
> 	at java.lang.Object.wait(Native Method)
> 	- waiting on <0xadca3968> (a java.lang.Object)
> 	at java.lang.Object.wait(Object.java:485)
> 	at org.apache.http.nio.util.SharedInputBuffer.waitForData(SharedInputBuffer.java:105)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.util.SharedInputBuffer.read(SharedInputBuffer.java:155)
> 	- locked <0xadca3968> (a java.lang.Object)
> 	at org.apache.http.nio.entity.ContentInputStream.read(ContentInputStream.java:59)
> 	at org.apache.http.util.EntityUtils.toByteArray(EntityUtils.java:79)
> 	at TestExecutorTermination$NHttpServer$HttpFileHandler.handle(TestExecutorTermination.java:314)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.handleRequest(ThrottlingHttpServiceHandler.java:477)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler.access$000(ThrottlingHttpServiceHandler.java:91)
> 	at org.apache.http.nio.protocol.ThrottlingHttpServiceHandler$1.run(ThrottlingHttpServiceHandler.java:195)
> 	at TestExecutorTermination$SimpleExecutor$1.run(TestExecutorTermination.java:208)
> =======
> The source code:
> =======
> import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.io.OutputStreamWriter;
> import java.net.InetSocketAddress;
> import java.net.Socket;
> import java.net.URLDecoder;
> import java.text.DateFormat;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpResponse;
> import org.apache.http.HttpStatus;
> import org.apache.http.MethodNotSupportedException;
> import org.apache.http.entity.ContentProducer;
> import org.apache.http.entity.EntityTemplate;
> import org.apache.http.entity.FileEntity;
> import org.apache.http.impl.DefaultConnectionReuseStrategy;
> import org.apache.http.impl.DefaultHttpResponseFactory;
> import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
> import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
> import org.apache.http.nio.NHttpConnection;
> import org.apache.http.nio.NHttpServerConnection;
> import org.apache.http.nio.protocol.EventListener;
> import org.apache.http.nio.protocol.ThrottlingHttpServiceHandler;
> import org.apache.http.nio.reactor.IOEventDispatch;
> import org.apache.http.nio.reactor.ListeningIOReactor;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.CoreConnectionPNames;
> import org.apache.http.params.CoreProtocolPNames;
> import org.apache.http.params.HttpParams;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.HttpContext;
> import org.apache.http.protocol.HttpRequestHandler;
> import org.apache.http.protocol.HttpRequestHandlerRegistry;
> import org.apache.http.protocol.ResponseConnControl;
> import org.apache.http.protocol.ResponseContent;
> import org.apache.http.protocol.ResponseDate;
> import org.apache.http.protocol.ResponseServer;
> import org.apache.http.util.EntityUtils;
> import org.apache.http.util.concurrent.Executor;
> public class TestExecutorTermination
> {
>     /**
>      * This value is used to configure the HttpParams and the delay in posting the 
>      * HttpRequest entity content.
>      */
>     static private final int SOCKET_TIMEOUT = 2000;
>     
>     static private final String DOC_ROOT = "/home/lorenzo/";
>     
>     /**
>      * HTTP POST request sent to the server. It has an incomplete content. The remaining character
>      * will be sent to the server after a delay, to simulate a slow producer.
>      * 
>      *  The document bla.html must be present in the doc root.
>      */
>     static private final String REQUEST = 
>         "POST /bla.html HTTP/1.1\r\nConnection: Close\r\nContent-Length: 5\r\n\r\n1234";
>     
>     /**
>      * User-friendly time to math the time an HTTP request is issued and the corresponding
>      * executor thread (the time is in the thread name and will be visible using a
>      * thread dump). 
>      */
>     static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "hh:mm:ss.sss" );
>     
>     /**
>      * Start an HTTP server, perform a first request that creates an executor thread that
>      * hangs, then perform a second successful request.
>      */
>     static public void main( String[] args ) throws Exception
>     {
>         Thread.currentThread().setName( "RequestProducer" );
>         
>         // Start NHttpServer on a separate thread
>         Thread server = new Thread( "server" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     NHttpServer.main( new String[] { DOC_ROOT } );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         server.start();
>         
>         // Wait for the server to be up (hopefully)
>         Thread.sleep( 1000 );
>         
>         hangExecutorThread();
>         System.out.println( "\n\n" );
>         successfulExecutorTermination();
>     }
>     
>     /**
>      * Connect to the server, but send the whole entity before a socket timeout occurs
>      */
>     static private void successfulExecutorTermination() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT - 1900 );
>     }
>     /**
>      * Connect to the server, but send the whole entity after a socket timeout 
>      * has closed the connection
>      */
>     static private void hangExecutorThread() throws Exception
>     {
>         queryHttpServer( SOCKET_TIMEOUT * 2 );
>     }
>     /**
>      * Will start to write a post request to the HTTP server, start a thread
>      * awaiting for the response, and then will finish writing the request to the
>      * server after a delay.
>      * 
>      * The file bla.html exists in the doc root and contains the string "bla"
>      * 
>      * It seems that if the delay is longer than the configured socket timeout,
>      * the executor thread will never terminate (hangs inside
>      * org.apache.http.nio.util.SharedInputBuffer.waitForData()). If the delay is shorter than
>      * the socket timeout, then all content is sent to the server and the executor thread
>      * terminates properly.
>      * 
>      * @param contentDelay
>      *            millis before the entity content is completely sent to the
>      *            HTTP server.
>      */
>     static private void queryHttpServer( int contentDelay ) throws Exception
>     {
>         Date start = new Date();
>         System.out.println( "Request started at " + DATE_FORMAT.format( start ) + "" );
>         
>         // start the producer, but send only 4 of the 5 expected bytes of the entity content
>         final Socket socket = new Socket( "localhost", 8080 );
>         socket.getOutputStream().write( REQUEST.getBytes() );
>         
>         // start the consumer on a different thread
>         Thread client = new Thread( "ResponseConsumer" )
>         {
>             public void run()
>             {
>                 try
>                 {
>                     System.out.println( "Response content\n" + 
>                                         "->" + getResponse( socket.getInputStream() ) + "<-" );
>                 }
>                 catch ( Throwable t )
>                 {
>                     t.printStackTrace();
>                 }
>             }
>         };
>         client.start();
>    
>         // simulate a slow producer: the end of the content is sent after contentDelay
>         Thread.sleep( contentDelay );
>         socket.getOutputStream().write( "5".getBytes() );
>         
>         // wait for the consumer to finish
>         client.join();
>         socket.close();
>         
>         // console feedback to know when client.join() finishes
>         System.out.println( "Request completed (" + ( System.currentTimeMillis() - start.getTime() ) + " ms)" );
>     }
>     
>     static private String getResponse( InputStream is ) throws IOException
>     {
>         ByteArrayOutputStream bos = new ByteArrayOutputStream();
>         int b;
>         while ( ( b = is.read() ) != -1 )
>         {
>             bos.write( b );
>         }
>         
>         return new String( bos.toByteArray() );
>     }
>     
>     /**
>      * This executor starts a thread for the execution of the command.
>      * It is used by the ThrottlingHttpServiceHandler instance.
>      */
>     static private final class SimpleExecutor implements Executor
>     {
>         public void execute( final Runnable command )
>         {
>             new Thread( "Executor_" + DATE_FORMAT.format( new Date() ) )
>             {
>                 public void run()
>                 {
>                     try
>                     {
>                         command.run();
>                     }
>                     catch ( Throwable t )
>                     {
>                         t.printStackTrace();
>                     }
>                 }
>             }.start();
>         }    
>     }
>     
>     /**
>      * NHttpServer taken from the HttpCore example directory with the following
>      * changes: 
>      * 1) Socket timeout set to 2 seconds. 
>      * 2) BufferingHttpServiceHandler
>      *    replaced with a ThrottlingHttpServiceHandler that uses a SimpleExecutor
>      *    (defined above) to execute incoming requests.
>      * 3) EventLogger prints the connection hash code to easily identify connection instances 
>      *    when closed.
>      */
>     static private class NHttpServer {
>         public static void main(String[] args) throws Exception {
>             if (args.length < 1) {
>                 System.err.println("Please specify document root directory");
>                 System.exit(1);
>             }
>             HttpParams params = new BasicHttpParams(null);
>             params
>                 .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT)
>                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
>                 .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
>                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
>                 .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "Jakarta-HttpComponents-NIO/1.1");
>             BasicHttpProcessor httpproc = new BasicHttpProcessor();
>             httpproc.addInterceptor(new ResponseDate());
>             httpproc.addInterceptor(new ResponseServer());
>             httpproc.addInterceptor(new ResponseContent());
>             httpproc.addInterceptor(new ResponseConnControl());
>             
>             /*
>             BufferingHttpServiceHandler handler = new BufferingHttpServiceHandler(
>                     httpproc,
>                     new DefaultHttpResponseFactory(),
>                     new DefaultConnectionReuseStrategy(),
>                     params);*/
>             
>             ThrottlingHttpServiceHandler handler = 
>                 new ThrottlingHttpServiceHandler( httpproc,
>                                                   new DefaultHttpResponseFactory(),
>                                                   new DefaultConnectionReuseStrategy(),
>                                                   new SimpleExecutor(),
>                                                   params )
>             {
>                 public void closed(final NHttpServerConnection conn) 
>                 {
>                     //shutdownConnection( conn, null ); // un-comment this line for the workaround 
>                     super.closed( conn );
>                 }
>             };
>             
>             // Set up request handlers
>             HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
>             reqistry.register("*", new HttpFileHandler(args[0]));
>             
>             handler.setHandlerResolver(reqistry);
>             
>             // Provide an event logger
>             handler.setEventListener(new EventLogger());
>             
>             IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(handler, params);
>             ListeningIOReactor ioReactor = new DefaultListeningIOReactor(1, params);
>             try {
>                 ioReactor.listen(new InetSocketAddress(8080));
>                 ioReactor.execute(ioEventDispatch);
>             } catch (InterruptedIOException ex) {
>                 System.err.println("Interrupted");
>             } catch (IOException e) {
>                 System.err.println("I/O error: " + e.getMessage());
>             }
>             System.out.println("Shutdown");
>         }
>         static class HttpFileHandler implements HttpRequestHandler  {
>             
>             private final String docRoot;
>             
>             public HttpFileHandler(final String docRoot) {
>                 super();
>                 this.docRoot = docRoot;
>             }
>             
>             public void handle(
>                     final HttpRequest request, 
>                     final HttpResponse response,
>                     final HttpContext context) throws HttpException, IOException {
>                 String method = request.getRequestLine().getMethod().toUpperCase();
>                 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
>                     throw new MethodNotSupportedException(method + " method not supported"); 
>                 }
>                 if (request instanceof HttpEntityEnclosingRequest) {
>                     HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
>                     byte[] entityContent = EntityUtils.toByteArray(entity);
>                     System.out.println("Incoming entity content (bytes): " + entityContent.length);
>                 }
>                 
>                 String target = request.getRequestLine().getUri();
>                 final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8"));
>                 if (!file.exists()) {
>                     response.setStatusCode(HttpStatus.SC_NOT_FOUND);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("File ");
>                             writer.write(file.getPath());
>                             writer.write(" not found");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("File " + file.getPath() + " not found");
>                     
>                 } else if (!file.canRead() || file.isDirectory()) {
>                     
>                     response.setStatusCode(HttpStatus.SC_FORBIDDEN);
>                     EntityTemplate body = new EntityTemplate(new ContentProducer() {
>                         
>                         public void writeTo(final OutputStream outstream) throws IOException {
>                             OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8"); 
>                             writer.write("<html><body><h1>");
>                             writer.write("Access denied");
>                             writer.write("</h1></body></html>");
>                             writer.flush();
>                         }
>                         
>                     });
>                     body.setContentType("text/html; charset=UTF-8");
>                     response.setEntity(body);
>                     System.out.println("Cannot read file " + file.getPath());
>                     
>                 } else {
>                     
>                     response.setStatusCode(HttpStatus.SC_OK);
>                     FileEntity body = new FileEntity(file, "text/html");
>                     response.setEntity(body);
>                     System.out.println("Serving file " + file.getPath());
>                     
>                 }
>             }
>             
>         }
>         
>         static class EventLogger implements EventListener {
>             public void connectionOpen(final NHttpConnection conn) {
>                 System.out.println("Connection open: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionTimeout(final NHttpConnection conn) {
>                 System.out.println("Connection timed out: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void connectionClosed(final NHttpConnection conn) {
>                 System.out.println("Connection closed: " + conn + " (" + conn.hashCode() + ")");
>             }
>             public void fatalIOException(final IOException ex, final NHttpConnection conn) {
>                 System.err.println("I/O error: " + ex.getMessage());
>             }
>             public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
>                 System.err.println("HTTP error: " + ex.getMessage());
>             }
>             
>         }
>     }
> }
> =======
> Thanks for your time,
> -- Lorenzo

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org