487714 - Avoid NPE in close race for async write

The race has not been solved, as with lock-free style it is difficult to prevent a
close racing with a write in progress. Instead, the code has been made more
resiliant to such state changes and exceptions thrown are converted to IOExceptions.
This commit is contained in:
Greg Wilkins 2016-02-16 10:06:25 +01:00
parent 26b6c848f3
commit fd5b3a8062
4 changed files with 80 additions and 6 deletions

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.http;
import java.io.EOFException;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
@ -342,8 +343,10 @@ public class HttpGenerator
{
if (info==null)
return Result.NEED_INFO;
switch(info.getVersion())
HttpVersion version=info.getVersion();
if (version==null)
throw new IllegalStateException("No version");
switch(version)
{
case HTTP_1_0:
if (_persistent==null)
@ -356,7 +359,7 @@ public class HttpGenerator
break;
default:
throw new IllegalArgumentException(info.getVersion()+" not supported");
throw new IllegalArgumentException(version+" not supported");
}
// Do we need a response header

View File

@ -689,6 +689,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
switch (result)
{
case NEED_INFO:
throw new EofException("request lifecycle violation");
case NEED_HEADER:
{
_header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);

View File

@ -163,12 +163,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
write(content, complete, blocker);
blocker.block();
}
catch (Throwable failure)
catch (Exception failure)
{
if (LOG.isDebugEnabled())
LOG.debug(failure);
abort(failure);
throw failure;
if (failure instanceof IOException)
throw failure;
throw new IOException(failure);
}
}

View File

@ -28,6 +28,8 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.AsyncContext;
@ -36,8 +38,12 @@ import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.HttpOutput.Interceptor;
import org.eclipse.jetty.server.HttpOutputTest.ChainedInterceptor;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.HotSwapHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.resource.Resource;
import org.hamcrest.Matchers;
import org.junit.After;
@ -53,6 +59,7 @@ public class HttpOutputTest
private Server _server;
private LocalConnector _connector;
private ContentHandler _handler;
private HotSwapHandler _swap;
@Before
public void init() throws Exception
@ -66,8 +73,10 @@ public class HttpOutputTest
_connector = new LocalConnector(_server,http,null);
_server.addConnector(_connector);
_swap=new HotSwapHandler();
_handler=new ContentHandler();
_server.setHandler(_handler);
_swap.setHandler(_handler);
_server.setHandler(_swap);
_server.start();
}
@ -598,6 +607,55 @@ public class HttpOutputTest
assertThat(response,Matchers.not(containsString("simple text")));
}
@Test
public void testWriteInterception() throws Exception
{
final Resource big = Resource.newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown=false;
_handler._content=BufferUtil.toBuffer(big,false);
_handler._arrayBuffer=new byte[1024];
_handler._interceptor = new ChainedInterceptor()
{
Interceptor _next;
@Override
public void write(ByteBuffer content, boolean complete, Callback callback)
{
String s = BufferUtil.toString(content).toUpperCase().replaceAll("BIG","BIGGER");
_next.write(BufferUtil.toBuffer(s),complete,callback);
}
@Override
public boolean isOptimizedForDirectBuffers()
{
return _next.isOptimizedForDirectBuffers();
}
@Override
public Interceptor getNextInterceptor()
{
return _next;
}
@Override
public void setNext(Interceptor interceptor)
{
_next=interceptor;
}
};
String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response,containsString("HTTP/1.1 200 OK"));
assertThat(response,Matchers.not(containsString("Content-Length")));
assertThat(response,containsString("400\tTHIS IS A BIGGER FILE"));
}
interface ChainedInterceptor extends HttpOutput.Interceptor
{
default void init(Request baseRequest) {};
void setNext(Interceptor interceptor);
}
static class ContentHandler extends AbstractHandler
{
AtomicInteger _owp = new AtomicInteger();
@ -608,11 +666,19 @@ public class HttpOutputTest
InputStream _contentInputStream;
ReadableByteChannel _contentChannel;
ByteBuffer _content;
ChainedInterceptor _interceptor;
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (_interceptor!=null)
{
_interceptor.init(baseRequest);
_interceptor.setNext(baseRequest.getResponse().getHttpOutput().getInterceptor());
baseRequest.getResponse().getHttpOutput().setInterceptor(_interceptor);
}
response.setContentType("text/plain");
final HttpOutput out = (HttpOutput) response.getOutputStream();