Shutting down JMS listener container waiting for shutdown of message listener invokers - spring

Post invoking shutdown on JMS listener container it is waiting for Message listener invokers to be shutdown.
We are calling this shut down in Job event listener post completion of the job, and there are no messages to be consumed from request queue, so not sure why these message listeners are not shutting down. below are logs from Server post shutdown call.
INFO [org.springframework.batch.core.launch.support.SimpleJobLauncher] (aspAsyncExecutor-1) Job: [FlowJob: [name=SENEXTRACT]] completed with the following parameters: [{​​​​​​​--listId=15195, --letterId=BF2025, --randomId=99a3d764-8cbf-4dbd-81c8-a5442e6e67e5}​​​​​​​] and the following status: [COMPLETED] in 4m20s353ms
DEBUG [org.springframework.integration.channel.DirectChannel] (aspAsyncExecutor-1) preSend on channel 'bean 'controlChannel'', message: GenericMessage [payload=#senExtractInGateway.stop(), headers={​​​​​​​id=6d7cdaaa-21e5-9a2f-e2ab-d83523747837, timestamp=1620803972934}​​​​​​​]
DEBUG [org.springframework.integration.handler.ServiceActivatingHandler] (aspAsyncExecutor-1) ServiceActivator for [org.springframework.integration.handler.ExpressionCommandMessageProcessor#2decec67] (org.springframework.integration.config.ExpressionControlBusFactoryBean#0) received message: GenericMessage [payload=#senExtractInGateway.stop(), headers={​​​​​​​id=6d7cdaaa-21e5-9a2f-e2ab-d83523747837, timestamp=1620803972934}​​​​​​​]
DEBUG [org.springframework.jms.listener.DefaultMessageListenerContainer] (aspAsyncExecutor-1) Shutting down JMS listener container
DEBUG [org.springframework.jms.listener.DefaultMessageListenerContainer] (aspAsyncExecutor-1) Waiting for shutdown of message listener invokers
DEBUG [org.springframework.jms.listener.DefaultMessageListenerContainer] (aspAsyncExecutor-1) Still waiting for shutdown of 30 message listener invokers (iteration 0)
Configuration is as follows.
<int-jms:inbound-gateway
id="senExtractInGateway" connection-factory="connectionFactory"
correlation-key="JMSCorrelationID"
request-channel="senExtractProcessingRequestChannel"
request-destination-name="senExtractRequestQueue"
reply-channel="senExtractProcessingReplyChannel"
default-reply-queue-name="senExtractReplyQueue"
concurrent-consumers="1" max-concurrent-consumers="30"
max-messages-per-task="1" reply-timeout="1800000"
receive-timeout="1800000" auto-startup="false"/>
<integration:channel id="controlChannel" />
<integration:control-bus input-channel="controlChannel" />
Code Snippet:
MessageChannel controlChannel = appContext.getBean("controlChannel", MessageChannel.class);
controlChannel.send(new GenericMessage<String>("#senExtractInGateway.start()"));
logger.info("Received before adapter started: ");
//controlChannel.send(new GenericMessage<String>("#senExtractSrvActivator.start()"));
JobExecution execution = jobLauncher.run(job, jobParameters);
controlChannel.send(new GenericMessage<String>("#senExtractInGateway.stop()"));
Server Threads

You have this config receive-timeout="1800000". So, it is not a surprise that your consumer is blocked for those 30 mins. See its JavaDocs:
/**
* Actually receive a message from the given consumer.
* #param consumer the JMS MessageConsumer to receive with
* #param timeout the receive timeout (a negative value indicates
* a no-wait receive; 0 indicates an indefinite wait attempt)
* #return the JMS Message received, or {#code null} if none
* #throws JMSException if thrown by JMS API methods
* #since 4.3
* #see #RECEIVE_TIMEOUT_NO_WAIT
* #see #RECEIVE_TIMEOUT_INDEFINITE_WAIT
*/
#Nullable
protected Message receiveFromConsumer(MessageConsumer consumer, long timeout) throws JMSException {
As long as consumers are blocked waiting for a message in the destination, the container cannot claim its state as stopped, therefor it waits for those consumers to become free and release resources.

Related

RabbitHandler to create consumer and retry on Fatal Exception in Spring for queue on listening to RabbitMQ

I am using Spring AMQP RabbitHandler and have written the following code:
#RabbitListener(queues = "#{testQueue.name}")
public class Tut4Receiver {
#RabbitHandler
public void receiveMessage(String message){
System.out.println("Message received "+message);
}
}
The Queue is defined like:-
#Bean
public Queue testQueue() {
return new AnonymousQueue();
}
I am using separate code to initialize the Connection Factory.
My question is if RabbitMQ is down for some time, it keeps on retrying to create a consumer but only if it receives a ConnectionRefused error. But suppose the user does not exist in RabbitMQ and there is a gap in which a new user will be created, then it receives a fatal error from RabbitMQ and it never retries due to which the result is auto delete queue would be created on RabbitMQ without any consumers.
Stack Trace:
SimpleMessageListenerContainer] [SimpleAsyncTaskExecutor-11] [|] [|||] Consumer received fatal exception on startup
org.springframework.amqp.rabbit.listener.exception.FatalListenerStartupException: Authentication failure
at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:476)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1280)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.amqp.AmqpAuthenticationException: com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.
at org.springframework.amqp.rabbit.support.RabbitExceptionTranslator.convertRabbitAccessException(RabbitExceptionTranslator.java:65)
at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:309)
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.createConnection(CachingConnectionFactory.java:547)
at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils$1.createConnection(ConnectionFactoryUtils.java:90)
at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.doGetTransactionalResourceHolder(ConnectionFactoryUtils.java:140)
at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactoryUtils.java:76)
at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:472)
... 2 common frames omitted
Caused by: com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.
at com.rabbitmq.client.impl.AMQConnection.start(AMQConnection.java:339)
at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:813)
at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:767)
at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:887)
at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:300)
SimpleMessageListenerContainer] [SimpleAsyncTaskExecutor-11] [|] [|||] Stopping container from aborted consumer
[|] [|||] Waiting for workers to finish.
[|] [|||] Successfully waited for workers to finish.
Any way to retry even on fatal exceptions like when the user does not exist?
Authentication failures are considered fatal by default and not retried.
You can override this behavior by setting a property on the listener container (possibleAuthenticationFailureFatal). The property is not available as a boot property so you have to override boot's container factory...
#Bean(name = "rabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setContainerConfigurer(smlc -> smlc.setPossibleAuthenticationFailureFatal(false));
return factory;
}

