423930 - SPDY streams are leaked.
With this fix, when streams are closed or failed, the stream is closed and its idle timeout is canceled, avoiding the leaking.
This commit is contained in:
parent
f4a5f68bdd
commit
6f316f9887
|
@ -114,6 +114,15 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
StreamFrameListener listener = this.listener;
|
||||
if (listener != null)
|
||||
listener.onFailure(this, timeout);
|
||||
// The stream is now gone, we must close it to
|
||||
// avoid that its idle timeout is rescheduled.
|
||||
close();
|
||||
}
|
||||
|
||||
private void close()
|
||||
{
|
||||
closeState = CloseState.CLOSED;
|
||||
onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -189,13 +198,13 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
if (local)
|
||||
throw new IllegalStateException();
|
||||
else
|
||||
closeState = CloseState.CLOSED;
|
||||
close();
|
||||
break;
|
||||
}
|
||||
case REMOTELY_CLOSED:
|
||||
{
|
||||
if (local)
|
||||
closeState = CloseState.CLOSED;
|
||||
close();
|
||||
else
|
||||
throw new IllegalStateException();
|
||||
break;
|
||||
|
@ -369,12 +378,13 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
notIdle();
|
||||
if (isClosed() || isReset())
|
||||
{
|
||||
close();
|
||||
promise.failed(new StreamException(getId(), StreamStatus.STREAM_ALREADY_CLOSED,
|
||||
"Stream: " + this + " already closed or reset!"));
|
||||
return;
|
||||
}
|
||||
PushSynInfo pushSynInfo = new PushSynInfo(getId(), pushInfo);
|
||||
session.syn(pushSynInfo, null, promise);
|
||||
session.syn(pushSynInfo, null, new StreamPromise(promise));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -393,11 +403,14 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
{
|
||||
notIdle();
|
||||
if (isUnidirectional())
|
||||
{
|
||||
close();
|
||||
throw new IllegalStateException("Protocol violation: cannot send SYN_REPLY frames in unidirectional streams");
|
||||
}
|
||||
openState = OpenState.REPLY_SENT;
|
||||
updateCloseState(replyInfo.isClose(), true);
|
||||
SynReplyFrame frame = new SynReplyFrame(session.getVersion(), replyInfo.getFlags(), getId(), replyInfo.getHeaders());
|
||||
session.control(this, frame, replyInfo.getTimeout(), replyInfo.getUnit(), callback);
|
||||
session.control(this, frame, replyInfo.getTimeout(), replyInfo.getUnit(), new StreamCallback(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -417,18 +430,18 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
notIdle();
|
||||
if (!canSend())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter());
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new StreamCallback());
|
||||
throw new IllegalStateException("Protocol violation: cannot send a DATA frame before a SYN_REPLY frame");
|
||||
}
|
||||
if (isLocallyClosed())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter());
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new StreamCallback());
|
||||
throw new IllegalStateException("Protocol violation: cannot send a DATA frame on a locally closed stream");
|
||||
}
|
||||
|
||||
// Cannot update the close state here, because the data that we send may
|
||||
// be flow controlled, so we need the stream to update the window size.
|
||||
session.data(this, dataInfo, dataInfo.getTimeout(), dataInfo.getUnit(), callback);
|
||||
session.data(this, dataInfo, dataInfo.getTimeout(), dataInfo.getUnit(), new StreamCallback(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -448,18 +461,18 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
notIdle();
|
||||
if (!canSend())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter());
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new StreamCallback());
|
||||
throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame before a SYN_REPLY frame");
|
||||
}
|
||||
if (isLocallyClosed())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter());
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new StreamCallback());
|
||||
throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame on a closed stream");
|
||||
}
|
||||
|
||||
updateCloseState(headersInfo.isClose(), true);
|
||||
HeadersFrame frame = new HeadersFrame(session.getVersion(), headersInfo.getFlags(), getId(), headersInfo.getHeaders());
|
||||
session.control(this, frame, headersInfo.getTimeout(), headersInfo.getUnit(), callback);
|
||||
session.control(this, frame, headersInfo.getTimeout(), headersInfo.getUnit(), new StreamCallback(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -527,4 +540,55 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
{
|
||||
OPENED, LOCALLY_CLOSED, REMOTELY_CLOSED, CLOSED
|
||||
}
|
||||
|
||||
private class StreamCallback implements Callback
|
||||
{
|
||||
private final Callback callback;
|
||||
|
||||
private StreamCallback()
|
||||
{
|
||||
this(new Adapter());
|
||||
}
|
||||
|
||||
private StreamCallback(Callback callback)
|
||||
{
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
close();
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
private class StreamPromise implements Promise<Stream>
|
||||
{
|
||||
private final Promise<Stream> promise;
|
||||
|
||||
public StreamPromise(Promise<Stream> promise)
|
||||
{
|
||||
this.promise = promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded(Stream result)
|
||||
{
|
||||
promise.succeeded(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
close();
|
||||
promise.failed(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,16 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.HashSet;
|
||||
|
@ -38,6 +28,8 @@ import java.util.concurrent.Executors;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
|
@ -46,6 +38,7 @@ import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
|
|||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
import org.eclipse.jetty.spdy.api.PushInfo;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
import org.eclipse.jetty.spdy.api.RstInfo;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
|
@ -58,7 +51,6 @@ import org.eclipse.jetty.spdy.api.SynInfo;
|
|||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
|
@ -66,9 +58,10 @@ import org.eclipse.jetty.util.FuturePromise;
|
|||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.eclipse.jetty.util.thread.TimerScheduler;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
@ -78,6 +71,16 @@ import org.mockito.invocation.InvocationOnMock;
|
|||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StandardSessionTest
|
||||
{
|
||||
|
@ -101,7 +104,7 @@ public class StandardSessionTest
|
|||
public void setUp() throws Exception
|
||||
{
|
||||
threadPool = Executors.newCachedThreadPool();
|
||||
scheduler = new TimerScheduler();
|
||||
scheduler = new ScheduledExecutorScheduler();
|
||||
scheduler.start();
|
||||
session = new StandardSession(VERSION, bufferPool, scheduler, controller, endPoint, null, 1, null,
|
||||
generator, new FlowControlStrategy.None());
|
||||
|
@ -131,8 +134,7 @@ public class StandardSessionTest
|
|||
callback.succeeded();
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.when(controller).write(any(Callback.class), any(ByteBuffer.class));
|
||||
}).when(controller).write(any(Callback.class), any(ByteBuffer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -429,14 +431,38 @@ public class StandardSessionTest
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testControllerWriteFailsInEndPointFlush() throws InterruptedException
|
||||
public void testControllerWriteFails() throws Exception
|
||||
{
|
||||
setControllerWriteExpectation(true);
|
||||
final AtomicInteger writes = new AtomicInteger();
|
||||
final AtomicBoolean fail = new AtomicBoolean();
|
||||
Controller controller = new Controller()
|
||||
{
|
||||
@Override
|
||||
public void write(Callback callback, ByteBuffer... buffers)
|
||||
{
|
||||
writes.incrementAndGet();
|
||||
if (fail.get())
|
||||
callback.failed(new ClosedChannelException());
|
||||
else
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
final CountDownLatch failedCalledLatch = new CountDownLatch(2);
|
||||
SynStreamFrame synStreamFrame = new SynStreamFrame(VERSION, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, null);
|
||||
IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null, null, null);
|
||||
@Override
|
||||
public void close(boolean onlyOutput)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
ISession session = new StandardSession(VERSION, bufferPool, scheduler, controller, endPoint, null, 1, null, generator, null);
|
||||
IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
|
||||
stream.updateWindowSize(8192);
|
||||
|
||||
// Send a reply to comply with the API usage
|
||||
stream.reply(new ReplyInfo(false), new Callback.Adapter());
|
||||
|
||||
// Make the controller fail
|
||||
fail.set(true);
|
||||
final CountDownLatch failedCalledLatch = new CountDownLatch(1);
|
||||
Callback.Adapter callback = new Callback.Adapter()
|
||||
{
|
||||
@Override
|
||||
|
@ -445,14 +471,11 @@ public class StandardSessionTest
|
|||
failedCalledLatch.countDown();
|
||||
}
|
||||
};
|
||||
|
||||
// first data frame should fail on controller.write()
|
||||
stream.data(new StringDataInfo(5, TimeUnit.SECONDS, "data", false), callback);
|
||||
// second data frame should fail without controller.write() as the connection is expected to be broken after first controller.write() call failed.
|
||||
// Data frame should fail on controller.write()
|
||||
stream.data(new StringDataInfo(5, TimeUnit.SECONDS, "data", false), callback);
|
||||
|
||||
verify(controller, times(1)).write(any(Callback.class), any(ByteBuffer.class));
|
||||
assertThat("Callback.failed has been called twice", failedCalledLatch.await(5, TimeUnit.SECONDS), is(true));
|
||||
Assert.assertEquals(2, writes.get());
|
||||
Assert.assertTrue(failedCalledLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -18,6 +18,39 @@
|
|||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.PushInfo;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.StringDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
@ -29,33 +62,6 @@ import static org.mockito.Mockito.never;
|
|||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.PushInfo;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.StringDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StandardStreamTest
|
||||
{
|
||||
|
@ -71,9 +77,12 @@ public class StandardStreamTest
|
|||
scheduler.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link Stream#push(org.eclipse.jetty.spdy.api.PushInfo)}.
|
||||
*/
|
||||
@After
|
||||
public void tearDown() throws Exception
|
||||
{
|
||||
scheduler.stop();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSyn()
|
||||
|
@ -144,46 +153,106 @@ public class StandardStreamTest
|
|||
|
||||
@Test
|
||||
@Slow
|
||||
public void testIdleTimeout() throws InterruptedException, ExecutionException, TimeoutException
|
||||
public void testIdleTimeout() throws Exception
|
||||
{
|
||||
final CountDownLatch onFailCalledLatch = new CountDownLatch(1);
|
||||
IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
|
||||
stream.setIdleTimeout(500);
|
||||
long idleTimeout = 500;
|
||||
stream.setIdleTimeout(idleTimeout);
|
||||
|
||||
final AtomicInteger failureCount = new AtomicInteger();
|
||||
final CountDownLatch failureLatch = new CountDownLatch(1);
|
||||
stream.setStreamFrameListener(new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Stream stream, Throwable x)
|
||||
{
|
||||
assertThat("exception is a TimeoutException", x, is(instanceOf(TimeoutException.class)));
|
||||
onFailCalledLatch.countDown();
|
||||
failureCount.incrementAndGet();
|
||||
failureLatch.countDown();
|
||||
}
|
||||
});
|
||||
stream.process(new StringDataInfo("string", false));
|
||||
Thread.sleep(1000);
|
||||
assertThat("onFailure has been called", onFailCalledLatch.await(5, TimeUnit.SECONDS), is(true));
|
||||
|
||||
// Wait more than (2 * idleTimeout) to be sure to trigger a failureCount > 1
|
||||
Thread.sleep(3 * idleTimeout);
|
||||
|
||||
assertThat("onFailure has been called", failureLatch.await(5, TimeUnit.SECONDS), is(true));
|
||||
Assert.assertEquals(1, failureCount.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Slow
|
||||
public void testIdleTimeoutIsInterruptedWhenReceiving() throws InterruptedException, ExecutionException,
|
||||
TimeoutException
|
||||
public void testIdleTimeoutIsInterruptedWhenReceiving() throws Exception
|
||||
{
|
||||
final CountDownLatch onFailCalledLatch = new CountDownLatch(1);
|
||||
final CountDownLatch failureLatch = new CountDownLatch(1);
|
||||
IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
|
||||
long idleTimeout = 1000;
|
||||
stream.setIdleTimeout(idleTimeout);
|
||||
stream.setStreamFrameListener(new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Stream stream, Throwable x)
|
||||
{
|
||||
assertThat("exception is a TimeoutException", x, is(instanceOf(TimeoutException.class)));
|
||||
onFailCalledLatch.countDown();
|
||||
failureLatch.countDown();
|
||||
}
|
||||
});
|
||||
stream.process(new SynStreamFrame(SPDY.V3, (byte)0, 1, 0, (byte)0, (short)0, null));
|
||||
stream.process(new StringDataInfo("string", false));
|
||||
Thread.sleep(500);
|
||||
Thread.sleep(idleTimeout / 2);
|
||||
stream.process(new StringDataInfo("string", false));
|
||||
Thread.sleep(500);
|
||||
assertThat("onFailure has been called", onFailCalledLatch.await(1, TimeUnit.SECONDS), is(false));
|
||||
Thread.sleep(idleTimeout / 2);
|
||||
stream.process(new StringDataInfo("string", false));
|
||||
Thread.sleep(idleTimeout / 2);
|
||||
stream.process(new StringDataInfo("string", true));
|
||||
stream.reply(new ReplyInfo(true), new Callback.Adapter());
|
||||
Thread.sleep(idleTimeout);
|
||||
assertThat("onFailure has not been called", failureLatch.await(idleTimeout, TimeUnit.MILLISECONDS), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Slow
|
||||
public void testReplyFailureClosesStream() throws Exception
|
||||
{
|
||||
ISession session = new StandardSession(SPDY.V3, null, null, null, null, null, 1, null, null, null)
|
||||
{
|
||||
@Override
|
||||
public void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback)
|
||||
{
|
||||
callback.failed(new ClosedChannelException());
|
||||
}
|
||||
};
|
||||
IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
|
||||
final AtomicInteger failureCount = new AtomicInteger();
|
||||
stream.setStreamFrameListener(new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Stream stream, Throwable x)
|
||||
{
|
||||
failureCount.incrementAndGet();
|
||||
}
|
||||
});
|
||||
long idleTimeout = 500;
|
||||
stream.setIdleTimeout(idleTimeout);
|
||||
|
||||
stream.process(new SynStreamFrame(SPDY.V3, (byte)0, 1, 0, (byte)0, (short)0, null));
|
||||
|
||||
final CountDownLatch failureLatch = new CountDownLatch(1);
|
||||
stream.reply(new ReplyInfo(false), new Callback.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failureLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
// Make sure that the idle timeout never fires, since the failure above should have closed the stream
|
||||
Thread.sleep(3 * idleTimeout);
|
||||
|
||||
Assert.assertEquals(0, failureCount.get());
|
||||
Assert.assertTrue(stream.isClosed());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue