DATAKV-22
+ wrap up ListeningContainer implementation
This commit is contained in:
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.keyvalue.redis.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
@@ -34,6 +36,7 @@ import org.springframework.data.keyvalue.redis.connection.Message;
|
||||
import org.springframework.data.keyvalue.redis.connection.MessageListener;
|
||||
import org.springframework.data.keyvalue.redis.connection.RedisConnection;
|
||||
import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.keyvalue.redis.connection.Subscription;
|
||||
import org.springframework.data.keyvalue.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.scheduling.SchedulingAwareRunnable;
|
||||
@@ -171,7 +174,7 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
throw new UnsupportedOperationException();
|
||||
subscriptionTask.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +216,8 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
* the {@link #setTaskExecutor(Executor)} will be used. In some cases, this might be undersired as
|
||||
* the listening to the connection is a long running task.
|
||||
*
|
||||
* <p/>Note: This implementation uses at most one thread (depending on whether there are any listeners registered or not).
|
||||
* <p/>Note: This implementation uses at most one long running thread (depending on whether there are any listeners registered or not)
|
||||
* and up to two threads during the initial registration.
|
||||
*
|
||||
* @param subscriptionExecutor The subscriptionExecutor to set.
|
||||
*/
|
||||
@@ -279,29 +283,41 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
}
|
||||
|
||||
/**
|
||||
* Method inspecting whether listening for messages (and thus using a thread) is actually needed.
|
||||
* Method inspecting whether listening for messages (and thus using a thread) is actually needed and triggering it.
|
||||
*/
|
||||
private void lazyListen() {
|
||||
boolean debug = log.isDebugEnabled();
|
||||
boolean started = false;
|
||||
|
||||
if (channelMapping.size() > 0 || patternMapping.size() > 0) {
|
||||
subscriptionExecutor.execute(subscriptionTask);
|
||||
listening = true;
|
||||
if (!listening) {
|
||||
synchronized (monitor) {
|
||||
if (!listening) {
|
||||
if (channelMapping.size() > 0 || patternMapping.size() > 0) {
|
||||
subscriptionExecutor.execute(subscriptionTask);
|
||||
listening = true;
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
listening = false;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
log.debug("Started listening for Redis messages");
|
||||
}
|
||||
}
|
||||
else {
|
||||
listening = false;
|
||||
if (debug) {
|
||||
log.debug("Postpone listening for Redis messages until actual listeners are added");
|
||||
if (started) {
|
||||
log.debug("Started listening for Redis messages");
|
||||
}
|
||||
else {
|
||||
log.debug("Postpone listening for Redis messages until actual listeners are added");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void addListener(MessageListener listener, Collection<Topic> topics) {
|
||||
List<byte[]> channels = new ArrayList<byte[]>(topics.size());
|
||||
List<byte[]> patterns = new ArrayList<byte[]>(topics.size());
|
||||
|
||||
for (Topic topic : topics) {
|
||||
|
||||
ArrayHolder holder = new ArrayHolder(serializer.serialize(topic.getTopic()));
|
||||
@@ -313,6 +329,7 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
channelMapping.put(holder, collection);
|
||||
}
|
||||
collection.add(listener);
|
||||
channels.add(holder.array);
|
||||
}
|
||||
|
||||
else if (topic instanceof PatternTopic) {
|
||||
@@ -322,12 +339,22 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
patternMapping.put(holder, collection);
|
||||
}
|
||||
collection.add(listener);
|
||||
patterns.add(holder.array);
|
||||
}
|
||||
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown topic type '" + topic.getClass() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// check the current listening state
|
||||
if (listening) {
|
||||
subscriptionTask.subscribeChannel(channels.toArray(new byte[channels.size()][]));
|
||||
subscriptionTask.subscribePattern(patterns.toArray(new byte[patterns.size()][]));
|
||||
}
|
||||
else {
|
||||
lazyListen();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,6 +365,52 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
*/
|
||||
private class SubscriptionTask implements SchedulingAwareRunnable {
|
||||
|
||||
/**
|
||||
* Runnable used, on a parallel thread, to do the initial pSubscribe.
|
||||
* This is required since, during initialization, both subscribe and pSubscribe
|
||||
* might be needed but since the first call is blocking, the second call needs to
|
||||
* executed in parallel.
|
||||
*
|
||||
* @author Costin Leau
|
||||
*/
|
||||
private class PatternSubscriptionTask implements SchedulingAwareRunnable {
|
||||
|
||||
private long WAIT = 1000;
|
||||
private long ROUNDS = 3;
|
||||
|
||||
@Override
|
||||
public boolean isLongLived() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// wait for subscription to be initialized
|
||||
boolean done = false;
|
||||
// wait 3 rounds for subscription to be initialized
|
||||
for (int i = 0; i < ROUNDS || done; i++) {
|
||||
if (connection != null) {
|
||||
synchronized (localMonitor) {
|
||||
if (connection != null && connection.isSubscribed()) {
|
||||
done = true;
|
||||
connection.getSubscription().pSubscribe(unwrap(patternMapping.keySet()));
|
||||
}
|
||||
else {
|
||||
try {
|
||||
Thread.sleep(WAIT);
|
||||
} catch (InterruptedException ex) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private volatile RedisConnection connection;
|
||||
private final Object localMonitor = new Object();
|
||||
|
||||
@Override
|
||||
public boolean isLongLived() {
|
||||
return true;
|
||||
@@ -345,10 +418,9 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
RedisConnection connection = connectionFactory.getConnection();
|
||||
connection = connectionFactory.getConnection();
|
||||
try {
|
||||
if (connection.isSubscribed()) {
|
||||
listening = false;
|
||||
throw new IllegalStateException("Retrieved connection is already subscribed; aborting listening");
|
||||
}
|
||||
|
||||
@@ -357,14 +429,26 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
// subscribe one way or the other
|
||||
// and schedule the rest
|
||||
if (!channelMapping.isEmpty()) {
|
||||
// schedule the rest of the subscription
|
||||
subscriptionExecutor.execute(new PatternSubscriptionTask());
|
||||
connection.subscribe(new DispatchMessageListener(), unwrap(channelMapping.keySet()));
|
||||
}
|
||||
else {
|
||||
connection.pSubscribe(new DispatchMessageListener(), unwrap(patternMapping.keySet()));
|
||||
}
|
||||
|
||||
} finally {
|
||||
// this block is executed once the subscription has ended
|
||||
// meaning cleanup is required
|
||||
listening = false;
|
||||
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
synchronized (localMonitor) {
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,6 +467,80 @@ public class RedisListeningContainer implements InitializingBean, DisposableBean
|
||||
|
||||
return unwrapped;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (connection != null) {
|
||||
synchronized (localMonitor) {
|
||||
if (connection != null) {
|
||||
Subscription sub = connection.getSubscription();
|
||||
if (sub != null) {
|
||||
sub.pUnsubscribe();
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void subscribeChannel(byte[]... channels) {
|
||||
if (channels != null && channels.length > 0) {
|
||||
if (connection != null) {
|
||||
synchronized (localMonitor) {
|
||||
if (connection != null) {
|
||||
Subscription sub = connection.getSubscription();
|
||||
if (sub != null) {
|
||||
sub.subscribe(channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void subscribePattern(byte[]... patterns) {
|
||||
if (patterns != null && patterns.length > 0) {
|
||||
if (connection != null) {
|
||||
synchronized (localMonitor) {
|
||||
if (connection != null) {
|
||||
Subscription sub = connection.getSubscription();
|
||||
if (sub != null) {
|
||||
sub.pSubscribe(patterns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unsubscribeChannel(byte[]... channels) {
|
||||
if (channels != null && channels.length > 0) {
|
||||
if (connection != null) {
|
||||
synchronized (localMonitor) {
|
||||
if (connection != null) {
|
||||
Subscription sub = connection.getSubscription();
|
||||
if (sub != null) {
|
||||
sub.unsubscribe(channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unsubscribePattern(byte[]... patterns) {
|
||||
if (patterns != null && patterns.length > 0) {
|
||||
if (connection != null) {
|
||||
synchronized (localMonitor) {
|
||||
if (connection != null) {
|
||||
Subscription sub = connection.getSubscription();
|
||||
if (sub != null) {
|
||||
sub.pUnsubscribe(patterns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user