Kafka outbound channel can't take message from a channel

There is a channel :
<integration:channel id="sampleChannel">
</integration:channel>
And there is a kafka outbound channel with sampleChannel value for channel property :
<int-kafka:outbound-channel-adapter id="kafkaOutboundChannelAdapter"
kafka-template="template"
auto-startup="false"
channel="sampleChannel"
topic="foo"
sync="false"
send-failure-channel="errorChannel"
partition-id-expression="1">
</int-kafka:outbound-channel-adapter>
When message sends and get to sampleChannel, this exception throws :
org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'org.springframework.context.support.ClassPathXmlApplicationContext#1623b78d.sampleChannel'
Why kafka outbound can't take message from the sampleChannel ?
Your auto-startup is set to false, so at the time when Application Context is started there is no subscriber to the sampleChannel.
Set auto-startup to true. Or change your sampleChannel to be pollable of publish-subscribe channel.

Spring Integration JMS with JTA rollback although exception is handled in ErrorHandler

I am using Spring Integration with JTA support through Atomikos and JMS bound to different Webshpere MQ and a Oracle Database.
Obviosly for others it seems to be the same thread as in Spring Integration JMS with JTA rollback when message goes to errorChannel but it isn't not at all.
The flow is the following:
message-driven-channel-adapter receive the message within transaction
some transformations
ServiceActivator with deeper business logic
DataBase Update
Everything works really fine expect for one szenario:
If an unchecked exception occurs (maybe due to inkonsistent data which shouldn't be) within the ServiceActivator the message is rethrown and handled within an ErrorHandler (via ErrorChannel).
There - in some cases - the orgininal incoming message should be send to a DeadLetter Queue (Webshere MQ). This is done with outbound-channel-adapter.
See enclosed configuration:
<jms:message-driven-channel-adapter id="midxMessageDrivenAdapter"
channel="midxReplyChannel"
acknowledge="auto"
transaction-manager="transactionManager"
connection-factory="connectionFactory"
concurrent-consumers="1"
error-channel="errorChannel"
max-concurrent-consumers="${cmab.integration.midx.max.consumer}"
idle-task-execution-limit="${cmab.integration.midx.idleTaskExecutionLimit}"
receive-timeout="${cmab.integration.midx.receiveTimeout}"
destination="midxReplyQueue"/>
................
<int:service-activator input-channel="midxReplyProcessChannel" ref="processMidxReplyDbWriter" />
<int:service-activator input-channel="errorChannel" ref="inputErrorHandler" />
<jms:outbound-channel-adapter id="deadLetterOutboundChannelAdapter"
channel="errorDlChannel" destination="deadLetterQueue"
delivery-persistent="true" connection-factory="nonXAConnectionFactory"/>
Some important hints:
message-driven-channel-adapter:
connectionFactory is a MQXAQueueConnectionFactory wihtin an AtomikosConnectionFactoryBean
transaction-manager is Spring JtaTransactionManager
outbound-channel-adapter:
connection-factory is a nonXAConnectionFactory.
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
.....
cachingConnectionFactory.setTargetConnectionFactory(mqQueueConnectionFactory);
return cachingConnectionFactory;
And now the error which I observed:
Although I handled the unchecked exception in my ErrorHandler (ref="inputErrorHandler"; send the message to DeadLetter-Queue) an rollback is initiated and the message-driven-channel-adapter receives the message again and again.
Curious is the fact that indeed the message is delivered to the DeadLetterQueue via the outbound-channel-adapter. The destination (deadLetterQueue) contains the failed messages.
Question: What I'm doing wrong? The failed original incoming message is rolled back although I handled the exception in my ErrorHandler.
Any help really appreciate. Many thanks in advance.
concerning to my comment see enclose code of my InputErrorHandler:
if (throwable instanceof MessageHandlingException) {
MessageHandlingException messageHandlingException = (MessageHandlingException) throwable;
if (messageHandlingException.getCause() != null
&& (messageHandlingException.getCause() instanceof CmabProcessDbException
|| messageHandlingException.getCause() instanceof CmabReplyFormatException)) {
String appMessage = ((CmabException) messageHandlingException.getCause()).getAppMessagesAsString();
LOGGER.error(String.format("cmab rollback exception occured: %s", appMessage));
LOGGER.error("******** initiate rollback *********");
throw throwable.getCause();
} else {
ErrorDto payload = fillMessagePayload(messageHandlingException, throwableClassName);
sendMessageToTargetQueue(payload);
}
As I mentioned the "business" ServiceActivator throws an unchecked exception so in this case the ELSE-Statements are calling.
Within that I build up a Message with MessagBuilder and sends it ti the errorDlChannel (s. above the outbound-channel-adapter!).
private void sendMessageToDeadLetterQueue(Message<?> message, String description, String cause) {
.......
Message<?> mb =
MessageBuilder.withPayload(message.getPayload())
.copyHeaders(message.getHeaders())
.setHeaderIfAbsent("senderObject", this.getClass().getName())
.setHeaderIfAbsent("errorText", description)
.setHeaderIfAbsent("errorCause", errorCause).build();
errorDlChannel.send(mb);
}
That's all. This is for this case the last statement. There is nothing anything else in the main method of my ErrorHandler. No rethrow or other stuff.
So this is the reason of my confusion. For me the exception is handled by sending it to the errorDlChannel (outbound-channel-adapter -> DeadLetterQueue).
I saw the message on the DeadLetter Queue but nevertheless a jta rollback occurs...and IMO this shouldn't bee.

AMQAuthenticationException for WSO2 ESB Publisher

I'm trying to integrate WSO2 ESB (4.7.0) with WSO2 Message Broker (2.1.0).
This is my use case:
A generic HTTP Client sends a REST request to a PassThrough Proxy
deployed on WSO2 ESB
The ESB Proxy has an outsequence: forwards the request to the real
REST service, then, in the outsequence, it sends the response to a
mediator class (deployed inside the WSO2 ESB)
The mediator class make some stuff and has this method inside, by
which it can publish an event to a topic on the Message Broker:
Code:
private void publishEvent(){
String topicName = "MyEvent";
Properties properties = new Properties();
TopicConnection topicConnection = null;
properties.put("java.naming.factory.initial", "org.wso2.andes.jndi.PropertiesFileInitialContextFactory");
String connectionString = "amqp://admin:admin#clientID/carbon?brokerlist='tcp://localhost:5673'";
properties.put("connectionfactory.QueueConnectionFactory", connectionString);
try {
InitialContext ctx = new InitialContext(properties);
TopicConnectionFactory tcf = (TopicConnectionFactory) ctx.lookup("QueueConnectionFactory");
TopicConnection connection = tcf.createTopicConnection();
TopicSession session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(topicName);
TopicPublisher publisher= session.createPublisher(topic);
TextMessage textMessage =
session.createTextMessage("<asd>sono il publisher di WSO2 message Broker!</asd>");
publisher.publish(textMessage);
publisher.close();
}catch (Exception e){
e.printStackTrace();
}
}
On the other end there is a subscriber to the "MyEvent" topic which
is already running.
(I took the code both for Publisher and subscriber from this URL: http://wso2.com/library/articles/2011/12/wso2-esb-example-pubsub-soa/)
When the client sends the REST request to the proxy (1), the mediator is invoked correctly but when it tries to execute the PublishEvent method (3), nothing happens and the WSO2 ESB logs this 530 error:
INFO - ConnectionCloseMethodHandler ConnectionClose frame received
[2013-10-17 12:43:47,733] INFO - ConnectionCloseMethodHandler Error :530: not allowed:Thread-33
[2013-10-17 12:43:47,734] ERROR - AMQStateManager No Waiters for error saving as last error:Attempt to redeclare exchange: amq.topic of type topic to null.
[2013-10-17 12:43:47,735] ERROR - AMQConnection Throwable Received but no listener set: org.wso2.andes.client.AMQAuthenticationException: Attempt to redeclare exchange: amq.topic of type topic to null. [error code 530: not allowed]
ERROR - AMQStateManager No Waiters for error saving as last error:Attempt to redeclare exchange: amq.topic of type topic to null.
[2013-10-17 20:53:44,996] ERROR - AMQConnection error:
org.wso2.andes.client.AMQAuthenticationException: Attempt to redeclare exchange: amq.topic of type topic to null. [error code 530: not allowed]
at org.wso2.andes.client.handler.ConnectionCloseMethodHandler.methodReceived(ConnectionCloseMethodHandler.java:79)
at org.wso2.andes.client.handler.ClientMethodDispatcherImpl.dispatchConnectionClose(ClientMethodDispatcherImpl.java:192)
at org.wso2.andes.framing.amqp_0_91.ConnectionCloseBodyImpl.execute(ConnectionCloseBodyImpl.java:140)
at org.wso2.andes.client.state.AMQStateManager.methodReceived(AMQStateManager.java:111)
at org.wso2.andes.client.protocol.AMQProtocolHandler.methodBodyReceived(AMQProtocolHandler.java:515)
at org.wso2.andes.client.protocol.AMQProtocolSession.methodFrameReceived(AMQProtocolSession.java:456)
at org.wso2.andes.framing.AMQMethodBodyImpl.handle(AMQMethodBodyImpl.java:96)
at org.wso2.andes.client.protocol.AMQProtocolHandler$2.run(AMQProtocolHandler.java:466)
at org.wso2.andes.pool.Job.processAll(Job.java:109)
at org.wso2.andes.pool.Job.run(Job.java:157)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
javax.jms.JMSException: Error closing connection: org.wso2.andes.client.AMQAuthenticationException: Attempt to redeclare exchange: amq.topic of type topic to null. [error code 530: not allowed]
at org.wso2.andes.client.AMQConnection.doClose(AMQConnection.java:920)
at org.wso2.andes.client.AMQConnection.close(AMQConnection.java:855)
at org.wso2.andes.client.AMQConnection.close(AMQConnection.java:846)
at org.wso2.andes.client.AMQConnection.close(AMQConnection.java:841)
at innova.esb.mediator.TopicPublisher.publishMessage(TopicPublisher.java:49)
at innova.esb.mediator.MediatorEventPublisher.mediate(MediatorEventPublisher.java:35)
at org.apache.synapse.mediators.ext.ClassMediator.mediate(ClassMediator.java:78)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:71)
at org.apache.synapse.mediators.base.SequenceMediator.mediate(SequenceMediator.java:114)
at org.apache.synapse.core.axis2.Axis2SynapseEnvironment.injectMessage(Axis2SynapseEnvironment.java:239)
at org.apache.synapse.core.axis2.SynapseCallbackReceiver.handleMessage(SynapseCallbackReceiver.java:443)
at org.apache.synapse.core.axis2.SynapseCallbackReceiver.receive(SynapseCallbackReceiver.java:166)
at org.apache.axis2.engine.AxisEngine.receive(AxisEngine.java:180)
at org.apache.synapse.transport.passthru.ClientWorker.run(ClientWorker.java:222)
at org.apache.axis2.transport.base.threads.NativeWorkerPool$1.run(NativeWorkerPool.java:172)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
Caused by: org.wso2.andes.client.AMQAuthenticationException: Attempt to redeclare exchange: amq.topic of type topic to null. [error code 530: not allowed]
at org.wso2.andes.client.handler.ConnectionCloseMethodHandler.methodReceived(ConnectionCloseMethodHandler.java:79)
at org.wso2.andes.client.handler.ClientMethodDispatcherImpl.dispatchConnectionClose(ClientMethodDispatcherImpl.java:192)
at org.wso2.andes.framing.amqp_0_91.ConnectionCloseBodyImpl.execute(ConnectionCloseBodyImpl.java:140)
at org.wso2.andes.client.state.AMQStateManager.methodReceived(AMQStateManager.java:111)
at org.wso2.andes.client.protocol.AMQProtocolHandler.methodBodyReceived(AMQProtocolHandler.java:515)
at org.wso2.andes.client.protocol.AMQProtocolSession.methodFrameReceived(AMQProtocolSession.java:456)
at org.wso2.andes.framing.AMQMethodBodyImpl.handle(AMQMethodBodyImpl.java:96)
at org.wso2.andes.client.protocol.AMQProtocolHandler$2.run(AMQProtocolHandler.java:466)
at org.wso2.andes.pool.Job.processAll(Job.java:109)
at org.wso2.andes.pool.Job.run(Job.java:157)
Obviously the subscriber doesn't catch any event.
What is wrong? Why do I have this Connection Exception?
Thanks a lot.

'Write-Only' over a TCP connection with Spring Integration

I am using int-ip:tcp-connection-factory and int-ip:tcp-outbound-gateway to communicate with an external server. The protocol for most of the services offered by the server follows the standard request-response style... which is working great. However, there are a few situations where I only need to send a request and no response is expected.
My question is, how do I configure my channels and connections so that I can specify whether or not to wait for a response? Currently I can't find a way and Spring always blocks after sending the request even if I am not expecting a response.
EDIT:
As suggested, I have used a tcp-outbound-channel-adapter. My config file has only the followings:
<int:channel id="requestChannel" />
<int-ip:tcp-outbound-channel-adapter
channel="requestChannel" connection-factory="client" />
<int-ip:tcp-connection-factory id="client"
type="client" host="smtp.gmail.com" port="587" single-use="false"
so-timeout="10000" />
And here's my Main class:
public final class Main {
private static final Logger LOGGER = Logger.getLogger(Main.class);
public static void main(final String... args) throws IOException {
LOGGER.debug("entered main()...");
final AbstractApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:*-context.xml");
MessageChannel requestChannel = context.getBean("requestChannel", MessageChannel.class);
requestChannel.send(MessageBuilder.withPayload("QUIT").build());
LOGGER.debug("exiting main()...");
}
}
Finally this is what I get in my log:
11:57:15.877 INFO [main][com.together.email.Main] entered main()...
11:57:16.295 INFO [main][org.springframework.integration.config.xml.DefaultConfiguringBeanFactoryPostProcessor] No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
11:57:16.295 INFO [main][org.springframework.integration.config.xml.DefaultConfiguringBeanFactoryPostProcessor] No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
11:57:16.480 INFO [main][org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory] started client
11:57:16.480 INFO [main][org.springframework.integration.endpoint.EventDrivenConsumer] Adding {ip:tcp-outbound-channel-adapter} as a subscriber to the 'requestChannel' channel
11:57:16.480 INFO [main][org.springframework.integration.channel.DirectChannel] Channel 'requestChannel' has 1 subscriber(s).
11:57:16.480 INFO [main][org.springframework.integration.endpoint.EventDrivenConsumer] started org.springframework.integration.config.ConsumerEndpointFactoryBean#0
11:57:16.480 INFO [main][org.springframework.integration.endpoint.EventDrivenConsumer] Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
11:57:16.481 INFO [main][org.springframework.integration.channel.PublishSubscribeChannel] Channel 'errorChannel' has 1 subscriber(s).
11:57:16.481 INFO [main][org.springframework.integration.endpoint.EventDrivenConsumer] started _org.springframework.integration.errorLogger
11:57:16.509 DEBUG [main][org.springframework.integration.channel.DirectChannel] preSend on channel 'requestChannel', message: [Payload=QUIT][Headers={timestamp=1381021036509, id=860ebe82-06c6-4393-95c7-0ece1a0a0e5d}]
11:57:16.509 DEBUG [main][org.springframework.integration.ip.tcp.TcpSendingMessageHandler] org.springframework.integration.ip.tcp.TcpSendingMessageHandler#0 received message: [Payload=QUIT][Headers={timestamp=1381021036509, id=860ebe82-06c6-4393-95c7-0ece1a0a0e5d}]
11:57:16.509 DEBUG [main][org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory] Opening new socket connection to smtp.gmail.com:587
11:57:16.745 DEBUG [main][org.springframework.integration.ip.tcp.connection.TcpNetConnection] New connection smtp.gmail.com:587:550c9b68-10a0-442d-b65d-d25d28df306b
11:57:16.748 DEBUG [main][org.springframework.integration.ip.tcp.TcpSendingMessageHandler] Got Connection smtp.gmail.com:587:550c9b68-10a0-442d-b65d-d25d28df306b
11:57:16.749 DEBUG [pool-1-thread-1][org.springframework.integration.ip.tcp.connection.TcpNetConnection] TcpListener exiting - no listener and not single use
11:57:16.750 DEBUG [main][org.springframework.integration.ip.tcp.connection.TcpNetConnection] Message sent [Payload=QUIT][Headers={timestamp=1381021036509, id=860ebe82-06c6-4393-95c7-0ece1a0a0e5d}]
11:57:16.750 DEBUG [main][org.springframework.integration.channel.DirectChannel] postSend (sent=true) on channel 'requestChannel', message: [Payload=QUIT][Headers={timestamp=1381021036509, id=860ebe82-06c6-4393-95c7-0ece1a0a0e5d}]
11:57:16.751 INFO [main][com.together.email.Main] exiting main()...
I have set Spring's logging to debug level so that I can give you more details. As you can see from the last line of the log, my main exits. However, unfortunately the application doesn't terminate as [pool-1-thread-1] continues running. This thread comes to life as soon as send is invoked on requestChannel. Any idea what's going on here?
[In this example, I am sending an SMTP QUIT message as soon as the application connects to Google. In practice, I would actually not start with a QUIT. For example, at the beginning I may start with a HELO message. I have tried hooking in a tcp-inbound-channel-adapter to get the message response and that works great. The problem is with messages where I don't expect a reply.]
So, I suggest you to inject into the <int-ip:tcp-connection-factory> some 'fake' task-executor:
public class NullExecutor implements Executor {
public void execute(Runnable command) {}
}
In this case your connection from AbstractClientConnectionFactory#obtainConnection() won't be configured (runned) to read from socket.
However System.exit(0); as last line of your main is enough. In this case all thread will be terminated, if they aren't daemons.

Resources