Merged branch 'jetty-9.3.x' into 'jetty-9.4.x'.

This commit is contained in:
Simone Bordet 2016-12-14 11:19:49 +01:00
commit dd3a73e57a
6 changed files with 192 additions and 43 deletions

View File

@ -240,7 +240,6 @@ public class HttpClient extends ContainerLifeCycle
@Override
protected void doStop() throws Exception
{
cookieStore.removeAll();
decoderFactories.clear();
handlers.clear();

View File

@ -23,12 +23,17 @@ import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -451,4 +456,139 @@ public class StreamResetTest extends AbstractTest
Assert.assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0));
}
}
@Test
public void testResetAfterAsyncRequestBlockingWriteStalledByFlowControl() throws Exception
{
int windowSize = FlowControlStrategy.DEFAULT_WINDOW_SIZE;
CountDownLatch writeLatch = new CountDownLatch(1);
start(new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
AsyncContext asyncContext = request.startAsync();
asyncContext.start(() ->
{
try
{
// Make sure we are in async wait before writing.
Thread.sleep(1000);
response.getOutputStream().write(new byte[10 * windowSize]);
asyncContext.complete();
}
catch (IOException x)
{
writeLatch.countDown();
}
catch (Throwable x)
{
x.printStackTrace();
}
});
}
});
Deque<Object> dataQueue = new ArrayDeque<>();
AtomicLong received = new AtomicLong();
CountDownLatch latch = new CountDownLatch(1);
Session client = newClient(new Session.Listener.Adapter());
MetaData.Request request = newRequest("GET", new HttpFields());
HeadersFrame frame = new HeadersFrame(request, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
dataQueue.offer(frame);
dataQueue.offer(callback);
// Do not consume the data yet.
if (received.addAndGet(frame.getData().remaining()) == windowSize)
latch.countDown();
}
});
Stream stream = promise.get(5, TimeUnit.SECONDS);
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
// Reset and consume.
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
dataQueue.stream()
.filter(item -> item instanceof Callback)
.map(item -> (Callback)item)
.forEach(Callback::succeeded);
Assert.assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testResetAfterAsyncRequestAsyncWriteStalledByFlowControl() throws Exception
{
int windowSize = FlowControlStrategy.DEFAULT_WINDOW_SIZE;
CountDownLatch writeLatch = new CountDownLatch(1);
start(new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
AsyncContext asyncContext = request.startAsync();
ServletOutputStream output = response.getOutputStream();
output.setWriteListener(new WriteListener()
{
private boolean written;
@Override
public void onWritePossible() throws IOException
{
while (output.isReady())
{
if (written)
{
asyncContext.complete();
break;
}
else
{
output.write(new byte[10 * windowSize]);
written = true;
}
}
}
@Override
public void onError(Throwable t)
{
writeLatch.countDown();
}
});
}
});
Deque<Callback> dataQueue = new ArrayDeque<>();
AtomicLong received = new AtomicLong();
CountDownLatch latch = new CountDownLatch(1);
Session client = newClient(new Session.Listener.Adapter());
MetaData.Request request = newRequest("GET", new HttpFields());
HeadersFrame frame = new HeadersFrame(request, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
dataQueue.offer(callback);
// Do not consume the data yet.
if (received.addAndGet(frame.getData().remaining()) == windowSize)
latch.countDown();
}
});
Stream stream = promise.get(5, TimeUnit.SECONDS);
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
// Reset and consume.
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
dataQueue.forEach(Callback::succeeded);
Assert.assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -296,6 +296,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
public void onFailure(Throwable failure)
{
getHttpTransport().onStreamFailure(failure);
if (onEarlyEOF())
handle();
else

View File

@ -197,6 +197,11 @@ public class HttpTransportOverHTTP2 implements HttpTransport
stream.data(frame, callback);
}
public void onStreamFailure(Throwable failure)
{
transportCallback.failed(failure);
}
public boolean onStreamTimeout(Throwable failure)
{
return transportCallback.onIdleTimeout(failure);
@ -264,9 +269,10 @@ public class HttpTransportOverHTTP2 implements HttpTransport
synchronized (this)
{
commit = this.commit;
if (state != State.TIMEOUT)
if (state == State.WRITING)
{
callback = this.callback;
this.callback = null;
this.state = State.IDLE;
}
}
@ -284,9 +290,10 @@ public class HttpTransportOverHTTP2 implements HttpTransport
synchronized (this)
{
commit = this.commit;
if (state != State.TIMEOUT)
if (state == State.WRITING)
{
callback = this.callback;
this.callback = null;
this.state = State.FAILED;
}
}
@ -317,6 +324,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
if (result)
{
callback = this.callback;
this.callback = null;
this.state = State.TIMEOUT;
}
}

