INT-3163 Force Connection Close on Send Exception

Previously, when an exception occurred on a send, the
connection was not forcibly closed. When using a
CachingClientConnectionFactory, this prevented the connection
(albeit stale) from being returned to the cache.

Perform a forced (physical) close whenever a send fails.

Add test cases for both Net and NIO implementations, using
a CCCF, to verify the connection is returned to the pool so
the closed state can be detected on the next retrieval, causing
a refresh.

Also, change the synchronization in the NIO send to
synchronize on the socketChannel, not the mapper (which is
shared).

Conflicts:

	spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetConnection.java
	spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioConnection.java
	spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/CachingClientConnectionFactoryTests.java

Resolved.

JIRA: https://jira.springsource.org/browse/INT-3163
This commit is contained in:
Gary Russell
2013-10-06 13:10:12 +03:00
committed by Artem Bilan
parent 2938d1b32d
commit 5fdbb41e5c
3 changed files with 93 additions and 3 deletions

View File

@@ -72,7 +72,13 @@ public class TcpNetConnection extends AbstractTcpConnection {
public synchronized void send(Message<?> message) throws Exception {
Object object = this.getMapper().fromMessage(message);
this.lastSend = System.currentTimeMillis();
((Serializer<Object>) this.getSerializer()).serialize(object, this.socket.getOutputStream());
try {
((Serializer<Object>) this.getSerializer()).serialize(object, this.socket.getOutputStream());
}
catch (Exception e) {
this.closeConnection(true);
throw e;
}
this.afterSend(message);
}

View File

@@ -118,10 +118,16 @@ public class TcpNioConnection extends AbstractTcpConnection {
@SuppressWarnings("unchecked")
public void send(Message<?> message) throws Exception {
synchronized(this.getMapper()) {
synchronized(this.socketChannel) {
Object object = this.getMapper().fromMessage(message);
this.lastSend = System.currentTimeMillis();
((Serializer<Object>) this.getSerializer()).serialize(object, this.getChannelOutputStream());
try {
((Serializer<Object>) this.getSerializer()).serialize(object, this.getChannelOutputStream());
}
catch (Exception e) {
this.closeConnection(true);
throw e;
}
this.afterSend(message);
}
}

View File

@@ -20,12 +20,19 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
@@ -34,15 +41,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer;
import org.springframework.integration.ip.util.TestingUtilities;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
@@ -281,6 +291,74 @@ public class CachingClientConnectionFactoryTests {
verify(mockConn2).close();
}
@Test
public void testExceptionOnSendNet() throws Exception {
AbstractTcpConnection conn1 = mockedTcpNetConnection();
AbstractTcpConnection conn2 = mockedTcpNetConnection();
CachingClientConnectionFactory cccf = createCCCFWith2Connections(conn1, conn2);
doTestCloseOnSendError(conn1, conn2, cccf);
}
@Test
public void testExceptionOnSendNio() throws Exception {
AbstractTcpConnection conn1 = mockedTcpNioConnection();
AbstractTcpConnection conn2 = mockedTcpNioConnection();
CachingClientConnectionFactory cccf = createCCCFWith2Connections(conn1, conn2);
doTestCloseOnSendError(conn1, conn2, cccf);
}
private void doTestCloseOnSendError(TcpConnection conn1, TcpConnection conn2,
CachingClientConnectionFactory cccf) throws Exception {
TcpConnection cached1 = cccf.getConnection();
try {
cached1.send(new GenericMessage<String>("foo"));
fail("Expected IOException");
}
catch (IOException e) {
assertEquals("Foo", e.getMessage());
}
// Before INT-3163 this failed with a timeout - connection not returned to pool after failure on send()
TcpConnection cached2 = cccf.getConnection();
assertTrue(cached1.getConnectionId().contains(conn1.getConnectionId()));
assertTrue(cached2.getConnectionId().contains(conn2.getConnectionId()));
}
private CachingClientConnectionFactory createCCCFWith2Connections(AbstractTcpConnection conn1, AbstractTcpConnection conn2)
throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
when(factory.getConnection()).thenReturn(conn1, conn2);
CachingClientConnectionFactory cccf = new CachingClientConnectionFactory(factory, 1);
cccf.setConnectionWaitTimeout(1);
cccf.start();
return cccf;
}
private AbstractTcpConnection mockedTcpNetConnection() throws IOException {
Socket socket = mock(Socket.class);
when(socket.isClosed()).thenReturn(true); // closed when next retrieved
OutputStream stream = mock(OutputStream.class);
doThrow(new IOException("Foo")).when(stream).write(Mockito.any(byte[].class));
when(socket.getOutputStream()).thenReturn(stream);
TcpNetConnection conn = new TcpNetConnection(socket, false, false);
conn.setMapper(new TcpMessageMapper());
conn.setSerializer(new ByteArrayCrLfSerializer());
return conn;
}
private AbstractTcpConnection mockedTcpNioConnection() throws Exception {
SocketChannel socketChannel = mock(SocketChannel.class);
new DirectFieldAccessor(socketChannel).setPropertyValue("open", false);
doThrow(new IOException("Foo")).when(socketChannel).write(Mockito.any(ByteBuffer.class));
when(socketChannel.socket()).thenReturn(mock(Socket.class));
TcpNioConnection conn = new TcpNioConnection(socketChannel, false, false);
conn.setMapper(new TcpMessageMapper());
conn.setSerializer(new ByteArrayCrLfSerializer());
return conn;
}
private TcpConnection makeMockConnection(String name) {
return makeMockConnection(name, false);
}