Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.SelectorProvider;
Expand All @@ -51,11 +52,15 @@ public class TomcatAcceptFixConfig {
@Value("${server.tomcat.retryRebindTimeoutSecs:10}")
int retryRebindTimeoutSecs;

@Value("${apiml.tcpStackAwareSocketChannel.enabled:false}")
boolean tcpStackAwareSocketChannelEnabled;

private static final Field ENDPOINT_FIELD;
private static final Field NIO_SOCKET_FIELD;

private static final MethodHandle IMPL_CLOSE_SELECTABGLE_CHANNEL_HANLE; // NOSONAR
private static final MethodHandle IMPL_CLOSE_SELECTABLE_CHANNEL_HANDLE; // NOSONAR
private static final MethodHandle IMPL_CONFIGURE_BLOCKING; // NOSONAR
private static final String NETWORK_RECYCLED_EXCEPTION_CLASS = "com.ibm.net.NetworkRecycledException";

/**
* To mitigate parallel treatment of socket rebinding
Expand All @@ -69,7 +74,7 @@ public class TomcatAcceptFixConfig {

Method implCloseSelectableChannel = AbstractSelectableChannel.class.getDeclaredMethod("implCloseSelectableChannel");
implCloseSelectableChannel.setAccessible(true); // NOSONAR
IMPL_CLOSE_SELECTABGLE_CHANNEL_HANLE = MethodHandles.lookup().unreflect(implCloseSelectableChannel);
IMPL_CLOSE_SELECTABLE_CHANNEL_HANDLE = MethodHandles.lookup().unreflect(implCloseSelectableChannel);

Method implConfigureBlocking = AbstractSelectableChannel.class.getDeclaredMethod("implConfigureBlocking", boolean.class);
implConfigureBlocking.setAccessible(true); // NOSONAR
Expand Down Expand Up @@ -143,12 +148,32 @@ public void stopping() {
running.set(false);
}

static boolean isRecycledClass(Throwable t) {
Comment thread
balhar-jakub marked this conversation as resolved.
return NETWORK_RECYCLED_EXCEPTION_CLASS.equals(t.getClass().getName());
}

static boolean isTcpStackRestarted(Throwable t) {
if ((t.getMessage() != null) && t.getMessage().contains("EDC5122I")) {
return true;
}

if (isRecycledClass(t)) {
return true;
}

Throwable cause = t.getCause();
if ((cause != null) && (cause != t)) {
return isTcpStackRestarted(cause);
}

return false;
}

/**
* Socket implementation wrapper to handle rebinding on TCP Stack restart
*/
class FixedServerSocketChannel extends ServerSocketChannel {

private static final String NETWORK_RECYCLED_EXCEPTION_CLASS = "com.ibm.net.NetworkRecycledException";

/**
* Wrapper server socket inside
Expand Down Expand Up @@ -181,7 +206,7 @@ class FixedServerSocketChannel extends ServerSocketChannel {
@Override
protected void implCloseSelectableChannel() throws IOException {
try {
IMPL_CLOSE_SELECTABGLE_CHANNEL_HANLE.invoke(socket);
IMPL_CLOSE_SELECTABLE_CHANNEL_HANDLE.invoke(socket);
} catch (IOException | RuntimeException e) {
throw e;
} catch (Throwable t) {
Expand Down Expand Up @@ -249,43 +274,33 @@ private synchronized void rebind(int stateBefore) throws IOException {
}
}

boolean isRecycledClass(Throwable t) {
return NETWORK_RECYCLED_EXCEPTION_CLASS.equals(t.getClass().getName());
}

boolean isTcpStackRestarted(Throwable t) {
if ((t.getMessage() != null) && t.getMessage().contains("EDC5122I")) {
return true;
}

if (isRecycledClass(t)) {
return true;
}

Throwable cause = t.getCause();
if ((cause != null) && (cause != t)) {
return isTcpStackRestarted(cause);
}

return false;
return TomcatAcceptFixConfig.isTcpStackRestarted(t);
}

public SocketChannel accept() throws IOException {
// obtain current state of rebinding to detection parallel actions
final int stateBefore = state.get();
try {
return socket.accept();
return wrapIfEnabled(socket.accept());
} catch (IOException ioe) {
if (isTcpStackRestarted(ioe)) {
if (TomcatAcceptFixConfig.isTcpStackRestarted(ioe)) {
// the fix solve just one issue about stopped TCP/IP stack
log.debug("The TCP/IP stack was probably restarted. The socket of Tomcat will rebind.");
rebind(stateBefore);
return socket.accept();
return wrapIfEnabled(socket.accept());
}
throw ioe;
}
}

private SocketChannel wrapIfEnabled(SocketChannel socketChannel) {
if (tcpStackAwareSocketChannelEnabled) {
return new TcpStackAwareSocketChannel(socketChannel);
}
return socketChannel;
}

}

/**
Expand All @@ -310,4 +325,129 @@ private interface Overridden {

}

/**
* The list of methods excluded from Lombok @Delegate for TcpStackAwareSocketChannel.
* It contains methods inherited from SocketChannel that must not be delegated and
* read/write/close-related methods that are wrapped explicitly in this class.
*/
private interface ExcludedSocketOps {
Comment thread
balhar-jakub marked this conversation as resolved.

SelectorProvider provider();
boolean isRegistered();
SelectionKey keyFor(Selector sel);
SelectionKey register(Selector sel, int ops, Object att);
SelectionKey register(Selector sel, int ops) throws ClosedChannelException;
boolean isBlocking();
Object blockingLock();
SelectableChannel configureBlocking(boolean block) throws IOException;
void implCloseChannel() throws IOException;
void close() throws IOException;
boolean isOpen();
int validOps();
long read(ByteBuffer[] dsts) throws IOException;
long write(ByteBuffer[] srcs) throws IOException;
int read(ByteBuffer dst) throws IOException;
long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
int write(ByteBuffer src) throws IOException;
long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
void implCloseSelectableChannel() throws IOException;
void implConfigureBlocking(boolean block) throws IOException;

}

/**
* SocketChannel wrapper that detects z/OS TCP/IP stack restarts during I/O operations.
* When EDC5122I or NetworkRecycledException is caught on read/write, the underlying
* socket is closed and the exception is re-thrown so Tomcat can discard the connection.
*/
class TcpStackAwareSocketChannel extends SocketChannel {
Comment thread
balhar-jakub marked this conversation as resolved.

@Delegate(excludes = ExcludedSocketOps.class)
private final SocketChannel delegate;

TcpStackAwareSocketChannel(SocketChannel delegate) {
super(delegate.provider());
this.delegate = delegate;
}

@Override
public int read(ByteBuffer dst) throws IOException {
try {
return delegate.read(dst);
} catch (IOException e) {
if (TomcatAcceptFixConfig.isTcpStackRestarted(e)) {
log.debug("TCP/IP stack restart detected during read on client socket; closing connection", e);
safeClose(delegate);
}
throw e;
}
}

@Override
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
try {
return delegate.read(dsts, offset, length);
} catch (IOException e) {
if (TomcatAcceptFixConfig.isTcpStackRestarted(e)) {
log.debug("TCP/IP stack restart detected during scatter read on client socket; closing connection", e);
safeClose(delegate);
}
throw e;
}
}

@Override
public int write(ByteBuffer src) throws IOException {
try {
return delegate.write(src);
} catch (IOException e) {
if (TomcatAcceptFixConfig.isTcpStackRestarted(e)) {
log.debug("TCP/IP stack restart detected during write on client socket; closing connection", e);
safeClose(delegate);
Comment thread
balhar-jakub marked this conversation as resolved.
}
throw e;
}
}

@Override
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
try {
return delegate.write(srcs, offset, length);
} catch (IOException e) {
if (TomcatAcceptFixConfig.isTcpStackRestarted(e)) {
log.debug("TCP/IP stack restart detected during scatter write on client socket; closing connection", e);
safeClose(delegate);
}
throw e;
}
}

@Override
protected void implCloseSelectableChannel() throws IOException {
Comment thread
balhar-jakub marked this conversation as resolved.
// SocketChannel.close() is final; it reaches this override through
// AbstractInterruptibleChannel.close().
delegate.close();
}

@Override
protected void implConfigureBlocking(boolean block) throws IOException {
try {
IMPL_CONFIGURE_BLOCKING.invoke(delegate, block);
} catch (IOException | RuntimeException e) {
throw e;
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}

private static void safeClose(SocketChannel ch) {
try {
ch.close();
} catch (IOException e) {
log.trace("Best-effort close of client socket failed", e);
}
}

}

}
Loading
Loading