Changed to single access for sub/unsub (now passes unit tests). Changed to ConcurrentHashMapV8, 174/87 ns/op
This commit is contained in:
parent
4eef758e89
commit
b8f06b7c33
@ -1,10 +1,8 @@
|
|||||||
package net.engio.mbassy.multi;
|
package net.engio.mbassy.multi;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.LockSupport;
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
@ -15,7 +13,6 @@ import net.engio.mbassy.multi.common.TransferQueue;
|
|||||||
import net.engio.mbassy.multi.error.IPublicationErrorHandler;
|
import net.engio.mbassy.multi.error.IPublicationErrorHandler;
|
||||||
import net.engio.mbassy.multi.error.PublicationError;
|
import net.engio.mbassy.multi.error.PublicationError;
|
||||||
import net.engio.mbassy.multi.subscription.Subscription;
|
import net.engio.mbassy.multi.subscription.Subscription;
|
||||||
import net.engio.mbassy.multi.subscription.SubscriptionManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for all message bus implementations with support for asynchronous message dispatch
|
* The base class for all message bus implementations with support for asynchronous message dispatch
|
||||||
@ -30,12 +27,16 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// this handler will receive all errors that occur during message dispatch or message handling
|
// this handler will receive all errors that occur during message dispatch or message handling
|
||||||
private final Collection<IPublicationErrorHandler> errorHandlers = new ArrayDeque<IPublicationErrorHandler>();
|
private final Collection<IPublicationErrorHandler> errorHandlers = new ArrayDeque<IPublicationErrorHandler>();
|
||||||
|
|
||||||
private final SubscriptionManager subscriptionManager;
|
|
||||||
private final TransferQueue<Runnable> dispatchQueue = new LinkedTransferQueue<Runnable>();
|
private final TransferQueue<Runnable> dispatchQueue = new LinkedTransferQueue<Runnable>();
|
||||||
|
|
||||||
|
|
||||||
|
private final SubscriptionManager subscriptionManager;
|
||||||
|
|
||||||
// all threads that are available for asynchronous message dispatching
|
// all threads that are available for asynchronous message dispatching
|
||||||
private List<Thread> threads;
|
private final int numberOfThreads;
|
||||||
|
private final Collection<Thread> threads;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultiMBassador() {
|
public MultiMBassador() {
|
||||||
this(Runtime.getRuntime().availableProcessors());
|
this(Runtime.getRuntime().availableProcessors());
|
||||||
@ -46,9 +47,10 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
if (numberOfThreads < 1) {
|
if (numberOfThreads < 1) {
|
||||||
numberOfThreads = 1; // at LEAST 1 thread
|
numberOfThreads = 1; // at LEAST 1 thread
|
||||||
}
|
}
|
||||||
|
this.numberOfThreads = numberOfThreads;
|
||||||
|
|
||||||
this.subscriptionManager = new SubscriptionManager(numberOfThreads);
|
this.subscriptionManager = new SubscriptionManager(numberOfThreads);
|
||||||
this.threads = new ArrayList<Thread>(numberOfThreads);
|
this.threads = new ArrayDeque<Thread>(numberOfThreads);
|
||||||
|
|
||||||
NamedThreadFactory dispatchThreadFactory = new NamedThreadFactory("MessageBus");
|
NamedThreadFactory dispatchThreadFactory = new NamedThreadFactory("MessageBus");
|
||||||
for (int i = 0; i < numberOfThreads; i++) {
|
for (int i = 0; i < numberOfThreads; i++) {
|
||||||
@ -88,6 +90,8 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
this.threads.add(runner);
|
this.threads.add(runner);
|
||||||
runner.start();
|
runner.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -105,19 +109,19 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unsubscribe(Object listener) {
|
public void subscribe(final Object listener) {
|
||||||
this.subscriptionManager.unsubscribe(listener);
|
MultiMBassador.this.subscriptionManager.subscribe(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void subscribe(Object listener) {
|
public void unsubscribe(final Object listener) {
|
||||||
this.subscriptionManager.subscribe(listener);
|
MultiMBassador.this.subscriptionManager.unsubscribe(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPendingMessages() {
|
public boolean hasPendingMessages() {
|
||||||
return !this.dispatchQueue.isEmpty();
|
return this.dispatchQueue.getWaitingConsumerCount() != this.numberOfThreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
package net.engio.mbassy.multi.subscription;
|
package net.engio.mbassy.multi;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import net.engio.mbassy.multi.common.ConcurrentHashMapV8;
|
||||||
import net.engio.mbassy.multi.common.IdentityObjectTree;
|
import net.engio.mbassy.multi.common.IdentityObjectTree;
|
||||||
import net.engio.mbassy.multi.common.ReflectionUtils;
|
import net.engio.mbassy.multi.common.ReflectionUtils;
|
||||||
import net.engio.mbassy.multi.common.StrongConcurrentSet;
|
import net.engio.mbassy.multi.common.StrongConcurrentSet;
|
||||||
import net.engio.mbassy.multi.listener.MessageHandler;
|
import net.engio.mbassy.multi.listener.MessageHandler;
|
||||||
import net.engio.mbassy.multi.listener.MetadataReader;
|
import net.engio.mbassy.multi.listener.MetadataReader;
|
||||||
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
import net.engio.mbassy.multi.subscription.Subscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The subscription managers responsibility is to consistently handle and synchronize the message listener subscription process.
|
* The subscription managers responsibility is to consistently handle and synchronize the message listener subscription process.
|
||||||
@ -19,31 +20,43 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
|||||||
* listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines
|
* listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines
|
||||||
* message handlers.
|
* message handlers.
|
||||||
*
|
*
|
||||||
|
*
|
||||||
|
* Subscribe/Unsubscribe, while it is possible for them to be 100% concurrent (in relation to listeners per subscription),
|
||||||
|
* getting an accurate reflection of the number of subscriptions, or guaranteeing a "HAPPENS-BEFORE" relationship really
|
||||||
|
* complicates this.
|
||||||
|
*
|
||||||
|
* Given these restrictions and complexity, it is much easier to create a MPSC blocking queue, and have a single thread
|
||||||
|
* manage sub/unsub.
|
||||||
|
*
|
||||||
* @author bennidi
|
* @author bennidi
|
||||||
* Date: 5/11/13
|
* Date: 5/11/13
|
||||||
* @author dorkbox, llc
|
* @author dorkbox, llc
|
||||||
* Date: 2/2/15
|
* Date: 2/2/15
|
||||||
*/
|
*/
|
||||||
public class SubscriptionManager {
|
public class SubscriptionManager {
|
||||||
private final int MAP_STRIPING;
|
private final int STRIPE_SIZE;
|
||||||
private float LOAD_FACTOR;
|
private float LOAD_FACTOR;
|
||||||
|
|
||||||
// the metadata reader that is used to inspect objects passed to the subscribe method
|
// the metadata reader that is used to inspect objects passed to the subscribe method
|
||||||
private static final MetadataReader metadataReader = new MetadataReader();
|
private static final MetadataReader metadataReader = new MetadataReader();
|
||||||
|
|
||||||
|
// remember already processed classes that do not contain any message handlers
|
||||||
|
private final Map<Class<?>, Boolean> nonListeners;
|
||||||
|
|
||||||
// all subscriptions per message type. We perpetually KEEP the types, as this lowers the amount of locking required
|
// all subscriptions per message type. We perpetually KEEP the types, as this lowers the amount of locking required
|
||||||
// this is the primary list for dispatching a specific message
|
// this is the primary list for dispatching a specific message
|
||||||
// write access is synchronized and happens only when a listener of a specific class is registered the first time
|
// write access is synchronized and happens only when a listener of a specific class is registered the first time
|
||||||
private final ConcurrentHashMap<Class<?>, Collection<Subscription>> subscriptionsPerMessageSingle;
|
private final Map<Class<?>, Collection<Subscription>> subscriptionsPerMessageSingle;
|
||||||
private final IdentityObjectTree<Class<?>, Collection<Subscription>> subscriptionsPerMessageMulti;
|
private final IdentityObjectTree<Class<?>, Collection<Subscription>> subscriptionsPerMessageMulti;
|
||||||
|
// synchronize read/write access to the subscription maps
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
|
||||||
|
|
||||||
// all subscriptions per messageHandler type
|
// all subscriptions per messageHandler type
|
||||||
// this map provides fast access for subscribing and unsubscribing
|
// this map provides fast access for subscribing and unsubscribing
|
||||||
// write access is synchronized and happens very infrequently
|
// write access is synchronized and happens very infrequently
|
||||||
// once a collection of subscriptions is stored it does not change
|
// once a collection of subscriptions is stored it does not change
|
||||||
private final ConcurrentHashMap<Class<?>, Collection<Subscription>> subscriptionsPerListener;
|
private final Map<Class<?>, Collection<Subscription>> subscriptionsPerListener;
|
||||||
|
|
||||||
private final Object holder = new Object[0];
|
|
||||||
|
|
||||||
private final Map<Class<?>, Set<Class<?>>> superClassesCache;
|
private final Map<Class<?>, Set<Class<?>>> superClassesCache;
|
||||||
|
|
||||||
@ -54,62 +67,36 @@ public class SubscriptionManager {
|
|||||||
// private final IdentityObjectTree<Class<?>, Collection<Subscription>> superClassSubscriptionsMulti = new IdentityObjectTree<Class<?>, Collection<Subscription>>();
|
// private final IdentityObjectTree<Class<?>, Collection<Subscription>> superClassSubscriptionsMulti = new IdentityObjectTree<Class<?>, Collection<Subscription>>();
|
||||||
|
|
||||||
|
|
||||||
// remember already processed classes that do not contain any message handlers
|
SubscriptionManager(int numberOfThreads) {
|
||||||
private final Map<Class<?>, Object> nonListeners;
|
this.STRIPE_SIZE = numberOfThreads;
|
||||||
|
|
||||||
|
|
||||||
public SubscriptionManager(int numberOfThreads) {
|
|
||||||
this.MAP_STRIPING = numberOfThreads;
|
|
||||||
this.LOAD_FACTOR = 0.8f;
|
this.LOAD_FACTOR = 0.8f;
|
||||||
|
|
||||||
this.subscriptionsPerMessageSingle = new ConcurrentHashMap<Class<?>, Collection<Subscription>>(4, this.LOAD_FACTOR, this.MAP_STRIPING);
|
this.nonListeners = new ConcurrentHashMapV8<Class<?>, Boolean>(4, 0.8F, this.STRIPE_SIZE);
|
||||||
|
|
||||||
|
this.subscriptionsPerMessageSingle = new ConcurrentHashMapV8<Class<?>, Collection<Subscription>>(4, this.LOAD_FACTOR);
|
||||||
this.subscriptionsPerMessageMulti = new IdentityObjectTree<Class<?>, Collection<Subscription>>();
|
this.subscriptionsPerMessageMulti = new IdentityObjectTree<Class<?>, Collection<Subscription>>();
|
||||||
|
|
||||||
// only used during SUB/UNSUB
|
// only used during SUB/UNSUB
|
||||||
this.subscriptionsPerListener = new ConcurrentHashMap<Class<?>, Collection<Subscription>>(4, this.LOAD_FACTOR, this.MAP_STRIPING);
|
this.subscriptionsPerListener = new ConcurrentHashMapV8<Class<?>, Collection<Subscription>>(4, this.LOAD_FACTOR);
|
||||||
|
|
||||||
this.superClassesCache = new ConcurrentHashMap<Class<?>, Set<Class<?>>>(8, this.LOAD_FACTOR, this.MAP_STRIPING);
|
this.superClassesCache = new ConcurrentHashMapV8<Class<?>, Set<Class<?>>>(8, this.LOAD_FACTOR, this.STRIPE_SIZE);
|
||||||
// superClassSubscriptions keeps track of all subscriptions of super classes. SUB/UNSUB dumps it, so it is recreated dynamically.
|
// superClassSubscriptions keeps track of all subscriptions of super classes. SUB/UNSUB dumps it, so it is recreated dynamically.
|
||||||
// it's a hit on SUB/UNSUB, but improves performance on handlers
|
// it's a hit on SUB/UNSUB, but improves performance on handlers
|
||||||
this.superClassSubscriptions = new ConcurrentHashMap<Class<?>, Set<Subscription>>(8, this.LOAD_FACTOR, this.MAP_STRIPING);
|
this.superClassSubscriptions = new ConcurrentHashMapV8<Class<?>, Set<Subscription>>(8, this.LOAD_FACTOR, this.STRIPE_SIZE);
|
||||||
|
|
||||||
this.nonListeners = new ConcurrentHashMap<Class<?>, Object>(4, this.LOAD_FACTOR, this.MAP_STRIPING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final void resetSuperClassSubs() {
|
private final void resetSuperClassSubs() {
|
||||||
this.superClassSubscriptions.clear();
|
this.superClassSubscriptions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unsubscribe(Object listener) {
|
/**
|
||||||
|
* Can ONLY be called by a single thread, in order to guarantee a "happens-before" relationship to subscriptions
|
||||||
|
*/
|
||||||
|
public void subscribe(Object listener) {
|
||||||
if (listener == null) {
|
if (listener == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> listenerClass = listener.getClass();
|
|
||||||
// these are a concurrent collection
|
|
||||||
Collection<Subscription> subscriptions = this.subscriptionsPerListener.get(listenerClass);
|
|
||||||
|
|
||||||
if (subscriptions != null) {
|
|
||||||
for (Subscription subscription : subscriptions) {
|
|
||||||
subscription.unsubscribe(listener);
|
|
||||||
|
|
||||||
// purposefully DO NOT do anything else. We keep references to Class<?>/subscription, because
|
|
||||||
// it acts as a "cache" of sorts, so that future add operations are quicker.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ThreadLocal<Collection<Subscription>> subInitialValue = new ThreadLocal<Collection<Subscription>>() {
|
|
||||||
@Override
|
|
||||||
protected java.util.Collection<Subscription> initialValue() {
|
|
||||||
return new StrongConcurrentSet<Subscription>(8, SubscriptionManager.this.LOAD_FACTOR);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// when a class is subscribed, the registrations for that class are permanent in the "subscriptionsPerListener"?
|
|
||||||
public void subscribe(Object listener) {
|
|
||||||
Class<?> listenerClass = listener.getClass();
|
Class<?> listenerClass = listener.getClass();
|
||||||
|
|
||||||
if (this.nonListeners.containsKey(listenerClass)) {
|
if (this.nonListeners.containsKey(listenerClass)) {
|
||||||
@ -117,25 +104,22 @@ public class SubscriptionManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.lock.lock();
|
||||||
|
try {
|
||||||
Collection<Subscription> subscriptions = this.subscriptionsPerListener.get(listenerClass);
|
Collection<Subscription> subscriptions = this.subscriptionsPerListener.get(listenerClass);
|
||||||
if (subscriptions == null) {
|
if (subscriptions == null) {
|
||||||
// a listener is subscribed for the first time
|
// a listener is subscribed for the first time
|
||||||
|
resetSuperClassSubs();
|
||||||
|
|
||||||
Collection<MessageHandler> messageHandlers = SubscriptionManager.metadataReader.getMessageListener(listenerClass).getHandlers();
|
Collection<MessageHandler> messageHandlers = SubscriptionManager.metadataReader.getMessageListener(listenerClass).getHandlers();
|
||||||
int handlersSize = messageHandlers.size();
|
int handlersSize = messageHandlers.size();
|
||||||
|
|
||||||
if (handlersSize == 0) {
|
if (handlersSize == 0) {
|
||||||
// remember the class as non listening class if no handlers are found
|
// remember the class as non listening class if no handlers are found
|
||||||
this.nonListeners.put(listenerClass, this.holder);
|
this.nonListeners.put(listener.getClass(), Boolean.TRUE);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
Collection<Subscription> putIfAbsent = this.subscriptionsPerListener.putIfAbsent(listenerClass, this.subInitialValue.get());
|
subscriptions = new StrongConcurrentSet<Subscription>(messageHandlers.size(), this.LOAD_FACTOR);
|
||||||
if (putIfAbsent != null) {
|
|
||||||
subscriptions = putIfAbsent;
|
|
||||||
} else {
|
|
||||||
subscriptions = this.subInitialValue.get();
|
|
||||||
this.subInitialValue.set(new StrongConcurrentSet<Subscription>(8, this.LOAD_FACTOR));
|
|
||||||
}
|
|
||||||
|
|
||||||
resetSuperClassSubs();
|
|
||||||
|
|
||||||
// create NEW subscriptions for all detected message handlers
|
// create NEW subscriptions for all detected message handlers
|
||||||
for (MessageHandler messageHandler : messageHandlers) {
|
for (MessageHandler messageHandler : messageHandlers) {
|
||||||
@ -145,36 +129,28 @@ public class SubscriptionManager {
|
|||||||
|
|
||||||
subscriptions.add(subscription);
|
subscriptions.add(subscription);
|
||||||
|
|
||||||
//
|
// now add this subscription to each of the handled types
|
||||||
// save the subscription per message type
|
|
||||||
//
|
|
||||||
Class<?>[] handledMessageTypes = subscription.getHandledMessageTypes();
|
Class<?>[] handledMessageTypes = subscription.getHandledMessageTypes();
|
||||||
int size = handledMessageTypes.length;
|
int size = handledMessageTypes.length;
|
||||||
boolean acceptsSubtypes = subscription.acceptsSubtypes();
|
|
||||||
|
|
||||||
if (size == 1) {
|
if (size == 1) {
|
||||||
// single
|
// single
|
||||||
Class<?> clazz = handledMessageTypes[0];
|
Class<?> clazz = handledMessageTypes[0];
|
||||||
|
|
||||||
Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz);
|
Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz);
|
||||||
if (subs == null) {
|
if (subs == null || subs.isEmpty()) {
|
||||||
putIfAbsent = this.subscriptionsPerMessageSingle.putIfAbsent(clazz, this.subInitialValue.get());
|
subs = new StrongConcurrentSet<Subscription>(8, this.LOAD_FACTOR);
|
||||||
if (putIfAbsent != null) {
|
this.subscriptionsPerMessageSingle.put(clazz, subs);
|
||||||
subs = putIfAbsent;
|
|
||||||
} else {
|
|
||||||
subs = this.subInitialValue.get();
|
|
||||||
this.subInitialValue.set(new StrongConcurrentSet<Subscription>(8, this.LOAD_FACTOR));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subs.add(subscription);
|
subs.add(subscription);
|
||||||
|
|
||||||
if (acceptsSubtypes) {
|
|
||||||
// race conditions will result in duplicate answers, which we don't care about
|
|
||||||
setupSuperClassCache(clazz);
|
setupSuperClassCache(clazz);
|
||||||
|
} else {
|
||||||
|
// multiversion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// order is critical for safe publication
|
||||||
|
this.subscriptionsPerListener.put(listenerClass, subscriptions);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// subscriptions already exist and must only be updated
|
// subscriptions already exist and must only be updated
|
||||||
@ -183,6 +159,21 @@ public class SubscriptionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
this.lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// } else {
|
||||||
|
// // subscriptions already exist and must only be updated
|
||||||
|
// for (Subscription subscription : subscriptions) {
|
||||||
|
// subscription.subscribe(listener);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
//
|
//
|
||||||
// if (subscriptions != null) {
|
// if (subscriptions != null) {
|
||||||
// // subscriptions already exist and must only be updated
|
// // subscriptions already exist and must only be updated
|
||||||
@ -293,6 +284,85 @@ public class SubscriptionManager {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can ONLY be called by a single thread, in order to guarantee a "happens-before" relationship to subscriptions
|
||||||
|
*/
|
||||||
|
public final void unsubscribe(Object listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> listenerClass = listener.getClass();
|
||||||
|
if (this.nonListeners.containsKey(listenerClass)) {
|
||||||
|
// early reject of known classes that do not define message handlers
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSuperClassSubs();
|
||||||
|
|
||||||
|
this.lock.lock();
|
||||||
|
try {
|
||||||
|
// these are a concurrent collection
|
||||||
|
Collection<Subscription> subscriptions = this.subscriptionsPerListener.get(listenerClass);
|
||||||
|
if (subscriptions != null) {
|
||||||
|
|
||||||
|
for (Subscription subscription : subscriptions) {
|
||||||
|
subscription.unsubscribe(listener); // this is thread safe, but the following stuff is NOT thread safe.
|
||||||
|
|
||||||
|
boolean isEmpty = subscription.isEmpty();
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
// single or multi?
|
||||||
|
Class<?>[] handledMessageTypes = subscription.getHandledMessageTypes();
|
||||||
|
int size = handledMessageTypes.length;
|
||||||
|
if (size == 1) {
|
||||||
|
// single
|
||||||
|
Class<?> clazz = handledMessageTypes[0];
|
||||||
|
|
||||||
|
Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz);
|
||||||
|
if (subs != null) {
|
||||||
|
subs.remove(subscription);
|
||||||
|
|
||||||
|
if (subs.isEmpty()) {
|
||||||
|
// remove element
|
||||||
|
this.subscriptionsPerMessageSingle.remove(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// // NOTE: Not thread-safe! must be synchronized in outer scope
|
||||||
|
// IdentityObjectTree<Class<?>, Collection<Subscription>> tree;
|
||||||
|
//
|
||||||
|
// switch (size) {
|
||||||
|
// case 2: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes[0], handledMessageTypes[1]); break;
|
||||||
|
// case 3: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes[1], handledMessageTypes[1], handledMessageTypes[2]); break;
|
||||||
|
// default: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes); break;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (tree != null) {
|
||||||
|
// Collection<Subscription> subs = tree.getValue();
|
||||||
|
// if (subs != null) {
|
||||||
|
// subs.remove(subscription);
|
||||||
|
//
|
||||||
|
// if (subs.isEmpty()) {
|
||||||
|
// // remove tree element
|
||||||
|
// switch (size) {
|
||||||
|
// case 2: this.subscriptionsPerMessageMulti.remove(handledMessageTypes[0], handledMessageTypes[1]); break;
|
||||||
|
// case 3: this.subscriptionsPerMessageMulti.remove(handledMessageTypes[1], handledMessageTypes[1], handledMessageTypes[2]); break;
|
||||||
|
// default: this.subscriptionsPerMessageMulti.remove(handledMessageTypes); break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// CAN RETURN NULL
|
// CAN RETURN NULL
|
||||||
public final Collection<Subscription> getSubscriptionsByMessageType(Class<?> messageType) {
|
public final Collection<Subscription> getSubscriptionsByMessageType(Class<?> messageType) {
|
||||||
return this.subscriptionsPerMessageSingle.get(messageType);
|
return this.subscriptionsPerMessageSingle.get(messageType);
|
||||||
@ -323,11 +393,10 @@ public class SubscriptionManager {
|
|||||||
|
|
||||||
if (subsPerType == null) {
|
if (subsPerType == null) {
|
||||||
// this caches our class hierarchy. This is never cleared.
|
// this caches our class hierarchy. This is never cleared.
|
||||||
Set<Class<?>> types = this.superClassesCache.get(superType);
|
Collection<Class<?>> types = setupSuperClassCache(superType);
|
||||||
if (types == null || types.isEmpty()) {
|
if (types.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
subsPerType = new StrongConcurrentSet<Subscription>(types.size() + 1, this.LOAD_FACTOR);
|
subsPerType = new StrongConcurrentSet<Subscription>(types.size() + 1, this.LOAD_FACTOR);
|
||||||
|
|
||||||
Iterator<Class<?>> iterator = types.iterator();
|
Iterator<Class<?>> iterator = types.iterator();
|
||||||
@ -474,11 +543,13 @@ public class SubscriptionManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* race conditions will result in duplicate answers, which we don't care if happens
|
* race conditions will result in duplicate answers, which we don't care if happens
|
||||||
|
* never returns null
|
||||||
*/
|
*/
|
||||||
private void setupSuperClassCache(Class<?> clazz) {
|
private Collection<Class<?>> setupSuperClassCache(Class<?> clazz) {
|
||||||
if (!this.superClassesCache.containsKey(clazz)) {
|
Collection<Class<?>> superTypes = this.superClassesCache.get(clazz);
|
||||||
|
if (superTypes == null) {
|
||||||
// it doesn't matter if concurrent access stomps on values, since they are always the same.
|
// it doesn't matter if concurrent access stomps on values, since they are always the same.
|
||||||
Set<Class<?>> superTypes = ReflectionUtils.getSuperTypes(clazz);
|
superTypes = ReflectionUtils.getSuperTypes(clazz);
|
||||||
StrongConcurrentSet<Class<?>> set = new StrongConcurrentSet<Class<?>>(superTypes.size() + 1, this.LOAD_FACTOR);
|
StrongConcurrentSet<Class<?>> set = new StrongConcurrentSet<Class<?>>(superTypes.size() + 1, this.LOAD_FACTOR);
|
||||||
for (Class<?> c : superTypes) {
|
for (Class<?> c : superTypes) {
|
||||||
set.add(c);
|
set.add(c);
|
||||||
@ -487,53 +558,7 @@ public class SubscriptionManager {
|
|||||||
// race conditions will result in duplicate answers, which we don't care about
|
// race conditions will result in duplicate answers, which we don't care about
|
||||||
this.superClassesCache.put(clazz, set);
|
this.superClassesCache.put(clazz, set);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static class SuperClassIterator implements Iterator<Class<?>> {
|
return superTypes;
|
||||||
private final Iterator<Class<?>> iterator;
|
|
||||||
private Class<?> clazz;
|
|
||||||
|
|
||||||
public SuperClassIterator(Class<?> clazz, Collection<Class<?>> types) {
|
|
||||||
this.clazz = clazz;
|
|
||||||
if (types != null) {
|
|
||||||
this.iterator = types.iterator();
|
|
||||||
} else {
|
|
||||||
this.iterator = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
if (this.clazz != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.iterator != null) {
|
|
||||||
return this.iterator.hasNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?> next() {
|
|
||||||
if (this.clazz != null) {
|
|
||||||
Class<?> clazz2 = this.clazz;
|
|
||||||
this.clazz = null;
|
|
||||||
|
|
||||||
return clazz2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.iterator != null) {
|
|
||||||
return this.iterator.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove() {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@ package net.engio.mbassy.multi.common;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
|
|
||||||
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
||||||
@ -19,6 +20,8 @@ import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock;
|
|||||||
* Date: 2/12/12
|
* Date: 2/12/12
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractConcurrentSet<T> implements Set<T> {
|
public abstract class AbstractConcurrentSet<T> implements Set<T> {
|
||||||
|
private static final AtomicLong id = new AtomicLong();
|
||||||
|
private final long ID = id.getAndIncrement();
|
||||||
|
|
||||||
// Internal state
|
// Internal state
|
||||||
protected final transient ReentrantReadWriteUpdateLock lock = new ReentrantReadWriteUpdateLock();
|
protected final transient ReentrantReadWriteUpdateLock lock = new ReentrantReadWriteUpdateLock();
|
||||||
@ -135,13 +138,12 @@ public abstract class AbstractConcurrentSet<T> implements Set<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object[] toArray() {
|
public Object[] toArray() {
|
||||||
throw new NotImplementedException();
|
return this.entries.entrySet().toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("hiding")
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T[] toArray(T[] a) {
|
public <T> T[] toArray(T[] a) {
|
||||||
throw new NotImplementedException();
|
return this.entries.entrySet().toArray(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -164,6 +166,32 @@ public abstract class AbstractConcurrentSet<T> implements Set<T> {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + (int) (this.ID ^ this.ID >>> 32);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AbstractConcurrentSet other = (AbstractConcurrentSet) obj;
|
||||||
|
if (this.ID != other.ID) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public abstract static class Entry<T> implements ISetEntry<T> {
|
public abstract static class Entry<T> implements ISetEntry<T> {
|
||||||
|
|
||||||
|
4349
src/main/java/net/engio/mbassy/multi/common/ConcurrentHashMapV8.java
Normal file
4349
src/main/java/net/engio/mbassy/multi/common/ConcurrentHashMapV8.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -21,11 +21,11 @@ public class IdentityObjectTree<KEY, VALUE> {
|
|||||||
public IdentityObjectTree() {
|
public IdentityObjectTree() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// can be overridded to provide a custom backing map
|
/**
|
||||||
|
* can be overridden to provide a custom backing map
|
||||||
|
*/
|
||||||
protected Map<KEY, IdentityObjectTree<KEY, VALUE>> createChildren() {
|
protected Map<KEY, IdentityObjectTree<KEY, VALUE>> createChildren() {
|
||||||
//TODO: this needs to be concurrent?!?!?
|
|
||||||
return new ConcurrentHashMap<KEY, IdentityObjectTree<KEY, VALUE>>(2, 0.75f, 1);
|
return new ConcurrentHashMap<KEY, IdentityObjectTree<KEY, VALUE>>(2, 0.75f, 1);
|
||||||
// return new Reference2ReferenceOpenHashMap<KEY, IdentityObjectTree<KEY, VALUE>>(2, 0.75f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public VALUE getValue() {
|
public VALUE getValue() {
|
||||||
|
@ -76,11 +76,16 @@ public class NamedThreadFactory implements ThreadFactory {
|
|||||||
stringBuilder.append('-');
|
stringBuilder.append('-');
|
||||||
stringBuilder.append(this.threadID.getAndIncrement());
|
stringBuilder.append(this.threadID.getAndIncrement());
|
||||||
|
|
||||||
|
|
||||||
|
return newThread(stringBuilder.toString(), r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread newThread(String name, Runnable r) {
|
||||||
// stack size is arbitrary based on JVM implementation. Default is 0
|
// stack size is arbitrary based on JVM implementation. Default is 0
|
||||||
// 8k is the size of the android stack. Depending on the version of android, this can either change, or will always be 8k
|
// 8k is the size of the android stack. Depending on the version of android, this can either change, or will always be 8k
|
||||||
// To be honest, 8k is pretty reasonable for an asynchronous/event based system (32bit) or 16k (64bit)
|
// To be honest, 8k is pretty reasonable for an asynchronous/event based system (32bit) or 16k (64bit)
|
||||||
// Setting the size MAY or MAY NOT have any effect!!!
|
// Setting the size MAY or MAY NOT have any effect!!!
|
||||||
Thread t = new Thread(this.group, r, stringBuilder.toString(), NamedThreadFactory.stackSizeForThreads);
|
Thread t = new Thread(this.group, r, name, NamedThreadFactory.stackSizeForThreads);
|
||||||
t.setDaemon(true);// FORCE these threads to finish before allowing the JVM to exit
|
t.setDaemon(true);// FORCE these threads to finish before allowing the JVM to exit
|
||||||
if (t.getPriority() != Thread.NORM_PRIORITY) {
|
if (t.getPriority() != Thread.NORM_PRIORITY) {
|
||||||
t.setPriority(Thread.NORM_PRIORITY);
|
t.setPriority(Thread.NORM_PRIORITY);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.engio.mbassy.multi.common;
|
package net.engio.mbassy.multi.common;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This implementation uses strong references to the elements.
|
* This implementation uses strong references to the elements.
|
||||||
@ -17,7 +16,7 @@ public class StrongConcurrentSet<T> extends AbstractConcurrentSet<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public StrongConcurrentSet(int size, float loadFactor) {
|
public StrongConcurrentSet(int size, float loadFactor) {
|
||||||
super(new ConcurrentHashMap<T, ISetEntry<T>>(size, loadFactor, 1));
|
super(new ConcurrentHashMapV8<T, ISetEntry<T>>(size, loadFactor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -61,6 +61,7 @@ public class MessageHandler {
|
|||||||
return this.isSynchronized;
|
return this.isSynchronized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only in unit test
|
||||||
public boolean isFromListener(Class<?> listener){
|
public boolean isFromListener(Class<?> listener){
|
||||||
return this.listenerConfig.isFromListener(listener);
|
return this.listenerConfig.isFromListener(listener);
|
||||||
}
|
}
|
||||||
|
@ -28,15 +28,11 @@ public class MessageListener {
|
|||||||
this.listenerDefinition = listenerDefinition;
|
this.listenerDefinition = listenerDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only in unit test
|
||||||
public boolean isFromListener(Class<?> listener) {
|
public boolean isFromListener(Class<?> listener) {
|
||||||
return this.listenerDefinition.equals(listener);
|
return this.listenerDefinition.equals(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageListener addHandlers(Collection<? extends MessageHandler> c) {
|
|
||||||
this.handlers.addAll(c);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addHandler(MessageHandler messageHandler) {
|
public boolean addHandler(MessageHandler messageHandler) {
|
||||||
return this.handlers.add(messageHandler);
|
return this.handlers.add(messageHandler);
|
||||||
}
|
}
|
||||||
@ -44,8 +40,4 @@ public class MessageListener {
|
|||||||
public Collection<MessageHandler> getHandlers() {
|
public Collection<MessageHandler> getHandlers() {
|
||||||
return this.handlers;
|
return this.handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Class<?> getListerDefinition() {
|
|
||||||
return this.listenerDefinition;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package net.engio.mbassy.multi.subscription;
|
||||||
|
|
||||||
|
import net.engio.mbassy.multi.PubSubSupport;
|
||||||
|
|
||||||
|
import com.lmax.disruptor.WorkHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public class EventProcessor implements WorkHandler<MessageHolder> {
|
||||||
|
private final PubSubSupport publisher;
|
||||||
|
|
||||||
|
public EventProcessor(PubSubSupport publisher) {
|
||||||
|
this.publisher = publisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(MessageHolder event) throws Exception {
|
||||||
|
MessageType messageType = event.messageType;
|
||||||
|
switch (messageType) {
|
||||||
|
case ONE: {
|
||||||
|
this.publisher.publish(event.message1);
|
||||||
|
event.message1 = null; // cleanup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case TWO: {
|
||||||
|
this.publisher.publish(event.message1, event.message2);
|
||||||
|
event.message1 = null; // cleanup
|
||||||
|
event.message2 = null; // cleanup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case THREE: {
|
||||||
|
this.publisher.publish(event.message1, event.message2, event.message3);
|
||||||
|
event.message1 = null; // cleanup
|
||||||
|
event.message2 = null; // cleanup
|
||||||
|
event.message3 = null; // cleanup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case ARRAY: {
|
||||||
|
this.publisher.publish(event.messages);
|
||||||
|
event.messages = null; // cleanup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.engio.mbassy.multi.subscription;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public class MessageHolder {
|
||||||
|
public MessageType messageType = MessageType.ONE;
|
||||||
|
|
||||||
|
public Object message1 = null;
|
||||||
|
public Object message2 = null;
|
||||||
|
public Object message3 = null;
|
||||||
|
public Object[] messages = null;
|
||||||
|
|
||||||
|
public MessageHolder() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package net.engio.mbassy.multi.subscription;
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public enum MessageType {
|
||||||
|
ONE, TWO, THREE, ARRAY
|
||||||
|
}
|
@ -38,9 +38,9 @@ public class Subscription {
|
|||||||
private final MessageHandler handlerMetadata;
|
private final MessageHandler handlerMetadata;
|
||||||
|
|
||||||
private final IHandlerInvocation invocation;
|
private final IHandlerInvocation invocation;
|
||||||
private final Collection<Object> listeners;
|
private final StrongConcurrentSet<Object> listeners;
|
||||||
|
|
||||||
Subscription(MessageHandler handler) {
|
public Subscription(MessageHandler handler) {
|
||||||
this.handlerMetadata = handler;
|
this.handlerMetadata = handler;
|
||||||
this.listeners = new StrongConcurrentSet<Object>();
|
this.listeners = new StrongConcurrentSet<Object>();
|
||||||
|
|
||||||
@ -60,6 +60,10 @@ public class Subscription {
|
|||||||
return this.handlerMetadata.acceptsSubtypes();
|
return this.handlerMetadata.acceptsSubtypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.listeners.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public void subscribe(Object listener) {
|
public void subscribe(Object listener) {
|
||||||
this.listeners.add(listener);
|
this.listeners.add(listener);
|
||||||
}
|
}
|
||||||
@ -96,7 +100,6 @@ public class Subscription {
|
|||||||
try {
|
try {
|
||||||
invocation.invoke(listener, handler, handleIndex, message);
|
invocation.invoke(listener, handler, handleIndex, message);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
e.printStackTrace();
|
|
||||||
errorHandler.handlePublicationError(new PublicationError()
|
errorHandler.handlePublicationError(new PublicationError()
|
||||||
.setMessage("Error during invocation of message handler. " +
|
.setMessage("Error during invocation of message handler. " +
|
||||||
"The class or method is not accessible")
|
"The class or method is not accessible")
|
||||||
@ -105,7 +108,6 @@ public class Subscription {
|
|||||||
.setListener(listener)
|
.setListener(listener)
|
||||||
.setPublishedObject(message));
|
.setPublishedObject(message));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
e.printStackTrace();
|
|
||||||
errorHandler.handlePublicationError(new PublicationError()
|
errorHandler.handlePublicationError(new PublicationError()
|
||||||
.setMessage("Error during invocation of message handler. " +
|
.setMessage("Error during invocation of message handler. " +
|
||||||
"Wrong arguments passed to method. Was: " + message.getClass()
|
"Wrong arguments passed to method. Was: " + message.getClass()
|
||||||
@ -115,7 +117,6 @@ public class Subscription {
|
|||||||
.setListener(listener)
|
.setListener(listener)
|
||||||
.setPublishedObject(message));
|
.setPublishedObject(message));
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
e.printStackTrace();
|
|
||||||
errorHandler.handlePublicationError(new PublicationError()
|
errorHandler.handlePublicationError(new PublicationError()
|
||||||
.setMessage("Error during invocation of message handler. " +
|
.setMessage("Error during invocation of message handler. " +
|
||||||
"Message handler threw exception")
|
"Message handler threw exception")
|
||||||
@ -124,7 +125,6 @@ public class Subscription {
|
|||||||
.setListener(listener)
|
.setListener(listener)
|
||||||
.setPublishedObject(message));
|
.setPublishedObject(message));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
|
||||||
errorHandler.handlePublicationError(new PublicationError()
|
errorHandler.handlePublicationError(new PublicationError()
|
||||||
.setMessage("Error during invocation of message handler. " +
|
.setMessage("Error during invocation of message handler. " +
|
||||||
"The handler code threw an exception")
|
"The handler code threw an exception")
|
||||||
|
@ -2,7 +2,6 @@ package net.engio.mbassy.multi;
|
|||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import net.engio.mbassy.multi.MultiMBassador;
|
|
||||||
import net.engio.mbassy.multi.common.ConcurrentExecutor;
|
import net.engio.mbassy.multi.common.ConcurrentExecutor;
|
||||||
import net.engio.mbassy.multi.common.ListenerFactory;
|
import net.engio.mbassy.multi.common.ListenerFactory;
|
||||||
import net.engio.mbassy.multi.common.MessageBusTest;
|
import net.engio.mbassy.multi.common.MessageBusTest;
|
||||||
@ -77,19 +76,22 @@ public class MBassadorTest extends MessageBusTest {
|
|||||||
Runnable publishAndCheck = new Runnable() {
|
Runnable publishAndCheck = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
bus.publishAsync(MessageTypes.Simple);
|
bus.publishAsync(MessageTypes.Simple);
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ConcurrentExecutor.runConcurrent(publishAndCheck, 1);
|
ConcurrentExecutor.runConcurrent(publishAndCheck, 1);
|
||||||
messageManager.waitForMessages(processingTimeInMS);
|
while (bus.hasPendingMessages()) {
|
||||||
|
pause(10);
|
||||||
|
}
|
||||||
|
|
||||||
MessageTypes.resetAll();
|
MessageTypes.resetAll();
|
||||||
ConcurrentExecutor.runConcurrent(publishAndCheck, ConcurrentUnits);
|
ConcurrentExecutor.runConcurrent(publishAndCheck, ConcurrentUnits);
|
||||||
messageManager.waitForMessages(processingTimeInMS);
|
|
||||||
|
|
||||||
|
while (bus.hasPendingMessages()) {
|
||||||
|
pause(10);
|
||||||
|
}
|
||||||
|
messageManager.waitForMessages(600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -119,14 +121,18 @@ public class MBassadorTest extends MessageBusTest {
|
|||||||
|
|
||||||
// single threaded
|
// single threaded
|
||||||
ConcurrentExecutor.runConcurrent(publishAndCheck, 1);
|
ConcurrentExecutor.runConcurrent(publishAndCheck, 1);
|
||||||
pause(processingTimeInMS);
|
while (bus.hasPendingMessages()) {
|
||||||
|
pause(10);
|
||||||
|
}
|
||||||
assertEquals(InstancesPerListener, exceptionCount.get());
|
assertEquals(InstancesPerListener, exceptionCount.get());
|
||||||
|
|
||||||
|
|
||||||
// multi threaded
|
// multi threaded
|
||||||
exceptionCount.set(0);
|
exceptionCount.set(0);
|
||||||
ConcurrentExecutor.runConcurrent(publishAndCheck, ConcurrentUnits);
|
ConcurrentExecutor.runConcurrent(publishAndCheck, ConcurrentUnits);
|
||||||
pause(processingTimeInMS);
|
while (bus.hasPendingMessages()) {
|
||||||
|
pause(10);
|
||||||
|
}
|
||||||
assertEquals(InstancesPerListener * ConcurrentUnits, exceptionCount.get());
|
assertEquals(InstancesPerListener * ConcurrentUnits, exceptionCount.get());
|
||||||
|
|
||||||
bus.shutdown();
|
bus.shutdown();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.engio.mbassy.multi;
|
package net.engio.mbassy.multi;
|
||||||
|
|
||||||
import net.engio.mbassy.multi.IMessageBus;
|
|
||||||
import net.engio.mbassy.multi.annotations.Handler;
|
import net.engio.mbassy.multi.annotations.Handler;
|
||||||
import net.engio.mbassy.multi.common.MessageBusTest;
|
import net.engio.mbassy.multi.common.MessageBusTest;
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ import net.engio.mbassy.multi.messages.IMultipartMessage;
|
|||||||
import net.engio.mbassy.multi.messages.MessageTypes;
|
import net.engio.mbassy.multi.messages.MessageTypes;
|
||||||
import net.engio.mbassy.multi.messages.MultipartMessage;
|
import net.engio.mbassy.multi.messages.MultipartMessage;
|
||||||
import net.engio.mbassy.multi.messages.StandardMessage;
|
import net.engio.mbassy.multi.messages.StandardMessage;
|
||||||
import net.engio.mbassy.multi.subscription.SubscriptionManager;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@ -162,8 +161,8 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
Overloading.ListenerBase.class,
|
Overloading.ListenerBase.class,
|
||||||
Overloading.ListenerSub.class);
|
Overloading.ListenerSub.class);
|
||||||
|
|
||||||
SubscriptionManager subscriptionManager = new SubscriptionManager(ConcurrentUnits);
|
SubscriptionManager subscriptionManager = new SubscriptionManager(1);
|
||||||
ConcurrentExecutor.runConcurrent(TestUtil.subscriber(subscriptionManager, listeners), ConcurrentUnits);
|
ConcurrentExecutor.runConcurrent(TestUtil.subscriber(subscriptionManager, listeners), 1);
|
||||||
|
|
||||||
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
||||||
.listener(Overloading.ListenerBase.class).handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class)
|
.listener(Overloading.ListenerBase.class).handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class)
|
||||||
@ -181,13 +180,13 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void runTestWith(final ListenerFactory listeners, final SubscriptionValidator validator){
|
private void runTestWith(final ListenerFactory listeners, final SubscriptionValidator validator){
|
||||||
final SubscriptionManager subscriptionManager = new SubscriptionManager(ConcurrentUnits);
|
final SubscriptionManager subscriptionManager = new SubscriptionManager(1);
|
||||||
|
|
||||||
ConcurrentExecutor.runConcurrent(TestUtil.subscriber(subscriptionManager, listeners), ConcurrentUnits);
|
ConcurrentExecutor.runConcurrent(TestUtil.subscriber(subscriptionManager, listeners), 1);
|
||||||
|
|
||||||
validator.validate(subscriptionManager);
|
validator.validate(subscriptionManager);
|
||||||
|
|
||||||
ConcurrentExecutor.runConcurrent(TestUtil.unsubscriber(subscriptionManager, listeners), ConcurrentUnits);
|
ConcurrentExecutor.runConcurrent(TestUtil.unsubscriber(subscriptionManager, listeners), 1);
|
||||||
|
|
||||||
listeners.clear();
|
listeners.clear();
|
||||||
validator.clear();
|
validator.clear();
|
||||||
|
@ -20,7 +20,6 @@ public abstract class MessageBusTest extends AssertSupport {
|
|||||||
// this value probably needs to be adjusted depending on the performance of the underlying plattform
|
// this value probably needs to be adjusted depending on the performance of the underlying plattform
|
||||||
// otherwise the tests will fail since asynchronous processing might not have finished when
|
// otherwise the tests will fail since asynchronous processing might not have finished when
|
||||||
// evaluation is run
|
// evaluation is run
|
||||||
protected static final int processingTimeInMS = 6000;
|
|
||||||
protected static final int InstancesPerListener = 5000;
|
protected static final int InstancesPerListener = 5000;
|
||||||
protected static final int ConcurrentUnits = 10;
|
protected static final int ConcurrentUnits = 10;
|
||||||
protected static final int IterationsPerThread = 100;
|
protected static final int IterationsPerThread = 100;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package net.engio.mbassy.multi.common;
|
package net.engio.mbassy.multi.common;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import net.engio.mbassy.multi.SubscriptionManager;
|
||||||
import net.engio.mbassy.multi.subscription.Subscription;
|
import net.engio.mbassy.multi.subscription.Subscription;
|
||||||
import net.engio.mbassy.multi.subscription.SubscriptionManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -39,13 +40,24 @@ public class SubscriptionValidator extends AssertSupport{
|
|||||||
// for each tuple of subscriber and message type the specified number of listeners must exist
|
// for each tuple of subscriber and message type the specified number of listeners must exist
|
||||||
public void validate(SubscriptionManager manager) {
|
public void validate(SubscriptionManager manager) {
|
||||||
for (Class<?> messageType : this.messageTypes) {
|
for (Class<?> messageType : this.messageTypes) {
|
||||||
Collection<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageType);
|
|
||||||
Collection<ValidationEntry> validationEntries = getEntries(messageType);
|
Collection<ValidationEntry> validationEntries = getEntries(messageType);
|
||||||
assertEquals(subscriptions.size(), validationEntries.size());
|
|
||||||
|
// we split subs + superSubs into TWO calls.
|
||||||
|
Collection<Subscription> collection = new ArrayDeque<Subscription>(8);
|
||||||
|
Collection<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageType);
|
||||||
|
if (subscriptions != null) {
|
||||||
|
collection.addAll(subscriptions);
|
||||||
|
}
|
||||||
|
Collection<Subscription> superSubs = manager.getSuperSubscriptions(messageType);
|
||||||
|
if (superSubs != null) {
|
||||||
|
collection.addAll(superSubs);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(validationEntries.size(), collection.size());
|
||||||
for(ValidationEntry validationValidationEntry : validationEntries){
|
for(ValidationEntry validationValidationEntry : validationEntries){
|
||||||
Subscription matchingSub = null;
|
Subscription matchingSub = null;
|
||||||
// one of the subscriptions must belong to the subscriber type
|
// one of the subscriptions must belong to the subscriber type
|
||||||
for(Subscription sub : subscriptions){
|
for(Subscription sub : collection){
|
||||||
if(sub.belongsTo(validationValidationEntry.subscriber)){
|
if(sub.belongsTo(validationValidationEntry.subscriber)){
|
||||||
matchingSub = sub;
|
matchingSub = sub;
|
||||||
break;
|
break;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package net.engio.mbassy.multi.common;
|
package net.engio.mbassy.multi.common;
|
||||||
|
|
||||||
import net.engio.mbassy.multi.MultiMBassador;
|
|
||||||
import net.engio.mbassy.multi.PubSubSupport;
|
|
||||||
import net.engio.mbassy.multi.subscription.SubscriptionManager;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.engio.mbassy.multi.MultiMBassador;
|
||||||
|
import net.engio.mbassy.multi.PubSubSupport;
|
||||||
|
import net.engio.mbassy.multi.SubscriptionManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Todo: Add javadoc
|
* Todo: Add javadoc
|
||||||
*
|
*
|
||||||
@ -81,15 +81,16 @@ public class TestUtil {
|
|||||||
|
|
||||||
for(int i = 0; i < numberOfThreads; i++){
|
for(int i = 0; i < numberOfThreads; i++){
|
||||||
final int partitionStart = i * partitionSize;
|
final int partitionStart = i * partitionSize;
|
||||||
final int partitionEnd = (i+1 < numberOfThreads)
|
final int partitionEnd = i+1 < numberOfThreads
|
||||||
? partitionStart + partitionSize + 1
|
? partitionStart + partitionSize + 1
|
||||||
: listeners.size();
|
: listeners.size();
|
||||||
setupUnits[i] = new Runnable() {
|
setupUnits[i] = new Runnable() {
|
||||||
|
|
||||||
private List<Object> listenerSubset = listeners.subList(partitionStart, partitionEnd);
|
private List<Object> listenerSubset = listeners.subList(partitionStart, partitionEnd);
|
||||||
|
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
for(Object listener : listenerSubset){
|
for(Object listener : this.listenerSubset){
|
||||||
bus.subscribe(listener);
|
bus.subscribe(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user