HTTP2: Close encoder and decoder on channelInactive and initialize clientPrefaceString on handlerAdded.

Motivation:

- The encoder and decoder should be closed right after the handler releases its resources.
- The clientPrefaceString is allocated in the constructor but releases in handlerRemoved.
  If the handler is never added to the pipeline, the clientPrefaceString will never be
  released.

Modifications:

- Call encoder.close() and decoder.close() on channelInactive.
- Release the clientPrefaceString on handlerRemoved.

Result:

- The encoder and decoder get closed right after the handler's resources are freed.
- It's easier to verify that the clientPrefaceString will also get released.
This commit is contained in:
Jakob Buchgraber 2015-03-18 14:09:19 -07:00 committed by nmittler
parent 3405aee2ab
commit 9bad408de5
2 changed files with 47 additions and 24 deletions

View File

@ -15,6 +15,7 @@
package io.netty.handler.codec.http2; package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID; import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
import static io.netty.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH;
import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf; import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception; import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
@ -99,8 +100,6 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
if (encoder.connection() != decoder.connection()) { if (encoder.connection() != decoder.connection()) {
throw new IllegalArgumentException("Encoder and Decoder do not share the same connection object"); throw new IllegalArgumentException("Encoder and Decoder do not share the same connection object");
} }
clientPrefaceString = clientPrefaceString(encoder.connection());
} }
public Http2Connection connection() { public Http2Connection connection() {
@ -159,14 +158,21 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
@Override @Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
clientPrefaceString = clientPrefaceString(encoder.connection());
// This handler was just added to the context. In case it was handled after // This handler was just added to the context. In case it was handled after
// the connection became active, send the connection preface now. // the connection became active, send the connection preface now.
sendPreface(ctx); sendPreface(ctx);
} }
/**
* Releases the {@code clientPrefaceString}. Any active streams will be left in the open.
*/
@Override @Override
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
dispose(); if (clientPrefaceString != null) {
clientPrefaceString.release();
clientPrefaceString = null;
}
} }
@Override @Override
@ -226,11 +232,19 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
try {
ChannelFuture future = ctx.newSucceededFuture(); ChannelFuture future = ctx.newSucceededFuture();
final Collection<Http2Stream> streams = connection().activeStreams(); final Collection<Http2Stream> streams = connection().activeStreams();
for (Http2Stream s : streams.toArray(new Http2Stream[streams.size()])) { for (Http2Stream s : streams.toArray(new Http2Stream[streams.size()])) {
closeStream(s, future); closeStream(s, future);
} }
} finally {
try {
encoder().close();
} finally {
decoder().close();
}
}
super.channelInactive(ctx); super.channelInactive(ctx);
} }
@ -450,18 +464,6 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
ChannelFutureListener.CLOSE_ON_FAILURE); ChannelFutureListener.CLOSE_ON_FAILURE);
} }
/**
* Disposes of all resources.
*/
private void dispose() {
encoder.close();
decoder.close();
if (clientPrefaceString != null) {
clientPrefaceString.release();
clientPrefaceString = null;
}
}
/** /**
* Decodes the client connection preface string from the input buffer. * Decodes the client connection preface string from the input buffer.
* *

View File

@ -117,22 +117,27 @@ public class Http2ConnectionHandlerTest {
when(ctx.newSucceededFuture()).thenReturn(future); when(ctx.newSucceededFuture()).thenReturn(future);
when(ctx.newPromise()).thenReturn(promise); when(ctx.newPromise()).thenReturn(promise);
when(ctx.write(any())).thenReturn(future); when(ctx.write(any())).thenReturn(future);
handler = newHandler();
} }
private Http2ConnectionHandler newHandler() { private Http2ConnectionHandler newHandler() throws Exception {
return new Http2ConnectionHandler(decoderBuilder, encoderBuilder); Http2ConnectionHandler handler = new Http2ConnectionHandler(decoderBuilder, encoderBuilder);
handler.handlerAdded(ctx);
return handler;
} }
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
if (handler != null) {
handler.handlerRemoved(ctx); handler.handlerRemoved(ctx);
} }
}
@Test @Test
public void clientShouldSendClientPrefaceStringWhenActive() throws Exception { public void clientShouldSendClientPrefaceStringWhenActive() throws Exception {
when(connection.isServer()).thenReturn(false); when(connection.isServer()).thenReturn(false);
when(channel.isActive()).thenReturn(false);
handler = newHandler();
when(channel.isActive()).thenReturn(true);
handler.channelActive(ctx); handler.channelActive(ctx);
verify(ctx).write(eq(connectionPrefaceBuf())); verify(ctx).write(eq(connectionPrefaceBuf()));
} }
@ -140,6 +145,9 @@ public class Http2ConnectionHandlerTest {
@Test @Test
public void serverShouldNotSendClientPrefaceStringWhenActive() throws Exception { public void serverShouldNotSendClientPrefaceStringWhenActive() throws Exception {
when(connection.isServer()).thenReturn(true); when(connection.isServer()).thenReturn(true);
when(channel.isActive()).thenReturn(false);
handler = newHandler();
when(channel.isActive()).thenReturn(true);
handler.channelActive(ctx); handler.channelActive(ctx);
verify(ctx, never()).write(eq(connectionPrefaceBuf())); verify(ctx, never()).write(eq(connectionPrefaceBuf()));
} }
@ -158,18 +166,21 @@ public class Http2ConnectionHandlerTest {
@Test @Test
public void serverReceivingValidClientPrefaceStringShouldContinueReadingFrames() throws Exception { public void serverReceivingValidClientPrefaceStringShouldContinueReadingFrames() throws Exception {
when(connection.isServer()).thenReturn(true); when(connection.isServer()).thenReturn(true);
handler = newHandler();
handler.channelRead(ctx, connectionPrefaceBuf()); handler.channelRead(ctx, connectionPrefaceBuf());
verify(decoder).decodeFrame(eq(ctx), any(ByteBuf.class), Matchers.<List<Object>>any()); verify(decoder).decodeFrame(eq(ctx), any(ByteBuf.class), Matchers.<List<Object>>any());
} }
@Test @Test
public void channelInactiveShouldCloseStreams() throws Exception { public void channelInactiveShouldCloseStreams() throws Exception {
handler = newHandler();
handler.channelInactive(ctx); handler.channelInactive(ctx);
verify(stream).close(); verify(stream).close();
} }
@Test @Test
public void connectionErrorShouldStartShutdown() throws Exception { public void connectionErrorShouldStartShutdown() throws Exception {
handler = newHandler();
Http2Exception e = new Http2Exception(PROTOCOL_ERROR); Http2Exception e = new Http2Exception(PROTOCOL_ERROR);
when(remote.lastStreamCreated()).thenReturn(STREAM_ID); when(remote.lastStreamCreated()).thenReturn(STREAM_ID);
handler.exceptionCaught(ctx, e); handler.exceptionCaught(ctx, e);
@ -178,4 +189,14 @@ public class Http2ConnectionHandlerTest {
captor.capture(), eq(promise)); captor.capture(), eq(promise));
captor.getValue().release(); captor.getValue().release();
} }
@Test
public void encoderAndDecoderAreClosedOnChannelInactive() throws Exception {
handler = newHandler();
handler.channelActive(ctx);
when(channel.isActive()).thenReturn(false);
handler.channelInactive(ctx);
verify(encoder).close();
verify(decoder).close();
}
} }