View File

@ -345,7 +345,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
{
if (_response.isCommitted())
{
LOG.warn("Error Dispatch already committed");
if (LOG.isDebugEnabled())
LOG.debug("Could not perform Error Dispatch because the response is already committed, aborting");
_transport.abort((Throwable)_request.getAttribute(ERROR_EXCEPTION));
}
else
@ -359,7 +360,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
_request.setAttribute(ERROR_STATUS_CODE,code);
_request.setHandled(false);
_response.getHttpOutput().reopen();
try
{
_request.setDispatcherType(DispatcherType.ERROR);
@ -421,7 +422,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
}
}
catch (Throwable failure)
{
{
if ("org.eclipse.jetty.continuation.ContinuationThrowable".equals(failure.getClass().getName()))
LOG.ignore(failure);
else
@ -554,13 +555,13 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
_oldIdleTimeout=getIdleTimeout();
if (idleTO>=0 && _oldIdleTimeout!=idleTO)
setIdleTimeout(idleTO);
_request.setMetaData(request);
if (LOG.isDebugEnabled())
LOG.debug("REQUEST for {} on {}{}{} {} {}{}{}",request.getURIString(),this,System.lineSeparator(),
request.getMethod(),request.getURIString(),request.getHttpVersion(),System.lineSeparator(),
request.getFields());
request.getMethod(),request.getURIString(),request.getHttpVersion(),System.lineSeparator(),
request.getFields());
}
public boolean onContent(HttpInput.Content content)
@ -582,14 +583,14 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
{
if (LOG.isDebugEnabled())
LOG.debug("COMPLETE for {} written={}",getRequest().getRequestURI(),getBytesWritten());
if (_requestLog!=null )
_requestLog.log(_request, _response);
long idleTO=_configuration.getIdleTimeout();
if (idleTO>=0 && getIdleTimeout()!=_oldIdleTimeout)
setIdleTimeout(_oldIdleTimeout);
_transport.onCompleted();
}
@ -606,7 +607,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
Action action;
try
{
action=_state.handling();
action=_state.handling();
}
catch(IllegalStateException e)
{
@ -651,12 +652,12 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
if (LOG.isDebugEnabled())
LOG.debug("sendResponse info={} content={} complete={} committing={} callback={}",
info,
BufferUtil.toDetailString(content),
complete,
committing,
callback);
info,
BufferUtil.toDetailString(content),
complete,
committing,
callback);
if (committing)
{
// We need an info to commit
@ -705,8 +706,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
_committedMetaData=info;
if (LOG.isDebugEnabled())
LOG.debug("COMMIT for {} on {}{}{} {} {}{}{}",getRequest().getRequestURI(),this,System.lineSeparator(),
info.getStatus(),info.getReason(),info.getHttpVersion(),System.lineSeparator(),
info.getFields());
info.getStatus(),info.getReason(),info.getHttpVersion(),System.lineSeparator(),
info.getFields());
}
public boolean isCommitted()
@ -727,8 +728,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
_written+=BufferUtil.length(content);
sendResponse(null,content,complete,callback);
}
@Override
@Override
public void resetBuffer()
{
if(isCommitted())

View File

@ -73,7 +73,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
private final SessionFactory sessionFactory;
private final DecoratedObjectFactory objectFactory;
private Masker masker;
private final int id = ThreadLocalRandom.current().nextInt();
/**
@ -229,14 +229,14 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
this.httpClient.setExecutor(executor);
this.httpClient.setByteBufferPool(bufferPool);
addBean(this.httpClient);
if (objectFactory == null)
this.objectFactory = new DecoratedObjectFactory();
else
this.objectFactory = objectFactory;
this.extensionRegistry = new WebSocketExtensionFactory(this);
this.masker = new RandomMasker();
this.eventDriverFactory = new EventDriverFactory(policy);
this.sessionFactory = new WebSocketSessionFactory(this);
@ -263,7 +263,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
this.httpClient = new HttpClient(sslContextFactory);
this.httpClient.setExecutor(scope.getExecutor());
addBean(this.httpClient);
this.objectFactory = new DecoratedObjectFactory();
this.extensionRegistry = new WebSocketExtensionFactory(this);
@ -298,7 +298,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
{
return connect(websocket,toUri,request,(UpgradeListener)null);
}
/**
* Connect to remote websocket endpoint
*
@ -357,9 +357,9 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
LOG.debug("connect websocket {} to {}",websocket,toUri);
init();
WebSocketUpgradeRequest wsReq = new WebSocketUpgradeRequest(this,httpClient,request);
wsReq.setUpgradeListener(upgradeListener);
return wsReq.sendAsync();
}
@ -370,14 +370,14 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
if (LOG.isDebugEnabled())
LOG.debug("Stopping {}",this);
if (ShutdownThread.isRegistered(this))
{
ShutdownThread.deregister(this);
}
super.doStop();
if (LOG.isDebugEnabled())
LOG.debug("Stopped {}",this);
}
@ -390,7 +390,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
/**
* Return the number of milliseconds for a timeout of an attempted write operation.
*
*
* @return number of milliseconds for timeout of an attempted write operation
*/
public long getAsyncWriteTimeout()
@ -446,7 +446,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
/**
* Get the maximum size for buffering of a binary message.
*
*
* @return the maximum size of a binary message buffer.
*/
public int getMaxBinaryMessageBufferSize()
@ -456,7 +456,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
/**
* Get the maximum size for a binary message.
*
*
* @return the maximum size of a binary message.
*/
public long getMaxBinaryMessageSize()
@ -466,7 +466,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
/**
* Get the max idle timeout for new connections.
*
*
* @return the max idle timeout in milliseconds for new connections.
*/
public long getMaxIdleTimeout()
@ -476,7 +476,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
/**
* Get the maximum size for buffering of a text message.
*
*
* @return the maximum size of a text message buffer.
*/
public int getMaxTextMessageBufferSize()
@ -486,7 +486,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
/**
* Get the maximum size for a text message.
*
*
* @return the maximum size of a text message.
*/
public long getMaxTextMessageSize()
@ -539,7 +539,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
/**
* Factory method for new ConnectionManager
*
*
* @return the ConnectionManager instance to use
* @deprecated use HttpClient instead
*/
@ -594,7 +594,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
/**
* Set the timeout for connecting to the remote server.
*
*
* @param ms
* the timeout in milliseconds
*/
@ -618,7 +618,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
{
this.httpClient.setDispatchIO(dispatchIO);
}
public void setExecutor(Executor executor)
{
this.httpClient.setExecutor(executor);
@ -638,7 +638,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
* Set the max idle timeout for new connections.
* <p>
* Existing connections will not have their max idle timeout adjusted.
*
*
* @param ms
* the timeout in milliseconds
*/
@ -664,7 +664,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
{
return this.httpClient;
}
@Override
public String toString()
{