459081 - http2 push failures.
Introduced ExecutionStrategy.dispatch() to handle the case where resources that are being pushed block.
This commit is contained in:
parent
a88d52b4e0
commit
48887377c9
|
@ -19,10 +19,10 @@
|
|||
package org.eclipse.jetty.http2.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
|
@ -242,4 +242,86 @@ public class PushCacheFilterTest extends AbstractTest
|
|||
});
|
||||
Assert.assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushWithoutPrimaryResponseContent() throws Exception
|
||||
{
|
||||
final String primaryResource = "/primary.html";
|
||||
final String secondaryResource = "/secondary.png";
|
||||
start(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
String requestURI = request.getRequestURI();
|
||||
final ServletOutputStream output = response.getOutputStream();
|
||||
if (requestURI.endsWith(secondaryResource))
|
||||
output.write("SECONDARY".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
});
|
||||
|
||||
final Session session = newClient(new Session.Listener.Adapter());
|
||||
|
||||
// Request for the primary and secondary resource to build the cache.
|
||||
final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
|
||||
HttpFields primaryFields = new HttpFields();
|
||||
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
||||
final CountDownLatch warmupLatch = new CountDownLatch(1);
|
||||
session.newStream(new HeadersFrame(0, primaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
if (frame.isEndStream())
|
||||
{
|
||||
// Request for the secondary resource.
|
||||
HttpFields secondaryFields = new HttpFields();
|
||||
secondaryFields.put(HttpHeader.REFERER, primaryURI);
|
||||
MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
|
||||
session.newStream(new HeadersFrame(0, secondaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
warmupLatch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
Assert.assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Request again the primary resource, we should get the secondary resource pushed.
|
||||
primaryRequest = newRequest("GET", primaryResource, primaryFields);
|
||||
final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
|
||||
final CountDownLatch pushLatch = new CountDownLatch(1);
|
||||
session.newStream(new HeadersFrame(0, primaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
if (frame.isEndStream())
|
||||
primaryResponseLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
||||
{
|
||||
return new Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
callback.succeeded();
|
||||
if (frame.isEndStream())
|
||||
pushLatch.countDown();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
Assert.assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
|
||||
Assert.assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public class HTTP2Connection extends AbstractConnection implements Connection.Up
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("HTTP2 onUpgradeTo {} {}", this, BufferUtil.toDetailString(prefilled));
|
||||
producer.prefill(prefilled);
|
||||
producer.buffer = prefilled;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -117,9 +117,11 @@ public class HTTP2Connection extends AbstractConnection implements Connection.Up
|
|||
return false;
|
||||
}
|
||||
|
||||
protected void offerTask(Runnable task)
|
||||
protected void offerTask(Runnable task, boolean dispatch)
|
||||
{
|
||||
tasks.offer(task);
|
||||
if (dispatch)
|
||||
executionStrategy.dispatch();
|
||||
}
|
||||
|
||||
private class HTTP2Producer implements ExecutionStrategy.Producer
|
||||
|
@ -135,12 +137,14 @@ public class HTTP2Connection extends AbstractConnection implements Connection.Up
|
|||
if (task != null)
|
||||
return task;
|
||||
|
||||
if (isFillInterested())
|
||||
return null;
|
||||
|
||||
if (buffer == null)
|
||||
buffer = byteBufferPool.acquire(bufferSize, false); // TODO: make directness customizable
|
||||
boolean looping = BufferUtil.hasContent(buffer);
|
||||
while (true)
|
||||
{
|
||||
if (buffer == null)
|
||||
buffer = byteBufferPool.acquire(bufferSize, false);
|
||||
|
||||
if (looping)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
|
@ -177,14 +181,9 @@ public class HTTP2Connection extends AbstractConnection implements Connection.Up
|
|||
}
|
||||
}
|
||||
|
||||
public void prefill(ByteBuffer prefilledBuffer)
|
||||
{
|
||||
buffer=prefilledBuffer;
|
||||
}
|
||||
|
||||
private void release()
|
||||
{
|
||||
if (BufferUtil.isEmpty(buffer))
|
||||
if (buffer != null && !buffer.hasRemaining())
|
||||
{
|
||||
byteBufferPool.release(buffer);
|
||||
buffer = null;
|
||||
|
|
|
@ -72,16 +72,16 @@ public class HTTP2ServerConnection extends HTTP2Connection
|
|||
LOG.debug("Processing {} on {}", frame, stream);
|
||||
HttpChannelOverHTTP2 channel = provideHttpChannel(connector, stream);
|
||||
Runnable task = channel.onRequest(frame);
|
||||
offerTask(task);
|
||||
offerTask(task, false);
|
||||
}
|
||||
|
||||
public void onPush(Connector connector, IStream stream, MetaData.Request request)
|
||||
public void push(Connector connector, IStream stream, MetaData.Request request)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Processing push {} on {}", request, stream);
|
||||
HttpChannelOverHTTP2 channel = provideHttpChannel(connector, stream);
|
||||
Runnable task = channel.onPushRequest(request);
|
||||
offerTask(task);
|
||||
offerTask(task, true);
|
||||
}
|
||||
|
||||
private HttpChannelOverHTTP2 provideHttpChannel(Connector connector, IStream stream)
|
||||
|
@ -97,20 +97,27 @@ public class HTTP2ServerConnection extends HTTP2Connection
|
|||
{
|
||||
HttpTransportOverHTTP2 transport = new HttpTransportOverHTTP2(connector, this);
|
||||
transport.setStream(stream);
|
||||
channel = new HttpChannelOverHTTP2(connector, httpConfig, getEndPoint(), transport)
|
||||
{
|
||||
@Override
|
||||
public void onCompleted()
|
||||
{
|
||||
super.onCompleted();
|
||||
recycle();
|
||||
channels.offer(this);
|
||||
}
|
||||
};
|
||||
channel = new ServerHttpChannelOverHTTP2(connector, httpConfig, getEndPoint(), transport);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Creating channel {} for {}", channel, this);
|
||||
}
|
||||
stream.setAttribute(IStream.CHANNEL_ATTRIBUTE, channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
private class ServerHttpChannelOverHTTP2 extends HttpChannelOverHTTP2
|
||||
{
|
||||
public ServerHttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport)
|
||||
{
|
||||
super(connector, configuration, endPoint, transport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted()
|
||||
{
|
||||
super.onCompleted();
|
||||
recycle();
|
||||
channels.offer(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
{
|
||||
onRequest(request);
|
||||
getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE);
|
||||
onRequestComplete();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
|
|
|
@ -151,7 +151,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
@Override
|
||||
public void succeeded(Stream pushStream)
|
||||
{
|
||||
connection.onPush(connector, (IStream)pushStream, request);
|
||||
connection.push(connector, (IStream)pushStream, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -36,7 +36,20 @@ import org.eclipse.jetty.util.thread.strategy.ExecuteProduceRun;
|
|||
public interface ExecutionStrategy
|
||||
{
|
||||
/**
|
||||
* Initiates (or resumes) the task production and execution.
|
||||
* <p>Initiates (or resumes) the task production and execution.</p>
|
||||
* <p>This method guarantees that the task is never run by the
|
||||
* thread that called this method.</p>
|
||||
*
|
||||
* @see #execute()
|
||||
*/
|
||||
public void dispatch();
|
||||
|
||||
/**
|
||||
* <p>Initiates (or resumes) the task production and execution.</p>
|
||||
* <p>The produced task may be run by the same thread that called
|
||||
* this method.</p>
|
||||
*
|
||||
* @see #dispatch()
|
||||
*/
|
||||
public void execute();
|
||||
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
package org.eclipse.jetty.util.thread.strategy;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -47,6 +45,7 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(ExecuteProduceRun.class);
|
||||
private final SpinLock _lock = new SpinLock();
|
||||
private final Runnable _resumer = new Resumer();
|
||||
private final Producer _producer;
|
||||
private final Executor _executor;
|
||||
private boolean _idle=true;
|
||||
|
@ -90,9 +89,31 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
if (produce)
|
||||
produceAndRun();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispatch()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} spawning",this);
|
||||
boolean dispatch=false;
|
||||
try (Lock locked = _lock.lock())
|
||||
{
|
||||
if (_idle)
|
||||
dispatch=true;
|
||||
else
|
||||
_execute=true;
|
||||
}
|
||||
if (dispatch)
|
||||
_executor.execute(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
execute();
|
||||
}
|
||||
|
||||
private void resume()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} run",this);
|
||||
|
@ -105,7 +126,7 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
produce=_producing=true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (produce)
|
||||
produceAndRun();
|
||||
}
|
||||
|
@ -115,7 +136,7 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} produce enter",this);
|
||||
|
||||
loop: while (true)
|
||||
while (true)
|
||||
{
|
||||
// If we got here, then we are the thread that is producing.
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -141,12 +162,12 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
_idle=false;
|
||||
_producing=true;
|
||||
_execute=false;
|
||||
continue loop;
|
||||
continue;
|
||||
}
|
||||
|
||||
// ... and no additional calls to execute, so we are idle
|
||||
_idle=true;
|
||||
break loop;
|
||||
break;
|
||||
}
|
||||
|
||||
// We have a task, which we will run ourselves,
|
||||
|
@ -166,7 +187,7 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
// Spawn a new thread to continue production by running the produce loop.
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} dispatch",this);
|
||||
_executor.execute(this);
|
||||
_executor.execute(_resumer);
|
||||
}
|
||||
|
||||
// Run the task.
|
||||
|
@ -181,7 +202,7 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
{
|
||||
// Is another thread already producing or we are now idle?
|
||||
if (_producing || _idle)
|
||||
break loop;
|
||||
break;
|
||||
_producing=true;
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +223,6 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("EPR ");
|
||||
builder.append(" ");
|
||||
try (Lock locked = _lock.lock())
|
||||
{
|
||||
builder.append(_idle?"Idle/":"");
|
||||
|
@ -213,4 +233,13 @@ public class ExecuteProduceRun implements ExecutionStrategy, Runnable
|
|||
builder.append(_producer);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private class Resumer implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,4 +58,10 @@ public class ProduceExecuteRun implements ExecutionStrategy
|
|||
_executor.execute(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch()
|
||||
{
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,13 +26,15 @@ import org.eclipse.jetty.util.thread.ExecutionStrategy;
|
|||
* <p>A strategy where the caller thread iterates over task production, submitting each
|
||||
* task to an {@link Executor} for execution.</p>
|
||||
*/
|
||||
public class ProduceRun implements ExecutionStrategy
|
||||
public class ProduceRun implements ExecutionStrategy, Runnable
|
||||
{
|
||||
private final Producer _producer;
|
||||
private final Executor _executor;
|
||||
|
||||
public ProduceRun(Producer producer)
|
||||
public ProduceRun(Producer producer, Executor executor)
|
||||
{
|
||||
this._producer = producer;
|
||||
this._executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,4 +53,16 @@ public class ProduceRun implements ExecutionStrategy
|
|||
task.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch()
|
||||
{
|
||||
_executor.execute(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue