Optimize SslHandler's detection of supressable exceptions, so it will not break on different OS's or jdk impls. See #79

This commit is contained in:
norman 2012-07-05 09:18:59 +02:00
parent e40c430976
commit db09c421d9

View File

@ -29,10 +29,15 @@ import io.netty.channel.ChannelPipeline;
import io.netty.channel.DefaultChannelFuture; import io.netty.channel.DefaultChannelFuture;
import io.netty.logging.InternalLogger; import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory; import io.netty.logging.InternalLoggerFactory;
import io.netty.util.internal.DetectionUtil;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -149,9 +154,8 @@ public class SslHandler
private static final InternalLogger logger = private static final InternalLogger logger =
InternalLoggerFactory.getInstance(SslHandler.class); InternalLoggerFactory.getInstance(SslHandler.class);
private static final Pattern IGNORABLE_ERROR_MESSAGE = Pattern.compile( private static final Pattern IGNORABLE_CLASS_IN_STACK = Pattern.compile(
"^.*(?:connection.*reset|connection.*closed|broken.*pipe).*$", "^.*(Socket|DatagramChannel|SctpChannel).*$");
Pattern.CASE_INSENSITIVE);
private volatile ChannelHandlerContext ctx; private volatile ChannelHandlerContext ctx;
private final SSLEngine engine; private final SSLEngine engine;
@ -438,29 +442,89 @@ public class SslHandler
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof IOException && engine.isOutboundDone()) { if (ignoreException(cause)) {
String message = String.valueOf(cause.getMessage()).toLowerCase(); // It is safe to ignore the 'connection reset by peer' or
if (IGNORABLE_ERROR_MESSAGE.matcher(message).matches()) { // 'broken pipe' error after sending closure_notify.
// It is safe to ignore the 'connection reset by peer' or if (logger.isDebugEnabled()) {
// 'broken pipe' error after sending closure_notify. logger.debug(
if (logger.isDebugEnabled()) { "Swallowing a 'connection reset by peer / " +
logger.debug( "broken pipe' error occurred while writing " +
"Swallowing a 'connection reset by peer / " + "'closure_notify'", cause);
"broken pipe' error occurred while writing " +
"'closure_notify'", cause);
}
// Close the connection explicitly just in case the transport
// did not close the connection automatically.
if (ctx.channel().isActive()) {
ctx.close();
}
return;
} }
// Close the connection explicitly just in case the transport
// did not close the connection automatically.
if (ctx.channel().isActive()) {
ctx.close();
}
return;
} }
super.exceptionCaught(ctx, cause); super.exceptionCaught(ctx, cause);
} }
/**
* Checks if the given {@link Throwable} can be ignore and just "swallowed"
*
* When an ssl connection is closed a close_notify message is sent.
* After that the peer also sends close_notify however, it's not mandatory to receive
* the close_notify. The party who sent the initial close_notify can close the connection immediately
* then the peer will get connection reset error.
*
*/
private boolean ignoreException(Throwable t) {
if (!(t instanceof SSLException) && t instanceof IOException && engine.isOutboundDone()) {
// Inspect the StackTraceElements to see if it was a connection reset / broken pipe or not
StackTraceElement[] elements = t.getStackTrace();
for (StackTraceElement element: elements) {
String classname = element.getClassName();
String methodname = element.getMethodName();
// skip all classes that belong to the io.netty package
if (classname.startsWith("io.netty.")) {
continue;
}
// check if the method name is read if not skip it
if (!methodname.equals("read")) {
continue;
}
// This will also match against SocketInputStream which is used by openjdk 7 and maybe
// also others
if (IGNORABLE_CLASS_IN_STACK.matcher(classname).matches()) {
return true;
}
try {
// No match by now.. Try to load the class via classloader and inspect it.
// This is mainly done as other JDK implementations may differ in name of
// the impl.
Class<?> clazz = getClass().getClassLoader().loadClass(classname);
if (SocketChannel.class.isAssignableFrom(clazz)
|| DatagramChannel.class.isAssignableFrom(clazz)
|| Socket.class.isAssignableFrom(clazz)
|| DatagramSocket.class.isAssignableFrom(clazz)) {
return true;
}
// also match against SctpChannel via String matching as it may not present.
if (DetectionUtil.javaVersion() >= 7
&& "com.sun.nio.sctp.SctpChannel".equals(clazz.getSuperclass().getName())) {
return true;
}
} catch (ClassNotFoundException e) {
// This should not happen just ignore
}
}
}
return false;
}
@Override @Override
public void inboundBufferUpdated(final ChannelHandlerContext ctx) throws Exception { public void inboundBufferUpdated(final ChannelHandlerContext ctx) throws Exception {
final ByteBuf in = ctx.inboundByteBuffer(); final ByteBuf in = ctx.inboundByteBuffer();