Merged branch 'jetty-9.3.x' into 'jetty-9.4.x'.
This commit is contained in:
commit
dd3a73e57a
|
@ -240,7 +240,6 @@ public class HttpClient extends ContainerLifeCycle
|
|||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
cookieStore.removeAll();
|
||||
decoderFactories.clear();
|
||||
handlers.clear();
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -296,6 +296,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
|
||||
public void onFailure(Throwable failure)
|
||||
{
|
||||
getHttpTransport().onStreamFailure(failure);
|
||||
if (onEarlyEOF())
|
||||
handle();
|
||||
else
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue