Skip to content

Commit 85faeef

Browse files
committed
DefaultMessageListenerContainer immediately invokes stop callback when not running
Issue: SPR-14233 (cherry picked from commit e45d33f)
1 parent d49ecab commit 85faeef

File tree

2 files changed

+74
-23
lines changed

2 files changed

+74
-23
lines changed

spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,12 @@ public void start() throws JmsException {
614614
@Override
615615
public void stop(Runnable callback) throws JmsException {
616616
synchronized (this.lifecycleMonitor) {
617+
if (!isRunning() || this.stopCallback != null) {
618+
// Not started, already stopped, or previous stop attempt in progress
619+
// -> return immediately, no stop process to control anymore.
620+
callback.run();
621+
return;
622+
}
617623
this.stopCallback = callback;
618624
}
619625
stop();

spring-jms/src/test/java/org/springframework/jms/listener/DefaultMessageListenerContainerTests.java

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.jms.listener;
1818

19+
import java.util.concurrent.CountDownLatch;
20+
import java.util.concurrent.TimeUnit;
1921
import javax.jms.Connection;
2022
import javax.jms.ConnectionFactory;
2123
import javax.jms.Destination;
@@ -32,73 +34,90 @@
3234
import static org.mockito.BDDMockito.*;
3335

3436
/**
35-
*
3637
* @author Stephane Nicoll
3738
*/
3839
public class DefaultMessageListenerContainerTests {
3940

4041
@Test
4142
public void applyBackOff() {
42-
BackOff mock = mock(BackOff.class);
43+
BackOff backOff = mock(BackOff.class);
4344
BackOffExecution execution = mock(BackOffExecution.class);
4445
given(execution.nextBackOff()).willReturn(BackOffExecution.STOP);
45-
given(mock.start()).willReturn(execution);
46+
given(backOff.start()).willReturn(execution);
4647

47-
DefaultMessageListenerContainer container = createContainer(mock, createFailingContainerFactory());
48+
DefaultMessageListenerContainer container = createContainer(createFailingContainerFactory());
49+
container.setBackOff(backOff);
4850
container.start();
4951
assertEquals(true, container.isRunning());
5052

5153
container.refreshConnectionUntilSuccessful();
5254

5355
assertEquals(false, container.isRunning());
54-
verify(mock).start();
56+
verify(backOff).start();
5557
verify(execution).nextBackOff();
5658
}
5759

5860
@Test
5961
public void applyBackOffRetry() {
60-
BackOff mock = mock(BackOff.class);
62+
BackOff backOff = mock(BackOff.class);
6163
BackOffExecution execution = mock(BackOffExecution.class);
6264
given(execution.nextBackOff()).willReturn(50L, BackOffExecution.STOP);
63-
given(mock.start()).willReturn(execution);
65+
given(backOff.start()).willReturn(execution);
6466

65-
DefaultMessageListenerContainer container = createContainer(mock, createFailingContainerFactory());
67+
DefaultMessageListenerContainer container = createContainer(createFailingContainerFactory());
68+
container.setBackOff(backOff);
6669
container.start();
6770
container.refreshConnectionUntilSuccessful();
6871

6972
assertEquals(false, container.isRunning());
70-
verify(mock).start();
73+
verify(backOff).start();
7174
verify(execution, times(2)).nextBackOff();
7275
}
7376

7477
@Test
7578
public void recoverResetBackOff() {
76-
BackOff mock = mock(BackOff.class);
79+
BackOff backOff = mock(BackOff.class);
7780
BackOffExecution execution = mock(BackOffExecution.class);
7881
given(execution.nextBackOff()).willReturn(50L, 50L, 50L); // 3 attempts max
79-
given(mock.start()).willReturn(execution);
82+
given(backOff.start()).willReturn(execution);
8083

81-
DefaultMessageListenerContainer container = createContainer(mock, createRecoverableContainerFactory(1));
84+
DefaultMessageListenerContainer container = createContainer(createRecoverableContainerFactory(1));
85+
container.setBackOff(backOff);
8286
container.start();
8387
container.refreshConnectionUntilSuccessful();
8488

8589
assertEquals(true, container.isRunning());
86-
verify(mock).start();
90+
verify(backOff).start();
8791
verify(execution, times(1)).nextBackOff(); // only on attempt as the second one lead to a recovery
8892
}
8993

90-
private DefaultMessageListenerContainer createContainer(BackOff backOff, ConnectionFactory connectionFactory) {
94+
@Test
95+
public void runnableIsInvokedEvenIfContainerIsNotRunning() throws InterruptedException {
96+
DefaultMessageListenerContainer container = createRunningContainer();
97+
container.stop();
98+
99+
// container is stopped but should nevertheless invoke the runnable argument
100+
TestRunnable runnable2 = new TestRunnable();
101+
container.stop(runnable2);
102+
runnable2.waitForCompletion();
103+
}
91104

92-
Destination destination = new Destination() {};
93105

106+
private DefaultMessageListenerContainer createRunningContainer() {
107+
DefaultMessageListenerContainer container = createContainer(createSuccessfulConnectionFactory());
108+
container.afterPropertiesSet();
109+
container.start();
110+
return container;
111+
}
112+
113+
private DefaultMessageListenerContainer createContainer(ConnectionFactory connectionFactory) {
114+
Destination destination = new Destination() {};
94115

95116
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
96117
container.setConnectionFactory(connectionFactory);
97118
container.setCacheLevel(DefaultMessageListenerContainer.CACHE_NONE);
98119
container.setDestination(destination);
99-
container.setBackOff(backOff);
100120
return container;
101-
102121
}
103122

104123
private ConnectionFactory createFailingContainerFactory() {
@@ -112,8 +131,8 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
112131
});
113132
return connectionFactory;
114133
}
115-
catch (JMSException e) {
116-
throw new IllegalStateException(); // never happen
134+
catch (JMSException ex) {
135+
throw new IllegalStateException(ex); // never happen
117136
}
118137
}
119138

@@ -122,7 +141,6 @@ private ConnectionFactory createRecoverableContainerFactory(final int failingAtt
122141
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
123142
given(connectionFactory.createConnection()).will(new Answer<Object>() {
124143
int currentAttempts = 0;
125-
126144
@Override
127145
public Object answer(InvocationOnMock invocation) throws Throwable {
128146
currentAttempts++;
@@ -136,8 +154,35 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
136154
});
137155
return connectionFactory;
138156
}
139-
catch (JMSException e) {
140-
throw new IllegalStateException(); // never happen
157+
catch (JMSException ex) {
158+
throw new IllegalStateException(ex); // never happen
159+
}
160+
}
161+
162+
private ConnectionFactory createSuccessfulConnectionFactory() {
163+
try {
164+
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
165+
given(connectionFactory.createConnection()).willReturn(mock(Connection.class));
166+
return connectionFactory;
167+
}
168+
catch (JMSException ex) {
169+
throw new IllegalStateException(ex); // never happen
170+
}
171+
}
172+
173+
174+
private static class TestRunnable implements Runnable {
175+
176+
private final CountDownLatch countDownLatch = new CountDownLatch(1);
177+
178+
@Override
179+
public void run() {
180+
this.countDownLatch.countDown();
181+
}
182+
183+
public void waitForCompletion() throws InterruptedException {
184+
this.countDownLatch.await(2, TimeUnit.SECONDS);
185+
assertEquals("callback was not invoked", 0, this.countDownLatch.getCount());
141186
}
142187
}
143188

0 commit comments

Comments
 (0)