java 8 stamped lock is faster than reentrantlock. WIP caches
This commit is contained in:
parent
ab32656ce1
commit
e70adb2486
@ -14,7 +14,7 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Each message publication is isolated from all other running publications such that it does not interfere with them.
|
* Each message publication is isolated from all other running publications such that it does not interfere with them.
|
||||||
* Hence, the bus generally expects message handlers to be stateless as it may invoke them concurrently if multiple
|
* Hence, the bus generally expects message handlers to be stateless as it may invoke them concurrently if multiple
|
||||||
* messages get published asynchronously. If handlers are stateful and not thread-safe they can be marked to be invoked
|
* messages getSubscriptions published asynchronously. If handlers are stateful and not thread-safe they can be marked to be invoked
|
||||||
* in a synchronized fashion using @Synchronized annotation
|
* in a synchronized fashion using @Synchronized annotation
|
||||||
*
|
*
|
||||||
* <p/>
|
* <p/>
|
||||||
@ -25,7 +25,7 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
|||||||
* <p/>
|
* <p/>
|
||||||
* By default, the bus uses weak references to all listeners such that registered listeners do not need to
|
* By default, the bus uses weak references to all listeners such that registered listeners do not need to
|
||||||
* be explicitly unregistered to be eligible for garbage collection. Dead (garbage collected) listeners are
|
* be explicitly unregistered to be eligible for garbage collection. Dead (garbage collected) listeners are
|
||||||
* removed on-the-fly as messages get dispatched. This can be changed using the @Listener annotation.
|
* removed on-the-fly as messages getSubscriptions dispatched. This can be changed using the @Listener annotation.
|
||||||
*
|
*
|
||||||
* <p/>
|
* <p/>
|
||||||
* Generally message handlers will be invoked in inverse sequence of subscription but any
|
* Generally message handlers will be invoked in inverse sequence of subscription but any
|
||||||
@ -50,7 +50,7 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
|||||||
*
|
*
|
||||||
* <p/>
|
* <p/>
|
||||||
* NOTE: Generic type parameters of messages will not be taken into account, e.g. a List<Long> will
|
* NOTE: Generic type parameters of messages will not be taken into account, e.g. a List<Long> will
|
||||||
* get dispatched to all message handlers that take an instance of List as their parameter
|
* getSubscriptions dispatched to all message handlers that take an instance of List as their parameter
|
||||||
*
|
*
|
||||||
* @Author bennidi
|
* @Author bennidi
|
||||||
* Date: 2/8/12
|
* Date: 2/8/12
|
||||||
|
@ -1,29 +1,27 @@
|
|||||||
package dorkbox.util.messagebus;
|
package dorkbox.util.messagebus;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.jctools.util.Pow2;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.DeadMessage;
|
import dorkbox.util.messagebus.common.DeadMessage;
|
||||||
import dorkbox.util.messagebus.common.NamedThreadFactory;
|
import dorkbox.util.messagebus.common.NamedThreadFactory;
|
||||||
import dorkbox.util.messagebus.common.simpleq.MpmcMultiTransferArrayQueue;
|
import dorkbox.util.messagebus.common.simpleq.MpmcMultiTransferArrayQueue;
|
||||||
import dorkbox.util.messagebus.common.simpleq.MultiNode;
|
import dorkbox.util.messagebus.common.simpleq.MultiNode;
|
||||||
import dorkbox.util.messagebus.error.IPublicationErrorHandler;
|
import dorkbox.util.messagebus.error.IPublicationErrorHandler;
|
||||||
import dorkbox.util.messagebus.error.PublicationError;
|
import dorkbox.util.messagebus.error.PublicationError;
|
||||||
|
import dorkbox.util.messagebus.subscription.Matcher;
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
||||||
|
import org.jctools.util.Pow2;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*
|
*
|
||||||
* @Author bennidi
|
|
||||||
* @author dorkbox, llc
|
* @author dorkbox, llc
|
||||||
* Date: 2/2/15
|
* Date: 2/2/15
|
||||||
*/
|
*/
|
||||||
public class MultiMBassador implements IMessageBus {
|
public class MultiMBassador implements IMessageBus {
|
||||||
|
public static final String ERROR_HANDLER_MSG = "INFO: No error handler has been configured to handle exceptions during publication.\n" +
|
||||||
public static final String ERROR_HANDLER_MSG =
|
|
||||||
"INFO: No error handler has been configured to handle exceptions during publication.\n" +
|
|
||||||
"Publication error handlers can be added by bus.addErrorHandler()\n" +
|
"Publication error handlers can be added by bus.addErrorHandler()\n" +
|
||||||
"Falling back to console logger.";
|
"Falling back to console logger.";
|
||||||
|
|
||||||
@ -36,12 +34,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
|
|
||||||
private final Collection<Thread> threads;
|
private final Collection<Thread> threads;
|
||||||
|
|
||||||
/**
|
private final Matcher subscriptionMatcher;
|
||||||
* if true, only exact matching will be performed on classes. Setting this to true
|
|
||||||
* removes the ability to have subTypes and VarArg matching, and doing so doubles the speed of the
|
|
||||||
* system. By default, this is FALSE, to support subTypes and VarArg matching.
|
|
||||||
*/
|
|
||||||
private final boolean forceExactMatches;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the consumers during shutdown, that it's on purpose.
|
* Notifies the consumers during shutdown, that it's on purpose.
|
||||||
@ -66,7 +59,6 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
* @param forceExactMatches if TRUE, only exact matching will be performed on classes. Setting this to true
|
* @param forceExactMatches if TRUE, only exact matching will be performed on classes. Setting this to true
|
||||||
* removes the ability to have subTypes and VarArg matching, and doing so doubles the speed of the
|
* removes the ability to have subTypes and VarArg matching, and doing so doubles the speed of the
|
||||||
* system. By default, this is FALSE, to support subTypes and VarArg matching.
|
* system. By default, this is FALSE, to support subTypes and VarArg matching.
|
||||||
*
|
|
||||||
* @param numberOfThreads how many threads to have for dispatching async messages
|
* @param numberOfThreads how many threads to have for dispatching async messages
|
||||||
*/
|
*/
|
||||||
public MultiMBassador(boolean forceExactMatches, int numberOfThreads) {
|
public MultiMBassador(boolean forceExactMatches, int numberOfThreads) {
|
||||||
@ -74,11 +66,28 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
numberOfThreads = 2; // at LEAST 2 threads
|
numberOfThreads = 2; // at LEAST 2 threads
|
||||||
}
|
}
|
||||||
numberOfThreads = Pow2.roundToPowerOfTwo(numberOfThreads);
|
numberOfThreads = Pow2.roundToPowerOfTwo(numberOfThreads);
|
||||||
this.forceExactMatches = forceExactMatches;
|
|
||||||
|
|
||||||
this.dispatchQueue = new MpmcMultiTransferArrayQueue(numberOfThreads);
|
this.dispatchQueue = new MpmcMultiTransferArrayQueue(numberOfThreads);
|
||||||
|
|
||||||
this.subscriptionManager = new SubscriptionManager(numberOfThreads);
|
this.subscriptionManager = new SubscriptionManager(numberOfThreads);
|
||||||
|
|
||||||
|
|
||||||
|
if (forceExactMatches) {
|
||||||
|
subscriptionMatcher = new Matcher() {
|
||||||
|
@Override
|
||||||
|
public Subscription[] getSubscriptions(Class<?> messageClass) {
|
||||||
|
return subscriptionManager.getSubscriptionsForcedExact(messageClass);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subscriptionMatcher = new Matcher() {
|
||||||
|
@Override
|
||||||
|
public Subscription[] getSubscriptions(Class<?> messageClass) {
|
||||||
|
return subscriptionManager.getSubscriptions(messageClass);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.threads = new ArrayDeque<Thread>(numberOfThreads);
|
this.threads = new ArrayDeque<Thread>(numberOfThreads);
|
||||||
|
|
||||||
NamedThreadFactory dispatchThreadFactory = new NamedThreadFactory("MessageBus");
|
NamedThreadFactory dispatchThreadFactory = new NamedThreadFactory("MessageBus");
|
||||||
@ -95,31 +104,35 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
while (true) {
|
while (true) {
|
||||||
IN_QUEUE.take(node);
|
IN_QUEUE.take(node);
|
||||||
switch (node.messageType) {
|
switch (node.messageType) {
|
||||||
case 1: publish(node.item1); continue;
|
case 1:
|
||||||
case 2: publish(node.item1, node.item2); continue;
|
publish(node.item1);
|
||||||
case 3: publish(node.item1, node.item2, node.item3); continue;
|
continue;
|
||||||
|
case 2:
|
||||||
|
publish(node.item1, node.item2);
|
||||||
|
continue;
|
||||||
|
case 3:
|
||||||
|
publish(node.item1, node.item2, node.item3);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
if (!MultiMBassador.this.shuttingDown) {
|
if (!MultiMBassador.this.shuttingDown) {
|
||||||
switch (node.messageType) {
|
switch (node.messageType) {
|
||||||
case 1: {
|
case 1: {
|
||||||
handlePublicationError(new PublicationError()
|
handlePublicationError(
|
||||||
.setMessage("Thread interupted while processing message")
|
new PublicationError().setMessage("Thread interupted while processing message")
|
||||||
.setCause(e)
|
.setCause(e).setPublishedObject(node.item1));
|
||||||
.setPublishedObject(node.item1));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
handlePublicationError(new PublicationError()
|
handlePublicationError(
|
||||||
.setMessage("Thread interupted while processing message")
|
new PublicationError().setMessage("Thread interupted while processing message")
|
||||||
.setCause(e)
|
.setCause(e).setPublishedObject(node.item1, node.item2));
|
||||||
.setPublishedObject(node.item1, node.item2));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case 3: {
|
case 3: {
|
||||||
handlePublicationError(new PublicationError()
|
handlePublicationError(
|
||||||
.setMessage("Thread interupted while processing message")
|
new PublicationError().setMessage("Thread interupted while processing message")
|
||||||
.setCause(e)
|
.setCause(e)
|
||||||
.setPublishedObject(node.item1, node.item2, node.item3));
|
.setPublishedObject(node.item1, node.item2, node.item3));
|
||||||
continue;
|
continue;
|
||||||
@ -189,37 +202,35 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
return this.dispatchQueue.hasPendingMessages();
|
return this.dispatchQueue.hasPendingMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void publish(final Object message) {
|
public void publish(final Object message) {
|
||||||
try {
|
try {
|
||||||
boolean subsPublished = false;
|
boolean subsPublished = false;
|
||||||
SubscriptionManager manager = this.subscriptionManager;
|
final SubscriptionManager manager = this.subscriptionManager;
|
||||||
Class<?> messageClass = message.getClass();
|
final Class<?> messageClass = message.getClass();
|
||||||
|
|
||||||
Subscription[] subscriptions;
|
Subscription[] subscriptions;
|
||||||
Subscription sub;
|
Subscription sub;
|
||||||
|
|
||||||
if (this.forceExactMatches) {
|
subscriptions = subscriptionMatcher.getSubscriptions(messageClass);
|
||||||
subscriptions = manager.getSubscriptionsForcedExact(messageClass);
|
int c = 0;
|
||||||
} else {
|
|
||||||
subscriptions = manager.getSubscriptions(messageClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run subscriptions
|
// Run subscriptions
|
||||||
int length = subscriptions.length;
|
if (subscriptions != null) {
|
||||||
if (length > 0) {
|
|
||||||
for (int i=0;i<length;i++) {
|
|
||||||
sub = subscriptions[i];
|
|
||||||
|
|
||||||
sub.publish(message);
|
|
||||||
}
|
|
||||||
subsPublished = true;
|
subsPublished = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < subscriptions.length; i++) {
|
||||||
|
sub = subscriptions[i];
|
||||||
|
sub.publish(message);
|
||||||
|
c = sub.c();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// if (!this.forceExactMatches) {
|
// if (!this.forceExactMatches) {
|
||||||
// Subscription[] superSubscriptions = manager.getSuperSubscriptions(messageClass); // NOT return null
|
// Subscription[] superSubscriptions = manager.getSuperSubscriptions(messageClass); // NOT return null
|
||||||
// // now get superClasses
|
// // now getSubscriptions superClasses
|
||||||
// int length = superSubscriptions.length;
|
// int length = superSubscriptions.length;
|
||||||
//
|
//
|
||||||
// if (length > 0) {
|
// if (length > 0) {
|
||||||
@ -261,7 +272,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// ConcurrentSet<Subscription> varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass);
|
// ConcurrentSet<Subscription> varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass);
|
||||||
// // now get array based superClasses (but only if those ALSO accept vararg)
|
// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg)
|
||||||
// if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) {
|
// if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) {
|
||||||
// if (asArray == null) {
|
// if (asArray == null) {
|
||||||
// asArray = (Object[]) Array.newInstance(messageClass, 1);
|
// asArray = (Object[]) Array.newInstance(messageClass, 1);
|
||||||
@ -276,14 +287,13 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (!subsPublished) {
|
if (c == 0 && !subsPublished) {
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
// Dead Event must EXACTLY MATCH (no subclasses)
|
||||||
Subscription[] deadSubscriptions = manager.getSubscriptionsForcedExact(DeadMessage.class);
|
Subscription[] deadSubscriptions = manager.getSubscriptionsForcedExact(DeadMessage.class);
|
||||||
length = deadSubscriptions.length;
|
if (deadSubscriptions != null) {
|
||||||
if (length > 0) {
|
|
||||||
DeadMessage deadMessage = new DeadMessage(message);
|
DeadMessage deadMessage = new DeadMessage(message);
|
||||||
|
|
||||||
for (int i=0;i<length;i++) {
|
for (int i = 0; i < deadSubscriptions.length; i++) {
|
||||||
sub = deadSubscriptions[i];
|
sub = deadSubscriptions[i];
|
||||||
|
|
||||||
sub.publish(deadMessage);
|
sub.publish(deadMessage);
|
||||||
@ -291,9 +301,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
handlePublicationError(new PublicationError()
|
handlePublicationError(new PublicationError().setMessage("Error during invocation of message handler.").setCause(e)
|
||||||
.setMessage("Error during invocation of message handler.")
|
|
||||||
.setCause(e)
|
|
||||||
.setPublishedObject(message));
|
.setPublishedObject(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,7 +314,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// Class<?> messageClass2 = message2.getClass();
|
// Class<?> messageClass2 = message2.getClass();
|
||||||
//
|
//
|
||||||
// StrongConcurrentSet<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClass1, messageClass2);
|
// StrongConcurrentSet<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClass1, messageClass2);
|
||||||
// BooleanHolder subsPublished = this.booleanThreadLocal.get();
|
// BooleanHolder subsPublished = this.booleanThreadLocal.getSubscriptions();
|
||||||
// subsPublished.bool = false;
|
// subsPublished.bool = false;
|
||||||
//
|
//
|
||||||
// ISetEntry<Subscription> current;
|
// ISetEntry<Subscription> current;
|
||||||
@ -326,7 +334,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
//
|
//
|
||||||
// if (!this.forceExactMatches) {
|
// if (!this.forceExactMatches) {
|
||||||
// StrongConcurrentSet<Subscription> superSubscriptions = manager.getSuperSubscriptions(messageClass1, messageClass2);
|
// StrongConcurrentSet<Subscription> superSubscriptions = manager.getSuperSubscriptions(messageClass1, messageClass2);
|
||||||
// // now get superClasses
|
// // now getSubscriptions superClasses
|
||||||
// if (superSubscriptions != null) {
|
// if (superSubscriptions != null) {
|
||||||
// current = superSubscriptions.head;
|
// current = superSubscriptions.head;
|
||||||
// while (current != null) {
|
// while (current != null) {
|
||||||
@ -360,7 +368,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// StrongConcurrentSet<Subscription> varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1);
|
// StrongConcurrentSet<Subscription> varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1);
|
||||||
// // now get array based superClasses (but only if those ALSO accept vararg)
|
// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg)
|
||||||
// if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) {
|
// if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) {
|
||||||
// if (asArray == null) {
|
// if (asArray == null) {
|
||||||
// asArray = (Object[]) Array.newInstance(messageClass1, 2);
|
// asArray = (Object[]) Array.newInstance(messageClass1, 2);
|
||||||
@ -380,7 +388,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// } else {
|
// } else {
|
||||||
// StrongConcurrentSet<Subscription> varargSuperMultiSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1, messageClass2);
|
// StrongConcurrentSet<Subscription> varargSuperMultiSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1, messageClass2);
|
||||||
//
|
//
|
||||||
// // now get array based superClasses (but only if those ALSO accept vararg)
|
// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg)
|
||||||
// if (varargSuperMultiSubscriptions != null && !varargSuperMultiSubscriptions.isEmpty()) {
|
// if (varargSuperMultiSubscriptions != null && !varargSuperMultiSubscriptions.isEmpty()) {
|
||||||
// current = varargSuperMultiSubscriptions.head;
|
// current = varargSuperMultiSubscriptions.head;
|
||||||
// while (current != null) {
|
// while (current != null) {
|
||||||
@ -430,7 +438,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// Class<?> messageClass3 = message3.getClass();
|
// Class<?> messageClass3 = message3.getClass();
|
||||||
//
|
//
|
||||||
// StrongConcurrentSet<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClass1, messageClass2, messageClass3);
|
// StrongConcurrentSet<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClass1, messageClass2, messageClass3);
|
||||||
// BooleanHolder subsPublished = this.booleanThreadLocal.get();
|
// BooleanHolder subsPublished = this.booleanThreadLocal.getSubscriptions();
|
||||||
// subsPublished.bool = false;
|
// subsPublished.bool = false;
|
||||||
//
|
//
|
||||||
// ISetEntry<Subscription> current;
|
// ISetEntry<Subscription> current;
|
||||||
@ -451,7 +459,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
//
|
//
|
||||||
// if (!this.forceExactMatches) {
|
// if (!this.forceExactMatches) {
|
||||||
// StrongConcurrentSet<Subscription> superSubscriptions = manager.getSuperSubscriptions(messageClass1, messageClass2, messageClass3);
|
// StrongConcurrentSet<Subscription> superSubscriptions = manager.getSuperSubscriptions(messageClass1, messageClass2, messageClass3);
|
||||||
// // now get superClasses
|
// // now getSubscriptions superClasses
|
||||||
// if (superSubscriptions != null) {
|
// if (superSubscriptions != null) {
|
||||||
// current = superSubscriptions.head;
|
// current = superSubscriptions.head;
|
||||||
// while (current != null) {
|
// while (current != null) {
|
||||||
@ -485,7 +493,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// StrongConcurrentSet<Subscription> varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1);
|
// StrongConcurrentSet<Subscription> varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1);
|
||||||
// // now get array based superClasses (but only if those ALSO accept vararg)
|
// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg)
|
||||||
// if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) {
|
// if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) {
|
||||||
// if (asArray == null) {
|
// if (asArray == null) {
|
||||||
// asArray = (Object[]) Array.newInstance(messageClass1, 3);
|
// asArray = (Object[]) Array.newInstance(messageClass1, 3);
|
||||||
@ -506,7 +514,7 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
// } else {
|
// } else {
|
||||||
// StrongConcurrentSet<Subscription> varargSuperMultiSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1, messageClass2, messageClass3);
|
// StrongConcurrentSet<Subscription> varargSuperMultiSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1, messageClass2, messageClass3);
|
||||||
//
|
//
|
||||||
// // now get array based superClasses (but only if those ALSO accept vararg)
|
// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg)
|
||||||
// if (varargSuperMultiSubscriptions != null && !varargSuperMultiSubscriptions.isEmpty()) {
|
// if (varargSuperMultiSubscriptions != null && !varargSuperMultiSubscriptions.isEmpty()) {
|
||||||
// current = varargSuperMultiSubscriptions.head;
|
// current = varargSuperMultiSubscriptions.head;
|
||||||
// while (current != null) {
|
// while (current != null) {
|
||||||
@ -554,12 +562,11 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
try {
|
try {
|
||||||
this.dispatchQueue.transfer(message);
|
this.dispatchQueue.transfer(message);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handlePublicationError(new PublicationError()
|
handlePublicationError(new PublicationError().setMessage("Error while adding an asynchronous message").setCause(e)
|
||||||
.setMessage("Error while adding an asynchronous message")
|
|
||||||
.setCause(e)
|
|
||||||
.setPublishedObject(message));
|
.setPublishedObject(message));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw new NullPointerException("Message cannot be null.");
|
throw new NullPointerException("Message cannot be null.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -570,12 +577,11 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
try {
|
try {
|
||||||
this.dispatchQueue.transfer(message1, message2);
|
this.dispatchQueue.transfer(message1, message2);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handlePublicationError(new PublicationError()
|
handlePublicationError(new PublicationError().setMessage("Error while adding an asynchronous message").setCause(e)
|
||||||
.setMessage("Error while adding an asynchronous message")
|
|
||||||
.setCause(e)
|
|
||||||
.setPublishedObject(message1, message2));
|
.setPublishedObject(message1, message2));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw new NullPointerException("Messages cannot be null.");
|
throw new NullPointerException("Messages cannot be null.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -586,12 +592,11 @@ public class MultiMBassador implements IMessageBus {
|
|||||||
try {
|
try {
|
||||||
this.dispatchQueue.transfer(message1, message2, message3);
|
this.dispatchQueue.transfer(message1, message2, message3);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handlePublicationError(new PublicationError()
|
handlePublicationError(new PublicationError().setMessage("Error while adding an asynchronous message").setCause(e)
|
||||||
.setMessage("Error while adding an asynchronous message")
|
|
||||||
.setCause(e)
|
|
||||||
.setPublishedObject(message1, message2, message3));
|
.setPublishedObject(message1, message2, message3));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw new NullPointerException("Messages cannot be null.");
|
throw new NullPointerException("Messages cannot be null.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ 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.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.locks.StampedLock;
|
||||||
import dorkbox.util.messagebus.common.thread.StampedLock;
|
|
||||||
|
|
||||||
|
|
||||||
abstract class pad<T> extends item<T> {
|
abstract class pad<T> extends item<T> {
|
||||||
@ -21,15 +20,15 @@ abstract class pad<T> extends item<T> {
|
|||||||
* @author bennidi
|
* @author bennidi
|
||||||
* Date: 2/12/12
|
* Date: 2/12/12
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractConcurrentSet<T> extends pad<T> implements Set<T> {
|
public abstract class AbstractConcurrentSet<T> implements Set<T> {
|
||||||
private static final AtomicLong id = new AtomicLong();
|
private static final AtomicLong id = new AtomicLong();
|
||||||
private final transient long ID = id.getAndIncrement();
|
private final transient long ID = id.getAndIncrement();
|
||||||
|
|
||||||
// Internal state
|
// Internal state
|
||||||
protected final StampedLock lock = new StampedLock();
|
protected final StampedLock lock = new StampedLock();
|
||||||
private final transient Map<T, ISetEntry<T>> entries; // maintain a map of entries for O(log n) lookup
|
private final Map<T, ISetEntry<T>> entries; // maintain a map of entries for O(log n) lookup
|
||||||
|
|
||||||
volatile long y0, y1, y2, y4, y5, y6 = 7L;
|
public volatile Entry<T> head; // reference to the first element
|
||||||
volatile long z0, z1, z2, z4, z5, z6 = 7L;
|
volatile long z0, z1, z2, z4, z5, z6 = 7L;
|
||||||
|
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ public abstract class AbstractConcurrentSet<T> extends pad<T> implements Set<T>
|
|||||||
if (element == null) {
|
if (element == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
boolean changed = false;
|
boolean changed;
|
||||||
|
|
||||||
long stamp = this.lock.readLock();
|
long stamp = this.lock.readLock();
|
||||||
if (this.entries.containsKey(element)) {
|
if (this.entries.containsKey(element)) {
|
||||||
@ -67,15 +66,11 @@ public abstract class AbstractConcurrentSet<T> extends pad<T> implements Set<T>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(Object element) {
|
public boolean contains(Object element) {
|
||||||
long stamp = this.lock.tryOptimisticRead();
|
long stamp = this.lock.readLock();
|
||||||
|
|
||||||
ISetEntry<T> entry = this.entries.get(element);
|
ISetEntry<T> entry = this.entries.get(element);
|
||||||
|
|
||||||
if (!this.lock.validate(stamp)) {
|
|
||||||
stamp = this.lock.readLock();
|
|
||||||
entry = this.entries.get(element);
|
|
||||||
this.lock.unlockRead(stamp);
|
this.lock.unlockRead(stamp);
|
||||||
}
|
|
||||||
|
|
||||||
return entry != null && entry.getValue() != null;
|
return entry != null && entry.getValue() != null;
|
||||||
}
|
}
|
||||||
@ -125,15 +120,11 @@ public abstract class AbstractConcurrentSet<T> extends pad<T> implements Set<T>
|
|||||||
@Override
|
@Override
|
||||||
public boolean remove(Object element) {
|
public boolean remove(Object element) {
|
||||||
StampedLock lock = this.lock;
|
StampedLock lock = this.lock;
|
||||||
long stamp = lock.tryOptimisticRead();
|
long stamp = lock.readLock();
|
||||||
|
|
||||||
ISetEntry<T> entry = this.entries.get(element);
|
ISetEntry<T> entry = this.entries.get(element);
|
||||||
|
|
||||||
if (!lock.validate(stamp)) {
|
|
||||||
stamp = lock.readLock();
|
|
||||||
entry = this.entries.get(element);
|
|
||||||
lock.unlockRead(stamp);
|
lock.unlockRead(stamp);
|
||||||
}
|
|
||||||
|
|
||||||
if (entry == null || entry.getValue() == null) {
|
if (entry == null || entry.getValue() == null) {
|
||||||
return false; // fast exit
|
return false; // fast exit
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,15 @@
|
|||||||
package dorkbox.util.messagebus.common;
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
|
import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock;
|
||||||
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
|
|
||||||
import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple tree structure that is a map that contains a chain of keys to get to a value.
|
* Simple tree structure that is a map that contains a chain of keys to getSubscriptions to a value.
|
||||||
* <p>
|
* <p>
|
||||||
* THREAD SAFE, each level in the tree has it's own write lock, and there a tree-global read lock, to prevent writes
|
* THREAD SAFE, each level in the tree has it's own write lock, and there a tree-global read lock, to prevent writes
|
||||||
*
|
*
|
||||||
@ -506,7 +506,7 @@ public class HashMapTree<KEY, VALUE> {
|
|||||||
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> objectTree = null;
|
HashMapTree<KEY, VALUE> objectTree = null;
|
||||||
// get value from our children
|
// getSubscriptions value from our children
|
||||||
objectTree = getLeaf_NL(key); // protected by lock
|
objectTree = getLeaf_NL(key); // protected by lock
|
||||||
|
|
||||||
if (objectTree == null) {
|
if (objectTree == null) {
|
||||||
@ -525,7 +525,7 @@ public class HashMapTree<KEY, VALUE> {
|
|||||||
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> tree = null;
|
HashMapTree<KEY, VALUE> tree = null;
|
||||||
// get value from our children
|
// getSubscriptions value from our children
|
||||||
tree = getLeaf_NL(key1); // protected by lock
|
tree = getLeaf_NL(key1); // protected by lock
|
||||||
if (tree != null) {
|
if (tree != null) {
|
||||||
tree = tree.getLeaf_NL(key2); // protected by lock
|
tree = tree.getLeaf_NL(key2); // protected by lock
|
||||||
@ -547,7 +547,7 @@ public class HashMapTree<KEY, VALUE> {
|
|||||||
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> tree = null;
|
HashMapTree<KEY, VALUE> tree = null;
|
||||||
// get value from our children
|
// getSubscriptions value from our children
|
||||||
tree = getLeaf_NL(key1);
|
tree = getLeaf_NL(key1);
|
||||||
if (tree != null) {
|
if (tree != null) {
|
||||||
tree = tree.getLeaf_NL(key2);
|
tree = tree.getLeaf_NL(key2);
|
||||||
@ -573,7 +573,7 @@ public class HashMapTree<KEY, VALUE> {
|
|||||||
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> tree = null;
|
HashMapTree<KEY, VALUE> tree = null;
|
||||||
// get value from our children
|
// getSubscriptions value from our children
|
||||||
tree = getLeaf_NL(keys[0]);
|
tree = getLeaf_NL(keys[0]);
|
||||||
|
|
||||||
int size = keys.length;
|
int size = keys.length;
|
||||||
@ -625,7 +625,7 @@ public class HashMapTree<KEY, VALUE> {
|
|||||||
Lock READ = this.lock.readLock();
|
Lock READ = this.lock.readLock();
|
||||||
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
||||||
|
|
||||||
// get value from our children
|
// getSubscriptions value from our children
|
||||||
tree = getLeaf_NL(key1);
|
tree = getLeaf_NL(key1);
|
||||||
if (tree != null) {
|
if (tree != null) {
|
||||||
tree = tree.getLeaf_NL(key2);
|
tree = tree.getLeaf_NL(key2);
|
||||||
@ -642,7 +642,7 @@ public class HashMapTree<KEY, VALUE> {
|
|||||||
Lock READ = this.lock.readLock();
|
Lock READ = this.lock.readLock();
|
||||||
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
||||||
|
|
||||||
// get value from our children
|
// getSubscriptions value from our children
|
||||||
tree = getLeaf_NL(key1);
|
tree = getLeaf_NL(key1);
|
||||||
if (tree != null) {
|
if (tree != null) {
|
||||||
tree = tree.getLeaf_NL(key2);
|
tree = tree.getLeaf_NL(key2);
|
||||||
@ -668,7 +668,7 @@ public class HashMapTree<KEY, VALUE> {
|
|||||||
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
READ.lock(); // allows other readers, blocks others from acquiring update or write locks
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> tree = null;
|
HashMapTree<KEY, VALUE> tree = null;
|
||||||
// get value from our children
|
// getSubscriptions value from our children
|
||||||
tree = getLeaf_NL(keys[0]);
|
tree = getLeaf_NL(keys[0]);
|
||||||
|
|
||||||
for (int i=1;i<size;i++) {
|
for (int i=1;i<size;i++) {
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
package dorkbox.util.messagebus.common;
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
|
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
|
import dorkbox.util.messagebus.annotations.Handler;
|
||||||
|
import dorkbox.util.messagebus.annotations.Synchronized;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.annotations.Handler;
|
|
||||||
import dorkbox.util.messagebus.annotations.Synchronized;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains
|
* Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains
|
||||||
* the handler is called a message listener and more generally, any class containing a message handler in its class hierarchy
|
* the handler is called a message listener and more generally, any class containing a message handler in its class hierarchy
|
||||||
@ -30,11 +29,11 @@ import dorkbox.util.messagebus.annotations.Synchronized;
|
|||||||
*/
|
*/
|
||||||
public class MessageHandler {
|
public class MessageHandler {
|
||||||
|
|
||||||
// get all listeners defined by the given class (includes
|
// getSubscriptions all listeners defined by the given class (includes
|
||||||
// listeners defined in super classes)
|
// listeners defined in super classes)
|
||||||
public static final MessageHandler[] get(final Class<?> target) {
|
public static final MessageHandler[] get(final Class<?> target) {
|
||||||
|
|
||||||
// get all handlers (this will include all (inherited) methods directly annotated using @Handler)
|
// getSubscriptions all handlers (this will include all (inherited) methods directly annotated using @Handler)
|
||||||
final Method[] allMethods = ReflectionUtils.getMethods(target);
|
final Method[] allMethods = ReflectionUtils.getMethods(target);
|
||||||
final int length = allMethods.length;
|
final int length = allMethods.length;
|
||||||
|
|
||||||
@ -97,32 +96,32 @@ public class MessageHandler {
|
|||||||
this.acceptsVarArgs = handledMessages.length == 1 && handledMessages[0].isArray() && handlerConfig.acceptVarargs();
|
this.acceptsVarArgs = handledMessages.length == 1 && handledMessages[0].isArray() && handlerConfig.acceptVarargs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSynchronized(){
|
public final boolean isSynchronized() {
|
||||||
return this.isSynchronized;
|
return this.isSynchronized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MethodAccess getHandler() {
|
public final MethodAccess getHandler() {
|
||||||
return this.handler;
|
return this.handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMethodIndex() {
|
public final int getMethodIndex() {
|
||||||
return this.methodIndex;
|
return this.methodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Class<?>[] getHandledMessages() {
|
public final Class<?>[] getHandledMessages() {
|
||||||
return this.handledMessages;
|
return this.handledMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean acceptsSubtypes() {
|
public final boolean acceptsSubtypes() {
|
||||||
return this.acceptsSubtypes;
|
return this.acceptsSubtypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean acceptsVarArgs() {
|
public final boolean acceptsVarArgs() {
|
||||||
return this.acceptsVarArgs;
|
return this.acceptsVarArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public final int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = prime * result + (this.acceptsSubtypes ? 1231 : 1237);
|
result = prime * result + (this.acceptsSubtypes ? 1231 : 1237);
|
||||||
@ -133,7 +132,7 @@ public class MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public final boolean equals(Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ public class StrongConcurrentSetV8<T> extends StrongConcurrentSet<T> {
|
|||||||
|
|
||||||
public StrongConcurrentSetV8(int size, float loadFactor) {
|
public StrongConcurrentSetV8(int size, float loadFactor) {
|
||||||
// 1 for the stripe size, because that is the max concurrency with our concurrent set (since it uses R/W locks)
|
// 1 for the stripe size, because that is the max concurrency with our concurrent set (since it uses R/W locks)
|
||||||
super(new ConcurrentHashMapV8<T, ISetEntry<T>>(size, loadFactor, 1));
|
super(new ConcurrentHashMapV8<T, ISetEntry<T>>(size, loadFactor, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
public StrongConcurrentSetV8(int size, float loadFactor, int stripeSize) {
|
public StrongConcurrentSetV8(int size, float loadFactor, int stripeSize) {
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SuperClassUtils {
|
||||||
|
|
||||||
|
private final Map<Class<?>, Class<?>> versionCache;
|
||||||
|
private final Map<Class<?>, Class<?>[]> superClassesCache;
|
||||||
|
|
||||||
|
public SuperClassUtils(float loadFactor, int stripeSize) {
|
||||||
|
this.versionCache = new ConcurrentHashMapV8<Class<?>, Class<?>>(32, loadFactor, stripeSize);
|
||||||
|
this.superClassesCache = new ConcurrentHashMapV8<Class<?>, Class<?>[]>(32, loadFactor, stripeSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* never returns null
|
||||||
|
* never reset, since it never needs to be reset (as the class hierarchy doesn't change at runtime)
|
||||||
|
* <p>
|
||||||
|
* if parameter clazz is of type array, then the super classes are of array type as well
|
||||||
|
* <p>
|
||||||
|
* protected by read lock by caller. The cache version is called first, by write lock
|
||||||
|
*/
|
||||||
|
public final Class<?>[] getSuperClasses(final Class<?> clazz, final boolean isArray) {
|
||||||
|
// this is never reset, since it never needs to be.
|
||||||
|
final Map<Class<?>, Class<?>[]> local = this.superClassesCache;
|
||||||
|
|
||||||
|
Class<?>[] classes = local.get(clazz);
|
||||||
|
|
||||||
|
if (classes == null) {
|
||||||
|
// getSubscriptions all super types of class
|
||||||
|
final Class<?>[] superTypes = ReflectionUtils.getSuperTypes(clazz);
|
||||||
|
final int length = superTypes.length;
|
||||||
|
|
||||||
|
ArrayList<Class<?>> newList = new ArrayList<Class<?>>(length);
|
||||||
|
|
||||||
|
Class<?> c;
|
||||||
|
if (isArray) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
c = superTypes[i];
|
||||||
|
|
||||||
|
c = getArrayClass(c);
|
||||||
|
|
||||||
|
if (c != clazz) {
|
||||||
|
newList.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
c = superTypes[i];
|
||||||
|
|
||||||
|
if (c != clazz) {
|
||||||
|
newList.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
classes = newList.toArray(new Class<?>[newList.size()]);
|
||||||
|
local.put(clazz, classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* race conditions will result in duplicate answers, which we don't care if happens
|
||||||
|
* never returns null
|
||||||
|
* never reset
|
||||||
|
*/
|
||||||
|
public final Class<?> getArrayClass(final Class<?> c) {
|
||||||
|
final Map<Class<?>, Class<?>> versionCache = this.versionCache;
|
||||||
|
Class<?> clazz = versionCache.get(c);
|
||||||
|
|
||||||
|
if (clazz == null) {
|
||||||
|
// messy, but the ONLY way to do it. Array super types are also arrays
|
||||||
|
final Object[] newInstance = (Object[]) Array.newInstance(c, 1);
|
||||||
|
clazz = newInstance.getClass();
|
||||||
|
versionCache.put(c, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the caches on shutdown
|
||||||
|
*/
|
||||||
|
public final void shutdown() {
|
||||||
|
this.versionCache.clear();
|
||||||
|
this.superClassesCache.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,13 @@
|
|||||||
package dorkbox.util.messagebus.common;
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.thread.ConcurrentSet;
|
import dorkbox.util.messagebus.common.thread.ConcurrentSet;
|
||||||
import dorkbox.util.messagebus.common.thread.SubscriptionHolder;
|
import dorkbox.util.messagebus.common.thread.SubscriptionHolder;
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
import dorkbox.util.messagebus.subscription.SubscriptionUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
public class VarArgUtils {
|
public class VarArgUtils {
|
||||||
private final ConcurrentMap<Class<?>, ConcurrentSet<Subscription>> varArgSubscriptions;
|
private final ConcurrentMap<Class<?>, ConcurrentSet<Subscription>> varArgSubscriptions;
|
||||||
@ -22,8 +23,8 @@ public class VarArgUtils {
|
|||||||
private final Map<Class<?>, ArrayList<Subscription>> subscriptionsPerMessageSingle;
|
private final Map<Class<?>, ArrayList<Subscription>> subscriptionsPerMessageSingle;
|
||||||
|
|
||||||
|
|
||||||
public VarArgUtils(SubscriptionUtils utils, Map<Class<?>, ArrayList<Subscription>> subscriptionsPerMessageSingle,
|
public VarArgUtils(SubscriptionUtils utils, Map<Class<?>, ArrayList<Subscription>> subscriptionsPerMessageSingle, float loadFactor,
|
||||||
float loadFactor, int stripeSize) {
|
int stripeSize) {
|
||||||
|
|
||||||
this.utils = utils;
|
this.utils = utils;
|
||||||
this.subscriptionsPerMessageSingle = subscriptionsPerMessageSingle;
|
this.subscriptionsPerMessageSingle = subscriptionsPerMessageSingle;
|
||||||
@ -53,7 +54,7 @@ public class VarArgUtils {
|
|||||||
//
|
//
|
||||||
// // whenever our subscriptions change, this map is cleared.
|
// // whenever our subscriptions change, this map is cleared.
|
||||||
// SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent;
|
// SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent;
|
||||||
// ConcurrentSet<Subscription> subsPerType = subHolderConcurrent.get();
|
// ConcurrentSet<Subscription> subsPerType = subHolderConcurrent.getSubscriptions();
|
||||||
//
|
//
|
||||||
// // cache our subscriptions for super classes, so that their access can be fast!
|
// // cache our subscriptions for super classes, so that their access can be fast!
|
||||||
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(messageClass, subsPerType);
|
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(messageClass, subsPerType);
|
||||||
@ -67,7 +68,7 @@ public class VarArgUtils {
|
|||||||
// Iterator<Subscription> iterator;
|
// Iterator<Subscription> iterator;
|
||||||
// Subscription sub;
|
// Subscription sub;
|
||||||
//
|
//
|
||||||
// Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(arrayVersion);
|
// Collection<Subscription> subs = this.subscriptionsPerMessageSingle.getSubscriptions(arrayVersion);
|
||||||
// if (subs != null) {
|
// if (subs != null) {
|
||||||
// for (iterator = subs.iterator(); iterator.hasNext();) {
|
// for (iterator = subs.iterator(); iterator.hasNext();) {
|
||||||
// sub = iterator.next();
|
// sub = iterator.next();
|
||||||
@ -95,7 +96,7 @@ public class VarArgUtils {
|
|||||||
// ConcurrentMap<Class<?>, ConcurrentSet<Subscription>> local = this.varArgSuperClassSubscriptions;
|
// ConcurrentMap<Class<?>, ConcurrentSet<Subscription>> local = this.varArgSuperClassSubscriptions;
|
||||||
//
|
//
|
||||||
// SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent;
|
// SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent;
|
||||||
// ConcurrentSet<Subscription> subsPerType = subHolderConcurrent.get();
|
// ConcurrentSet<Subscription> subsPerType = subHolderConcurrent.getSubscriptions();
|
||||||
//
|
//
|
||||||
// // cache our subscriptions for super classes, so that their access can be fast!
|
// // cache our subscriptions for super classes, so that their access can be fast!
|
||||||
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(messageClass, subsPerType);
|
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(messageClass, subsPerType);
|
||||||
@ -122,7 +123,7 @@ public class VarArgUtils {
|
|||||||
// for (iterator = types.iterator(); iterator.hasNext();) {
|
// for (iterator = types.iterator(); iterator.hasNext();) {
|
||||||
// superClass = iterator.next();
|
// superClass = iterator.next();
|
||||||
//
|
//
|
||||||
// Collection<Subscription> subs = local2.get(superClass);
|
// Collection<Subscription> subs = local2.getSubscriptions(superClass);
|
||||||
// if (subs != null) {
|
// if (subs != null) {
|
||||||
// for (subIterator = subs.iterator(); subIterator.hasNext();) {
|
// for (subIterator = subs.iterator(); subIterator.hasNext();) {
|
||||||
// sub = subIterator.next();
|
// sub = subIterator.next();
|
||||||
@ -158,7 +159,7 @@ public class VarArgUtils {
|
|||||||
// subsPerType = subsPerTypeLeaf.getValue();
|
// subsPerType = subsPerTypeLeaf.getValue();
|
||||||
// } else {
|
// } else {
|
||||||
// SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent;
|
// SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent;
|
||||||
// subsPerType = subHolderConcurrent.get();
|
// subsPerType = subHolderConcurrent.getSubscriptions();
|
||||||
//
|
//
|
||||||
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(subsPerType, messageClass1, messageClass2);
|
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(subsPerType, messageClass1, messageClass2);
|
||||||
// if (putIfAbsent != null) {
|
// if (putIfAbsent != null) {
|
||||||
@ -192,7 +193,8 @@ public class VarArgUtils {
|
|||||||
// CAN NOT RETURN NULL
|
// CAN NOT RETURN NULL
|
||||||
// check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI
|
// check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI
|
||||||
// and then, returns the array'd version subscriptions
|
// and then, returns the array'd version subscriptions
|
||||||
public ConcurrentSet<Subscription> getVarArgSuperSubscriptions(final Class<?> messageClass1, final Class<?> messageClass2, final Class<?> messageClass3) {
|
public ConcurrentSet<Subscription> getVarArgSuperSubscriptions(final Class<?> messageClass1, final Class<?> messageClass2,
|
||||||
|
final Class<?> messageClass3) {
|
||||||
// HashMapTree<Class<?>, ConcurrentSet<Subscription>> local = this.varArgSuperClassSubscriptionsMulti;
|
// HashMapTree<Class<?>, ConcurrentSet<Subscription>> local = this.varArgSuperClassSubscriptionsMulti;
|
||||||
//
|
//
|
||||||
// // whenever our subscriptions change, this map is cleared.
|
// // whenever our subscriptions change, this map is cleared.
|
||||||
@ -205,7 +207,7 @@ public class VarArgUtils {
|
|||||||
// subsPerType = subsPerTypeLeaf.getValue();
|
// subsPerType = subsPerTypeLeaf.getValue();
|
||||||
// } else {
|
// } else {
|
||||||
// SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent;
|
// SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent;
|
||||||
// subsPerType = subHolderConcurrent.get();
|
// subsPerType = subHolderConcurrent.getSubscriptions();
|
||||||
//
|
//
|
||||||
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(subsPerType, messageClass1, messageClass2, messageClass3);
|
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(subsPerType, messageClass1, messageClass2, messageClass3);
|
||||||
// if (putIfAbsent != null) {
|
// if (putIfAbsent != null) {
|
||||||
|
@ -3,8 +3,7 @@ package dorkbox.util.messagebus.common;
|
|||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.locks.StampedLock;
|
||||||
import dorkbox.util.messagebus.common.thread.StampedLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This implementation uses weak references to the elements. Iterators automatically perform cleanups of
|
* This implementation uses weak references to the elements. Iterators automatically perform cleanups of
|
||||||
@ -38,6 +37,8 @@ public class WeakConcurrentSet<T> extends AbstractConcurrentSet<T>{
|
|||||||
|
|
||||||
StampedLock lock = WeakConcurrentSet.this.lock;
|
StampedLock lock = WeakConcurrentSet.this.lock;
|
||||||
long stamp = lock.writeLock();
|
long stamp = lock.writeLock();
|
||||||
|
// final Lock writeLock = WeakConcurrentSet.this.lock.writeLock();
|
||||||
|
// writeLock.lock();
|
||||||
try{
|
try{
|
||||||
do {
|
do {
|
||||||
ISetEntry<T> orphaned = this.current;
|
ISetEntry<T> orphaned = this.current;
|
||||||
@ -47,6 +48,7 @@ public class WeakConcurrentSet<T> extends AbstractConcurrentSet<T>{
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
lock.unlockWrite(stamp);
|
lock.unlockWrite(stamp);
|
||||||
|
// writeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,9 +120,5 @@ public class WeakConcurrentSet<T> extends AbstractConcurrentSet<T>{
|
|||||||
public T getValue() {
|
public T getValue() {
|
||||||
return this.value.get();
|
return this.value.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
src/main/java/dorkbox/util/messagebus/common/item3.java
Normal file
11
src/main/java/dorkbox/util/messagebus/common/item3.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
abstract class item3<T> implements Iterator<T> {
|
||||||
|
public ISetEntry<T> current;
|
||||||
|
|
||||||
|
public item3(ISetEntry<T> current) {
|
||||||
|
this.current = current;
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,12 @@
|
|||||||
package dorkbox.util.messagebus.common.simpleq;
|
package dorkbox.util.messagebus.common.simpleq;
|
||||||
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpItem1;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpItem2;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpItem3;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpThread;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpType;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.lvMessageType;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.lvThread;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.soThread;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.spItem1;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.spItem2;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.spItem3;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.spMessageType;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.spThread;
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.MultiNode.spType;
|
|
||||||
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
import org.jctools.queues.MpmcArrayQueue;
|
import org.jctools.queues.MpmcArrayQueue;
|
||||||
import org.jctools.util.UnsafeAccess;
|
import org.jctools.util.UnsafeAccess;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import static dorkbox.util.messagebus.common.simpleq.MultiNode.*;
|
||||||
|
|
||||||
|
|
||||||
public final class MpmcMultiTransferArrayQueue extends MpmcArrayQueue<Object> {
|
public final class MpmcMultiTransferArrayQueue extends MpmcArrayQueue<Object> {
|
||||||
private static final int TYPE_EMPTY = 0;
|
private static final int TYPE_EMPTY = 0;
|
||||||
@ -445,7 +432,7 @@ public final class MpmcMultiTransferArrayQueue extends MpmcArrayQueue<Object> {
|
|||||||
// Successful CAS: full barrier
|
// Successful CAS: full barrier
|
||||||
|
|
||||||
final Thread myThread = Thread.currentThread();
|
final Thread myThread = Thread.currentThread();
|
||||||
// final Object node = nodeThreadLocal.get();
|
// final Object node = nodeThreadLocal.getSubscriptions();
|
||||||
|
|
||||||
spType(node, TYPE_CONSUMER);
|
spType(node, TYPE_CONSUMER);
|
||||||
spThread(node, myThread);
|
spThread(node, myThread);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package dorkbox.util.messagebus.common.thread;
|
package dorkbox.util.messagebus.common.thread;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.common.ConcurrentHashMapV8;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.ConcurrentHashMapV8;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This data structure is optimized for non-blocking reads even when write operations occur.
|
* This data structure is optimized for non-blocking reads even when write operations occur.
|
||||||
* Running read iterators will not be affected by add operations since writes always insert at the head of the
|
* Running read iterators will not be affected by add operations since writes always insert at the head of the
|
||||||
@ -39,7 +39,7 @@ public class ConcurrentSet<T> extends ConcurrentLinkedQueue2<T> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// had to modify the super implementation so we get Node<T> back
|
// had to modify the super implementation so we getSubscriptions Node<T> back
|
||||||
Node<T> alreadyPresent = this.entries.putIfAbsent(element, this.IN_PROGRESS_MARKER);
|
Node<T> alreadyPresent = this.entries.putIfAbsent(element, this.IN_PROGRESS_MARKER);
|
||||||
if (alreadyPresent == null) {
|
if (alreadyPresent == null) {
|
||||||
// this doesn't already exist
|
// this doesn't already exist
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package dorkbox.util.messagebus.subscription;
|
||||||
|
|
||||||
|
public interface Matcher {
|
||||||
|
Subscription[] getSubscriptions(Class<?> messageClass);
|
||||||
|
}
|
@ -1,19 +1,17 @@
|
|||||||
package dorkbox.util.messagebus.subscription;
|
package dorkbox.util.messagebus.subscription;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import org.omg.CORBA.BooleanHolder;
|
|
||||||
|
|
||||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.MessageHandler;
|
import dorkbox.util.messagebus.common.MessageHandler;
|
||||||
import dorkbox.util.messagebus.common.StrongConcurrentSetV8;
|
import dorkbox.util.messagebus.common.StrongConcurrentSetV8;
|
||||||
import dorkbox.util.messagebus.dispatch.IHandlerInvocation;
|
import dorkbox.util.messagebus.dispatch.IHandlerInvocation;
|
||||||
import dorkbox.util.messagebus.dispatch.ReflectiveHandlerInvocation;
|
import dorkbox.util.messagebus.dispatch.ReflectiveHandlerInvocation;
|
||||||
import dorkbox.util.messagebus.dispatch.SynchronizedHandlerInvocation;
|
import dorkbox.util.messagebus.dispatch.SynchronizedHandlerInvocation;
|
||||||
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
||||||
|
import org.omg.CORBA.BooleanHolder;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A subscription is a thread-safe container that manages exactly one message handler of all registered
|
* A subscription is a thread-safe container that manages exactly one message handler of all registered
|
||||||
@ -43,9 +41,10 @@ public class Subscription {
|
|||||||
|
|
||||||
public Subscription(MessageHandler handler) {
|
public Subscription(MessageHandler handler) {
|
||||||
this.handlerMetadata = handler;
|
this.handlerMetadata = handler;
|
||||||
this.listeners = new StrongConcurrentSetV8<Object>(16, 0.85F, 16);
|
this.listeners = new StrongConcurrentSetV8<Object>(16, 0.85F, 15);
|
||||||
// this.listeners = new StrongConcurrentSet<Object>(16, 0.85F);
|
// this.listeners = new StrongConcurrentSet<Object>(16, 0.85F);
|
||||||
// this.listeners = new ConcurrentLinkedQueue2<Object>();
|
// this.listeners = new ConcurrentLinkedQueue2<Object>();
|
||||||
|
// this.listeners = new CopyOnWriteArrayList<Object>();
|
||||||
|
|
||||||
IHandlerInvocation invocation = new ReflectiveHandlerInvocation();
|
IHandlerInvocation invocation = new ReflectiveHandlerInvocation();
|
||||||
if (handler.isSynchronized()) {
|
if (handler.isSynchronized()) {
|
||||||
@ -59,26 +58,26 @@ public class Subscription {
|
|||||||
return this.handlerMetadata.getHandledMessages();
|
return this.handlerMetadata.getHandledMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean acceptsSubtypes() {
|
public final boolean acceptsSubtypes() {
|
||||||
return this.handlerMetadata.acceptsSubtypes();
|
return this.handlerMetadata.acceptsSubtypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean acceptsVarArgs() {
|
public final boolean acceptsVarArgs() {
|
||||||
return this.handlerMetadata.acceptsVarArgs();
|
return this.handlerMetadata.acceptsVarArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public final boolean isEmpty() {
|
||||||
return this.listeners.isEmpty();
|
return this.listeners.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void subscribe(Object listener) {
|
public final void subscribe(Object listener) {
|
||||||
this.listeners.add(listener);
|
this.listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return TRUE if the element was removed
|
* @return TRUE if the element was removed
|
||||||
*/
|
*/
|
||||||
public boolean unsubscribe(Object existingListener) {
|
public final boolean unsubscribe(Object existingListener) {
|
||||||
return this.listeners.remove(existingListener);
|
return this.listeners.remove(existingListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +95,9 @@ public class Subscription {
|
|||||||
* @return true if there were listeners for this publication, false if there was nothing
|
* @return true if there were listeners for this publication, false if there was nothing
|
||||||
*/
|
*/
|
||||||
public final void publish(final Object message) throws Throwable {
|
public final void publish(final Object message) throws Throwable {
|
||||||
MethodAccess handler = this.handlerMetadata.getHandler();
|
final MethodAccess handler = this.handlerMetadata.getHandler();
|
||||||
int handleIndex = this.handlerMetadata.getMethodIndex();
|
final int handleIndex = this.handlerMetadata.getMethodIndex();
|
||||||
IHandlerInvocation invocation = this.invocation;
|
final IHandlerInvocation invocation = this.invocation;
|
||||||
|
|
||||||
Iterator<Object> iterator;
|
Iterator<Object> iterator;
|
||||||
Object listener;
|
Object listener;
|
||||||
@ -107,7 +106,6 @@ public class Subscription {
|
|||||||
listener = iterator.next();
|
listener = iterator.next();
|
||||||
|
|
||||||
// this.c++;
|
// this.c++;
|
||||||
|
|
||||||
invocation.invoke(listener, handler, handleIndex, message);
|
invocation.invoke(listener, handler, handleIndex, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,25 @@
|
|||||||
package dorkbox.util.messagebus;
|
package dorkbox.util.messagebus.subscription;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.*;
|
import dorkbox.util.messagebus.common.*;
|
||||||
import dorkbox.util.messagebus.common.thread.ConcurrentSet;
|
import dorkbox.util.messagebus.common.thread.ConcurrentSet;
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.StampedLock;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
* It provides fast lookup of existing subscriptions when another instance of an already known
|
* It provides fast lookup of existing subscriptions when another instance of an already known
|
||||||
* 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.
|
||||||
*
|
* <p>
|
||||||
*
|
* <p>
|
||||||
* Subscribe/Unsubscribe, while it is possible for them to be 100% concurrent (in relation to listeners per subscription),
|
* 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
|
* getting an accurate reflection of the number of subscriptions, or guaranteeing a "HAPPENS-BEFORE" relationship really
|
||||||
* complicates this, so it has been modified for subscribe/unsubscibe to be mutually exclusive.
|
* complicates this, so it has been modified for subscribe/unsubscibe to be mutually exclusive.
|
||||||
*
|
* <p>
|
||||||
* Given these restrictions and complexity, it is much easier to create a MPSC blocking queue, and have a single thread
|
* Given these restrictions and complexity, it is much easier to create a MPSC blocking queue, and have a single thread
|
||||||
* manage sub/unsub.
|
* manage sub/unsub.
|
||||||
*
|
*
|
||||||
@ -58,24 +55,25 @@ public class SubscriptionManager {
|
|||||||
private final VarArgUtils varArgUtils;
|
private final VarArgUtils varArgUtils;
|
||||||
|
|
||||||
|
|
||||||
// private final StampedLock lock = new StampedLock();
|
private final StampedLock lock = new StampedLock();
|
||||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
// private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
SubscriptionManager(int numberOfThreads) {
|
public SubscriptionManager(int numberOfThreads) {
|
||||||
float loadFactor = SubscriptionManager.LOAD_FACTOR;
|
float loadFactor = SubscriptionManager.LOAD_FACTOR;
|
||||||
|
|
||||||
// modified ONLY during SUB/UNSUB
|
// modified ONLY during SUB/UNSUB
|
||||||
{
|
{
|
||||||
this.nonListeners = new ConcurrentHashMapV8<Class<?>, Boolean>(4, loadFactor, numberOfThreads);
|
this.nonListeners = new ConcurrentHashMapV8<Class<?>, Boolean>(4, loadFactor, numberOfThreads);
|
||||||
|
|
||||||
this.subscriptionsPerMessageSingle = new ConcurrentHashMapV8<Class<?>, ArrayList<Subscription>>(64, LOAD_FACTOR, 1);
|
this.subscriptionsPerMessageSingle = new ConcurrentHashMapV8<Class<?>, ArrayList<Subscription>>(32, LOAD_FACTOR, 1);
|
||||||
this.subscriptionsPerMessageMulti = new HashMapTree<Class<?>, ArrayList<Subscription>>(4, loadFactor);
|
this.subscriptionsPerMessageMulti = new HashMapTree<Class<?>, ArrayList<Subscription>>(4, loadFactor);
|
||||||
|
|
||||||
// only used during SUB/UNSUB
|
// only used during SUB/UNSUB
|
||||||
this.subscriptionsPerListener = new ConcurrentHashMapV8<Class<?>, Subscription[]>();
|
this.subscriptionsPerListener = new ConcurrentHashMapV8<Class<?>, Subscription[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.utils = new SubscriptionUtils(this.subscriptionsPerMessageSingle, this.subscriptionsPerMessageMulti, loadFactor, numberOfThreads);
|
this.utils = new SubscriptionUtils(this.subscriptionsPerMessageSingle, this.subscriptionsPerMessageMulti, loadFactor,
|
||||||
|
numberOfThreads);
|
||||||
|
|
||||||
// var arg subscriptions keep track of which subscriptions can handle varArgs. SUB/UNSUB dumps it, so it is recreated dynamically.
|
// var arg subscriptions keep track of which subscriptions can handle varArgs. SUB/UNSUB dumps it, so it is recreated dynamically.
|
||||||
// it's a hit on SUB/UNSUB, but improves performance of handlers
|
// it's a hit on SUB/UNSUB, but improves performance of handlers
|
||||||
@ -118,8 +116,11 @@ public class SubscriptionManager {
|
|||||||
if (subscriptions == null) {
|
if (subscriptions == null) {
|
||||||
// now write lock for the least expensive part. This is a deferred "double checked lock", but is necessary because
|
// now write lock for the least expensive part. This is a deferred "double checked lock", but is necessary because
|
||||||
// of the huge number of reads compared to writes.
|
// of the huge number of reads compared to writes.
|
||||||
Lock writeLock = this.lock.writeLock();
|
|
||||||
writeLock.lock();
|
StampedLock lock = this.lock;
|
||||||
|
long stamp = lock.writeLock();
|
||||||
|
// Lock writeLock = this.lock.writeLock();
|
||||||
|
// writeLock.lock();
|
||||||
|
|
||||||
ConcurrentMap<Class<?>, Subscription[]> subsPerListener2 = this.subscriptionsPerListener;
|
ConcurrentMap<Class<?>, Subscription[]> subsPerListener2 = this.subscriptionsPerListener;
|
||||||
subscriptions = subsPerListener2.get(listenerClass);
|
subscriptions = subsPerListener2.get(listenerClass);
|
||||||
@ -132,7 +133,8 @@ public class SubscriptionManager {
|
|||||||
// remember the class as non listening class if no handlers are found
|
// remember the class as non listening class if no handlers are found
|
||||||
if (handlersSize == 0) {
|
if (handlersSize == 0) {
|
||||||
this.nonListeners.put(listenerClass, Boolean.TRUE);
|
this.nonListeners.put(listenerClass, Boolean.TRUE);
|
||||||
writeLock.unlock();
|
lock.unlockWrite(stamp);
|
||||||
|
// writeLock.unlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,21 +159,25 @@ public class SubscriptionManager {
|
|||||||
subsPerListener.add(subscription); // activates this sub for sub/unsub
|
subsPerListener.add(subscription); // activates this sub for sub/unsub
|
||||||
|
|
||||||
// now add this subscription to each of the handled types
|
// now add this subscription to each of the handled types
|
||||||
subsForPublication = getSubsForPublication(messageHandler.getHandledMessages(), subsPerMessageSingle, subsPerMessageMulti, varArgPossibility);
|
subsForPublication = getSubsForPublication(messageHandler.getHandledMessages(), subsPerMessageSingle,
|
||||||
|
subsPerMessageMulti, varArgPossibility);
|
||||||
subsForPublication.add(subscription); // activates this sub for publication
|
subsForPublication.add(subscription); // activates this sub for publication
|
||||||
}
|
}
|
||||||
|
|
||||||
subsPerListener2.put(listenerClass, subsPerListener.toArray(new Subscription[subsPerListener.size()]));
|
subsPerListener2.put(listenerClass, subsPerListener.toArray(new Subscription[subsPerListener.size()]));
|
||||||
writeLock.unlock();
|
lock.unlockWrite(stamp);
|
||||||
|
// writeLock.unlock();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
writeLock.unlock();
|
else {
|
||||||
|
lock.unlockWrite(stamp);
|
||||||
|
// writeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// subscriptions already exist and must only be updated
|
// subscriptions already exist and must only be updated
|
||||||
// only get here if our single-check was OK, or our double-check was OK
|
// only getSubscriptions here if our single-check was OK, or our double-check was OK
|
||||||
Subscription subscription;
|
Subscription subscription;
|
||||||
|
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
for (int i = 0; i < subscriptions.length; i++) {
|
||||||
@ -200,15 +206,13 @@ public class SubscriptionManager {
|
|||||||
if (subs == null) {
|
if (subs == null) {
|
||||||
subs = new ArrayList<Subscription>();
|
subs = new ArrayList<Subscription>();
|
||||||
|
|
||||||
boolean isArray = utils.isArray(type0);
|
boolean isArray = type0.isArray();
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
varArgPossibility.set(true);
|
varArgPossibility.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache the super classes
|
// cache the super classes
|
||||||
// todo: makes it's own read/write lock. it's 2x as expensive when running inside the writelock for subscribe, VS on it's own
|
// utils.cacheSuperClasses(type0, isArray);
|
||||||
// maybe even use StampedLock
|
|
||||||
utils.cacheSuperClasses(type0, isArray);
|
|
||||||
|
|
||||||
subsPerMessageSingle.put(type0, subs);
|
subsPerMessageSingle.put(type0, subs);
|
||||||
}
|
}
|
||||||
@ -218,7 +222,7 @@ public class SubscriptionManager {
|
|||||||
case 2: {
|
case 2: {
|
||||||
// the HashMapTree uses read/write locks, so it is only accessible one thread at a time
|
// the HashMapTree uses read/write locks, so it is only accessible one thread at a time
|
||||||
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
||||||
// subsPerType = subHolderSingle.get();
|
// subsPerType = subHolderSingle.getSubscriptions();
|
||||||
//
|
//
|
||||||
// Collection<Subscription> putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, type0, types[1]);
|
// Collection<Subscription> putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, type0, types[1]);
|
||||||
// if (putIfAbsent != null) {
|
// if (putIfAbsent != null) {
|
||||||
@ -236,7 +240,7 @@ public class SubscriptionManager {
|
|||||||
case 3: {
|
case 3: {
|
||||||
// the HashMapTree uses read/write locks, so it is only accessible one thread at a time
|
// the HashMapTree uses read/write locks, so it is only accessible one thread at a time
|
||||||
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
||||||
// subsPerType = subHolderSingle.get();
|
// subsPerType = subHolderSingle.getSubscriptions();
|
||||||
//
|
//
|
||||||
// Collection<Subscription> putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, type0, types[1], types[2]);
|
// Collection<Subscription> putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, type0, types[1], types[2]);
|
||||||
// if (putIfAbsent != null) {
|
// if (putIfAbsent != null) {
|
||||||
@ -255,7 +259,7 @@ public class SubscriptionManager {
|
|||||||
default: {
|
default: {
|
||||||
// the HashMapTree uses read/write locks, so it is only accessible one thread at a time
|
// the HashMapTree uses read/write locks, so it is only accessible one thread at a time
|
||||||
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
||||||
// subsPerType = subHolderSingle.get();
|
// subsPerType = subHolderSingle.getSubscriptions();
|
||||||
//
|
//
|
||||||
// Collection<Subscription> putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, types);
|
// Collection<Subscription> putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, types);
|
||||||
// if (putIfAbsent != null) {
|
// if (putIfAbsent != null) {
|
||||||
@ -309,13 +313,18 @@ public class SubscriptionManager {
|
|||||||
this.varArgUtils.clear();
|
this.varArgUtils.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Subscription[] getListenerSubs(Class<?> listenerClass) {
|
private Subscription[] getListenerSubs(Class<?> listenerClass) {
|
||||||
Subscription[] subscriptions;
|
Subscription[] subscriptions;
|
||||||
|
|
||||||
Lock readLock = this.lock.readLock();
|
StampedLock lock = this.lock;
|
||||||
readLock.lock();
|
long stamp = lock.readLock();
|
||||||
|
// Lock readLock = this.lock.readLock();
|
||||||
|
// readLock.lock();
|
||||||
|
|
||||||
subscriptions = this.subscriptionsPerListener.get(listenerClass);
|
subscriptions = this.subscriptionsPerListener.get(listenerClass);
|
||||||
readLock.unlock();
|
|
||||||
|
lock.unlockRead(stamp);
|
||||||
|
// readLock.unlock();
|
||||||
|
|
||||||
return subscriptions;
|
return subscriptions;
|
||||||
}
|
}
|
||||||
@ -326,46 +335,59 @@ public class SubscriptionManager {
|
|||||||
ArrayList<Subscription> collection;
|
ArrayList<Subscription> collection;
|
||||||
Subscription[] subscriptions;
|
Subscription[] subscriptions;
|
||||||
|
|
||||||
Lock readLock = this.lock.readLock();
|
StampedLock lock = this.lock;
|
||||||
readLock.lock();
|
long stamp = lock.readLock();
|
||||||
|
// Lock readLock = this.lock.readLock();
|
||||||
|
// readLock.lock();
|
||||||
|
|
||||||
collection = this.subscriptionsPerMessageSingle.get(messageClass);
|
collection = this.subscriptionsPerMessageSingle.get(messageClass);
|
||||||
|
//
|
||||||
if (collection != null) {
|
if (collection != null) {
|
||||||
subscriptions = collection.toArray(new Subscription[collection.size()]);
|
subscriptions = collection.toArray(new Subscription[collection.size()]);
|
||||||
} else {
|
}
|
||||||
subscriptions = EMPTY;
|
else {
|
||||||
|
// subscriptions = EMPTY;
|
||||||
|
subscriptions = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
readLock.unlock();
|
lock.unlockRead(stamp);
|
||||||
|
// readLock.unlock();
|
||||||
return subscriptions;
|
return subscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// never return null
|
// never return null
|
||||||
public final Subscription[] getSubscriptions(final Class<?> messageClass) {
|
public final Subscription[] getSubscriptions(final Class<?> messageClass) {
|
||||||
ArrayList<Subscription> collection;
|
ArrayList<Subscription> collection;
|
||||||
Subscription[] subscriptions = null;
|
|
||||||
|
|
||||||
Lock readLock = this.lock.readLock();
|
|
||||||
readLock.lock();
|
|
||||||
|
|
||||||
|
|
||||||
collection = this.subscriptionsPerMessageSingle.get(messageClass);
|
StampedLock lock = this.lock;
|
||||||
|
long stamp = lock.readLock();
|
||||||
|
// Lock readLock = this.lock.readLock();
|
||||||
|
// readLock.lock();
|
||||||
|
|
||||||
|
collection = this.subscriptionsPerMessageSingle.get(messageClass); // can return null
|
||||||
|
|
||||||
|
// now getSubscriptions superClasses
|
||||||
|
ArrayList<Subscription> superSubscriptions = this.utils.getSuperSubscriptions(messageClass); // NOT return null
|
||||||
|
|
||||||
if (collection != null) {
|
if (collection != null) {
|
||||||
collection = new ArrayList<Subscription>(collection);
|
collection = new ArrayList<Subscription>(collection);
|
||||||
|
|
||||||
// now get superClasses
|
|
||||||
ArrayList<Subscription> superSubscriptions = this.utils.getSuperSubscriptions(messageClass); // NOT return null
|
|
||||||
collection.addAll(superSubscriptions);
|
collection.addAll(superSubscriptions);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// now get superClasses
|
collection = superSubscriptions;
|
||||||
collection = this.utils.getSuperSubscriptions(messageClass); // NOT return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Subscription[] subscriptions;
|
||||||
|
if (collection != null) {
|
||||||
subscriptions = collection.toArray(new Subscription[collection.size()]);
|
subscriptions = collection.toArray(new Subscription[collection.size()]);
|
||||||
readLock.unlock();
|
}
|
||||||
|
else {
|
||||||
|
subscriptions = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock.unlockRead(stamp);
|
||||||
|
// readLock.unlock();
|
||||||
|
|
||||||
return subscriptions;
|
return subscriptions;
|
||||||
}
|
}
|
||||||
@ -381,7 +403,7 @@ public class SubscriptionManager {
|
|||||||
// readLock.lock();
|
// readLock.lock();
|
||||||
//
|
//
|
||||||
// try {
|
// try {
|
||||||
// collection = this.subscriptionsPerMessageSingle.get(messageType);
|
// collection = this.subscriptionsPerMessageSingle.getSubscriptions(messageType);
|
||||||
// if (collection != null) {
|
// if (collection != null) {
|
||||||
// subscriptions = collection.toArray(EMPTY);
|
// subscriptions = collection.toArray(EMPTY);
|
||||||
// }
|
// }
|
||||||
@ -394,7 +416,7 @@ public class SubscriptionManager {
|
|||||||
//
|
//
|
||||||
//// long stamp = this.lock.tryOptimisticRead(); // non blocking
|
//// long stamp = this.lock.tryOptimisticRead(); // non blocking
|
||||||
////
|
////
|
||||||
//// collection = this.subscriptionsPerMessageSingle.get(messageType);
|
//// collection = this.subscriptionsPerMessageSingle.getSubscriptions(messageType);
|
||||||
//// if (collection != null) {
|
//// if (collection != null) {
|
||||||
////// subscriptions = new ArrayDeque<>(collection);
|
////// subscriptions = new ArrayDeque<>(collection);
|
||||||
//// subscriptions = new ArrayList<>(collection);
|
//// subscriptions = new ArrayList<>(collection);
|
||||||
@ -407,7 +429,7 @@ public class SubscriptionManager {
|
|||||||
//// if (!this.lock.validate(stamp)) { // if a write occurred, try again with a read lock
|
//// if (!this.lock.validate(stamp)) { // if a write occurred, try again with a read lock
|
||||||
//// stamp = this.lock.readLock();
|
//// stamp = this.lock.readLock();
|
||||||
//// try {
|
//// try {
|
||||||
//// collection = this.subscriptionsPerMessageSingle.get(messageType);
|
//// collection = this.subscriptionsPerMessageSingle.getSubscriptions(messageType);
|
||||||
//// if (collection != null) {
|
//// if (collection != null) {
|
||||||
////// subscriptions = new ArrayDeque<>(collection);
|
////// subscriptions = new ArrayDeque<>(collection);
|
||||||
//// subscriptions = new ArrayList<>(collection);
|
//// subscriptions = new ArrayList<>(collection);
|
||||||
@ -448,7 +470,8 @@ public class SubscriptionManager {
|
|||||||
|
|
||||||
|
|
||||||
// CAN RETURN NULL
|
// CAN RETURN NULL
|
||||||
public final Collection<Subscription> getSubscriptionsByMessageType(Class<?> messageType1, Class<?> messageType2, Class<?> messageType3) {
|
public final Collection<Subscription> getSubscriptionsByMessageType(Class<?> messageType1, Class<?> messageType2,
|
||||||
|
Class<?> messageType3) {
|
||||||
return this.subscriptionsPerMessageMulti.getValue(messageType1, messageType2, messageType3);
|
return this.subscriptionsPerMessageMulti.getValue(messageType1, messageType2, messageType3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,7 +504,8 @@ public class SubscriptionManager {
|
|||||||
// CAN NOT RETURN NULL
|
// CAN NOT RETURN NULL
|
||||||
// check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI
|
// check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI
|
||||||
// and then, returns the array'd version subscriptions
|
// and then, returns the array'd version subscriptions
|
||||||
public Collection<Subscription> getVarArgSuperSubscriptions(final Class<?> messageClass1, final Class<?> messageClass2, final Class<?> messageClass3) {
|
public Collection<Subscription> getVarArgSuperSubscriptions(final Class<?> messageClass1, final Class<?> messageClass2,
|
||||||
|
final Class<?> messageClass3) {
|
||||||
return this.varArgUtils.getVarArgSuperSubscriptions(messageClass1, messageClass2, messageClass3);
|
return this.varArgUtils.getVarArgSuperSubscriptions(messageClass1, messageClass2, messageClass3);
|
||||||
}
|
}
|
||||||
|
|
@ -1,33 +1,24 @@
|
|||||||
package dorkbox.util.messagebus.common;
|
package dorkbox.util.messagebus.subscription;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.common.ConcurrentHashMapV8;
|
||||||
|
import dorkbox.util.messagebus.common.HashMapTree;
|
||||||
|
import dorkbox.util.messagebus.common.SuperClassUtils;
|
||||||
|
import dorkbox.util.messagebus.common.thread.ClassHolder;
|
||||||
|
import dorkbox.util.messagebus.common.thread.ConcurrentSet;
|
||||||
|
import dorkbox.util.messagebus.common.thread.SubscriptionHolder;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.thread.ClassHolder;
|
|
||||||
import dorkbox.util.messagebus.common.thread.ConcurrentSet;
|
|
||||||
import dorkbox.util.messagebus.common.thread.StampedLock;
|
|
||||||
import dorkbox.util.messagebus.common.thread.SubscriptionHolder;
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
|
||||||
|
|
||||||
public class SubscriptionUtils {
|
public class SubscriptionUtils {
|
||||||
private static final Class<?>[] SUPER_CLASS_EMPTY = new Class<?>[0];
|
private final SuperClassUtils superClass;
|
||||||
|
|
||||||
private StampedLock superClassLock = new StampedLock();
|
|
||||||
|
|
||||||
|
|
||||||
private final Map<Class<?>, Class<?>> arrayVersionCache;
|
|
||||||
private final Map<Class<?>, Boolean> isArrayCache;
|
|
||||||
|
|
||||||
private final Map<Class<?>, Class<?>[]> superClassesCache;
|
|
||||||
private final ClassHolder classHolderSingle;
|
private final ClassHolder classHolderSingle;
|
||||||
|
|
||||||
// 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 REALLY improves performance on handlers
|
// it's a hit on SUB/UNSUB, but REALLY improves performance on handlers
|
||||||
// it's faster to create a new one for SUB/UNSUB than it is to clear() on the original one
|
// it's faster to create a new one for SUB/UNSUB than it is to shutdown() on the original one
|
||||||
private final Map<Class<?>, ArrayList<Subscription>> superClassSubscriptions;
|
private final Map<Class<?>, ArrayList<Subscription>> superClassSubscriptions;
|
||||||
private final HashMapTree<Class<?>, ConcurrentSet<Subscription>> superClassSubscriptionsMulti;
|
private final HashMapTree<Class<?>, ConcurrentSet<Subscription>> superClassSubscriptionsMulti;
|
||||||
|
|
||||||
@ -40,16 +31,15 @@ public class SubscriptionUtils {
|
|||||||
|
|
||||||
|
|
||||||
public SubscriptionUtils(Map<Class<?>, ArrayList<Subscription>> subscriptionsPerMessageSingle,
|
public SubscriptionUtils(Map<Class<?>, ArrayList<Subscription>> subscriptionsPerMessageSingle,
|
||||||
HashMapTree<Class<?>, ArrayList<Subscription>> subscriptionsPerMessageMulti,
|
HashMapTree<Class<?>, ArrayList<Subscription>> subscriptionsPerMessageMulti, float loadFactor,
|
||||||
float loadFactor, int stripeSize) {
|
int stripeSize) {
|
||||||
|
|
||||||
|
this.superClass = new SuperClassUtils(loadFactor, 1);
|
||||||
|
|
||||||
this.subscriptionsPerMessageSingle = subscriptionsPerMessageSingle;
|
this.subscriptionsPerMessageSingle = subscriptionsPerMessageSingle;
|
||||||
this.subscriptionsPerMessageMulti = subscriptionsPerMessageMulti;
|
this.subscriptionsPerMessageMulti = subscriptionsPerMessageMulti;
|
||||||
|
|
||||||
this.arrayVersionCache = new ConcurrentHashMapV8<Class<?>, Class<?>>(32, loadFactor, stripeSize);
|
|
||||||
this.isArrayCache = new ConcurrentHashMapV8<Class<?>, Boolean>(32, loadFactor, stripeSize);
|
|
||||||
|
|
||||||
this.superClassesCache = new ConcurrentHashMapV8<Class<?>, Class<?>[]>(32, loadFactor, 8);
|
|
||||||
this.classHolderSingle = new ClassHolder(loadFactor, stripeSize);
|
this.classHolderSingle = new ClassHolder(loadFactor, stripeSize);
|
||||||
|
|
||||||
// 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.
|
||||||
@ -65,66 +55,15 @@ public class SubscriptionUtils {
|
|||||||
this.superClassSubscriptions.clear();
|
this.superClassSubscriptions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* never returns null
|
|
||||||
* never reset, since it never needs to be reset (as the class hierarchy doesn't change at runtime)
|
|
||||||
*
|
|
||||||
* if parameter clazz is of type array, then the super classes are of array type as well
|
|
||||||
*
|
|
||||||
* protected by read lock by caller. The cache version is called first, by write lock
|
|
||||||
*/
|
|
||||||
public final Class<?>[] getSuperClasses_NL(final Class<?> clazz, final boolean isArray) {
|
|
||||||
// this is never reset, since it never needs to be.
|
|
||||||
final Map<Class<?>, Class<?>[]> local = this.superClassesCache;
|
|
||||||
|
|
||||||
Class<?>[] classes = local.get(clazz);
|
|
||||||
|
|
||||||
if (classes == null) {
|
|
||||||
// get all super types of class
|
|
||||||
final Class<?>[] superTypes = ReflectionUtils.getSuperTypes(clazz);
|
|
||||||
int length = superTypes.length;
|
|
||||||
|
|
||||||
ArrayList<Class<?>> newList = new ArrayList<Class<?>>(length);
|
|
||||||
|
|
||||||
Class<?> c;
|
|
||||||
if (isArray) {
|
|
||||||
for (int i=0;i<length;i++) {
|
|
||||||
c = superTypes[i];
|
|
||||||
|
|
||||||
c = getArrayClass(c);
|
|
||||||
|
|
||||||
if (c != clazz) {
|
|
||||||
newList.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int i=0;i<length;i++) {
|
|
||||||
c = superTypes[i];
|
|
||||||
|
|
||||||
if (c != clazz) {
|
|
||||||
newList.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
classes = newList.toArray(new Class<?>[newList.size()]);
|
|
||||||
local.put(clazz, classes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return classes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// called inside sub/unsub write lock
|
// called inside sub/unsub write lock
|
||||||
public final void cacheSuperClasses(final Class<?> clazz) {
|
public final void cacheSuperClasses(final Class<?> clazz) {
|
||||||
getSuperClasses_NL(clazz, isArray(clazz));
|
this.superClass.getSuperClasses(clazz, clazz.isArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
// called inside sub/unsub write lock
|
// called inside sub/unsub write lock
|
||||||
public final void cacheSuperClasses(final Class<?> clazz, final boolean isArray) {
|
public final void cacheSuperClasses(final Class<?> clazz, final boolean isArray) {
|
||||||
getSuperClasses_NL(clazz, isArray);
|
this.superClass.getSuperClasses(clazz, isArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public final Class<?>[] getSuperClasses(Class<?> clazz, boolean isArray) {
|
// public final Class<?>[] getSuperClasses(Class<?> clazz, boolean isArray) {
|
||||||
@ -136,7 +75,7 @@ public class SubscriptionUtils {
|
|||||||
// long stamp = lock.tryOptimisticRead();
|
// long stamp = lock.tryOptimisticRead();
|
||||||
//
|
//
|
||||||
// if (stamp > 0) {
|
// if (stamp > 0) {
|
||||||
// ArrayList<Class<?>> arrayList = local.get(clazz);
|
// ArrayList<Class<?>> arrayList = local.getSubscriptions(clazz);
|
||||||
// if (arrayList != null) {
|
// if (arrayList != null) {
|
||||||
// classes = arrayList.toArray(SUPER_CLASS_EMPTY);
|
// classes = arrayList.toArray(SUPER_CLASS_EMPTY);
|
||||||
//
|
//
|
||||||
@ -145,7 +84,7 @@ public class SubscriptionUtils {
|
|||||||
// } else {
|
// } else {
|
||||||
// stamp = lock.readLock();
|
// stamp = lock.readLock();
|
||||||
//
|
//
|
||||||
// arrayList = local.get(clazz);
|
// arrayList = local.getSubscriptions(clazz);
|
||||||
// if (arrayList != null) {
|
// if (arrayList != null) {
|
||||||
// classes = arrayList.toArray(SUPER_CLASS_EMPTY);
|
// classes = arrayList.toArray(SUPER_CLASS_EMPTY);
|
||||||
// lock.unlockRead(stamp);
|
// lock.unlockRead(stamp);
|
||||||
@ -155,7 +94,7 @@ public class SubscriptionUtils {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // unable to get a valid subscription. Have to acquire a write lock
|
// // unable to getSubscriptions a valid subscription. Have to acquire a write lock
|
||||||
// long origStamp = stamp;
|
// long origStamp = stamp;
|
||||||
// if ((stamp = lock.tryConvertToWriteLock(stamp)) == 0) {
|
// if ((stamp = lock.tryConvertToWriteLock(stamp)) == 0) {
|
||||||
// lock.unlockRead(origStamp);
|
// lock.unlockRead(origStamp);
|
||||||
@ -163,7 +102,7 @@ public class SubscriptionUtils {
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// // get all super types of class
|
// // getSubscriptions all super types of class
|
||||||
// Collection<Class<?>> superTypes = ReflectionUtils.getSuperTypes(clazz);
|
// Collection<Class<?>> superTypes = ReflectionUtils.getSuperTypes(clazz);
|
||||||
// ArrayList<Class<?>> arrayList = new ArrayList<Class<?>>(superTypes.size());
|
// ArrayList<Class<?>> arrayList = new ArrayList<Class<?>>(superTypes.size());
|
||||||
// Iterator<Class<?>> iterator;
|
// Iterator<Class<?>> iterator;
|
||||||
@ -189,89 +128,51 @@ public class SubscriptionUtils {
|
|||||||
// return classes;
|
// return classes;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
|
||||||
* race conditions will result in duplicate answers, which we don't care if happens
|
|
||||||
* never returns null
|
|
||||||
* never reset
|
|
||||||
*/
|
|
||||||
public final Class<?> getArrayClass(final Class<?> c) {
|
|
||||||
final Map<Class<?>, Class<?>> arrayVersionCache = this.arrayVersionCache;
|
|
||||||
Class<?> clazz = arrayVersionCache.get(c);
|
|
||||||
|
|
||||||
if (clazz == null) {
|
|
||||||
// messy, but the ONLY way to do it. Array super types are also arrays
|
|
||||||
final Object[] newInstance = (Object[]) Array.newInstance(c, 1);
|
|
||||||
clazz = newInstance.getClass();
|
|
||||||
arrayVersionCache.put(c, clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache the values of JNI method, isArray(c)
|
|
||||||
* @return true if the class c is an array type
|
|
||||||
*/
|
|
||||||
// @SuppressWarnings("boxing")
|
|
||||||
public final boolean isArray(final Class<?> c) {
|
|
||||||
// final Map<Class<?>, Boolean> isArrayCache = this.isArrayCache;
|
|
||||||
//
|
|
||||||
// final Boolean isArray = isArrayCache.get(c);
|
|
||||||
// if (isArray == null) {
|
|
||||||
boolean b = c.isArray();
|
|
||||||
// isArrayCache.put(c, b);
|
|
||||||
return b;
|
|
||||||
// }
|
|
||||||
// return isArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
this.isArrayCache.clear();
|
this.superClass.shutdown();
|
||||||
this.arrayVersionCache.clear();
|
|
||||||
this.superClassesCache.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Subscription[] EMPTY = new Subscription[0];
|
|
||||||
private static Class<?>[] EMPTY2 = new Class<?>[0];
|
|
||||||
|
|
||||||
private StampedLock superSubLock = new StampedLock();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array COPY of the super subscriptions for the specified type.
|
* Returns an array COPY of the super subscriptions for the specified type.
|
||||||
*
|
* <p>
|
||||||
* This ALSO checks to see if the superClass accepts subtypes.
|
* This ALSO checks to see if the superClass accepts subtypes.
|
||||||
*
|
* <p>
|
||||||
* protected by read lock by caller
|
* protected by read lock by caller
|
||||||
*
|
*
|
||||||
* @return CAN NOT RETURN NULL
|
* @return CAN NOT RETURN NULL
|
||||||
*/
|
*/
|
||||||
public final ArrayList<Subscription> getSuperSubscriptions(final Class<?> superType) {
|
public final ArrayList<Subscription> getSuperSubscriptions(final Class<?> clazz) {
|
||||||
// whenever our subscriptions change, this map is cleared.
|
// whenever our subscriptions change, this map is cleared.
|
||||||
final Map<Class<?>, ArrayList<Subscription>> local = this.superClassSubscriptions;
|
final Map<Class<?>, ArrayList<Subscription>> local = this.superClassSubscriptions;
|
||||||
|
|
||||||
ArrayList<Subscription> superSubscriptions = local.get(superType);
|
ArrayList<Subscription> superSubscriptions = local.get(clazz);
|
||||||
|
|
||||||
if (superSubscriptions == null) {
|
if (superSubscriptions == null) {
|
||||||
final Class<?>[] superClasses = getSuperClasses_NL(superType, isArray(superType)); // never returns null, cached response
|
// types was not empty, so getSubscriptions subscriptions for each type and collate them
|
||||||
final int length = superClasses.length;
|
|
||||||
|
|
||||||
// types was not empty, so get subscriptions for each type and collate them
|
|
||||||
final Map<Class<?>, ArrayList<Subscription>> local2 = this.subscriptionsPerMessageSingle;
|
final Map<Class<?>, ArrayList<Subscription>> local2 = this.subscriptionsPerMessageSingle;
|
||||||
Class<?> superClass;
|
|
||||||
|
|
||||||
ArrayList<Subscription> subs;
|
// save the subscriptions
|
||||||
|
final Class<?>[] superClasses = this.superClass.getSuperClasses(clazz, clazz.isArray()); // never returns null, cached response
|
||||||
|
|
||||||
|
Class<?> superClass;
|
||||||
|
ArrayList<Subscription> superSubs;
|
||||||
Subscription sub;
|
Subscription sub;
|
||||||
|
|
||||||
|
final int length = superClasses.length;
|
||||||
|
int superSubLengh;
|
||||||
superSubscriptions = new ArrayList<Subscription>(length);
|
superSubscriptions = new ArrayList<Subscription>(length);
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
superClass = superClasses[i];
|
superClass = superClasses[i];
|
||||||
subs = local2.get(superClass);
|
superSubs = local2.get(superClass);
|
||||||
|
|
||||||
if (subs != null) {
|
if (superSubs != null) {
|
||||||
for (int j=0;j<subs.size();j++) {
|
superSubLengh = superSubs.size();
|
||||||
sub = subs.get(j);
|
for (int j = 0; j < superSubLengh; j++) {
|
||||||
|
sub = superSubs.get(j);
|
||||||
|
|
||||||
if (sub.acceptsSubtypes()) {
|
if (sub.acceptsSubtypes()) {
|
||||||
superSubscriptions.add(sub);
|
superSubscriptions.add(sub);
|
||||||
@ -280,7 +181,7 @@ public class SubscriptionUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
local.put(superType, superSubscriptions);
|
local.put(clazz, superSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return superSubscriptions;
|
return superSubscriptions;
|
||||||
@ -301,7 +202,7 @@ public class SubscriptionUtils {
|
|||||||
// subsPerType = subsPerTypeLeaf.getValue();
|
// subsPerType = subsPerTypeLeaf.getValue();
|
||||||
// } else {
|
// } else {
|
||||||
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
||||||
// subsPerType = subHolderSingle.get();
|
// subsPerType = subHolderSingle.getSubscriptions();
|
||||||
//
|
//
|
||||||
// // cache our subscriptions for super classes, so that their access can be fast!
|
// // cache our subscriptions for super classes, so that their access can be fast!
|
||||||
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(subsPerType, superType1, superType2);
|
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(subsPerType, superType1, superType2);
|
||||||
@ -387,7 +288,7 @@ public class SubscriptionUtils {
|
|||||||
// subsPerType = subsPerTypeLeaf.getValue();
|
// subsPerType = subsPerTypeLeaf.getValue();
|
||||||
// } else {
|
// } else {
|
||||||
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
// SubscriptionHolder subHolderSingle = this.subHolderSingle;
|
||||||
// subsPerType = subHolderSingle.get();
|
// subsPerType = subHolderSingle.getSubscriptions();
|
||||||
//
|
//
|
||||||
// // cache our subscriptions for super classes, so that their access can be fast!
|
// // cache our subscriptions for super classes, so that their access can be fast!
|
||||||
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(subsPerType, superType1, superType2, superType3);
|
// ConcurrentSet<Subscription> putIfAbsent = local.putIfAbsent(subsPerType, superType1, superType2, superType3);
|
@ -1,30 +1,12 @@
|
|||||||
package dorkbox.util.messagebus;
|
package dorkbox.util.messagebus;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.common.*;
|
||||||
|
import dorkbox.util.messagebus.listeners.*;
|
||||||
|
import dorkbox.util.messagebus.messages.*;
|
||||||
|
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.AssertSupport;
|
|
||||||
import dorkbox.util.messagebus.common.ConcurrentExecutor;
|
|
||||||
import dorkbox.util.messagebus.common.ListenerFactory;
|
|
||||||
import dorkbox.util.messagebus.common.SubscriptionValidator;
|
|
||||||
import dorkbox.util.messagebus.common.TestUtil;
|
|
||||||
import dorkbox.util.messagebus.listeners.AbstractMessageListener;
|
|
||||||
import dorkbox.util.messagebus.listeners.ICountableListener;
|
|
||||||
import dorkbox.util.messagebus.listeners.IMessageListener;
|
|
||||||
import dorkbox.util.messagebus.listeners.IMultipartMessageListener;
|
|
||||||
import dorkbox.util.messagebus.listeners.MessagesListener;
|
|
||||||
import dorkbox.util.messagebus.listeners.MultipartMessageListener;
|
|
||||||
import dorkbox.util.messagebus.listeners.Overloading;
|
|
||||||
import dorkbox.util.messagebus.listeners.StandardMessageListener;
|
|
||||||
import dorkbox.util.messagebus.messages.AbstractMessage;
|
|
||||||
import dorkbox.util.messagebus.messages.ICountable;
|
|
||||||
import dorkbox.util.messagebus.messages.IMessage;
|
|
||||||
import dorkbox.util.messagebus.messages.IMultipartMessage;
|
|
||||||
import dorkbox.util.messagebus.messages.MessageTypes;
|
|
||||||
import dorkbox.util.messagebus.messages.MultipartMessage;
|
|
||||||
import dorkbox.util.messagebus.messages.StandardMessage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* Test the subscriptions as generated and organized by the subscription manager. Tests use different sets of listeners
|
* Test the subscriptions as generated and organized by the subscription manager. Tests use different sets of listeners
|
||||||
* and corresponding expected set of subscriptions that should result from subscribing the listeners. The subscriptions
|
* and corresponding expected set of subscriptions that should result from subscribing the listeners. The subscriptions
|
||||||
* are tested for the type of messages they should handle and
|
* are tested for the type of messages they should handle and
|
||||||
@ -36,26 +18,19 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
|
|
||||||
private static final int InstancesPerListener = 5000;
|
private static final int InstancesPerListener = 5000;
|
||||||
|
|
||||||
@Test
|
@Test public void testIMessageListener() {
|
||||||
public void testIMessageListener(){
|
ListenerFactory listeners = listeners(IMessageListener.DefaultListener.class, IMessageListener.DisabledListener.class,
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
IMessageListener.DefaultListener.class,
|
|
||||||
IMessageListener.DisabledListener.class,
|
|
||||||
IMessageListener.NoSubtypesListener.class);
|
IMessageListener.NoSubtypesListener.class);
|
||||||
|
|
||||||
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners).listener(IMessageListener.DefaultListener.class)
|
||||||
.listener(IMessageListener.DefaultListener.class).handles(IMessage.class,
|
.handles(IMessage.class, AbstractMessage.class, IMultipartMessage.class, StandardMessage.class, MessageTypes.class)
|
||||||
AbstractMessage.class, IMultipartMessage.class, StandardMessage.class, MessageTypes.class)
|
|
||||||
.listener(IMessageListener.NoSubtypesListener.class).handles(IMessage.class);
|
.listener(IMessageListener.NoSubtypesListener.class).handles(IMessage.class);
|
||||||
|
|
||||||
runTestWith(listeners, expectedSubscriptions);
|
runTestWith(listeners, expectedSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test public void testAbstractMessageListener() {
|
||||||
public void testAbstractMessageListener(){
|
ListenerFactory listeners = listeners(AbstractMessageListener.DefaultListener.class, AbstractMessageListener.DisabledListener.class,
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
AbstractMessageListener.DefaultListener.class,
|
|
||||||
AbstractMessageListener.DisabledListener.class,
|
|
||||||
AbstractMessageListener.NoSubtypesListener.class);
|
AbstractMessageListener.NoSubtypesListener.class);
|
||||||
|
|
||||||
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
||||||
@ -65,11 +40,8 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
runTestWith(listeners, expectedSubscriptions);
|
runTestWith(listeners, expectedSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test public void testMessagesListener() {
|
||||||
public void testMessagesListener(){
|
ListenerFactory listeners = listeners(MessagesListener.DefaultListener.class, MessagesListener.DisabledListener.class,
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
MessagesListener.DefaultListener.class,
|
|
||||||
MessagesListener.DisabledListener.class,
|
|
||||||
MessagesListener.NoSubtypesListener.class);
|
MessagesListener.NoSubtypesListener.class);
|
||||||
|
|
||||||
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
||||||
@ -79,10 +51,8 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
runTestWith(listeners, expectedSubscriptions);
|
runTestWith(listeners, expectedSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test public void testMultipartMessageListener() {
|
||||||
public void testMultipartMessageListener(){
|
ListenerFactory listeners = listeners(MultipartMessageListener.DefaultListener.class,
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
MultipartMessageListener.DefaultListener.class,
|
|
||||||
MultipartMessageListener.DisabledListener.class,
|
MultipartMessageListener.DisabledListener.class,
|
||||||
MultipartMessageListener.NoSubtypesListener.class);
|
MultipartMessageListener.NoSubtypesListener.class);
|
||||||
|
|
||||||
@ -93,10 +63,8 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
runTestWith(listeners, expectedSubscriptions);
|
runTestWith(listeners, expectedSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test public void testIMultipartMessageListener() {
|
||||||
public void testIMultipartMessageListener(){
|
ListenerFactory listeners = listeners(IMultipartMessageListener.DefaultListener.class,
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
IMultipartMessageListener.DefaultListener.class,
|
|
||||||
IMultipartMessageListener.DisabledListener.class,
|
IMultipartMessageListener.DisabledListener.class,
|
||||||
IMultipartMessageListener.NoSubtypesListener.class);
|
IMultipartMessageListener.NoSubtypesListener.class);
|
||||||
|
|
||||||
@ -107,11 +75,8 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
runTestWith(listeners, expectedSubscriptions);
|
runTestWith(listeners, expectedSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test public void testStandardMessageListener() {
|
||||||
public void testStandardMessageListener(){
|
ListenerFactory listeners = listeners(StandardMessageListener.DefaultListener.class, StandardMessageListener.DisabledListener.class,
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
StandardMessageListener.DefaultListener.class,
|
|
||||||
StandardMessageListener.DisabledListener.class,
|
|
||||||
StandardMessageListener.NoSubtypesListener.class);
|
StandardMessageListener.NoSubtypesListener.class);
|
||||||
|
|
||||||
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
||||||
@ -121,32 +86,27 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
runTestWith(listeners, expectedSubscriptions);
|
runTestWith(listeners, expectedSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test public void testICountableListener() {
|
||||||
public void testICountableListener(){
|
ListenerFactory listeners = listeners(ICountableListener.DefaultListener.class, ICountableListener.DisabledListener.class,
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
ICountableListener.DefaultListener.class,
|
|
||||||
ICountableListener.DisabledListener.class,
|
|
||||||
ICountableListener.NoSubtypesListener.class);
|
ICountableListener.NoSubtypesListener.class);
|
||||||
|
|
||||||
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
||||||
.listener(ICountableListener.DefaultListener.class).handles(ICountable.class)
|
.listener(ICountableListener.DefaultListener.class).handles(ICountable.class)
|
||||||
.listener(ICountableListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class, ICountable.class, StandardMessage.class);
|
.listener(ICountableListener.DefaultListener.class)
|
||||||
|
.handles(MultipartMessage.class, IMultipartMessage.class, ICountable.class, StandardMessage.class);
|
||||||
|
|
||||||
runTestWith(listeners, expectedSubscriptions);
|
runTestWith(listeners, expectedSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test public void testMultipleMessageListeners() {
|
||||||
public void testMultipleMessageListeners(){
|
ListenerFactory listeners = listeners(ICountableListener.DefaultListener.class, ICountableListener.DisabledListener.class,
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
ICountableListener.DefaultListener.class,
|
|
||||||
ICountableListener.DisabledListener.class,
|
|
||||||
IMultipartMessageListener.DefaultListener.class,
|
IMultipartMessageListener.DefaultListener.class,
|
||||||
IMultipartMessageListener.DisabledListener.class,
|
IMultipartMessageListener.DisabledListener.class, MessagesListener.DefaultListener.class,
|
||||||
MessagesListener.DefaultListener.class,
|
|
||||||
MessagesListener.DisabledListener.class);
|
MessagesListener.DisabledListener.class);
|
||||||
|
|
||||||
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
||||||
.listener(ICountableListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class, ICountable.class, StandardMessage.class)
|
.listener(ICountableListener.DefaultListener.class)
|
||||||
|
.handles(MultipartMessage.class, IMultipartMessage.class, ICountable.class, StandardMessage.class)
|
||||||
.listener(IMultipartMessageListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class)
|
.listener(IMultipartMessageListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class)
|
||||||
.listener(MessagesListener.DefaultListener.class).handles(MessageTypes.class);
|
.listener(MessagesListener.DefaultListener.class).handles(MessageTypes.class);
|
||||||
|
|
||||||
@ -154,18 +114,15 @@ public class SubscriptionManagerTest extends AssertSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test public void testOverloadedMessageHandlers() {
|
||||||
public void testOverloadedMessageHandlers(){
|
ListenerFactory listeners = listeners(Overloading.ListenerBase.class, Overloading.ListenerSub.class);
|
||||||
ListenerFactory listeners = listeners(
|
|
||||||
Overloading.ListenerBase.class,
|
|
||||||
Overloading.ListenerSub.class);
|
|
||||||
|
|
||||||
SubscriptionManager subscriptionManager = new SubscriptionManager(1);
|
SubscriptionManager subscriptionManager = new SubscriptionManager(1);
|
||||||
ConcurrentExecutor.runConcurrent(TestUtil.subscriber(subscriptionManager, listeners), 1);
|
ConcurrentExecutor.runConcurrent(TestUtil.subscriber(subscriptionManager, listeners), 1);
|
||||||
|
|
||||||
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners)
|
SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners).listener(Overloading.ListenerBase.class)
|
||||||
.listener(Overloading.ListenerBase.class).handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class)
|
.handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class).listener(Overloading.ListenerSub.class)
|
||||||
.listener(Overloading.ListenerSub.class).handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class, Overloading.TestMessageB.class);
|
.handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class, Overloading.TestMessageB.class);
|
||||||
|
|
||||||
runTestWith(listeners, expectedSubscriptions);
|
runTestWith(listeners, expectedSubscriptions);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package dorkbox.util.messagebus;
|
package dorkbox.util.messagebus;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.annotations.Handler;
|
import dorkbox.util.messagebus.annotations.Handler;
|
||||||
import dorkbox.util.messagebus.annotations.Synchronized;
|
import dorkbox.util.messagebus.annotations.Synchronized;
|
||||||
import dorkbox.util.messagebus.common.MessageBusTest;
|
import dorkbox.util.messagebus.common.MessageBusTest;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Todo: Add javadoc
|
* Todo: Add javadoc
|
||||||
@ -49,7 +48,7 @@ public class SynchronizedHandlerTest extends MessageBusTest {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
public void handleMessage(Object o){
|
public void handleMessage(Object o){
|
||||||
counter.getAndIncrement();
|
counter.getAndIncrement();
|
||||||
// System.err.println(counter.get());
|
// System.err.println(counter.getSubscriptions());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
package dorkbox.util.messagebus.common;
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.SubscriptionManager;
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author bennidi
|
* @author bennidi
|
||||||
* Date: 5/25/13
|
* Date: 5/25/13
|
||||||
*/
|
*/
|
||||||
@ -105,7 +98,6 @@ public class SubscriptionValidator extends AssertSupport{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class Expectation {
|
public class Expectation {
|
||||||
|
|
||||||
private Class listener;
|
private Class listener;
|
||||||
@ -122,6 +114,7 @@ public class SubscriptionValidator extends AssertSupport{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class ValidationEntry {
|
private class ValidationEntry {
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package dorkbox.util.messagebus.common;
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.MultiMBassador;
|
import dorkbox.util.messagebus.MultiMBassador;
|
||||||
import dorkbox.util.messagebus.PubSubSupport;
|
import dorkbox.util.messagebus.PubSubSupport;
|
||||||
import dorkbox.util.messagebus.SubscriptionManager;
|
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Todo: Add javadoc
|
* Todo: Add javadoc
|
||||||
@ -19,8 +19,7 @@ public class TestUtil {
|
|||||||
public static Runnable subscriber(final SubscriptionManager manager, final ListenerFactory listeners) {
|
public static Runnable subscriber(final SubscriptionManager manager, final ListenerFactory listeners) {
|
||||||
final Iterator source = listeners.iterator();
|
final Iterator source = listeners.iterator();
|
||||||
return new Runnable() {
|
return new Runnable() {
|
||||||
@Override
|
@Override public void run() {
|
||||||
public void run() {
|
|
||||||
Object next;
|
Object next;
|
||||||
while ((next = source.next()) != null) {
|
while ((next = source.next()) != null) {
|
||||||
manager.subscribe(next);
|
manager.subscribe(next);
|
||||||
@ -32,8 +31,7 @@ public class TestUtil {
|
|||||||
public static Runnable unsubscriber(final SubscriptionManager manager, final ListenerFactory listeners) {
|
public static Runnable unsubscriber(final SubscriptionManager manager, final ListenerFactory listeners) {
|
||||||
final Iterator source = listeners.iterator();
|
final Iterator source = listeners.iterator();
|
||||||
return new Runnable() {
|
return new Runnable() {
|
||||||
@Override
|
@Override public void run() {
|
||||||
public void run() {
|
|
||||||
Object next;
|
Object next;
|
||||||
while ((next = source.next()) != null) {
|
while ((next = source.next()) != null) {
|
||||||
manager.unsubscribe(next);
|
manager.unsubscribe(next);
|
||||||
@ -45,8 +43,7 @@ public class TestUtil {
|
|||||||
public static Runnable subscriber(final PubSubSupport bus, final ListenerFactory listeners) {
|
public static Runnable subscriber(final PubSubSupport bus, final ListenerFactory listeners) {
|
||||||
final Iterator source = listeners.iterator();
|
final Iterator source = listeners.iterator();
|
||||||
return new Runnable() {
|
return new Runnable() {
|
||||||
@Override
|
@Override public void run() {
|
||||||
public void run() {
|
|
||||||
Object next;
|
Object next;
|
||||||
while ((next = source.next()) != null) {
|
while ((next = source.next()) != null) {
|
||||||
bus.subscribe(next);
|
bus.subscribe(next);
|
||||||
@ -58,8 +55,7 @@ public class TestUtil {
|
|||||||
public static Runnable unsubscriber(final PubSubSupport bus, final ListenerFactory listeners) {
|
public static Runnable unsubscriber(final PubSubSupport bus, final ListenerFactory listeners) {
|
||||||
final Iterator source = listeners.iterator();
|
final Iterator source = listeners.iterator();
|
||||||
return new Runnable() {
|
return new Runnable() {
|
||||||
@Override
|
@Override public void run() {
|
||||||
public void run() {
|
|
||||||
Object next;
|
Object next;
|
||||||
while ((next = source.next()) != null) {
|
while ((next = source.next()) != null) {
|
||||||
bus.unsubscribe(next);
|
bus.unsubscribe(next);
|
||||||
@ -81,15 +77,12 @@ 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 : listeners.size();
|
||||||
? partitionStart + partitionSize + 1
|
|
||||||
: 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
|
@Override public void run() {
|
||||||
public void run() {
|
|
||||||
for (Object listener : this.listenerSubset) {
|
for (Object listener : this.listenerSubset) {
|
||||||
bus.subscribe(listener);
|
bus.subscribe(listener);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user