Race condition in SharedOutputBuffer may cause a connection to hang.
--------------------------------------------------------------------

                 Key: HTTPCORE-170
                 URL: https://issues.apache.org/jira/browse/HTTPCORE-170
             Project: HttpComponents HttpCore
          Issue Type: Bug
          Components: HttpCore NIO
    Affects Versions: 4.0-beta2
         Environment: Synapse 1.2
            Reporter: Jason Walton


I found this problem while tracking down a problem in Synapse 1.2's nhttp 
transport.  Occasionally under heavy load, Synapse would dump out this 
exception:

<log4j:event logger="org.apache.synapse.transport.nhttp.ServerHandler" 
timestamp="1219446649613" level="ERROR" thread="HttpServerWorker-27">
<log4j:message><![CDATA[Unexpected HTTP protocol error : Response already 
submitted]]></log4j:message>
<log4j:throwable><![CDATA[org.apache.http.HttpException: Response already 
submitted
        at 
org.apache.http.impl.nio.DefaultNHttpServerConnection.submitResponse(DefaultNHttpServerConnection.java:202)
        at 
org.apache.synapse.transport.nhttp.ServerHandler.commitResponse(ServerHandler.java:212)
        at 
org.apache.synapse.transport.nhttp.ServerWorker.run(ServerWorker.java:208)
        at 
java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:885)
        at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
        at java.lang.Thread.run(Thread.java:619)
]]></log4j:throwable>
</log4j:event>

I added a bunch of "System.out.println"s to Synapse and to httpcore (printlns 
because httpcore doesn't appear to use any logging libraries).  Here's an 
example of a connection which has failed (the "33211218" is how I'm correlating 
data on a per-connection basis, and is the return value of 
DefaultNHttpServerConnection.hashCode()):
 
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.consumeInput()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: ServerHandler.requestReceived()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.consumeInput() - contentDecoder complete
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.resetInput
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: ServerHandler.commitResponse()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.submitResponse()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.produceOutput()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: ServerHandler.outputReady()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: ServerHandler.outputReady(): 
Wrote 0 and encoder complete: true
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.resetOutput()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.produceOutput()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.produceOutput() closing session2
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.consumeInput()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: ServerHandler.requestReceived()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.consumeInput() - contentDecoder complete
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.resetInput()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: ServerHandler.commitResponse()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.submitResponse()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.produceOutput()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: ServerHandler.outputReady()
Wed Aug 27 12:32:31 EDT 2008: jwalton: 33211218: ServerHandler.outputReady(): 
Wrote 0 and encoder complete: false
Wed Aug 27 12:37:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.consumeInput()
Wed Aug 27 12:37:31 EDT 2008: jwalton: 33211218: ServerHandler.requestReceived()
Wed Aug 27 12:37:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.consumeInput() - contentDecoder complete
Wed Aug 27 12:37:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.resetInput()
Wed Aug 27 12:37:31 EDT 2008: jwalton: 33211218: ServerHandler.commitResponse()
Wed Aug 27 12:37:31 EDT 2008: jwalton: 33211218: 
DefaultNHttpServerConnection.submitResponse()
Wed Aug 27 12:37:31 EDT 2008: jwalton: 33211218: RESPONSE ALREADY SUBMITTED!
Wed Aug 27 12:37:31 EDT 2008: jwalton: 33211218: 
ServerHandler.commitResponse(): HttpException: org.apache.http.HttpException: 
Response already submitted

As you can see, the failure occurs at the last entry with the timestamp 
12:32:31, where the ServerHandler tries to write the reply, but for some reason 
encoder.isComplete() returns false.  Another request comes in on the 
still-waiting-to-write-a-reply ServerHandler a full five minutes later, and we 
get our exception.

The problem appears to be in SharedOutputBuffer.  
SharedOutputBuffer.produceContent() is called from the [I/O Dispatcher] thread, 
and says:

  synchronized(this.mutex) {
      ...
      if (!this.endOfStream) {
          // suspend output events
          this.ioctrl.suspendOutput();
      }
      ...
  }

So, essentially, "If we've run out of stuff to write to the encoder, but we're 
expecting more (!this.endOfStream), then tell the IOReactor to stop sending us 
output events for now, because we don't have any data to write anyways."  Note 
that this is protected by a mutex.
  
  
SharedOutputBuffer.writeCompleted() is called from the [HttpServerWorker] 
thread says:

    public void writeCompleted() throws IOException {
        if (this.endOfStream) {
            return;
        }
        this.endOfStream = true;
        this.ioctrl.requestOutput();
    }

So, if this were called immediately after the producedContent() call, this 
would set endOfStream to true, and start up the output events again.  The next 
call into produceContent() would end up calling encoder.complete(), and all 
would be well in the world.
    
The problem here is that writeCompleted() is not protected by a mutex. This 
means that the "if(!this.endOfStream)" line in produceContent() could be 
executed by the I/O Dispatcher, followed immediately by writeCompleted() by the 
HttpServerWorker, followed by "this.ioctrl.suspendOutput()" in the I/O 
Dispatcher again.  This would set us up in a state where endOfStream is true, 
so we're ready to finish writing data to the encoder, however we have suspended 
output events, so we will never get that opportunity.

Fix is:

    public void writeCompleted() throws IOException {
+       synchronized(this.mutex) {
            if (this.endOfStream) {
                return;
            }
            this.endOfStream = true;
            this.ioctrl.requestOutput();
+       }
    }

-- 
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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to