GH-2736: Async Mode for TcpOutboundGateway
Resolves https://github.com/spring-projects/spring-integration/issues/2736 Support asynchronous request/reply. * - Add `async` to the schema - Fix tests1 * - Capture `isAsync` in a variable - Fix typo - Convert test to JUnit5
This commit is contained in:
@@ -248,6 +248,7 @@
|
||||
order="24"
|
||||
auto-startup="false"
|
||||
phase="127"
|
||||
async="true"
|
||||
/>
|
||||
|
||||
<int:channel id="tcpAdviceGateChannel">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -478,10 +478,12 @@ public class ParserUnitTests {
|
||||
assertThat(tcpOutboundGateway.getComponentType()).isEqualTo("ip:tcp-outbound-gateway");
|
||||
assertThat(cfC2.isLookupHost()).isTrue();
|
||||
assertThat(dfa.getPropertyValue("order")).isEqualTo(24);
|
||||
assertThat(dfa.getPropertyValue("async")).isEqualTo(Boolean.TRUE);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(outAdviceGateway, "remoteTimeoutExpression.expression"))
|
||||
.isEqualTo("4000");
|
||||
assertThat(TestUtils.getPropertyValue(outAdviceGateway, "closeStreamAfterSend")).isEqualTo(Boolean.TRUE);
|
||||
assertThat(TestUtils.getPropertyValue(outAdviceGateway, "async")).isEqualTo(Boolean.FALSE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,8 +26,10 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
@@ -49,9 +51,7 @@ import javax.net.ServerSocketFactory;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -71,16 +71,20 @@ import org.springframework.integration.ip.tcp.connection.FailoverClientConnectio
|
||||
import org.springframework.integration.ip.tcp.connection.TcpConnectionSupport;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNioClientConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer;
|
||||
import org.springframework.integration.ip.tcp.serializer.SoftEndOfStreamException;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.rule.Log4j2LevelAdjuster;
|
||||
import org.springframework.integration.test.support.LongRunningIntegrationTest;
|
||||
import org.springframework.integration.test.condition.LongRunningTest;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.messaging.support.ErrorMessage;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -88,21 +92,15 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
@LongRunningTest
|
||||
public class TcpOutboundGatewayTests {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TcpOutboundGatewayTests.class);
|
||||
|
||||
private final AsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
|
||||
|
||||
@ClassRule
|
||||
public static LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();
|
||||
|
||||
@Rule
|
||||
public Log4j2LevelAdjuster adjuster = Log4j2LevelAdjuster.trace();
|
||||
|
||||
|
||||
@Test
|
||||
public void testGoodNetSingle() throws Exception {
|
||||
void testGoodNetSingle() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean done = new AtomicBoolean();
|
||||
final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
|
||||
@@ -167,7 +165,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoodNetMultiplex() throws Exception {
|
||||
void testGoodNetMultiplex() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean done = new AtomicBoolean();
|
||||
final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
|
||||
@@ -223,7 +221,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoodNetTimeout() throws Exception {
|
||||
void testGoodNetTimeout() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean done = new AtomicBoolean();
|
||||
final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
|
||||
@@ -304,7 +302,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoodNetGWTimeout() throws Exception {
|
||||
void testGoodNetGWTimeout() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = buildCF(port);
|
||||
@@ -314,7 +312,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoodNetGWTimeoutCached() throws Exception {
|
||||
void testGoodNetGWTimeoutCached() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = buildCF(port);
|
||||
@@ -446,7 +444,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachingFailover() throws Exception {
|
||||
void testCachingFailover() throws Exception {
|
||||
final AtomicReference<ServerSocket> serverSocket = new AtomicReference<ServerSocket>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean done = new AtomicBoolean();
|
||||
@@ -528,7 +526,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailoverCached() throws Exception {
|
||||
void testFailoverCached() throws Exception {
|
||||
final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean done = new AtomicBoolean();
|
||||
@@ -619,7 +617,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetGWPropagatesSocketClose() throws Exception {
|
||||
void testNetGWPropagatesSocketClose() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
|
||||
@@ -633,7 +631,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNioGWPropagatesSocketClose() throws Exception {
|
||||
void testNioGWPropagatesSocketClose() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
|
||||
@@ -647,7 +645,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachedGWPropagatesSocketClose() throws Exception {
|
||||
void testCachedGWPropagatesSocketClose() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
|
||||
@@ -662,7 +660,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailoverGWPropagatesSocketClose() throws Exception {
|
||||
void testFailoverGWPropagatesSocketClose() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
|
||||
@@ -746,7 +744,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetGWPropagatesSocketTimeout() throws Exception {
|
||||
void testNetGWPropagatesSocketTimeout() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
|
||||
@@ -760,7 +758,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNioGWPropagatesSocketTimeout() throws Exception {
|
||||
void testNioGWPropagatesSocketTimeout() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
|
||||
@@ -774,7 +772,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetGWPropagatesSocketTimeoutSingleUse() throws Exception {
|
||||
void testNetGWPropagatesSocketTimeoutSingleUse() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
|
||||
@@ -788,7 +786,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNioGWPropagatesSocketTimeoutSingleUse() throws Exception {
|
||||
void testNioGWPropagatesSocketTimeoutSingleUse() throws Exception {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = serverSocket.getLocalPort();
|
||||
AbstractClientConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
|
||||
@@ -853,7 +851,7 @@ public class TcpOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNioSecondChance() throws Exception {
|
||||
void testNioSecondChance() throws Exception {
|
||||
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(0);
|
||||
final int port = server.getLocalPort();
|
||||
TcpOutboundGateway gateway = new TcpOutboundGateway();
|
||||
@@ -908,4 +906,164 @@ public class TcpOutboundGatewayTests {
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAsyncSingle() throws Exception {
|
||||
testAsync(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAsyncShared() throws Exception {
|
||||
testAsync(false);
|
||||
}
|
||||
|
||||
private void testAsync(boolean singleUse) throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean done = new AtomicBoolean();
|
||||
final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
|
||||
ThreadPoolTaskScheduler sched = new ThreadPoolTaskScheduler();
|
||||
sched.initialize();
|
||||
TcpOutboundGateway gateway = null;
|
||||
try {
|
||||
this.executor.execute(() -> {
|
||||
try {
|
||||
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(0, 100);
|
||||
serverSocket.set(server);
|
||||
latch.countDown();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
Socket socket = server.accept();
|
||||
InputStream is = socket.getInputStream();
|
||||
OutputStream os = socket.getOutputStream();
|
||||
ByteArrayCrLfSerializer deser = new ByteArrayCrLfSerializer();
|
||||
try {
|
||||
deser.deserialize(is);
|
||||
}
|
||||
catch (SoftEndOfStreamException e) {
|
||||
continue;
|
||||
}
|
||||
deser.serialize(("reply" + ++i).getBytes(), os);
|
||||
if (!singleUse) {
|
||||
deser.deserialize(is);
|
||||
deser.serialize(("reply" + ++i).getBytes(), os);
|
||||
}
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (!done.get()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
assertThat(latch.await(10000, TimeUnit.MILLISECONDS)).isTrue();
|
||||
AbstractClientConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost",
|
||||
serverSocket.get().getLocalPort());
|
||||
ccf.setSoTimeout(10000);
|
||||
ccf.setSingleUse(singleUse);
|
||||
ccf.start();
|
||||
gateway = new TcpOutboundGateway();
|
||||
gateway.setConnectionFactory(ccf);
|
||||
gateway.setAsync(true);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
AtomicReference<Thread> thread = new AtomicReference<>();
|
||||
replyChannel.addInterceptor(new ChannelInterceptor() {
|
||||
|
||||
@Override
|
||||
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
|
||||
thread.set(Thread.currentThread());
|
||||
}
|
||||
|
||||
});
|
||||
gateway.setRequiresReply(true);
|
||||
gateway.setOutputChannel(replyChannel);
|
||||
gateway.setBeanFactory(mock(BeanFactory.class));
|
||||
gateway.setTaskScheduler(sched);
|
||||
gateway.afterPropertiesSet();
|
||||
gateway.handleMessage(MessageBuilder.withPayload("Test1").build());
|
||||
gateway.handleMessage(MessageBuilder.withPayload("Test2").build());
|
||||
Message<?> reply = replyChannel.receive(10000);
|
||||
assertThat(reply).isNotNull();
|
||||
assertThat(reply.getPayload()).isEqualTo("reply1".getBytes());
|
||||
reply = replyChannel.receive(10000);
|
||||
assertThat(reply).isNotNull();
|
||||
assertThat(reply.getPayload()).isEqualTo("reply2".getBytes());
|
||||
assertThat(thread.get()).isNotSameAs(Thread.currentThread());
|
||||
}
|
||||
finally {
|
||||
if (gateway != null) {
|
||||
gateway.stop();
|
||||
}
|
||||
done.set(true);
|
||||
if (serverSocket.get() != null) {
|
||||
serverSocket.get().close();
|
||||
}
|
||||
sched.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAsyncTimeout() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CountDownLatch doneLatch = new CountDownLatch(1);
|
||||
final AtomicBoolean done = new AtomicBoolean();
|
||||
final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
|
||||
AbstractClientConnectionFactory ccf = null;
|
||||
ThreadPoolTaskScheduler sched = new ThreadPoolTaskScheduler();
|
||||
sched.initialize();
|
||||
try {
|
||||
this.executor.execute(() -> {
|
||||
try {
|
||||
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(0, 100);
|
||||
serverSocket.set(server);
|
||||
latch.countDown();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
Socket socket = server.accept();
|
||||
doneLatch.await(10, TimeUnit.SECONDS);
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (!done.get()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
assertThat(latch.await(10000, TimeUnit.MILLISECONDS)).isTrue();
|
||||
ccf = new TcpNetClientConnectionFactory("localhost",
|
||||
serverSocket.get().getLocalPort());
|
||||
ccf.setSoTimeout(10000);
|
||||
ccf.start();
|
||||
TcpOutboundGateway gateway = new TcpOutboundGateway();
|
||||
gateway.setConnectionFactory(ccf);
|
||||
gateway.setAsync(true);
|
||||
gateway.setRemoteTimeout(10);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
gateway.setRequiresReply(true);
|
||||
gateway.setOutputChannel(replyChannel);
|
||||
gateway.setBeanFactory(mock(BeanFactory.class));
|
||||
gateway.setTaskScheduler(sched);
|
||||
gateway.afterPropertiesSet();
|
||||
QueueChannel errorChannel = new QueueChannel();
|
||||
gateway.handleMessage(MessageBuilder.withPayload("Test1")
|
||||
.setErrorChannel(errorChannel)
|
||||
.build());
|
||||
Message<?> reply = errorChannel.receive(10000);
|
||||
assertThat(reply).isInstanceOf(ErrorMessage.class);
|
||||
assertThat(reply.getPayload()).isInstanceOf(MessageTimeoutException.class);
|
||||
doneLatch.countDown();
|
||||
gateway.stop();
|
||||
}
|
||||
finally {
|
||||
done.set(true);
|
||||
if (ccf != null) {
|
||||
ccf.stop();
|
||||
}
|
||||
if (serverSocket.get() != null) {
|
||||
serverSocket.get().close();
|
||||
}
|
||||
sched.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user