Skip to content

Commit 51d8635

Browse files
committed
Polishing.
- Add integration test to verify all removeMessageListener variants - Add unit tests for negative tests on removeMesssageListener invalid inputs - Clarify javadocs Original Pull Request #3237
1 parent fd245a8 commit 51d8635

4 files changed

Lines changed: 125 additions & 21 deletions

File tree

src/main/java/org/springframework/data/redis/listener/RedisMessageListenerContainer.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -585,12 +585,9 @@ public void addMessageListener(MessageListener listener, Topic topic) {
585585
/**
586586
* Removes a message listener from the given topics. If the container is running, the listener stops receiving
587587
* (matching) messages as soon as possible.
588-
* <p>
589-
* Note that this method obeys the Redis (p)unsubscribe semantics - meaning an empty/null collection will remove
590-
* listener from all channels.
591588
*
592589
* @param listener message listener.
593-
* @param topics message listener topics.
590+
* @param topics message topics to remove listener from or {@literal empty} to remove the listener from all topics.
594591
*/
595592
public void removeMessageListener(MessageListener listener, Collection<? extends Topic> topics) {
596593
removeListener(listener, topics);
@@ -599,27 +596,22 @@ public void removeMessageListener(MessageListener listener, Collection<? extends
599596
/**
600597
* Removes a message listener from the given topic. If the container is running, the listener stops receiving
601598
* (matching) messages as soon as possible.
602-
* <p>
603-
* Note that this method obeys the Redis (p)unsubscribe semantics - meaning an empty/null collection will remove
604-
* listener from all channels.
605599
*
606600
* @param listener message listener.
607601
* @param topic message topic.
608602
*/
609603
public void removeMessageListener(MessageListener listener, Topic topic) {
604+
Assert.notNull(topic, "Topic must not be null");
610605
removeMessageListener(listener, Collections.singleton(topic));
611606
}
612607

613608
/**
614-
* Removes the given message listener completely (from all topics). If the container is running, the listener stops
609+
* Removes the given message listener from all topics. If the container is running, the listener stops
615610
* receiving (matching) messages as soon as possible.
616611
*
617612
* @param listener message listener.
618613
*/
619614
public void removeMessageListener(MessageListener listener) {
620-
621-
Assert.notNull(listener, "MessageListener must not be null");
622-
623615
removeMessageListener(listener, Collections.emptySet());
624616
}
625617

src/test/java/org/springframework/data/redis/listener/PubSubResubscribeTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,9 @@ void testContainerChannelResubscribe() {
178178
String ANOTHER_CHANNEL = "pubsub::test::extra";
179179

180180
// bind listener on another channel
181+
container.addMessageListener(adapter, new ChannelTopic(CHANNEL));
181182
container.addMessageListener(adapter, new ChannelTopic(ANOTHER_CHANNEL));
182-
container.removeMessageListener(null, new ChannelTopic(CHANNEL));
183+
container.removeMessageListener(adapter, new ChannelTopic(CHANNEL));
183184

184185
// Listener removed from channel
185186
template.convertAndSend(CHANNEL, payload1);

src/test/java/org/springframework/data/redis/listener/RedisMessageListenerContainerIntegrationTests.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.awaitility.Awaitility.*;
2020

21+
import java.time.Duration;
2122
import java.util.Arrays;
2223
import java.util.Collection;
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
import java.util.Set;
2327
import java.util.concurrent.CompletableFuture;
2428
import java.util.concurrent.CountDownLatch;
2529
import java.util.concurrent.TimeUnit;
@@ -354,6 +358,102 @@ public void onChannelSubscribed(byte[] channel, long count) {
354358
container.destroy();
355359
}
356360

361+
/**
362+
* Tests the variants of {@link RedisMessageListenerContainer#removeMessageListener} to ensure the removal stops
363+
* message delivery and that the pattern and channel listeners do not interfere with one another upon removal.
364+
*/
365+
@Test // GH-3237
366+
void shouldRemoveListenerFromChannelsAndTopics() throws Exception {
367+
368+
AtomicInteger subscriptions = new AtomicInteger();
369+
Map<String, String> patternsMessages = new HashMap<>();
370+
Map<String, String> channelsMessages = new HashMap<>();
371+
372+
MessageListener patternListener = new CompositeListener() {
373+
@Override
374+
public void onMessage(Message message, byte @Nullable [] pattern) {
375+
patternsMessages.put(new String(pattern), message.toString());
376+
}
377+
378+
@Override
379+
public void onPatternSubscribed(byte[] pattern, long count) {
380+
subscriptions.incrementAndGet();
381+
}
382+
383+
};
384+
385+
CompositeListener channelListener = new CompositeListener() {
386+
@Override
387+
public void onMessage(Message message, byte @Nullable [] pattern) {
388+
channelsMessages.put(new String(pattern), message.toString());
389+
}
390+
391+
@Override
392+
public void onChannelSubscribed(byte[] channel, long count) {
393+
subscriptions.incrementAndGet();
394+
}
395+
396+
};
397+
398+
try {
399+
container.start();
400+
container.addMessageListener(patternListener, new PatternTopic("a-pattern-0"));
401+
container.addMessageListener(patternListener, new PatternTopic("a-pattern-1"));
402+
container.addMessageListener(patternListener, new PatternTopic("a-pattern-2"));
403+
container.addMessageListener(channelListener, new ChannelTopic("a-channel-0"));
404+
container.addMessageListener(channelListener, new ChannelTopic("a-channel-1"));
405+
406+
// Wait for the subscriptions to register
407+
await().untilAtomic(subscriptions, Matchers.is(5));
408+
409+
try (RedisConnection connection = connectionFactory.getConnection()) {
410+
411+
// remove patternListener from single topic
412+
container.removeMessageListener(patternListener, new PatternTopic("a-pattern-0"));
413+
414+
// send each topic a msg - removed listener should not get msg
415+
connection.publish("a-pattern-0".getBytes(), "pattern100".getBytes());
416+
connection.publish("a-pattern-1".getBytes(), "pattern101".getBytes());
417+
connection.publish("a-pattern-2".getBytes(), "pattern102".getBytes());
418+
await().atMost(Duration.ofSeconds(15)).untilAsserted(() -> {
419+
assertThat(patternsMessages).doesNotContain(entry("a-pattern-0", "pattern100"))
420+
.contains(entry("a-pattern-1", "pattern101"), entry("a-pattern-2", "pattern102"));
421+
});
422+
patternsMessages.clear();
423+
424+
// remove pattern listener from multiple (2 remaining topics)
425+
container.removeMessageListener(patternListener,
426+
Set.of(new PatternTopic("a-pattern-1"), new PatternTopic("a-pattern-2")));
427+
428+
// no listener should receive msgs
429+
connection.publish("a-pattern-0".getBytes(), "pattern100".getBytes());
430+
connection.publish("a-pattern-1".getBytes(), "pattern101".getBytes());
431+
connection.publish("a-pattern-2".getBytes(), "pattern102".getBytes());
432+
await().atMost(Duration.ofSeconds(15)).untilAsserted(() -> assertThat(patternsMessages).isEmpty());
433+
434+
// sanity check sending msgs to channels (removal of pattern listeners did not disrupt)
435+
connection.publish("a-channel-0".getBytes(), "channel100".getBytes());
436+
connection.publish("a-channel-1".getBytes(), "channel101".getBytes());
437+
await().atMost(Duration.ofSeconds(15)).untilAsserted(() -> {
438+
assertThat(channelsMessages).containsExactly(entry("a-channel-0", "channel100"),
439+
entry("a-channel-1", "channel101"));
440+
});
441+
channelsMessages.clear();
442+
443+
// remove channel listener from all channel topics
444+
container.removeMessageListener(channelListener);
445+
446+
// should receive no msgs
447+
connection.publish("a-channel-0".getBytes(), "channel100".getBytes());
448+
connection.publish("a-channel-1".getBytes(), "channel101".getBytes());
449+
await().atMost(Duration.ofSeconds(15)).untilAsserted(() -> assertThat(channelsMessages).isEmpty());
450+
}
451+
}
452+
finally {
453+
container.destroy();
454+
}
455+
}
456+
357457
interface CompositeListener extends MessageListener, SubscriptionListener {
358458

359459
}

src/test/java/org/springframework/data/redis/listener/RedisMessageListenerContainerUnitTests.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.nio.charset.StandardCharsets;
2222
import java.util.Collections;
23+
import java.util.Set;
2324
import java.util.concurrent.CountDownLatch;
2425
import java.util.concurrent.Executor;
2526
import java.util.concurrent.atomic.AtomicBoolean;
@@ -225,19 +226,29 @@ void failsOnDuplicateInit() {
225226
assertThatIllegalStateException().isThrownBy(() -> container.afterPropertiesSet());
226227
}
227228

228-
@Test // GH-3009
229-
void shouldRemoveAllListenersWhenListenerIsNull() {
229+
@Test // GH-3237
230+
void removeListenerBySingleTopicShouldFailWhenTopicIsNull() {
231+
assertThatIllegalArgumentException().isThrownBy(() -> container.removeMessageListener(adapter, (Topic) null));
232+
}
230233

231-
MessageListener listener1 = mock(MessageListener.class);
232-
MessageListener listener2 = mock(MessageListener.class);
233-
Topic topic = new ChannelTopic("topic1");
234+
@Test // GH-3237
235+
void removeListenerBySingleTopicShouldFailWhenListenerIsNull() {
236+
assertThatIllegalArgumentException().isThrownBy(() -> container.removeMessageListener(null, new ChannelTopic("a")));
237+
}
234238

235-
container.addMessageListener(listener1, Collections.singletonList(topic));
236-
container.addMessageListener(listener2, Collections.singletonList(topic));
239+
@Test // GH-3237
240+
void removeListenerBySetShouldFailWhenListenerIsNull() {
241+
assertThatIllegalArgumentException().isThrownBy(() -> container.removeMessageListener(null, Collections.emptySet()));
242+
}
237243

238-
container.removeMessageListener(null, Collections.singletonList(topic));
244+
@Test // GH-3237
245+
void removeListenerBySetShouldFailWhenSetIsNull() {
246+
assertThatIllegalArgumentException().isThrownBy(() -> container.removeMessageListener(adapter, (Set)null));
247+
}
239248

240-
assertThatNoException().isThrownBy(() -> container.removeMessageListener(null, Collections.singletonList(topic)));
249+
@Test // GH-3237
250+
void removeListenerFromAllTopicsShouldFailWhenListenerIsNull() {
251+
assertThatIllegalArgumentException().isThrownBy(() -> container.removeMessageListener(null));
241252
}
242253

243254
@Test // GH-3208

0 commit comments

Comments
 (0)