WIP - polishing

This commit is contained in:
nathan 2016-02-06 16:15:16 +01:00
parent 3226d8ae20
commit d31e27eadd
55 changed files with 1915 additions and 5005 deletions

View File

@ -1,7 +1,7 @@
MessageBus
==========
The MessageBus is a fork from MBassador, and it is a high-performance, very-low GC, custom distribution that supports method signature
The MessageBus is a fork from MBassador, and it is a high-performance, zero GC, custom distribution that supports method signature
parameters > 1 & varity arguments.
*Many* features from the original MBassador have been removed, specifically the ONLY things to remain for a handler are
@ -10,7 +10,8 @@ parameters > 1 & varity arguments.
Additionally, the bus *must* explicitly be started now (because of errorhandling when starting the disruptor),
ie: ```.start()```, and conversely ```.shutdown()``` is necessary to shutdown the disruptor/thread pool. During the distillation
process, the API has changed, and the only way to publish now is to actually call ```bus.publish()``` or ```bus.publishAsync()```.
process, the API has changed, and the only way to publish now is to actually call ```bus.publish()``` or ```bus.publishAsync()```, of
note, the asynchronous publication of messages is not in a guaranteed order.
The largest change however, is the ability to publish N-number of objects. A single object (or all-matching-types, when more than one)

View File

@ -109,12 +109,6 @@ public interface IMessageBus extends PubSubSupport {
* Will publish to listeners with this exact message signature, as well as listeners that match the super class types signatures.
*/
ExactWithSuperTypes,
/**
* Will publish to listeners with this exact message signature, as well as listeners that match the super class types signatures.
* and to listeners that have matching varity arguments. (ie: a listener that matches Object[], will accept messages of type Object)
*/
ExactWithSuperTypesAndVarity,
}
/**

View File

@ -20,8 +20,9 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.publication.Publisher;
import dorkbox.util.messagebus.publication.PublisherExact;
import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypes;
import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypesAndVarity;
import dorkbox.util.messagebus.subscription.SubscriptionManager;
import dorkbox.util.messagebus.synchrony.AsyncABQ;
import dorkbox.util.messagebus.synchrony.AsyncABQ_noGc;
import dorkbox.util.messagebus.synchrony.AsyncDisruptor;
import dorkbox.util.messagebus.synchrony.Sync;
import dorkbox.util.messagebus.synchrony.Synchrony;
@ -37,6 +38,36 @@ import dorkbox.util.messagebus.synchrony.Synchrony;
*/
public
class MessageBus implements IMessageBus {
public static boolean useDisruptorForAsyncPublish = true;
public static boolean useAsmForDispatch = true;
public static boolean useNoGarbageVersionOfABQ = true;
static {
// check to see if we can use ASM for method access (it's a LOT faster than reflection). By default, we use ASM.
if (useAsmForDispatch) {
// only bother checking if we are different that the defaults
try {
Class.forName("com.esotericsoftware.reflectasm.MethodAccess");
} catch (Exception e) {
useAsmForDispatch = false;
}
}
// check to see if we can use the disruptor for publication (otherwise, we use native java). The disruptor is a lot faster, but
// not available on all platforms/JRE's because of it's use of UNSAFE.
if (useDisruptorForAsyncPublish) {
// only bother checking if we are different that the defaults
try {
Class.forName("com.lmax.disruptor.RingBuffer");
} catch (Exception e) {
useDisruptorForAsyncPublish = false;
}
}
}
private final ErrorHandlingSupport errorHandler;
private final SubscriptionManager subscriptionManager;
@ -47,7 +78,7 @@ class MessageBus implements IMessageBus {
/**
* By default, will permit subTypes and Varity Argument matching, and will use half of CPUs available for dispatching async messages
* By default, will permit subType matching, and will use half of CPUs available for dispatching async messages
*/
public
MessageBus() {
@ -55,7 +86,7 @@ class MessageBus implements IMessageBus {
}
/**
* By default, will permit subTypes and Varity Argument matching
* By default, will permit subType matching
*
* @param numberOfThreads how many threads to use for dispatching async messages
*/
@ -74,6 +105,7 @@ class MessageBus implements IMessageBus {
MessageBus(final PublishMode publishMode) {
this(publishMode, Runtime.getRuntime().availableProcessors());
}
/**
* @param publishMode Specifies which publishMode to operate the publication of messages.
* @param numberOfThreads how many threads to use for dispatching async messages
@ -96,17 +128,26 @@ class MessageBus implements IMessageBus {
break;
case ExactWithSuperTypes:
default:
publisher = new PublisherExactWithSuperTypes(errorHandler, subscriptionManager);
break;
case ExactWithSuperTypesAndVarity:
default:
publisher = new PublisherExactWithSuperTypesAndVarity(errorHandler, subscriptionManager);
}
syncPublication = new Sync();
// asyncPublication = new PubAsync(numberOfThreads, errorHandler, publisher, syncPublication);
asyncPublication = new AsyncDisruptor(numberOfThreads, errorHandler, publisher, syncPublication);
// the disruptor is preferred, but if it cannot be loaded -- we want to try to continue working, hence the use of ArrayBlockingQueue
if (useDisruptorForAsyncPublish) {
asyncPublication = new AsyncDisruptor(numberOfThreads, errorHandler, publisher, syncPublication);
} else {
if (useNoGarbageVersionOfABQ) {
// no garbage is created, but this is slow (but faster than other messagebus implementations)
asyncPublication = new AsyncABQ_noGc(numberOfThreads, errorHandler, publisher, syncPublication);
}
else {
// garbage is created, but this is fast
asyncPublication = new AsyncABQ(numberOfThreads, errorHandler, publisher, syncPublication);
}
}
}
/**
@ -160,12 +201,6 @@ class MessageBus implements IMessageBus {
publisher.publish(syncPublication, message1, message2, message3);
}
@Override
public
void publish(final Object[] messages) {
publisher.publish(syncPublication, messages);
}
@Override
public
void publishAsync(final Object message) {
@ -204,22 +239,6 @@ class MessageBus implements IMessageBus {
// }
}
@Override
public
void publishAsync(final Object[] messages) {
// if (messages != null) {
// try {
// this.dispatchQueue.transfer(messages, MessageType.ARRAY);
// } catch (Exception e) {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Error while adding an asynchronous message").setCause(e).setPublishedObject(messages));
// }
// }
// else {
// throw new NullPointerException("Message cannot be null.");
// }
}
@Override
public final
boolean hasPendingMessages() {

View File

@ -1,776 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus;
import dorkbox.util.messagebus.publication.disruptor.MessageType;
import org.jctools.queues.MpmcArrayQueue;
import org.jctools.util.UnsafeAccess;
import java.util.Random;
import static org.jctools.util.UnsafeRefArrayAccess.lpElement;
import static org.jctools.util.UnsafeRefArrayAccess.spElement;
@SuppressWarnings("Duplicates")
final
class MpmcMultiTransferArrayQueue extends MpmcArrayQueue<Object> {
private static final int TYPE_EMPTY = 0;
private static final int TYPE_CONSUMER = 1;
private static final int TYPE_PRODUCER = 2;
/**
* Is it multi-processor?
*/
private static final boolean MP = Runtime.getRuntime().availableProcessors() > 1;
private static final int INPROGRESS_SPINS = MP ? 32 : 0;
private static final int PRODUCER_CAS_FAIL_SPINS = MP ? 512 : 0;
private static final int CONSUMER_CAS_FAIL_SPINS = MP ? 512 : 0;
/**
* The number of times to spin before blocking in timed waits.
* The value is empirically derived -- it works well across a
* variety of processors and OSes. Empirically, the best value
* seems not to vary with number of CPUs (beyond 2) so is just
* a constant.
*/
private static final int PARK_TIMED_SPINS = MP ? 32 : 0;
/**
* The number of times to spin before blocking in untimed waits.
* This is greater than timed value because untimed waits spin
* faster since they don't need to check times on each spin.
*/
private static final int PARK_UNTIMED_SPINS = PARK_TIMED_SPINS * 16;
private static final ThreadLocal<Object> nodeThreadLocal = new ThreadLocal<Object>() {
@Override
protected
Object initialValue() {
return new MultiNode();
}
};
private static final ThreadLocal<Random> randomThreadLocal = new ThreadLocal<Random>() {
@Override
protected
Random initialValue() {
return new Random();
}
};
private final int consumerCount;
public
MpmcMultiTransferArrayQueue(final int consumerCount) {
super(1024); // must be power of 2
this.consumerCount = consumerCount;
}
/**
* PRODUCER method
* <p>
* Place an item on the queue, and wait as long as necessary for a corresponding consumer to take it.
* <p>
* The item can be a single object (MessageType.ONE), or an array object (MessageType.ARRAY)
* </p>
*/
public
void transfer(final Object item, final int messageType) throws InterruptedException {
// local load of field to avoid repeated loads after volatile reads
final long mask = this.mask;
final Object[] buffer = this.buffer;
final long[] sBuffer = this.sequenceBuffer;
long consumerIndex;
long producerIndex;
int lastType;
while (true) {
consumerIndex = lvConsumerIndex();
producerIndex = lvProducerIndex();
if (consumerIndex == producerIndex) {
lastType = TYPE_EMPTY;
}
else {
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
if (previousElement == null) {
// the last producer hasn't finished setting the object yet
busySpin(INPROGRESS_SPINS);
continue;
}
lastType = MultiNode.lpType(previousElement);
}
if (lastType != TYPE_CONSUMER) {
// TYPE_EMPTY, TYPE_PRODUCER
// empty or same mode = push+park onto queue
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
final long delta = seq - producerIndex;
if (delta == 0) {
// this is expected if we see this first time around
final long newProducerIndex = producerIndex + 1;
if (casProducerIndex(producerIndex, newProducerIndex)) {
// Successful CAS: full barrier
final Thread myThread = Thread.currentThread();
final Object node = nodeThreadLocal.get();
MultiNode.spType(node, TYPE_PRODUCER);
MultiNode.spThread(node, myThread);
MultiNode.spMessageType(node, messageType);
MultiNode.spItem1(node, item);
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(producerIndex, mask);
spElement(buffer, offset, node);
// increment sequence by 1, the value expected by consumer
// (seeing this value from a producer will lead to retry 2)
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
park(node, myThread);
return;
}
else {
busySpin(PRODUCER_CAS_FAIL_SPINS);
}
}
}
else {
// TYPE_CONSUMER
// complimentary mode = pop+unpark off queue
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
final long newConsumerIndex = consumerIndex + 1;
final long delta = seq - newConsumerIndex;
if (delta == 0) {
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
// Successful CAS: full barrier
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(consumerIndex, mask);
final Object e = lpElement(buffer, offset);
spElement(buffer, offset, null);
// Move sequence ahead by capacity, preparing it for next offer
// (seeing this value from a consumer will lead to retry 2)
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
MultiNode.spMessageType(e, messageType);
MultiNode.spItem1(e, item);
unpark(e); // StoreStore
return;
}
else {
busySpin(CONSUMER_CAS_FAIL_SPINS);
}
}
}
}
}
/**
* PRODUCER method
* <p/>
* Place two items in the same slot on the queue, and wait as long as necessary for a corresponding consumer to take it.
*/
public
void transfer(final Object item1, final Object item2) throws InterruptedException {
// local load of field to avoid repeated loads after volatile reads
final long mask = this.mask;
final Object[] buffer = this.buffer;
final long[] sBuffer = this.sequenceBuffer;
long consumerIndex;
long producerIndex;
int lastType;
while (true) {
consumerIndex = lvConsumerIndex();
producerIndex = lvProducerIndex();
if (consumerIndex == producerIndex) {
lastType = TYPE_EMPTY;
}
else {
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
if (previousElement == null) {
// the last producer hasn't finished setting the object yet
busySpin(INPROGRESS_SPINS);
continue;
}
lastType = MultiNode.lpType(previousElement);
}
if (lastType != TYPE_CONSUMER) {
// TYPE_EMPTY, TYPE_PRODUCER
// empty or same mode = push+park onto queue
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
final long delta = seq - producerIndex;
if (delta == 0) {
// this is expected if we see this first time around
final long newProducerIndex = producerIndex + 1;
if (casProducerIndex(producerIndex, newProducerIndex)) {
// Successful CAS: full barrier
final Thread myThread = Thread.currentThread();
final Object node = nodeThreadLocal.get();
MultiNode.spType(node, TYPE_PRODUCER);
MultiNode.spThread(node, myThread);
MultiNode.spMessageType(node, MessageType.TWO);
MultiNode.spItem1(node, item1);
MultiNode.spItem2(node, item2);
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(producerIndex, mask);
spElement(buffer, offset, node);
// increment sequence by 1, the value expected by consumer
// (seeing this value from a producer will lead to retry 2)
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
park(node, myThread);
return;
}
else {
busySpin(PRODUCER_CAS_FAIL_SPINS);
}
}
}
else {
// TYPE_CONSUMER
// complimentary mode = pop+unpark off queue
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
final long newConsumerIndex = consumerIndex + 1;
final long delta = seq - newConsumerIndex;
if (delta == 0) {
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
// Successful CAS: full barrier
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(consumerIndex, mask);
final Object e = lpElement(buffer, offset);
spElement(buffer, offset, null);
// Move sequence ahead by capacity, preparing it for next offer
// (seeing this value from a consumer will lead to retry 2)
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
MultiNode.spMessageType(e, MessageType.TWO);
MultiNode.spItem1(e, item1);
MultiNode.spItem2(e, item2);
unpark(e); // StoreStore
return;
}
else {
busySpin(CONSUMER_CAS_FAIL_SPINS);
}
}
}
}
}
/**
* PRODUCER method
* <p/>
* Place three items in the same slot on the queue, and wait as long as necessary for a corresponding consumer to take it.
*/
public
void transfer(final Object item1, final Object item2, final Object item3) throws InterruptedException {
// local load of field to avoid repeated loads after volatile reads
final long mask = this.mask;
final Object[] buffer = this.buffer;
final long[] sBuffer = this.sequenceBuffer;
long consumerIndex;
long producerIndex;
int lastType;
while (true) {
consumerIndex = lvConsumerIndex();
producerIndex = lvProducerIndex();
if (consumerIndex == producerIndex) {
lastType = TYPE_EMPTY;
}
else {
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
if (previousElement == null) {
// the last producer hasn't finished setting the object yet
busySpin(INPROGRESS_SPINS);
continue;
}
lastType = MultiNode.lpType(previousElement);
}
if (lastType != TYPE_CONSUMER) {
// TYPE_EMPTY, TYPE_PRODUCER
// empty or same mode = push+park onto queue
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
final long delta = seq - producerIndex;
if (delta == 0) {
// this is expected if we see this first time around
final long newProducerIndex = producerIndex + 1;
if (casProducerIndex(producerIndex, newProducerIndex)) {
// Successful CAS: full barrier
final Thread myThread = Thread.currentThread();
final Object node = nodeThreadLocal.get();
MultiNode.spType(node, TYPE_PRODUCER);
MultiNode.spThread(node, myThread);
MultiNode.spMessageType(node, MessageType.THREE);
MultiNode.spItem1(node, item1);
MultiNode.spItem2(node, item2);
MultiNode.spItem3(node, item3);
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(producerIndex, mask);
spElement(buffer, offset, node);
// increment sequence by 1, the value expected by consumer
// (seeing this value from a producer will lead to retry 2)
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
park(node, myThread);
return;
}
else {
busySpin(PRODUCER_CAS_FAIL_SPINS);
}
}
}
else {
// TYPE_CONSUMER
// complimentary mode = pop+unpark off queue
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
final long newConsumerIndex = consumerIndex + 1;
final long delta = seq - newConsumerIndex;
if (delta == 0) {
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
// Successful CAS: full barrier
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(consumerIndex, mask);
final Object e = lpElement(buffer, offset);
spElement(buffer, offset, null);
// Move sequence ahead by capacity, preparing it for next offer
// (seeing this value from a consumer will lead to retry 2)
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
MultiNode.spMessageType(e, MessageType.THREE);
MultiNode.spItem1(e, item1);
MultiNode.spItem2(e, item2);
MultiNode.spItem3(e, item3);
unpark(e); // StoreStore
return;
}
else {
busySpin(CONSUMER_CAS_FAIL_SPINS);
}
}
}
}
}
/**
* CONSUMER
* <p/>
* Remove an item from the queue. If there are no items on the queue, wait for a producer to place an item on the queue. This will
* as long as necessary.
* <p/>
* This method does not depend on thread-local for node information, and so is more efficient.
* <p/>
* Also, the node used by this method will contain the data.
*/
public
void take(final MultiNode node) throws InterruptedException {
// local load of field to avoid repeated loads after volatile reads
final long mask = this.mask;
final Object[] buffer = this.buffer;
final long[] sBuffer = this.sequenceBuffer;
long consumerIndex;
long producerIndex;
int lastType;
while (true) {
consumerIndex = lvConsumerIndex();
producerIndex = lvProducerIndex();
if (consumerIndex == producerIndex) {
lastType = TYPE_EMPTY;
}
else {
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
if (previousElement == null) {
// the last producer hasn't finished setting the object yet
busySpin(INPROGRESS_SPINS);
continue;
}
lastType = MultiNode.lpType(previousElement);
}
if (lastType != TYPE_PRODUCER) {
// TYPE_EMPTY, TYPE_CONSUMER
// empty or same mode = push+park onto queue
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
final long delta = seq - producerIndex;
if (delta == 0) {
// this is expected if we see this first time around
final long newProducerIndex = producerIndex + 1;
if (casProducerIndex(producerIndex, newProducerIndex)) {
// Successful CAS: full barrier
final Thread myThread = Thread.currentThread();
// final Object node = nodeThreadLocal.publish();
MultiNode.spType(node, TYPE_CONSUMER);
MultiNode.spThread(node, myThread);
// The unpark thread sets our contents
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(producerIndex, mask);
spElement(buffer, offset, node);
// increment sequence by 1, the value expected by consumer
// (seeing this value from a producer will lead to retry 2)
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
park(node, myThread);
return;
}
else {
busySpin(PRODUCER_CAS_FAIL_SPINS);
}
}
}
else {
// TYPE_PRODUCER
// complimentary mode = pop+unpark off queue
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
final long newConsumerIndex = consumerIndex + 1;
final long delta = seq - newConsumerIndex;
if (delta == 0) {
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
// Successful CAS: full barrier
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(consumerIndex, mask);
final Object e = lpElement(buffer, offset);
spElement(buffer, offset, null);
// Move sequence ahead by capacity, preparing it for next offer
// (seeing this value from a consumer will lead to retry 2)
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
MultiNode.spMessageType(node, MultiNode.lvMessageType(e)); // LoadLoad
MultiNode.spItem1(node, MultiNode.lpItem1(e));
MultiNode.spItem2(node, MultiNode.lpItem2(e));
MultiNode.spItem3(node, MultiNode.lpItem3(e));
unpark(e);
return;
}
else {
busySpin(CONSUMER_CAS_FAIL_SPINS);
}
}
}
}
}
// modification of super implementation, as to include a small busySpin on contention
@Override
public
boolean offer(Object item) {
// local load of field to avoid repeated loads after volatile reads
final long mask = this.mask;
final long capacity = mask + 1;
final Object[] buffer = this.buffer;
final long[] sBuffer = this.sequenceBuffer;
long producerIndex;
long pSeqOffset;
long consumerIndex = Long.MAX_VALUE;// start with bogus value, hope we don't need it
while (true) {
producerIndex = lvProducerIndex(); // LoadLoad
pSeqOffset = calcSequenceOffset(producerIndex, mask);
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
final long delta = seq - producerIndex;
if (delta == 0) {
// this is expected if we see this first time around
final long newProducerIndex = producerIndex + 1;
if (casProducerIndex(producerIndex, newProducerIndex)) {
// Successful CAS: full barrier
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(producerIndex, mask);
spElement(buffer, offset, item);
// increment sequence by 1, the value expected by consumer
// (seeing this value from a producer will lead to retry 2)
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
return true;
}
else {
busySpin(PRODUCER_CAS_FAIL_SPINS);
}
// failed cas, retry 1
}
else if (delta < 0 && // poll has not moved this value forward
producerIndex - capacity <= consumerIndex && // test against cached cIndex
producerIndex - capacity <= (consumerIndex = lvConsumerIndex())) { // test against latest cIndex
// Extra check required to ensure [Queue.offer == false iff queue is full]
return false;
}
// another producer has moved the sequence by one, retry 2
}
}
// modification of super implementation, as to include a small busySpin on contention
@Override
public
Object poll() {
// local load of field to avoid repeated loads after volatile reads
final long mask = this.mask;
final Object[] buffer = this.buffer;
final long[] sBuffer = this.sequenceBuffer;
long consumerIndex;
long cSeqOffset;
long producerIndex = -1; // start with bogus value, hope we don't need it
while (true) {
consumerIndex = lvConsumerIndex(); // LoadLoad
cSeqOffset = calcSequenceOffset(consumerIndex, mask);
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
final long newConsumerIndex = consumerIndex + 1;
final long delta = seq - newConsumerIndex;
if (delta == 0) {
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
// Successful CAS: full barrier
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(consumerIndex, mask);
final Object e = lpElement(buffer, offset);
spElement(buffer, offset, null);
// Move sequence ahead by capacity, preparing it for next offer
// (seeing this value from a consumer will lead to retry 2)
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
return e;
}
else {
busySpin(CONSUMER_CAS_FAIL_SPINS);
}
// failed cas, retry 1
}
else if (delta < 0 && // slot has not been moved by producer
consumerIndex >= producerIndex && // test against cached pIndex
consumerIndex == (producerIndex = lvProducerIndex())) { // update pIndex if we must
// strict empty check, this ensures [Queue.poll() == null iff isEmpty()]
return null;
}
// another consumer beat us and moved sequence ahead, retry 2
}
}
@Override
public
boolean isEmpty() {
// Order matters!
// Loading consumer before producer allows for producer increments after consumer index is read.
// This ensures this method is conservative in it's estimate. Note that as this is an MPMC there is
// nothing we can do to make this an exact method.
return lvConsumerIndex() == lvProducerIndex();
}
@Override
public
Object peek() {
// local load of field to avoid repeated loads after volatile reads
final long mask = this.mask;
final Object[] buffer = this.buffer;
long currConsumerIndex;
Object e;
do {
currConsumerIndex = lvConsumerIndex();
// other consumers may have grabbed the element, or queue might be empty
e = lpElement(buffer, calcElementOffset(currConsumerIndex, mask));
// only return null if queue is empty
} while (e == null && currConsumerIndex != lvProducerIndex());
return e;
}
@Override
public
int size() {
/*
* It is possible for a thread to be interrupted or reschedule between the read of the producer and
* consumer indices, therefore protection is required to ensure size is within valid range. In the
* event of concurrent polls/offers to this method the size is OVER estimated as we read consumer
* index BEFORE the producer index.
*/
long after = lvConsumerIndex();
while (true) {
final long before = after;
final long currentProducerIndex = lvProducerIndex();
after = lvConsumerIndex();
if (before == after) {
//noinspection NumericCastThatLosesPrecision
return (int) (currentProducerIndex - after);
}
}
}
public
boolean hasPendingMessages() {
// local load of field to avoid repeated loads after volatile reads
final long mask = this.mask;
final Object[] buffer = this.buffer;
long consumerIndex;
long producerIndex;
while (true) {
consumerIndex = lvConsumerIndex();
producerIndex = lvProducerIndex();
if (consumerIndex == producerIndex) {
return true;
}
else {
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
if (previousElement == null) {
// the last producer hasn't finished setting the object yet
busySpin(INPROGRESS_SPINS);
continue;
}
return MultiNode.lpType(previousElement) != TYPE_CONSUMER || consumerIndex + this.consumerCount != producerIndex;
}
}
}
private static
void busySpin(int spins) {
for (; ; ) {
if (spins > 0) {
--spins;
}
else {
return;
}
}
}
@SuppressWarnings("null")
private
void park(final Object node, final Thread myThread) throws InterruptedException {
int spins = -1; // initialized after first item and cancel checks
Random randomYields = null; // bound if needed
for (; ; ) {
if (MultiNode.lvThread(node) == null) {
return;
}
else if (myThread.isInterrupted()) {
throw new InterruptedException();
}
else if (spins < 0) {
spins = PARK_UNTIMED_SPINS;
randomYields = randomThreadLocal.get();
}
else if (spins > 0) {
if (randomYields.nextInt(1024) == 0) {
UnsafeAccess.UNSAFE.park(false, 1L);
}
--spins;
}
else {
// park can return for NO REASON (must check for thread values)
UnsafeAccess.UNSAFE.park(false, 0L);
}
}
}
private
void unpark(Object node) {
final Object thread = MultiNode.lpThread(node);
MultiNode.soThread(node, null); // StoreStore
UnsafeAccess.UNSAFE.unpark(thread);
}
}

View File

@ -1,169 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus;
import dorkbox.util.messagebus.publication.disruptor.MessageType;
import org.jctools.util.UnsafeAccess;
abstract class ColdItems {
private int type = 0;
private int messageType = MessageType.ONE;
private Object item1 = null;
private Object item2 = null;
private Object item3 = null;
}
abstract class Pad0 extends ColdItems {
@SuppressWarnings("unused")
volatile long y0, y1, y2, y4, y5, y6 = 7L;
}
abstract class HotItem1 extends Pad0 {
private Thread thread;
}
public
class MultiNode extends HotItem1 {
private static final long TYPE;
private static final long MESSAGETYPE;
private static final long ITEM1;
private static final long ITEM2;
private static final long ITEM3;
private static final long THREAD;
static {
try {
TYPE = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("type"));
MESSAGETYPE = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("messageType"));
ITEM1 = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("item1"));
ITEM2 = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("item2"));
ITEM3 = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("item3"));
THREAD = UnsafeAccess.UNSAFE.objectFieldOffset(HotItem1.class.getDeclaredField("thread"));
// now make sure we can access UNSAFE
MultiNode node = new MultiNode();
Object o = new Object();
spItem1(node, o);
Object lpItem1 = lpItem1(node);
spItem1(node, null);
if (lpItem1 != o) {
throw new Exception("Cannot access unsafe fields");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static
void spMessageType(Object node, int messageType) {
UnsafeAccess.UNSAFE.putInt(node, MESSAGETYPE, messageType);
}
static
void spItem1(Object node, Object item) {
UnsafeAccess.UNSAFE.putObject(node, ITEM1, item);
}
static
void spItem2(Object node, Object item) {
UnsafeAccess.UNSAFE.putObject(node, ITEM2, item);
}
static
void spItem3(Object node, Object item) {
UnsafeAccess.UNSAFE.putObject(node, ITEM3, item);
}
// only used by the single transfer(item) method. Not used by the multi-transfer(*, *, *, *) method
static
void soItem1(Object node, Object item) {
UnsafeAccess.UNSAFE.putOrderedObject(node, ITEM1, item);
}
// only used by the single take() method. Not used by the void take(node)
static
Object lvItem1(Object node) {
return UnsafeAccess.UNSAFE.getObjectVolatile(node, ITEM1);
}
static
Object lpMessageType(Object node) {
return UnsafeAccess.UNSAFE.getObject(node, MESSAGETYPE);
}
/**
* Must call lvMessageType() BEFORE lpItem*() is called, because this ensures a LoadLoad for the data occurs.
*/
public static
int lvMessageType(Object node) {
return UnsafeAccess.UNSAFE.getIntVolatile(node, MESSAGETYPE);
}
public static
Object lpItem1(Object node) {
return UnsafeAccess.UNSAFE.getObject(node, ITEM1);
}
public static
Object lpItem2(Object node) {
return UnsafeAccess.UNSAFE.getObject(node, ITEM2);
}
public static
Object lpItem3(Object node) {
return UnsafeAccess.UNSAFE.getObject(node, ITEM3);
}
//////////////
static
void spType(Object node, int type) {
UnsafeAccess.UNSAFE.putInt(node, TYPE, type);
}
static
int lpType(Object node) {
return UnsafeAccess.UNSAFE.getInt(node, TYPE);
}
///////////
static
void spThread(Object node, Object thread) {
UnsafeAccess.UNSAFE.putObject(node, THREAD, thread);
}
static
void soThread(Object node, Object thread) {
UnsafeAccess.UNSAFE.putOrderedObject(node, THREAD, thread);
}
static
Object lpThread(Object node) {
return UnsafeAccess.UNSAFE.getObject(node, THREAD);
}
static
Object lvThread(Object node) {
return UnsafeAccess.UNSAFE.getObjectVolatile(node, THREAD);
}
// post-padding
@SuppressWarnings("unused")
volatile long z0, z1, z2, z4, z5, z6 = 7L;
public MultiNode() {
}
}

View File

@ -90,14 +90,6 @@ public interface PubSubSupport {
*/
void publish(Object message1, Object message2, Object message3);
/**
* Synchronously publish <b>AN ARRAY</b> of messages to all registered listeners (that match the signature). This
* includes listeners defined for super types of the given message type, provided they are not configured
* to reject valid subtypes. The call returns when all matching handlers of all registered listeners have
* been notified (invoked) of the message.
*/
void publish(Object[] message);
/**
* Publish the message asynchronously to all registered listeners (that match the signature). This includes
* listeners defined for super types of the given message type, provided they are not configured to reject
@ -136,17 +128,4 @@ public interface PubSubSupport {
* return.
*/
void publishAsync(Object message1, Object message2, Object message3);
/**
* Publish <b>AN ARRAY</b> of messages asynchronously to all registered listeners (that match the signature). This
* includes listeners defined for super types of the given message type, provided they are not configured to
* reject valid subtypes. The call returns when all matching handlers of all registered listeners have been
* notified (invoked) of the message.
* <p>
* <p>
* The behavior of this method depends on availability of workers. If all workers are busy, then this method
* will block until there is an available worker. If workers are available, then this method will immediately
* return.
*/
void publishAsync(Object[] messages);
}

View File

@ -54,14 +54,6 @@ import java.lang.annotation.*;
public
@interface Handler {
/**
* Define whether or not the handler accepts variable arguments it declares in its signature.
* VarArg "acceptance" means that the handler, handle(String... s), will accept a publication
* of ("s"), ("s", "s"), or (String[3]{"s", "s", "s"}). By default, handle(String... s) will
* only handle publications that are exactly an array (String[3]{"s"})
*/
boolean acceptVarargs() default false;
/**
* Define whether or not the handler accepts sub types of the message type it declares in its
* signature.

View File

@ -1,40 +0,0 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package dorkbox.util.messagebus.annotations;
import java.lang.annotation.*;
/**
* This annotation is meant to carry configuration that is shared among all instances of the annotated
* listener. Supported configurations are:
* <p/>
* Reference type: The bus will use either strong or weak references to its registered listeners,
* depending on which reference type (@see References) is set
*
* @author bennidi
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Inherited
public
@interface Listener {}

View File

@ -1,252 +0,0 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package dorkbox.util.messagebus.common;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.StampedLock;
/**
* 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
* structure. Remove operations can affect any running iterator such that a removed element that has not yet
* been reached by the iterator will not appear in that iterator anymore.
*
* @author bennidi
* Date: 2/12/12
*/
public abstract
class AbstractConcurrentSet<T> implements Set<T> {
private static final AtomicLong id = new AtomicLong();
private final transient long ID = id.getAndIncrement();
// Internal state
protected final StampedLock lock = new StampedLock();
private final Map<T, ISetEntry<T>> entries; // maintain a map of entries for O(log n) lookup
public volatile Entry<T> head; // reference to the first element
volatile long z0, z1, z2, z4, z5, z6 = 7L;
protected
AbstractConcurrentSet(Map<T, ISetEntry<T>> entries) {
this.entries = entries;
}
protected abstract
Entry<T> createEntry(T value, Entry<T> next);
@Override
public
boolean add(T element) {
if (element == null) {
return false;
}
boolean changed;
final StampedLock lock = this.lock;
long stamp = lock.readLock();
if (this.entries.containsKey(element)) {
lock.unlockRead(stamp);
return false;
}
long origStamp = stamp;
if ((stamp = lock.tryConvertToWriteLock(stamp)) == 0) {
lock.unlockRead(origStamp);
stamp = lock.writeLock();
}
changed = insert(element);
lock.unlock(stamp);
return changed;
}
@Override
public
boolean contains(Object element) {
final StampedLock lock = this.lock;
long stamp = lock.readLock();
ISetEntry<T> entry = this.entries.get(element);
lock.unlockRead(stamp);
return entry != null && entry.getValue() != null;
}
private
boolean insert(T element) {
if (!this.entries.containsKey(element)) {
this.head = createEntry(element, this.head);
this.entries.put(element, this.head);
return true;
}
return false;
}
@Override
public
int size() {
return this.entries.size();
}
@Override
public
boolean isEmpty() {
return this.head == null;
}
@Override
public
boolean addAll(Collection<? extends T> elements) {
StampedLock lock = this.lock;
boolean changed = false;
long stamp = lock.writeLock();
try {
for (T element : elements) {
if (element != null) {
changed |= insert(element);
}
}
} finally {
lock.unlockWrite(stamp);
}
return changed;
}
/**
* @return TRUE if the element was successfully removed
*/
@Override
public
boolean remove(Object element) {
StampedLock lock = this.lock;
long stamp = lock.readLock();
ISetEntry<T> entry = this.entries.get(element);
lock.unlockRead(stamp);
if (entry == null || entry.getValue() == null) {
return false; // fast exit
}
else {
stamp = lock.writeLock();
try {
if (entry != this.head) {
entry.remove();
}
else {
// if it was second, now it's first
this.head = this.head.next();
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
}
this.entries.remove(element);
return true;
} finally {
lock.unlockWrite(stamp);
}
}
}
@Override
public
Object[] toArray() {
return this.entries.entrySet().toArray();
}
@Override
public
<T2> T2[] toArray(T2[] a) {
return this.entries.entrySet().toArray(a);
}
@Override
public
boolean containsAll(Collection<?> c) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public
boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public
boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public
void clear() {
StampedLock lock = this.lock;
long stamp = lock.writeLock();
this.head = null;
this.entries.clear();
lock.unlockWrite(stamp);
}
@Override
public
int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (this.ID ^ this.ID >>> 32);
return result;
}
@Override
public
boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
@SuppressWarnings("rawtypes")
AbstractConcurrentSet other = (AbstractConcurrentSet) obj;
if (this.ID != other.ID) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,215 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.common;
import com.esotericsoftware.kryo.util.IdentityMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* Simple tree structure that is a map that contains a chain of keys to publish to a value.
*
*
* This Tree store "message classes" as the key, and a unique object as the "value". This map is NEVER cleared (shutdown clears it), and
* the "value" object is used to store/lookup in another map
*
* This data structure is used to keep track of multi-messages - where there is more that one parameter for publish().
*
*
* @author dorkbox, llc
* Date: 2/2/15
*/
public class ClassTree<KEY> {
public static int INITIAL_SIZE = 4;
public static float LOAD_FACTOR = 0.8F;
private static
final ThreadLocal<IdentityMap> keyCache = new ThreadLocal<IdentityMap>() {
@Override
protected
IdentityMap initialValue() {
return new IdentityMap(INITIAL_SIZE, LOAD_FACTOR);
}
};
private static
final ThreadLocal<MultiClass> valueCache = new ThreadLocal<MultiClass>();
private AtomicReference<Object> children = new AtomicReference<Object>();
private AtomicReference<MultiClass> value = new AtomicReference<MultiClass>();
private AtomicInteger valueId = new AtomicInteger(Integer.MIN_VALUE);
@SuppressWarnings("unchecked")
public static <KEY> IdentityMap<KEY, ClassTree<KEY>> cast(Object o) {
return (IdentityMap<KEY, ClassTree<KEY>>) o;
}
public
ClassTree() {
}
public final void clear() {
// gc handles the rest
this.children.set(null);
}
public final
MultiClass get(KEY key) {
if (key == null) {
throw new NullPointerException("keys");
}
ClassTree<KEY> leaf = getOrCreateLeaf(key);
return getOrCreateValue(leaf);
}
public final
MultiClass get(KEY key1, KEY key2) {
if (key1 == null || key2 == null) {
throw new NullPointerException("keys");
}
// have to put value into our children
ClassTree<KEY> leaf = getOrCreateLeaf(key1);
leaf = leaf.getOrCreateLeaf(key2);
return getOrCreateValue(leaf);
}
public final
MultiClass get(KEY key1, KEY key2, KEY key3) {
if (key1 == null || key2 == null || key3 == null) {
throw new NullPointerException("keys");
}
// have to put value into our children
ClassTree<KEY> leaf = getOrCreateLeaf(key1);
leaf = leaf.getOrCreateLeaf(key2);
leaf = leaf.getOrCreateLeaf(key3);
return getOrCreateValue(leaf);
}
public final
MultiClass get(KEY... keys) {
if (keys == null) {
throw new NullPointerException("keys");
}
int length = keys.length;
// have to put value into our children
ClassTree<KEY> leaf = getOrCreateLeaf(keys[0]);
for (int i=1;i<length;i++) {
leaf = leaf.getOrCreateLeaf(keys[i]);
}
return getOrCreateValue(leaf);
}
/**
* creates a child (if necessary) in an atomic way. The tree returned will either be the current one, or a new one.
*
* @param key the key for the new child
* @return
*/
@SuppressWarnings("unchecked")
private
ClassTree<KEY> getOrCreateLeaf(KEY key) {
if (key == null) {
return null;
}
// create the children, then insert a tree @ KEY location inside children
final Object cached = keyCache.get();
final Object checked = children.get();
IdentityMap<KEY, ClassTree<KEY>> kids;
// create the children, if necessary
if (checked == null) {
final boolean success = children.compareAndSet(null, cached);
if (success) {
keyCache.set(new IdentityMap(INITIAL_SIZE, LOAD_FACTOR));
kids = cast(cached);
}
else {
kids = cast(children.get());
}
}
else {
kids = cast(checked);
}
// create a new tree inside the children, if necessary
ClassTree<KEY> targetTree = kids.get(key);
if (targetTree == null) {
synchronized (this) {
// only one thread can insert into the kids. DCL is safe because we safely publish at the end
targetTree = kids.get(key);
if (targetTree == null) {
targetTree = new ClassTree<KEY>();
kids.put(key, targetTree);
}
}
}
final boolean success = children.compareAndSet(kids, kids); // make sure our kids are what we expect and ALSO publishes them
if (!success) {
throw new RuntimeException("Error setting children in leaf!");
}
return targetTree;
}
/**
* Gets, or creates, in an atomic way, the value for a MapTree
*
* @param leaf where to get/create the value
* @return non-null
*/
private
MultiClass getOrCreateValue(final ClassTree<KEY> leaf) {
MultiClass value = leaf.value.get();
if (value == null) {
MultiClass multiClass = valueCache.get();
if (multiClass == null) {
multiClass = new MultiClass(valueId.getAndIncrement());
}
final boolean success = leaf.value.compareAndSet(null, multiClass);
if (success) {
valueCache.set(new MultiClass(valueId.getAndIncrement()));
return multiClass;
} else {
valueCache.set(multiClass);
return leaf.value.get();
}
}
return value;
}
}

View File

@ -15,38 +15,21 @@
*/
package dorkbox.util.messagebus.common;
abstract class pad0<T> {
volatile long z0, z1, z2, z4, z5, z6 = 7L;
}
// not thread-safe! must be synchronized in enclosing context
public class Entry {
private Object value;
private Entry next;
private Entry prev;
abstract class item1<T> extends pad0<T>{
Entry<T> next;
}
public Entry(Object value, Entry next) {
if (next != null) {
this.next = next;
next.prev = this;
}
abstract class pad1<T> extends item1<T> {
volatile long z0, z1, z2, z4, z5, z6 = 7L;
}
abstract class item2<T> extends pad1<T> {
Entry<T> prev;
}
abstract class pad2<T> extends item2<T> {
volatile long z0, z1, z2, z4, z5, z6 = 7L;
}
public abstract class Entry<T> extends pad2<T> implements ISetEntry<T> {
protected Entry(Entry<T> next) {
this.next = next;
next.prev = this;
this.value = value;
}
protected Entry() {
}
// not thread-safe! must be synchronized in enclosing context
@Override
public void remove() {
if (this.prev != null) {
this.prev.next = this.next;
@ -62,13 +45,16 @@ public abstract class Entry<T> extends pad2<T> implements ISetEntry<T> {
//predecessor = null;
}
@Override
public Entry<T> next() {
public Entry next() {
return this.next;
}
@Override
public void clear() {
this.next = null;
}
public
Object getValue() {
return value;
}
}

View File

@ -1,504 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.common;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* Simple tree structure that is a map that contains a chain of keys to publish to a value.
*
* @author dorkbox, llc
* Date: 2/2/15
*/
public class HashMapTree<KEY, VALUE> {
public static int INITIAL_SIZE = 4;
public static float LOAD_FACTOR = 0.8F;
private static
final ThreadLocal<Object> keyCache = new ThreadLocal<Object>() {
@Override
protected
Object initialValue() {
return new ConcurrentHashMap(INITIAL_SIZE, LOAD_FACTOR, 1);
}
};
private static
final ThreadLocal<Object> valueCache = new ThreadLocal<Object>() {
@Override
protected
Object initialValue() {
return new HashMapTree();
}
};
// Map<KEY, HashMapTree<KEY, VALUE>>
private AtomicReference<Object> children = new AtomicReference<Object>();
private VALUE value;
@SuppressWarnings("unchecked")
public static <KEY, VALUE> ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> cast(Object o) {
return (ConcurrentMap<KEY, HashMapTree<KEY, VALUE>>) o;
}
public HashMapTree() {
}
public final VALUE getValue() {
return this.value;
}
public final void putValue(VALUE value) {
this.value = value;
}
public final void removeValue() {
this.value = null;
}
public final void clear() {
// if (this.children != null) {
// Set<Entry<KEY, HashMapTree<KEY, VALUE>>> entrySet = this.children.entrySet();
// for (Entry<KEY, HashMapTree<KEY, VALUE>> entry : entrySet) {
// entry.getValue().clear();
// }
//
// this.children.clear();
// this.value = null;
// }
}
public final VALUE put(KEY key, VALUE value) {
if (key == null) {
throw new NullPointerException("keys");
}
// have to put value into our children
HashMapTree<KEY, VALUE> leaf = createLeaf(key);
VALUE prev = leaf.value;
leaf.value = value;
return prev;
}
public final VALUE put(VALUE value, KEY key1, KEY key2) {
if (key1 == null || key2 == null) {
throw new NullPointerException("keys");
}
// have to put value into our children
HashMapTree<KEY, VALUE> leaf = createLeaf(key1);
leaf = leaf.createLeaf(key2);
VALUE prev = leaf.value;
leaf.value = value;
return prev;
}
public final VALUE put(VALUE value, KEY key1, KEY key2, KEY key3) {
if (key1 == null || key2 == null || key3 == null) {
throw new NullPointerException("keys");
}
// have to put value into our children
HashMapTree<KEY, VALUE> leaf = createLeaf(key1);
leaf = leaf.createLeaf(key2);
leaf = leaf.createLeaf(key3);
VALUE prev = leaf.value;
leaf.value = value;
return prev;
}
public final VALUE put(VALUE value, KEY... keys) {
if (keys == null) {
throw new NullPointerException("keys");
}
int length = keys.length;
// have to put value into our children
HashMapTree<KEY, VALUE> leaf = createLeaf(keys[0]);
for (int i=1;i<length;i++) {
leaf = leaf.createLeaf(keys[i]);
}
VALUE prev = leaf.value;
leaf.value = value;
return prev;
}
public final HashMapTree<KEY, VALUE> createLeaf(KEY... keys) {
if (keys == null) {
return this;
}
int length = keys.length;
// have to put value into our children
HashMapTree<KEY, VALUE> leaf = createLeaf(keys[0]);
for (int i=1;i<length;i++) {
leaf = leaf.createLeaf(keys[i]);
}
return leaf;
}
/**
* creates a child (if necessary) in an atomic way. The tree returned will either be the current one, or a new one.
*
* @param key the key for the new child
* @return
*/
@SuppressWarnings("unchecked")
private
HashMapTree<KEY, VALUE> createLeaf(KEY key) {
if (key == null) {
return null;
}
final Object cached = keyCache.get();
final Object checked = children.get();
ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> kids;
if (checked == null) {
final boolean success = children.compareAndSet(null, cached);
if (success) {
keyCache.set(new ConcurrentHashMap(INITIAL_SIZE, LOAD_FACTOR, 1));
kids = cast(cached);
}
else {
kids = cast(children.get());
}
}
else {
kids = cast(checked);
}
final Object cached2 = valueCache.get();
final HashMapTree<KEY, VALUE> tree = kids.putIfAbsent(key, (HashMapTree<KEY, VALUE>) cached2);
if (tree == null) {
// was absent
valueCache.set(new HashMapTree());
return (HashMapTree<KEY, VALUE>) cached2;
}
else {
return tree;
}
}
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
public final VALUE get(KEY key) {
if (key == null) {
return null;
}
HashMapTree<KEY, VALUE> objectTree;
// publish value from our children
objectTree = getLeaf(key);
if (objectTree == null) {
return null;
}
return objectTree.value;
}
public final VALUE get(KEY key1, KEY key2) {
HashMapTree<KEY, VALUE> tree;
// publish value from our children
tree = getLeaf(key1); // protected by lock
if (tree != null) {
tree = tree.getLeaf(key2);
}
if (tree == null) {
return null;
}
return tree.value;
}
public final VALUE get(KEY key1, KEY key2, KEY key3) {
HashMapTree<KEY, VALUE> tree;
// publish value from our children
tree = getLeaf(key1);
if (tree != null) {
tree = tree.getLeaf(key2);
}
if (tree != null) {
tree = tree.getLeaf(key3);
}
if (tree == null) {
return null;
}
return tree.value;
}
@SuppressWarnings("unchecked")
public final VALUE get(KEY... keys) {
HashMapTree<KEY, VALUE> tree;
// publish value from our children
tree = getLeaf(keys[0]);
int size = keys.length;
for (int i=1;i<size;i++) {
if (tree != null) {
tree = tree.getLeaf(keys[i]);
} else {
return null;
}
}
if (tree == null) {
return null;
}
return tree.value;
}
public final HashMapTree<KEY, VALUE> getLeaf(KEY key) {
if (key == null) {
return null;
}
HashMapTree<KEY, VALUE> tree;
if (this.children == null) {
tree = null;
} else {
final ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> o = cast(this.children.get());
tree = o.get(key);
}
return tree;
}
public final HashMapTree<KEY, VALUE> getLeaf(KEY key1, KEY key2) {
HashMapTree<KEY, VALUE> tree;
// publish value from our children
tree = getLeaf(key1);
if (tree != null) {
tree = tree.getLeaf(key2);
}
return tree;
}
public final HashMapTree<KEY, VALUE> getLeaf(KEY key1, KEY key2, KEY key3) {
HashMapTree<KEY, VALUE> tree;
// publish value from our children
tree = getLeaf(key1);
if (tree != null) {
tree = tree.getLeaf(key2);
}
if (tree != null) {
tree = tree.getLeaf(key3);
}
return tree;
}
@SuppressWarnings("unchecked")
public final HashMapTree<KEY, VALUE> getLeaf(KEY... keys) {
int size = keys.length;
if (size == 0) {
return null;
}
HashMapTree<KEY, VALUE> tree;
// publish value from our children
tree = getLeaf(keys[0]);
for (int i=1;i<size;i++) {
if (tree != null) {
tree = tree.getLeaf(keys[i]);
} else {
return null;
}
}
return tree;
}
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
/**
* This <b>IS NOT</b> safe to call outside of root.remove(...)
* <p>
* Removes a branch from the tree, and cleans up, if necessary
*/
public final void remove(KEY key) {
if (key != null) {
removeLeaf(key);
}
}
/**
* This <b>IS NOT</b> safe to call outside of root.remove(...)
* <p>
* Removes a branch from the tree, and cleans up, if necessary
*/
public final void remove(KEY key1, KEY key2) {
if (key1 == null || key2 == null) {
return;
}
HashMapTree<KEY, VALUE> leaf;
if (this.children != null) {
leaf = getLeaf(key1);
if (leaf != null) {
leaf.removeLeaf(key2);
final ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> o = cast(this.children.get());
o.remove(key1);
if (o.isEmpty()) {
this.children.compareAndSet(o, null);
}
}
}
}
/**
* This <b>IS NOT</b> safe to call outside of root.remove(...)
* <p>
* Removes a branch from the tree, and cleans up, if necessary
*/
public final void remove(KEY key1, KEY key2, KEY key3) {
if (key1 == null || key2 == null) {
return;
}
// HashMapTree<KEY, VALUE> leaf;
// if (this.children != null) {
// leaf = getLeaf(key1);
//
// if (leaf != null) {
// leaf.remove(key2, key3);
// this.children.remove(key1);
//
// if (this.children.isEmpty()) {
// this.children = null;
// }
// }
// }
}
/**
* This <b>IS NOT</b> safe to call outside of root.remove(...)
* <p>
* Removes a branch from the tree, and cleans up, if necessary
*/
@SuppressWarnings("unchecked")
public final void remove(KEY... keys) {
if (keys == null) {
return;
}
removeLeaf(0, keys);
}
/**
* Removes a branch from the tree, and cleans up, if necessary
*/
private void removeLeaf(KEY key) {
if (key != null) {
final ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> kids = cast(this.children.get());
if (kids != null) {
kids.remove(key);
if (kids.isEmpty()) {
this.children.compareAndSet(kids, null);
}
}
}
}
// keys CANNOT be null here!
private void removeLeaf(int index, KEY[] keys) {
if (index == keys.length) {
// we have reached the leaf to remove!
this.value = null;
this.children = null;
} else {
final ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> kids = cast(this.children.get());
if (kids != null) {
// HashMapTree<KEY, VALUE> leaf = this.children.get(keys[index]);
// if (leaf != null) {
// leaf.removeLeaf(index + 1, keys);
// if (leaf.children == null && leaf.value == null) {
// this.children.remove(keys[index]);
// }
//
// if (this.children.isEmpty()) {
// this.children = null;
// }
// }
}
}
}
}

View File

@ -1,41 +0,0 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package dorkbox.util.messagebus.common;
/**
* Todo: Add javadoc
*
* @author bennidi
* Date: 3/29/13
*/
public interface ISetEntry<T> {
T getValue();
// not thread-safe! must be synchronized in enclosing context
void remove();
ISetEntry<T> next();
void clear();
}

View File

@ -37,14 +37,12 @@
*/
package dorkbox.util.messagebus.common;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.util.messagebus.annotations.Handler;
import dorkbox.util.messagebus.annotations.Synchronized;
import dorkbox.util.messagebus.utils.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains
@ -104,38 +102,24 @@ class MessageHandler {
return finalMethods.toArray(new MessageHandler[0]);
}
private final MethodAccess handler;
private final int methodIndex;
private final Method method;
private final Class<?>[] handledMessages;
private final boolean acceptsSubtypes;
private final Class<?> varArgClass;
private final boolean isSynchronized;
public
MessageHandler(Method handler, Handler handlerConfig) {
super();
if (handler == null) {
throw new IllegalArgumentException("The message handler configuration may not be null");
private
MessageHandler(final Method method, final Handler config) {
if (method == null) {
throw new IllegalArgumentException("The message method configuration may not be null");
}
Class<?>[] handledMessages = handler.getParameterTypes();
this.handler = MethodAccess.get(handler.getDeclaringClass());
this.methodIndex = this.handler.getIndex(handler.getName(), handledMessages);
this.acceptsSubtypes = handlerConfig.acceptSubtypes();
this.isSynchronized = ReflectionUtils.getAnnotation(handler, Synchronized.class) != null;
this.handledMessages = handledMessages;
if (handledMessages.length == 1 && handledMessages[0].isArray() && handlerConfig.acceptVarargs()) {
this.varArgClass = handledMessages[0].getComponentType();
}
else {
this.varArgClass = null;
}
this.method = method;
this.acceptsSubtypes = config.acceptSubtypes();
this.handledMessages = method.getParameterTypes();
this.isSynchronized = ReflectionUtils.getAnnotation(method, Synchronized.class) != null;
}
public final
@ -143,14 +127,11 @@ class MessageHandler {
return this.isSynchronized;
}
public final
MethodAccess getHandler() {
return this.handler;
}
public final
int getMethodIndex() {
return this.methodIndex;
Method getMethod() {
return this.method;
}
public final
@ -158,31 +139,15 @@ class MessageHandler {
return this.handledMessages;
}
public final
Class<?> getVarArgClass() {
return this.varArgClass;
}
public final
boolean acceptsSubtypes() {
return this.acceptsSubtypes;
}
public final
boolean acceptsVarArgs() {
return this.varArgClass != null;
}
@Override
public final
int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (this.acceptsSubtypes ? 1231 : 1237);
result = prime * result + Arrays.hashCode(this.handledMessages);
result = prime * result + (this.handler == null ? 0 : this.handler.hashCode());
result = prime * result + (this.isSynchronized ? 1231 : 1237);
return result;
return this.method.hashCode();
}
@Override
@ -197,24 +162,8 @@ class MessageHandler {
if (getClass() != obj.getClass()) {
return false;
}
MessageHandler other = (MessageHandler) obj;
if (this.acceptsSubtypes != other.acceptsSubtypes) {
return false;
}
if (!Arrays.equals(this.handledMessages, other.handledMessages)) {
return false;
}
if (this.handler == null) {
if (other.handler != null) {
return false;
}
}
else if (!this.handler.equals(other.handler)) {
return false;
}
if (this.isSynchronized != other.isSynchronized) {
return false;
}
return true;
return this.method.equals(other.method);
}
}

View File

@ -0,0 +1,50 @@
package dorkbox.util.messagebus.common;
/**
*
*/
public
class MultiClass implements Comparable<MultiClass> {
private final int value;
public
MultiClass(int value) {
this.value = value;
}
@Override
public
int compareTo(final MultiClass o) {
if (value < o.value) {
return -1;
}
else if (value == o.value) {
return 0;
}
else {
return 1;
}
}
@Override
public
boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final MultiClass that = (MultiClass) o;
return value == that.value;
}
@Override
public
int hashCode() {
return value;
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.common.thread;
package dorkbox.util.messagebus.common;
import java.lang.management.RuntimeMXBean;
import java.util.List;
@ -68,7 +68,7 @@ class NamedThreadFactory implements ThreadFactory {
else {
try {
value = Integer.parseInt(stackSize.substring(4));
} catch (Exception e) {
} catch (Exception ignored) {
}
}

View File

@ -1,138 +0,0 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.common;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* This implementation uses strong references to the elements.
* <p/>
*
* @author bennidi
* Date: 2/12/12
*/
public
class StrongConcurrentSet<T> extends AbstractConcurrentSet<T> {
public
StrongConcurrentSet() {
this(16, 0.75f);
}
public
StrongConcurrentSet(int size, float loadFactor) {
this(new HashMap<T, ISetEntry<T>>(size, loadFactor));
}
public
StrongConcurrentSet(Map<T, ISetEntry<T>> entries) {
super(entries);
}
@Override
public
Iterator<T> iterator() {
return new Iterator<T>() {
private ISetEntry<T> current = StrongConcurrentSet.this.head;
@Override
public
boolean hasNext() {
return this.current != null;
}
@Override
public
T next() {
if (this.current == null) {
return null;
}
else {
T value = this.current.getValue();
this.current = this.current.next();
return value;
}
}
@Override
public
void remove() {
if (this.current == null) {
return;
}
ISetEntry<T> newCurrent = this.current.next();
StrongConcurrentSet.this.remove(this.current.getValue());
this.current = newCurrent;
}
};
}
@Override
protected
Entry<T> createEntry(T value, Entry<T> next) {
return next != null ? new StrongEntry<T>(value, next) : new StrongEntry<T>(value);
}
public static
class StrongEntry<T> extends Entry<T> {
private T value;
private
StrongEntry(T value, Entry<T> next) {
super(next);
this.value = value;
}
private
StrongEntry(T value) {
super();
this.value = value;
}
@Override
public
T getValue() {
return this.value;
}
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.common;
import java.util.concurrent.ConcurrentHashMap;
/**
* This implementation uses strong references to the elements, uses an IdentityHashMap
* <p/>
*
* @author dorkbox
* Date: 2/2/15
*/
public class StrongConcurrentSetV8<T> extends StrongConcurrentSet<T> {
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)
super(new ConcurrentHashMap<T, ISetEntry<T>>(size, loadFactor, 16));
}
public StrongConcurrentSetV8(int size, float loadFactor, int stripeSize) {
// 1 for the stripe size, because that is the max concurrency with our concurrent set (since it uses R/W locks)
super(new ConcurrentHashMap<T, ISetEntry<T>>(size, loadFactor, stripeSize));
}
}

View File

@ -1,157 +0,0 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package dorkbox.util.messagebus.common;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.WeakHashMap;
import java.util.concurrent.locks.StampedLock;
/**
* This implementation uses weak references to the elements. Iterators automatically perform cleanups of
* garbage collected objects during iteration -> no dedicated maintenance operations need to be called or run in background.
*
* @author bennidi
* Date: 2/12/12
*/
public
class WeakConcurrentSet<T> extends AbstractConcurrentSet<T> {
public
WeakConcurrentSet() {
super(new WeakHashMap<T, ISetEntry<T>>());
}
@Override
public
Iterator<T> iterator() {
return new Iterator<T>() {
// the current list element of this iterator
// used to keep track of the iteration process
private ISetEntry<T> current = WeakConcurrentSet.this.head;
// this method will remove all orphaned entries
// until it finds the first entry whose value has not yet been garbage collected
// the method assumes that the current element is already orphaned and will remove it
private
void removeOrphans() {
StampedLock lock = WeakConcurrentSet.this.lock;
long stamp = lock.writeLock();
// final Lock writeLock = WeakConcurrentSet.this.lock.writeLock();
// writeLock.lock();
try {
do {
ISetEntry<T> orphaned = this.current;
this.current = this.current.next();
orphaned.remove();
} while (this.current != null && this.current.getValue() == null);
} finally {
lock.unlockWrite(stamp);
// writeLock.unlock();
}
}
@Override
public
boolean hasNext() {
if (this.current == null) {
return false;
}
if (this.current.getValue() == null) {
// trigger removal of orphan references
// because a null value indicates that the value has been garbage collected
removeOrphans();
return this.current != null; // if any entry is left then it will have a value
}
else {
return true;
}
}
@Override
public
T next() {
if (this.current == null) {
return null;
}
T value = this.current.getValue();
if (value == null) { // auto-removal of orphan references
removeOrphans();
return next();
}
else {
this.current = this.current.next();
return value;
}
}
@Override
public
void remove() {
//throw new UnsupportedOperationException("Explicit removal of set elements is only allowed via the controlling set. Sorry!");
if (this.current == null) {
return;
}
ISetEntry<T> newCurrent = this.current.next();
WeakConcurrentSet.this.remove(this.current.getValue());
this.current = newCurrent;
}
};
}
@Override
protected
Entry<T> createEntry(T value, Entry<T> next) {
return next != null ? new WeakEntry<T>(value, next) : new WeakEntry<T>(value);
}
public static
class WeakEntry<T> extends Entry<T> {
private WeakReference<T> value;
private
WeakEntry(T value, Entry<T> next) {
super(next);
this.value = new WeakReference<T>(value);
}
private
WeakEntry(T value) {
super();
this.value = new WeakReference<T>(value);
}
@Override
public
T getValue() {
return this.value.get();
}
}
}

View File

@ -1,943 +0,0 @@
/*
* Written by Doug Lea and Martin Buchholz with assistance from members of
* JCP JSR-166 Expert Group and released to the public domain, as explained
* at http://creativecommons.org/publicdomain/zero/1.0/
*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.common.thread;
import com.esotericsoftware.kryo.util.IdentityMap;
import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
/**
* An unbounded thread-safe {@linkplain Queue queue} based on linked nodes.
* This queue orders elements FIFO (first-in-first-out).
* The <em>head</em> of the queue is that element that has been on the
* queue the longest time.
* The <em>tail</em> of the queue is that element that has been on the
* queue the shortest time. New elements
* are inserted at the tail of the queue, and the queue retrieval
* operations obtain elements at the head of the queue.
* A {@code ConcurrentLinkedQueue} is an appropriate choice when
* many threads will share access to a common collection.
* Like most other concurrent collection implementations, this class
* does not permit the use of {@code null} elements.
*
* <p>This implementation employs an efficient <em>non-blocking</em>
* algorithm based on one described in <a
* href="http://www.cs.rochester.edu/u/michael/PODC96.html"> Simple,
* Fast, and Practical Non-Blocking and Blocking Concurrent Queue
* Algorithms</a> by Maged M. Michael and Michael L. Scott.
*
* <p>Iterators are <i>weakly consistent</i>, returning elements
* reflecting the state of the queue at some point at or since the
* creation of the iterator. They do <em>not</em> throw {@link
* java.util.ConcurrentModificationException}, and may proceed concurrently
* with other operations. Elements contained in the queue since the creation
* of the iterator will be returned exactly once.
*
* <p>Beware that, unlike in most collections, the {@code size} method
* is <em>NOT</em> a constant-time operation. Because of the
* asynchronous nature of these queues, determining the current number
* of elements requires a traversal of the elements, and so may report
* inaccurate results if this collection is modified during traversal.
* Additionally, the bulk operations {@code addAll},
* {@code removeAll}, {@code retainAll}, {@code containsAll},
* {@code equals}, and {@code toArray} are <em>not</em> guaranteed
* to be performed atomically. For example, an iterator operating
* concurrently with an {@code addAll} operation might view only some
* of the added elements.
*
* <p>This class and its iterator implement all of the <em>optional</em>
* methods of the {@link Queue} and {@link Iterator} interfaces.
*
* <p>Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
* {@code ConcurrentLinkedQueue}
* <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
* actions subsequent to the access or removal of that element from
* the {@code ConcurrentLinkedQueue} in another thread.
*
* <p>This class is a member of the
* <a href="{@docRoot}/../technotes/guides/collections/index.html">
* Java Collections Framework</a>.
*
* @since 1.5
* @author Doug Lea
* @param <E> the type of elements held in this collection
*/
public class ConcurrentLinkedQueue2<E> extends AbstractQueue<E>
implements Queue<E>, java.io.Serializable {
private static final long serialVersionUID = 196745693267521676L;
/*
* This is a modification of the Michael & Scott algorithm,
* adapted for a garbage-collected environment, with support for
* interior node deletion (to support remove(Object)). For
* explanation, read the paper.
*
* Note that like most non-blocking algorithms in this package,
* this implementation relies on the fact that in garbage
* collected systems, there is no possibility of ABA problems due
* to recycled nodes, so there is no need to use "counted
* pointers" or related techniques seen in versions used in
* non-GC'ed settings.
*
* The fundamental invariants are:
* - There is exactly one (last) Node with a null next reference,
* which is CASed when enqueueing. This last Node can be
* reached in O(1) time from tail, but tail is merely an
* optimization - it can always be reached in O(N) time from
* head as well.
* - The elements contained in the queue are the non-null items in
* Nodes that are reachable from head. CASing the item
* reference of a Node to null atomically removes it from the
* queue. Reachability of all elements from head must remain
* true even in the case of concurrent modifications that cause
* head to advance. A dequeued Node may remain in use
* indefinitely due to creation of an Iterator or simply a
* poll() that has lost its time slice.
*
* The above might appear to imply that all Nodes are GC-reachable
* from a predecessor dequeued Node. That would cause two problems:
* - allow a rogue Iterator to cause unbounded memory retention
* - cause cross-generational linking of old Nodes to new Nodes if
* a Node was tenured while live, which generational GCs have a
* hard time dealing with, causing repeated major collections.
* However, only non-deleted Nodes need to be reachable from
* dequeued Nodes, and reachability does not necessarily have to
* be of the kind understood by the GC. We use the trick of
* linking a Node that has just been dequeued to itself. Such a
* self-link implicitly means to advance to head.
*
* Both head and tail are permitted to lag. In fact, failing to
* update them every time one could is a significant optimization
* (fewer CASes). As with LinkedTransferQueue (see the internal
* documentation for that class), we use a slack threshold of two;
* that is, we update head/tail when the current pointer appears
* to be two or more steps away from the first/last node.
*
* Since head and tail are updated concurrently and independently,
* it is possible for tail to lag behind head (why not)?
*
* CASing a Node's item reference to null atomically removes the
* element from the queue. Iterators skip over Nodes with null
* items. Prior implementations of this class had a race between
* poll() and remove(Object) where the same element would appear
* to be successfully removed by two concurrent operations. The
* method remove(Object) also lazily unlinks deleted Nodes, but
* this is merely an optimization.
*
* When constructing a Node (before enqueuing it) we avoid paying
* for a volatile write to item by using Unsafe.putObject instead
* of a normal write. This allows the cost of enqueue to be
* "one-and-a-half" CASes.
*
* Both head and tail may or may not point to a Node with a
* non-null item. If the queue is empty, all items must of course
* be null. Upon creation, both head and tail refer to a dummy
* Node with null item. Both head and tail are only updated using
* CAS, so they never regress, although again this is merely an
* optimization.
*/
private static class Node<E> {
volatile E item;
volatile Node<E> next;
/**
* Constructs a new node. Uses relaxed write because item can
* only be seen after publication via casNext.
*/
Node(E item) {
UNSAFE.putObject(this, itemOffset, item);
}
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long itemOffset;
private static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = Node.class;
itemOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("item"));
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
/**
* A node from which the first live (non-deleted) node (if any)
* can be reached in O(1) time.
* Invariants:
* - all live nodes are reachable from head via succ()
* - head != null
* - (tmp = head).next != tmp || tmp != head
* Non-invariants:
* - head.item may or may not be null.
* - it is permitted for tail to lag behind head, that is, for tail
* to not be reachable from head!
*/
private transient volatile Node<E> head;
/**
* A node from which the last node on list (that is, the unique
* node with node.next == null) can be reached in O(1) time.
* Invariants:
* - the last node is always reachable from tail via succ()
* - tail != null
* Non-invariants:
* - tail.item may or may not be null.
* - it is permitted for tail to lag behind head, that is, for tail
* to not be reachable from head!
* - tail.next may or may not be self-pointing to tail.
*/
private transient volatile Node<E> tail;
/**
* Creates a {@code ConcurrentLinkedQueue} that is initially empty.
*/
public ConcurrentLinkedQueue2() {
head = tail = new Node<E>(null);
}
/**
* Creates a {@code ConcurrentLinkedQueue}
* initially containing the elements of the given collection,
* added in traversal order of the collection's iterator.
*
* @param c the collection of elements to initially contain
* @throws NullPointerException if the specified collection or any
* of its elements are null
*/
public ConcurrentLinkedQueue2(Collection<? extends E> c) {
Node<E> h = null, t = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
// Have to override just to update the javadoc
/**
* Inserts the specified element at the tail of this queue.
* As the queue is unbounded, this method will never throw
* {@link IllegalStateException} or return {@code false}.
*
* @return {@code true} (as specified by {@link Collection#add})
* @throws NullPointerException if the specified element is null
*/
public boolean add(E e) {
return offer(e);
}
/**
* Tries to CAS head to p. If successful, repoint old head to itself
* as sentinel for succ(), below.
*/
final void updateHead(Node<E> h, Node<E> p) {
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
/**
* Returns the successor of p, or the head node if p.next has been
* linked to self, which will only be true if traversing with a
* stale pointer that is now off the list.
*/
final Node<E> succ(Node<E> p) {
Node<E> next = p.next;
return (p == next) ? head : next;
}
// can ONLY be touched by a single reader/writer (for add/offer/remove/poll/etc)
private final IdentityMap<Object, Node<E>> quickLookup = new IdentityMap<Object, Node<E>>(32);
/**
* Inserts the specified element at the tail of this queue.
* As the queue is unbounded, this method will never return {@code false}.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
quickLookup.put(e, newNode);
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
// Successful CAS is the linearization point
// for item to be removed from this queue.
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
quickLookup.remove(item);
return item;
}
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
public E peek() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null || (q = p.next) == null) {
updateHead(h, p);
return item;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
/**
* Returns the first live (non-deleted) node on list, or null if none.
* This is yet another variant of poll/peek; here returning the
* first node, not element. We could make peek() a wrapper around
* first(), but that would cost an extra volatile read of item,
* and the need to add a retry loop to deal with the possibility
* of losing a race to a concurrent poll().
*/
Node<E> first() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
boolean hasItem = (p.item != null);
if (hasItem || (q = p.next) == null) {
updateHead(h, p);
return hasItem ? p : null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
/**
* Returns {@code true} if this queue contains no elements.
*
* @return {@code true} if this queue contains no elements
*/
public boolean isEmpty() {
return first() == null;
}
/**
* Returns the number of elements in this queue. If this queue
* contains more than {@code Integer.MAX_VALUE} elements, returns
* {@code Integer.MAX_VALUE}.
*
* <p>Beware that, unlike in most collections, this method is
* <em>NOT</em> a constant-time operation. Because of the
* asynchronous nature of these queues, determining the current
* number of elements requires an O(n) traversal.
* Additionally, if elements are added or removed during execution
* of this method, the returned result may be inaccurate. Thus,
* this method is typically not very useful in concurrent
* applications.
*
* @return the number of elements in this queue
*/
public int size() {
int count = 0;
for (Node<E> p = first(); p != null; p = succ(p))
if (p.item != null)
// Collection.size() spec says to max out
if (++count == Integer.MAX_VALUE)
break;
return count;
}
/**
* Returns {@code true} if this queue contains the specified element.
* More formally, returns {@code true} if and only if this queue contains
* at least one element {@code e} such that {@code o.equals(e)}.
*
* @param o object to be checked for containment in this queue
* @return {@code true} if this queue contains the specified element
*/
public boolean contains(Object o) {
if (o == null) return false;
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.item;
if (item != null && o.equals(item))
return true;
}
return false;
}
/**
* Removes a single instance of the specified element from this queue,
* if it is present. More formally, removes an element {@code e} such
* that {@code o.equals(e)}, if this queue contains one or more such
* elements.
* Returns {@code true} if this queue contained the specified element
* (or equivalently, if this queue changed as a result of the call).
*
* @param o element to be removed from this queue, if present
* @return {@code true} if this queue changed as a result of the call
*/
public boolean remove(Object o) {
if (o == null) return false;
Node<E> pred = null;
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.item;
if (item != null &&
o.equals(item) &&
p.casItem(item, null)) {
Node<E> next = succ(p);
if (pred != null && next != null)
pred.casNext(p, next);
quickLookup.remove(o);
return true;
}
pred = p;
}
return false;
}
/**
* Appends all of the elements in the specified collection to the end of
* this queue, in the order that they are returned by the specified
* collection's iterator. Attempts to {@code addAll} of a queue to
* itself result in {@code IllegalArgumentException}.
*
* @param c the elements to be inserted into this queue
* @return {@code true} if this queue changed as a result of the call
* @throws NullPointerException if the specified collection or any
* of its elements are null
* @throws IllegalArgumentException if the collection is this queue
*/
public boolean addAll(Collection<? extends E> c) {
if (c == this)
// As historically specified in AbstractQueue#addAll
throw new IllegalArgumentException();
// Copy c into a private chain of Nodes
Node<E> beginningOfTheEnd = null, last = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (beginningOfTheEnd == null)
beginningOfTheEnd = last = newNode;
else {
last.lazySetNext(newNode);
last = newNode;
}
}
if (beginningOfTheEnd == null)
return false;
// Atomically append the chain at the tail of this collection
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, beginningOfTheEnd)) {
// Successful CAS is the linearization point
// for all elements to be added to this queue.
if (!casTail(t, last)) {
// Try a little harder to update tail,
// since we may be adding many elements.
t = tail;
if (last.next == null)
casTail(t, last);
}
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
/**
* Returns an array containing all of the elements in this queue, in
* proper sequence.
*
* <p>The returned array will be "safe" in that no references to it are
* maintained by this queue. (In other words, this method must allocate
* a new array). The caller is thus free to modify the returned array.
*
* <p>This method acts as bridge between array-based and collection-based
* APIs.
*
* @return an array containing all of the elements in this queue
*/
public Object[] toArray() {
// Use ArrayList to deal with resizing.
ArrayList<E> al = new ArrayList<E>();
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.item;
if (item != null)
al.add(item);
}
return al.toArray();
}
/**
* Returns an array containing all of the elements in this queue, in
* proper sequence; the runtime type of the returned array is that of
* the specified array. If the queue fits in the specified array, it
* is returned therein. Otherwise, a new array is allocated with the
* runtime type of the specified array and the size of this queue.
*
* <p>If this queue fits in the specified array with room to spare
* (i.e., the array has more elements than this queue), the element in
* the array immediately following the end of the queue is set to
* {@code null}.
*
* <p>Like the {@link #toArray()} method, this method acts as bridge between
* array-based and collection-based APIs. Further, this method allows
* precise control over the runtime type of the output array, and may,
* under certain circumstances, be used to save allocation costs.
*
* <p>Suppose {@code x} is a queue known to contain only strings.
* The following code can be used to dump the queue into a newly
* allocated array of {@code String}:
*
* <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
*
* @param a the array into which the elements of the queue are to
* be stored, if it is big enough; otherwise, a new array of the
* same runtime type is allocated for this purpose
* @return an array containing all of the elements in this queue
* @throws ArrayStoreException if the runtime type of the specified array
* is not a supertype of the runtime type of every element in
* this queue
* @throws NullPointerException if the specified array is null
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
// try to use sent-in array
int k = 0;
Node<E> p;
for (p = first(); p != null && k < a.length; p = succ(p)) {
E item = p.item;
if (item != null)
a[k++] = (T)item;
}
if (p == null) {
if (k < a.length)
a[k] = null;
return a;
}
// If won't fit, use ArrayList version
ArrayList<E> al = new ArrayList<E>();
for (Node<E> q = first(); q != null; q = succ(q)) {
E item = q.item;
if (item != null)
al.add(item);
}
return al.toArray(a);
}
/**
* Returns an iterator over the elements in this queue in proper sequence.
* The elements will be returned in order from first (head) to last (tail).
*
* <p>The returned iterator is
* <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this queue in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
/**
* Next node to return item for.
*/
private Node<E> nextNode;
/**
* nextItem holds on to item fields because once we claim
* that an element exists in hasNext(), we must return it in
* the following next() call even if it was in the process of
* being removed when hasNext() was called.
*/
private E nextItem;
/**
* Node of the last returned item, to support remove.
*/
private Node<E> lastRet;
Itr() {
advance();
}
/**
* Moves to next valid node and returns item to return for
* next(), or null if no such.
*/
private E advance() {
lastRet = nextNode;
E x = nextItem;
Node<E> pred, p;
if (nextNode == null) {
p = first();
pred = null;
} else {
pred = nextNode;
p = succ(nextNode);
}
for (;;) {
if (p == null) {
nextNode = null;
nextItem = null;
return x;
}
E item = p.item;
if (item != null) {
nextNode = p;
nextItem = item;
return x;
} else {
// skip over nulls
Node<E> next = succ(p);
if (pred != null && next != null)
pred.casNext(p, next);
p = next;
}
}
}
public boolean hasNext() {
return nextNode != null;
}
public E next() {
if (nextNode == null) throw new NoSuchElementException();
return advance();
}
public void remove() {
Node<E> l = lastRet;
if (l == null) throw new IllegalStateException();
// rely on a future traversal to relink.
l.item = null;
lastRet = null;
}
}
/**
* Saves this queue to a stream (that is, serializes it).
*
* @param s the stream
* @throws java.io.IOException if an I/O error occurs
* @serialData All of the elements (each an {@code E}) in
* the proper order, followed by a null
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden stuff
s.defaultWriteObject();
// Write out all elements in the proper order.
for (Node<E> p = first(); p != null; p = succ(p)) {
Object item = p.item;
if (item != null)
s.writeObject(item);
}
// Use trailing null as sentinel
s.writeObject(null);
}
/**
* Reconstitutes this queue from a stream (that is, deserializes it).
* @param s the stream
* @throws ClassNotFoundException if the class of a serialized object
* could not be found
* @throws java.io.IOException if an I/O error occurs
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Read in elements until trailing null sentinel found
Node<E> h = null, t = null;
Object item;
while ((item = s.readObject()) != null) {
@SuppressWarnings("unchecked")
Node<E> newNode = new Node<E>((E) item);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
/** A customized variant of Spliterators.IteratorSpliterator */
static final class CLQSpliterator<E> implements Spliterator<E> {
static final int MAX_BATCH = 1 << 25; // max batch array size;
final ConcurrentLinkedQueue2<E> queue;
Node<E> current; // current node; null until initialized
int batch; // batch size for splits
boolean exhausted; // true when no more nodes
CLQSpliterator(ConcurrentLinkedQueue2<E> queue) {
this.queue = queue;
}
public Spliterator<E> trySplit() {
Node<E> p;
final ConcurrentLinkedQueue2<E> q = this.queue;
int b = batch;
int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1;
if (!exhausted &&
((p = current) != null || (p = q.first()) != null) &&
p.next != null) {
Object[] a = new Object[n];
int i = 0;
do {
if ((a[i] = p.item) != null)
++i;
if (p == (p = p.next))
p = q.first();
} while (p != null && i < n);
if ((current = p) == null)
exhausted = true;
if (i > 0) {
batch = i;
return Spliterators.spliterator
(a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL |
Spliterator.CONCURRENT);
}
}
return null;
}
public void forEachRemaining(Consumer<? super E> action) {
Node<E> p;
if (action == null) throw new NullPointerException();
final ConcurrentLinkedQueue2<E> q = this.queue;
if (!exhausted &&
((p = current) != null || (p = q.first()) != null)) {
exhausted = true;
do {
E e = p.item;
if (p == (p = p.next))
p = q.first();
if (e != null)
action.accept(e);
} while (p != null);
}
}
public boolean tryAdvance(Consumer<? super E> action) {
Node<E> p;
if (action == null) throw new NullPointerException();
final ConcurrentLinkedQueue2<E> q = this.queue;
if (!exhausted &&
((p = current) != null || (p = q.first()) != null)) {
E e;
do {
e = p.item;
if (p == (p = p.next))
p = q.first();
} while (e == null && p != null);
if ((current = p) == null)
exhausted = true;
if (e != null) {
action.accept(e);
return true;
}
}
return false;
}
public long estimateSize() { return Long.MAX_VALUE; }
public int characteristics() {
return Spliterator.ORDERED | Spliterator.NONNULL |
Spliterator.CONCURRENT;
}
}
/**
* Returns a {@link Spliterator} over the elements in this queue.
*
* <p>The returned spliterator is
* <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* <p>The {@code Spliterator} reports {@link Spliterator#CONCURRENT},
* {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}.
*
* @implNote
* The {@code Spliterator} implements {@code trySplit} to permit limited
* parallelism.
*
* @return a {@code Spliterator} over the elements in this queue
* @since 1.8
*/
@Override
public Spliterator<E> spliterator() {
return new CLQSpliterator<E>(this);
}
/**
* Throws NullPointerException if argument is null.
*
* @param v the element
*/
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
private boolean casTail(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
}
private boolean casHead(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long headOffset;
private static final long tailOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentLinkedQueue.class;
headOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("head"));
tailOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("tail"));
} catch (Exception e) {
throw new Error(e);
}
}
}

View File

@ -21,5 +21,4 @@ public interface Publisher {
void publish(final Synchrony synchrony, Object message1);
void publish(final Synchrony synchrony, Object message1, Object message2);
void publish(final Synchrony synchrony, Object message1, Object message2, Object message3);
void publish(final Synchrony synchrony, Object[] messages);
}

View File

@ -44,24 +44,14 @@ class PublisherExact implements Publisher {
// Run subscriptions
if (subscriptions != null) {
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1);
}
synchrony.publish(subscriptions, message1);
}
else {
// Dead Event must EXACTLY MATCH (no subclasses)
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1);
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
synchrony.publish(deadSubscriptions, new DeadMessage(message1));
}
}
} catch (Throwable e) {
@ -78,33 +68,18 @@ class PublisherExact implements Publisher {
final Class<?> messageClass1 = message1.getClass();
final Class<?> messageClass2 = message2.getClass();
// final StampedLock lock = this.lock;
// long stamp = lock.readLock();
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null
// lock.unlockRead(stamp);
// Run subscriptions
if (subscriptions != null) {
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2);
}
synchrony.publish(subscriptions, message1, message2);
}
else {
// Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2);
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2));
}
}
} catch (Throwable e) {
@ -122,33 +97,18 @@ class PublisherExact implements Publisher {
final Class<?> messageClass2 = message2.getClass();
final Class<?> messageClass3 = message3.getClass();
// final StampedLock lock = this.lock;
// long stamp = lock.readLock();
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null
// lock.unlockRead(stamp);
// Run subscriptions
if (subscriptions != null) {
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2, message3);
}
synchrony.publish(subscriptions, message1, message2, message3);
}
else {
// Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2, message3);
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2, message3));
}
}
} catch (Throwable e) {
@ -157,10 +117,4 @@ class PublisherExact implements Publisher {
.setPublishedObject(message1, message2, message3));
}
}
@Override
public
void publish(final Synchrony synchrony, final Object[] messages) {
publish(synchrony, (Object) messages);
}
}

View File

@ -51,9 +51,9 @@ class PublisherExactWithSuperTypes implements Publisher {
synchrony.publish(subscriptions, message1);
}
// Run superClasses
final Subscription[] superSubscriptions = subManager.getSuperSubs(message1Class); // can return null
if (superSubscriptions != null) {
// Run superSubscriptions
final Subscription[] superSubscriptions = subManager.getSuperSubs(message1Class); // NOT return null
if (superSubscriptions.length > 0) {
hasSubs = true;
synchrony.publish(superSubscriptions, message1);
}
@ -80,34 +80,31 @@ class PublisherExactWithSuperTypes implements Publisher {
try {
final Class<?> messageClass1 = message1.getClass();
final Class<?> messageClass2 = message2.getClass();
boolean hasSubs = false;
// final StampedLock lock = this.lock;
// long stamp = lock.readLock();
final Subscription[] subscriptions = subManager.getExactAndSuper(messageClass1, messageClass2); // can return null
// lock.unlockRead(stamp);
// Run subscriptions
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null
if (subscriptions != null) {
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2);
}
hasSubs = true;
synchrony.publish(subscriptions, message1, message2);
}
else {
// Run superSubscriptions
final Subscription[] superSubscriptions = subManager.getSuperSubs(messageClass1, messageClass2); // can return null
if (superSubscriptions != null) {
hasSubs = true;
synchrony.publish(superSubscriptions, message1, message2);
}
// Run dead message subscriptions
if (!hasSubs) {
// Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2);
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2));
}
}
} catch (Throwable e) {
@ -124,36 +121,31 @@ class PublisherExactWithSuperTypes implements Publisher {
final Class<?> messageClass1 = message1.getClass();
final Class<?> messageClass2 = message2.getClass();
final Class<?> messageClass3 = message3.getClass();
boolean hasSubs = false;
// final StampedLock lock = this.lock;
// long stamp = lock.readLock();
final Subscription[] subscriptions = subManager.getExactAndSuper(messageClass1, messageClass2,
messageClass3); // can return null
// lock.unlockRead(stamp);
// Run subscriptions
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null
if (subscriptions != null) {
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2, message3);
}
hasSubs = true;
synchrony.publish(subscriptions, message1, message2, message3);
}
else {
// Run superSubscriptions
final Subscription[] superSubscriptions = subManager.getSuperSubs(messageClass1, messageClass2, messageClass3); // can return null
if (superSubscriptions != null) {
hasSubs = true;
synchrony.publish(superSubscriptions, message1, message2, message3);
}
// Run dead message subscriptions
if (!hasSubs) {
// Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2, message3);
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2, message3));
}
}
} catch (Throwable e) {
@ -162,10 +154,4 @@ class PublisherExactWithSuperTypes implements Publisher {
.setPublishedObject(message1, message2, message3));
}
}
@Override
public
void publish(final Synchrony synchrony, final Object[] messages) {
publish(synchrony, (Object) messages);
}
}

View File

@ -1,344 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.publication;
import dorkbox.util.messagebus.error.DeadMessage;
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.error.PublicationError;
import dorkbox.util.messagebus.subscription.Subscription;
import dorkbox.util.messagebus.subscription.SubscriptionManager;
import dorkbox.util.messagebus.synchrony.Synchrony;
import dorkbox.util.messagebus.utils.VarArgUtils;
import java.lang.reflect.Array;
import java.util.concurrent.atomic.AtomicBoolean;
@SuppressWarnings("Duplicates")
public
class PublisherExactWithSuperTypesAndVarity implements Publisher {
private final ErrorHandlingSupport errorHandler;
private final SubscriptionManager subManager;
private final AtomicBoolean varArgPossibility;
final VarArgUtils varArgUtils;
public
PublisherExactWithSuperTypesAndVarity(final ErrorHandlingSupport errorHandler, final SubscriptionManager subManager) {
this.errorHandler = errorHandler;
this.subManager = subManager;
varArgPossibility = subManager.getVarArgPossibility();
varArgUtils = subManager.getVarArgUtils();
}
@Override
public
void publish(final Synchrony synchrony, final Object message1) {
try {
final Class<?> messageClass = message1.getClass();
final boolean isArray = messageClass.isArray();
Subscription[] subscriptions = subManager.getSubs(messageClass); // can return null
boolean hasSubs = false;
// Run subscriptions
if (subscriptions != null) {
hasSubs = true;
synchrony.publish(subscriptions, message1);
}
subscriptions = subManager.getSuperSubs(messageClass); // can return null
// publish to var arg, only if not already an array (because that would be unnecessary)
if (varArgPossibility.get() && !isArray) {
final Subscription[] varArgSubs = varArgUtils.getVarArgSubscriptions(messageClass, subManager); // CAN NOT RETURN NULL
Subscription sub;
int length = varArgSubs.length;
Object[] asArray = null;
if (length > 0) {
hasSubs = true;
asArray = (Object[]) Array.newInstance(messageClass, 1);
asArray[0] = message1;
synchrony.publish(varArgSubs, asArray);
for (int i = 0; i < length; i++) {
sub = varArgSubs[i];
sub.publish(asArray);
}
}
// now publish array based superClasses (but only if those ALSO accept vararg)
final Subscription[] varArgSuperSubs = varArgUtils.getVarArgSuperSubscriptions(messageClass, subManager); // CAN NOT RETURN NULL
length = varArgSuperSubs.length;
if (length > 0) {
hasSubs = true;
if (asArray == null) {
asArray = (Object[]) Array.newInstance(messageClass, 1);
asArray[0] = message1;
}
for (int i = 0; i < length; i++) {
sub = varArgSuperSubs[i];
sub.publish(asArray);
}
}
}
// only get here if there were no other subscriptions
// Dead Event must EXACTLY MATCH (no subclasses)
if (!hasSubs) {
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class);
if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1);
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
}
}
} catch (Throwable e) {
errorHandler.handlePublicationError(new PublicationError().setMessage("Error during invocation of message handler.")
.setCause(e)
.setPublishedObject(message1));
}
}
@Override
public
void publish(final Synchrony synchrony, final Object message1, final Object message2) {
try {
final Class<?> messageClass1 = message1.getClass();
final Class<?> messageClass2 = message2.getClass();
// final StampedLock lock = this.lock;
// long stamp = lock.readLock();
final Subscription[] subscriptions = subManager.getExactAndSuper(messageClass1, messageClass2); // can return null
// lock.unlockRead(stamp);
boolean hasSubs = false;
// Run subscriptions
if (subscriptions != null) {
hasSubs = true;
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2);
}
}
// publish to var arg, only if not already an array AND we are all of the same type
if (varArgPossibility.get() && !messageClass1.isArray() && !messageClass2.isArray()) {
// vararg can ONLY work if all types are the same
if (messageClass1 == messageClass2) {
// stamp = lock.readLock();
final Subscription[] varArgSubs = varArgUtils.getVarArgSubscriptions(messageClass1, subManager); // can NOT return null
// lock.unlockRead(stamp);
final int length = varArgSubs.length;
if (length > 0) {
hasSubs = true;
Object[] asArray = (Object[]) Array.newInstance(messageClass1, 2);
asArray[0] = message1;
asArray[1] = message2;
Subscription sub;
for (int i = 0; i < length; i++) {
sub = varArgSubs[i];
sub.publish(asArray);
}
}
}
// now publish array based superClasses (but only if those ALSO accept vararg)
// stamp = lock.readLock();
final Subscription[] varArgSuperSubs = varArgUtils.getVarArgSuperSubscriptions(messageClass1, messageClass2, subManager); // CAN NOT RETURN NULL
// lock.unlockRead(stamp);
final int length = varArgSuperSubs.length;
if (length > 0) {
hasSubs = true;
Class<?> arrayType;
Object[] asArray;
Subscription sub;
for (int i = 0; i < length; i++) {
sub = varArgSuperSubs[i];
arrayType = sub.getHandler().getVarArgClass();
asArray = (Object[]) Array.newInstance(arrayType, 2);
asArray[0] = message1;
asArray[1] = message2;
sub.publish(asArray);
}
}
}
if (!hasSubs) {
// Dead Event must EXACTLY MATCH (no subclasses)
// lock.unlockRead(stamp);
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2);
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
}
}
} catch (Throwable e) {
errorHandler.handlePublicationError(new PublicationError().setMessage("Error during invocation of message handler.")
.setCause(e)
.setPublishedObject(message1, message2));
}
}
@Override
public
void publish(final Synchrony synchrony, final Object message1, final Object message2, final Object message3) {
try {
final Class<?> messageClass1 = message1.getClass();
final Class<?> messageClass2 = message2.getClass();
final Class<?> messageClass3 = message3.getClass();
// final StampedLock lock = this.lock;
// long stamp = lock.readLock();
final Subscription[] subs = subManager.getExactAndSuper(messageClass1, messageClass2, messageClass3); // can return null
// lock.unlockRead(stamp);
boolean hasSubs = false;
// Run subscriptions
if (subs != null) {
hasSubs = true;
Subscription sub;
for (int i = 0; i < subs.length; i++) {
sub = subs[i];
sub.publish(message1, message2, message3);
}
}
// publish to var arg, only if not already an array AND we are all of the same type
if (varArgPossibility.get() && !messageClass1.isArray() && !messageClass2.isArray() &&
!messageClass3.isArray()) {
// vararg can ONLY work if all types are the same
if (messageClass1 == messageClass2 && messageClass1 == messageClass3) {
// stamp = lock.readLock();
final Subscription[] varArgSubs = varArgUtils.getVarArgSubscriptions(messageClass1, subManager); // can NOT return null
// lock.unlockRead(stamp);
final int length = varArgSubs.length;
if (length > 0) {
hasSubs = true;
Object[] asArray = (Object[]) Array.newInstance(messageClass1, 3);
asArray[0] = message1;
asArray[1] = message2;
asArray[2] = message3;
Subscription sub;
for (int i = 0; i < length; i++) {
sub = varArgSubs[i];
sub.publish(asArray);
}
}
}
// now publish array based superClasses (but only if those ALSO accept vararg)
// stamp = lock.readLock();
final Subscription[] varArgSuperSubs = varArgUtils.getVarArgSuperSubscriptions(messageClass1, messageClass2, messageClass3,
subManager); // CAN NOT RETURN NULL
// lock.unlockRead(stamp);
final int length = varArgSuperSubs.length;
if (length > 0) {
hasSubs = true;
Class<?> arrayType;
Object[] asArray;
Subscription sub;
for (int i = 0; i < length; i++) {
sub = varArgSuperSubs[i];
arrayType = sub.getHandler().getVarArgClass();
asArray = (Object[]) Array.newInstance(arrayType, 3);
asArray[0] = message1;
asArray[1] = message2;
asArray[2] = message3;
sub.publish(asArray);
}
}
}
if (!hasSubs) {
// Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2, message3);
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
}
}
} catch (Throwable e) {
errorHandler.handlePublicationError(new PublicationError().setMessage("Error during invocation of message handler.")
.setCause(e)
.setPublishedObject(message1, message2, message3));
}
}
@Override
public
void publish(final Synchrony synchrony, final Object[] messages) {
publish(synchrony, (Object) messages);
}
}

View File

@ -1,19 +0,0 @@
package dorkbox.util.messagebus.publication.disruptor;
import com.lmax.disruptor.EventFactory;
/**
* @author dorkbox, llc
* Date: 2/2/15
*/
public class EventBusFactory implements EventFactory<MessageHolder> {
public EventBusFactory() {
}
@Override
public
MessageHolder newInstance() {
return new MessageHolder();
}
}

View File

@ -1,20 +0,0 @@
package dorkbox.util.messagebus.publication.disruptor;
import dorkbox.util.messagebus.subscription.Subscription;
/**
* @author dorkbox, llc Date: 2/2/15
*/
public
class MessageHolder {
public int type = MessageType.ONE;
public Subscription[] subscriptions;
public Object message1 = null;
public Object message2 = null;
public Object message3 = null;
public Object[] messages = null;
public
MessageHolder() {}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.subscription;
import dorkbox.util.messagebus.common.MessageHandler;
/**
* @author dorkbox, llc Date: 2/3/16
*/
public
interface SubMaker {
Subscription create(final Class<?> listenerClass, final MessageHandler handler);
}

View File

@ -1,27 +1,5 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*
* Copyright 2015 dorkbox, llc
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -38,12 +16,8 @@
package dorkbox.util.messagebus.subscription;
import com.esotericsoftware.kryo.util.IdentityMap;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.util.messagebus.common.Entry;
import dorkbox.util.messagebus.common.MessageHandler;
import dorkbox.util.messagebus.dispatch.IHandlerInvocation;
import dorkbox.util.messagebus.dispatch.ReflectiveHandlerInvocation;
import dorkbox.util.messagebus.dispatch.SynchronizedHandlerInvocation;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
@ -59,11 +33,10 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
* This class uses the "single writer principle", so that the subscription are only MODIFIED by a single thread,
* but are READ by X number of threads (in a safe way). This uses object thread visibility/publication to work.
*
* @author bennidi
* @author dorkbox, llc
* Date: 2/2/15
* Date: 2/3/16
*/
public final
public abstract
class Subscription {
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
public final int ID = ID_COUNTER.getAndIncrement();
@ -74,62 +47,53 @@ class Subscription {
// the handler's metadata -> for each handler in a listener, a unique subscription context is created
private final MessageHandler handler;
private final IHandlerInvocation invocation;
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
private static final AtomicReferenceFieldUpdater<Subscription, Entry> headREF =
protected static final AtomicReferenceFieldUpdater<Subscription, Entry> headREF =
AtomicReferenceFieldUpdater.newUpdater(Subscription.class,
Entry.class,
"head");
// This is only touched by a single thread!
private final IdentityMap<Object, Entry> entries; // maintain a map of entries for FAST lookup during unsubscribe.
// this is still inside the single-writer, and can use the same techniques as subscription manager (for thread safe publication)
public volatile Entry head; // reference to the first element
public
protected
Subscription(final Class<?> listenerClass, final MessageHandler handler) {
this.listenerClass = listenerClass;
this.handler = handler;
IHandlerInvocation invocation = new ReflectiveHandlerInvocation();
if (handler.isSynchronized()) {
invocation = new SynchronizedHandlerInvocation(invocation);
}
this.invocation = invocation;
entries = new IdentityMap<>(32, SubscriptionManager.LOAD_FACTOR);
entries = new IdentityMap<Object, Entry>(32, SubscriptionManager.LOAD_FACTOR);
if (handler.acceptsSubtypes()) {
// keep a list of "super-class" messages that access this. This is updated by multiple threads. This is so we know WHAT
// TODO keep a list of "super-class" messages that access this. This is updated by multiple threads. This is so we know WHAT
// super-subscriptions to clear when we sub/unsub
}
}
// called on shutdown for GC purposes
public void clear() {
public final
void clear() {
this.entries.clear();
this.head.clear();
}
// only used in unit tests to verify that the subscription manager is working correctly
public
public final
Class<?> getListenerClass() {
return listenerClass;
}
public
public final
MessageHandler getHandler() {
return handler;
}
public
public final
void subscribe(final Object listener) {
// single writer principle!
Entry head = headREF.get(this);
@ -145,7 +109,7 @@ class Subscription {
/**
* @return TRUE if the element was removed
*/
public
public final
boolean unsubscribe(final Object listener) {
Entry entry = entries.get(listener);
if (entry == null || entry.getValue() == null) {
@ -174,68 +138,38 @@ class Subscription {
/**
* only used in unit tests
*/
public
public final
int size() {
return this.entries.size;
}
public
void publish(final Object message) throws Throwable {
final MethodAccess handler = this.handler.getHandler();
final int handleIndex = this.handler.getMethodIndex();
final IHandlerInvocation invocation = this.invocation;
/**
* @return true if messages were published
*/
public abstract
boolean publish(final Object message) throws Throwable;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
listener = current.getValue();
current = current.next();
/**
* @return true if messages were published
*/
public abstract
boolean publish(final Object message1, final Object message2) throws Throwable;
invocation.invoke(listener, handler, handleIndex, message);
}
}
public
void publish(final Object message1, final Object message2) throws Throwable {
final MethodAccess handler = this.handler.getHandler();
final int handleIndex = this.handler.getMethodIndex();
final IHandlerInvocation invocation = this.invocation;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
listener = current.getValue();
current = current.next();
invocation.invoke(listener, handler, handleIndex, message1, message2);
}
}
public
void publish(final Object message1, final Object message2, final Object message3) throws Throwable {
final MethodAccess handler = this.handler.getHandler();
final int handleIndex = this.handler.getMethodIndex();
final IHandlerInvocation invocation = this.invocation;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
listener = current.getValue();
current = current.next();
invocation.invoke(listener, handler, handleIndex, message1, message2, message3);
}
}
/**
* @return true if messages were published
*/
public abstract
boolean publish(final Object message1, final Object message2, final Object message3) throws Throwable;
@Override
public
public final
int hashCode() {
return this.ID;
}
@Override
public
public final
boolean equals(final Object obj) {
if (this == obj) {
return true;

View File

@ -16,22 +16,23 @@
package dorkbox.util.messagebus.subscription;
import com.esotericsoftware.kryo.util.IdentityMap;
import dorkbox.util.messagebus.MessageBus;
import dorkbox.util.messagebus.common.ClassTree;
import dorkbox.util.messagebus.common.MessageHandler;
import dorkbox.util.messagebus.common.MultiClass;
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.subscription.asm.SubMakerAsm;
import dorkbox.util.messagebus.subscription.reflection.SubMakerReflection;
import dorkbox.util.messagebus.utils.ClassUtils;
import dorkbox.util.messagebus.utils.SubscriptionUtils;
import dorkbox.util.messagebus.utils.VarArgUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* Permits subscriptions with a varying length of parameters as the signature, which must be match by the publisher for it to be accepted
*/
/**
*
*
* 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
* listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines
@ -44,10 +45,12 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public final
class SubscriptionManager {
public static final float LOAD_FACTOR = 0.8F;
public static final Subscription[] SUBSCRIPTIONS = new Subscription[0];
// TODO: during startup, pre-calculate the number of subscription listeners and x2 to save as subsPerListener expected max size
// controls if we use java reflection or ASM to access methods during publication
private final SubMaker subMaker;
// ONLY used by SUB/UNSUB
// remember already processed classes that do not contain any message handlers
@ -59,7 +62,6 @@ class SubscriptionManager {
// once a collection of subscriptions is stored it does not change
private final IdentityMap<Class<?>, Subscription[]> subsPerListener;
// We perpetually KEEP the types registered here, and just change what is sub/unsub
// all subscriptions of a message type.
@ -69,12 +71,6 @@ class SubscriptionManager {
// keeps track of all subscriptions of the super classes of a message type.
private volatile IdentityMap<Class<?>, Subscription[]> subsSuperSingle;
// keeps track of all subscriptions of varity (var-arg) classes of a message type
private volatile IdentityMap<Class<?>, Subscription[]> subsVaritySingle;
// keeps track of all subscriptions of super-class varity (var-arg) classes of a message type
private volatile IdentityMap<Class<?>, Subscription[]> subsSuperVaritySingle;
// In order to force the "Single writer principle" on subscribe & unsubscribe, they are within WRITE LOCKS. They could be dispatched
// to another thread, however we do NOT want them asynchronous - as publish() should ALWAYS succeed if a correct subscribe() is
// called before. A WriteLock doesn't perform any better here than synchronized does.
@ -86,13 +82,9 @@ class SubscriptionManager {
private final ErrorHandlingSupport errorHandler;
private final SubscriptionUtils subUtils;
private final VarArgUtils varArgUtils;
private final ClassTree<Class<?>> classTree;
// shortcut publication if we know there is no possibility of varArg (ie: a method that has an array as arguments)
private final AtomicBoolean varArgPossibility = new AtomicBoolean(false);
private final ClassUtils classUtils;
@ -113,16 +105,6 @@ class SubscriptionManager {
IdentityMap.class,
"subsSuperSingle");
private static final AtomicReferenceFieldUpdater<SubscriptionManager, IdentityMap> subsVaritySingleREF =
AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class,
IdentityMap.class,
"subsVaritySingle");
private static final AtomicReferenceFieldUpdater<SubscriptionManager, IdentityMap> subsSuperVaritySingleREF =
AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class,
IdentityMap.class,
"subsSuperVaritySingle");
//NOTE for multiArg, can use the memory address concatenated with other ones and then just put it in the 'single" map (convert single to
// use this too). it would likely have to be longs no idea what to do for arrays?? (arrays should verify all the elements are the
// correct type too)
@ -131,30 +113,27 @@ class SubscriptionManager {
SubscriptionManager(final int numberOfThreads, final ErrorHandlingSupport errorHandler) {
this.errorHandler = errorHandler;
if (MessageBus.useAsmForDispatch) {
this.subMaker = new SubMakerAsm();
}
else {
this.subMaker = new SubMakerReflection();
}
classUtils = new ClassUtils(SubscriptionManager.LOAD_FACTOR);
// modified ONLY during SUB/UNSUB
nonListeners = new IdentityMap<Class<?>, Boolean>(16, LOAD_FACTOR);
subsPerListener = new IdentityMap<>(32, LOAD_FACTOR);
subsPerListener = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
subsSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
subsMulti = new IdentityMap<MultiClass, Subscription[]>(32, LOAD_FACTOR);
subsSuperSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
subsVaritySingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
subsSuperVaritySingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
this.classTree = new ClassTree<Class<?>>();
this.subUtils = new SubscriptionUtils(classUtils, LOAD_FACTOR, numberOfThreads);
// 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
this.varArgUtils = new VarArgUtils(classUtils, LOAD_FACTOR, numberOfThreads);
classTree = new ClassTree<Class<?>>();
subUtils = new SubscriptionUtils(classUtils, LOAD_FACTOR, numberOfThreads);
}
public
@ -174,18 +153,14 @@ class SubscriptionManager {
}
}
this.nonListeners.clear();
this.subsPerListener.clear();
this.subsSingle.clear();
this.subsSuperSingle.clear();
this.subsVaritySingle.clear();
this.subsSuperVaritySingle.clear();
this.classTree.clear();
this.classUtils.shutdown();
}
@ -223,201 +198,158 @@ class SubscriptionManager {
// access a snapshot of the subscriptions (single-writer-principle)
final IdentityMap<Class<?>, Subscription[]> singleSubs = subsSingleREF.get(this);
// final IdentityMap<MultiClass, Subscription[]> multiSubs = subsMultiREF.get(this);
// final IdentityMap<Class<?>, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this);
// final IdentityMap<Class<?>, Subscription[]> localVaritySubs = subsVaritySingleREF.get(this);
// final IdentityMap<Class<?>, Subscription[]> localSuperVaritySubs = subsSuperVaritySingleREF.get(this);
final IdentityMap<MultiClass, Subscription[]> multiSubs = subsMultiREF.get(this);
Subscription subscription;
MessageHandler messageHandler;
Class<?>[] messageHandlerTypes;
int messageHandlerTypesSize;
MultiClass multiClass;
Class<?> handlerType;
// Prepare all of the subscriptions
// Prepare all of the subscriptions and add for publication AND subscribe since the data structures are consistent
for (int i = 0; i < handlersSize; i++) {
// THE HANDLER IS THE SAME FOR ALL SUBSCRIPTIONS OF THE SAME TYPE!
messageHandler = messageHandlers[i];
// is this handler able to accept var args?
// if (messageHandler.getVarArgClass() != null) {
// varArgPossibility.lazySet(true);
// }
subscription = subMaker.create(listenerClass, messageHandler);
subscription.subscribe(listener); // register this callback listener to this subscription
subscriptions[i] = subscription;
// now create a list of subscriptions for this specific handlerType (but don't add anything yet).
// we only store things based on the FIRST type (for lookup) then parse the rest of the types during publication
// register for publication
messageHandlerTypes = messageHandler.getHandledMessages();
// final int handlerSize = messageHandlerTypes.length;
// switch (handlerSize) {
// case 0: {
// // if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed)
// // This is the SAME THING as having Void as a parameter!!
// handlerType = Void.class;
//
// if (!singleSubs.containsKey(handlerType)) {
// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
// singleSubs.put(handlerType, SUBSCRIPTIONS);
// }
// break;
// }
// case 1: {
messageHandlerTypesSize = messageHandlerTypes.length;
switch (messageHandlerTypesSize) {
case 0: {
// if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed)
// This is the SAME THING as having Void as a parameter!!
handlerType = Void.class;
// makes this subscription visible for publication
final Subscription[] newSubs;
Subscription[] currentSubs = singleSubs.get(handlerType);
if (currentSubs != null) {
final int currentLength = currentSubs.length;
// add the new subscription to the array
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
newSubs[currentLength] = subscription;
} else {
newSubs = new Subscription[1];
newSubs[0] = subscription;
}
singleSubs.put(handlerType, newSubs);
break;
}
case 1: {
handlerType = messageHandlerTypes[0];
if (!singleSubs.containsKey(handlerType)) {
// this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
singleSubs.put(handlerType, SUBSCRIPTIONS);
// makes this subscription visible for publication
final Subscription[] newSubs;
Subscription[] currentSubs = singleSubs.get(handlerType);
if (currentSubs != null) {
final int currentLength = currentSubs.length;
// add the new subscription to the array
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
newSubs[currentLength] = subscription;
} else {
newSubs = new Subscription[1];
newSubs[0] = subscription;
}
// break;
// }
// case 2: {
// final MultiClass multiClass = classTree.get(messageHandlerTypes[0],
// messageHandlerTypes[1]);
//
// if (!multiSubs.containsKey(multiClass)) {
// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
// multiSubs.put(multiClass, SUBSCRIPTIONS);
// }
// break;
// }
// case 3: {
// final MultiClass multiClass = classTree.get(messageHandlerTypes[0],
// messageHandlerTypes[1],
// messageHandlerTypes[2]);
//
// if (!multiSubs.containsKey(multiClass)) {
// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
// multiSubs.put(multiClass, SUBSCRIPTIONS);
// }
// break;
// }
// default: {
// final MultiClass multiClass = classTree.get(messageHandlerTypes);
//
// if (!multiSubs.containsKey(multiClass)) {
// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
// multiSubs.put(multiClass, SUBSCRIPTIONS);
// }
// break;
// }
// }
// create the subscription. This can be thrown away if the subscription succeeds in another thread
subscription = new Subscription(listenerClass, messageHandler);
subscriptions[i] = subscription;
singleSubs.put(handlerType, newSubs);
break;
}
case 2: {
multiClass = classTree.get(messageHandlerTypes[0], messageHandlerTypes[1]);
// makes this subscription visible for publication
final Subscription[] newSubs;
Subscription[] currentSubs = multiSubs.get(multiClass);
if (currentSubs != null) {
final int currentLength = currentSubs.length;
// add the new subscription to the array
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
newSubs[currentLength] = subscription;
} else {
newSubs = new Subscription[1];
newSubs[0] = subscription;
}
multiSubs.put(multiClass, newSubs);
break;
}
case 3: {
multiClass = classTree.get(messageHandlerTypes[0], messageHandlerTypes[1], messageHandlerTypes[2]);
// makes this subscription visible for publication
final Subscription[] newSubs;
Subscription[] currentSubs = multiSubs.get(multiClass);
if (currentSubs != null) {
final int currentLength = currentSubs.length;
// add the new subscription to the array
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
newSubs[currentLength] = subscription;
} else {
newSubs = new Subscription[1];
newSubs[0] = subscription;
}
multiSubs.put(multiClass, newSubs);
break;
}
default: {
multiClass = classTree.get(messageHandlerTypes);
// makes this subscription visible for publication
final Subscription[] newSubs;
Subscription[] currentSubs = multiSubs.get(multiClass);
if (currentSubs != null) {
final int currentLength = currentSubs.length;
// add the new subscription to the array
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
newSubs[currentLength] = subscription;
} else {
newSubs = new Subscription[1];
newSubs[0] = subscription;
}
multiSubs.put(multiClass, newSubs);
break;
}
}
}
// now subsPerMessageSingle has a unique list of subscriptions for a specific handlerType, and MAY already have subscriptions
// activates this sub for sub/unsub (only used by the subscription writer thread)
subsPerListener.put(listenerClass, subscriptions);
// add for publication AND subscribe since the data structures are consistent
for (int i = 0; i < handlersSize; i++) {
subscription = subscriptions[i];
subscription.subscribe(listener); // register this callback listener to this subscription
// THE HANDLER IS THE SAME FOR ALL SUBSCRIPTIONS OF THE SAME TYPE!
messageHandler = messageHandlers[i];
// register for publication
messageHandlerTypes = messageHandler.getHandledMessages();
// final int handlerSize = messageHandlerTypes.length;
//
// switch (handlerSize) {
// case 0: {
// handlerType = Void.class;
//
// // makes this subscription visible for publication
// final Subscription[] currentSubs = singleSubs.get(handlerType);
// final int currentLength = currentSubs.length;
//
// // add the new subscription to the array
// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
// newSubs[currentLength] = subscription;
// singleSubs.put(handlerType, newSubs);
// break;
// }
//
// case 1: {
handlerType = messageHandlerTypes[0];
// makes this subscription visible for publication
final Subscription[] currentSubs = singleSubs.get(handlerType);
final int currentLength = currentSubs.length;
// add the new subscription to the array
final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
newSubs[currentLength] = subscription;
singleSubs.put(handlerType, newSubs);
// update the varity/super types
// registerExtraSubs(handlerType, singleSubs, localSuperSubs, localVaritySubs);
// break;
// }
//
// case 2: {
// final MultiClass multiClass = classTree.get(messageHandlerTypes[0],
// messageHandlerTypes[1]);
// // makes this subscription visible for publication
// final Subscription[] currentSubs = multiSubs.get(multiClass);
// final int currentLength = currentSubs.length;
//
// // add the new subscription to the array
// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
// newSubs[currentLength] = subscription;
// multiSubs.put(multiClass, newSubs);
//
// break;
// }
//
// case 3: {
// final MultiClass multiClass = classTree.get(messageHandlerTypes[0],
// messageHandlerTypes[1],
// messageHandlerTypes[2]);
// // makes this subscription visible for publication
// final Subscription[] currentSubs = multiSubs.get(multiClass);
// final int currentLength = currentSubs.length;
//
// // add the new subscription to the array
// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
// newSubs[currentLength] = subscription;
// multiSubs.put(multiClass, newSubs);
//
// break;
// }
//
// default: {
// final MultiClass multiClass = classTree.get(messageHandlerTypes);
// // makes this subscription visible for publication
// final Subscription[] currentSubs = multiSubs.get(multiClass);
// final int currentLength = currentSubs.length;
//
// // add the new subscription to the array
// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
// newSubs[currentLength] = subscription;
// multiSubs.put(multiClass, newSubs);
//
// break;
// }
// }
}
// save this snapshot back to the original (single writer principle)
subsSingleREF.lazySet(this, singleSubs);
// subsMultiREF.lazySet(this, multiSubs);
// subsSuperSingleREF.lazySet(this, localSuperSubs);
// subsVaritySingleREF.lazySet(this, localVaritySubs);
// subsSuperVaritySingleREF.lazySet(this, localSuperVaritySubs);
subsMultiREF.lazySet(this, multiSubs);
// only dump the super subscritions if it is a COMPLETELY NEW subscription. If it's not new, then the heirarchy isn't
// changing for super subscriptions
subsSuperSingleREF.lazySet(this, new IdentityMap(32));
// only dump the super subscriptions if it is a COMPLETELY NEW subscription.
// If it's not new, then the hierarchy isn't changing for super subscriptions
final IdentityMap<Class<?>, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this);
localSuperSubs.clear();
subsSuperSingleREF.lazySet(this, localSuperSubs);
}
else {
// subscriptions already exist and must only be updated
@ -475,7 +407,7 @@ class SubscriptionManager {
// for (int i = 0; i < length; i++) {
// sub = arraySubs[i];
//
// if (sub.getHandler().acceptsVarArgs()) {
// if (sub.getHandlerAccess().acceptsVarArgs()) {
// varArgSubsAsList.add(sub);
// }
// }
@ -501,7 +433,7 @@ class SubscriptionManager {
// for (int j = 0; j < superSubLength; j++) {
// sub = superSubs[j];
//
// if (sub.getHandler().acceptsSubtypes()) {
// if (sub.getHandlerAccess().acceptsSubtypes()) {
// subsAsList.add(sub);
// }
// }
@ -517,28 +449,6 @@ class SubscriptionManager {
public
AtomicBoolean getVarArgPossibility() {
return varArgPossibility;
}
public
VarArgUtils getVarArgUtils() {
return varArgUtils;
}
// can return null
public
Subscription[] getSubs(final Class<?> messageClass) {
@ -574,57 +484,54 @@ class SubscriptionManager {
}
// can return null
// can NOT return null
public
Subscription[] getSuperSubs(final Class<?> messageClass) {
final Class<?>[] superClasses = this.classUtils.getSuperClasses(messageClass); // never returns null, cached response
// The subscriptions that are remembered here DO NOT CHANGE (only the listeners inside them change).
// if we subscribe a NEW LISTENER super/child class -- THEN these subscriptions change!
// we also DO NOT care about duplicates (since they will be the same anyways)
final IdentityMap<Class<?>, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this);
final int length = superClasses.length;
final ArrayList<Subscription> subsAsList = new ArrayList<Subscription>(length);
Subscription[] subscriptions = localSuperSubs.get(messageClass);
// the only time this is null, is when subscriptions DO NOT exist, and they haven't been calculated. Otherwise, if they are
// calculated and do not exist - this will be an empty array.
if (subscriptions == null) {
final Class<?>[] superClasses = this.classUtils.getSuperClasses(messageClass); // never returns null, cached response
final IdentityMap<Class<?>, Subscription[]> localSubs = subsSingleREF.get(this);
final int length = superClasses.length;
final ArrayList<Subscription> subsAsList = new ArrayList<Subscription>(length);
Class<?> superClass;
Subscription sub;
Subscription[] superSubs;
boolean hasSubs = false;
final IdentityMap<Class<?>, Subscription[]> localSubs = subsSingleREF.get(this);
// walks through all of the subscriptions that might exist for super types, and if applicable, save them
for (int i = 0; i < length; i++) {
superClass = superClasses[i];
superSubs = localSubs.get(superClass);
Class<?> superClass;
Subscription sub;
Subscription[] superSubs;
if (superSubs != null) {
int superSubLength = superSubs.length;
for (int j = 0; j < superSubLength; j++) {
sub = superSubs[j];
// walks through all of the subscriptions that might exist for super types, and if applicable, save them
for (int i = 0; i < length; i++) {
superClass = superClasses[i];
superSubs = localSubs.get(superClass);
if (sub.getHandler().acceptsSubtypes()) {
subsAsList.add(sub);
hasSubs = true;
if (superSubs != null) {
int superSubLength = superSubs.length;
for (int j = 0; j < superSubLength; j++) {
sub = superSubs[j];
if (sub.getHandler().acceptsSubtypes()) {
subsAsList.add(sub);
}
}
}
}
// subsAsList now contains ALL of the super-class subscriptions.
subscriptions = subsAsList.toArray(new Subscription[0]);
localSuperSubs.put(messageClass, subscriptions);
subsSuperSingleREF.lazySet(this, localSuperSubs);
}
// subsAsList now contains ALL of the super-class subscriptions.
if (hasSubs) {
return subsAsList.toArray(new Subscription[0]);
}
else {
// TODO: shortcut out if there are no handlers that accept subtypes
return null;
}
// IT IS CRITICAL TO REMEMBER: The subscriptions that are remembered here DO NOT CHANGE (only the listeners inside them change). if we
// subscribe a super/child class -- THEN these subscriptions change!
// return (Subscription[]) subsSuperSingleREF.get(this).get(messageClass);
return subscriptions;
}
// can return null

View File

@ -35,7 +35,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.dispatch;
package dorkbox.util.messagebus.subscription.asm;
import com.esotericsoftware.reflectasm.MethodAccess;
@ -54,7 +54,7 @@ import com.esotericsoftware.reflectasm.MethodAccess;
* @author dorkbox, llc
* Date: 2/2/15
*/
public interface IHandlerInvocation {
public interface AsmInvocation {
/**
* Invoke the message delivery logic of this handler
@ -88,15 +88,4 @@ public interface IHandlerInvocation {
* @param handler The handler (method) that will be called via reflection
*/
void invoke(Object listener, MethodAccess handler, int methodIndex, Object message1, Object message2, Object message3) throws Throwable;
/**
* Invoke the message delivery logic of this handler
*
* @param listener The listener that will receive the message. This can be a reference to a method object
* from the java reflection api or any other wrapper that can be used to invoke the handler
* @param messages The message to be delivered to the handler. This can be any object compatible with the object
* type that the handler consumes
* @param handler The handler (method) that will be called via reflection
*/
void invoke(Object listener, MethodAccess handler, int methodIndex, Object... messages) throws Throwable;
}

View File

@ -35,7 +35,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.dispatch;
package dorkbox.util.messagebus.subscription.asm;
import com.esotericsoftware.reflectasm.MethodAccess;
@ -48,10 +48,10 @@ import com.esotericsoftware.reflectasm.MethodAccess;
* Date: 2/2/15
*/
public
class ReflectiveHandlerInvocation implements IHandlerInvocation {
class AsmReflectiveInvocation implements AsmInvocation {
public
ReflectiveHandlerInvocation() {
AsmReflectiveInvocation() {
super();
}
@ -63,21 +63,13 @@ class ReflectiveHandlerInvocation implements IHandlerInvocation {
@Override
public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2)
throws Throwable {
void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2) throws Throwable {
handler.invoke(listener, methodIndex, message1, message2);
}
@Override
public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2,
final Object message3) throws Throwable {
void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2, final Object message3) throws Throwable {
handler.invoke(listener, methodIndex, message1, message2, message3);
}
@Override
public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object... messages) throws Throwable {
handler.invoke(listener, methodIndex, messages);
}
}

View File

@ -35,7 +35,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.dispatch;
package dorkbox.util.messagebus.subscription.asm;
import com.esotericsoftware.reflectasm.MethodAccess;
@ -48,12 +48,12 @@ import com.esotericsoftware.reflectasm.MethodAccess;
* Date: 2/2/15
*/
public
class SynchronizedHandlerInvocation implements IHandlerInvocation {
class AsmSynchronizedInvocation implements AsmInvocation {
private IHandlerInvocation delegate;
private AsmInvocation delegate;
public
SynchronizedHandlerInvocation(IHandlerInvocation delegate) {
AsmSynchronizedInvocation(AsmInvocation delegate) {
this.delegate = delegate;
}
@ -67,8 +67,7 @@ class SynchronizedHandlerInvocation implements IHandlerInvocation {
@Override
public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2)
throws Throwable {
void invoke(final Object listener, final MethodAccess handler, int methodIndex, final Object message1, final Object message2) throws Throwable {
synchronized (listener) {
this.delegate.invoke(listener, handler, methodIndex, message1, message2);
}
@ -76,18 +75,9 @@ class SynchronizedHandlerInvocation implements IHandlerInvocation {
@Override
public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2,
final Object message3) throws Throwable {
void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2, final Object message3) throws Throwable {
synchronized (listener) {
this.delegate.invoke(listener, handler, methodIndex, message1, message2, message3);
}
}
@Override
public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object... messages) throws Throwable {
synchronized (listener) {
this.delegate.invoke(listener, handler, methodIndex, messages);
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.subscription.asm;
import dorkbox.util.messagebus.common.MessageHandler;
import dorkbox.util.messagebus.subscription.SubMaker;
import dorkbox.util.messagebus.subscription.Subscription;
/**
* @author dorkbox, llc Date: 2/3/16
*/
public
class SubMakerAsm implements SubMaker {
public
SubMakerAsm() {
}
@Override
public
Subscription create(final Class<?> listenerClass, final MessageHandler handler) {
return new SubscriptionAsm(listenerClass, handler);
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.subscription.asm;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.util.messagebus.common.Entry;
import dorkbox.util.messagebus.common.MessageHandler;
import dorkbox.util.messagebus.subscription.Subscription;
import java.lang.reflect.Method;
/**
* A subscription is a container that manages exactly one message handler of all registered
* message listeners of the same class, i.e. all subscribed instances (excluding subclasses) of a message
* will be referenced in the subscription created for a message.
* <p/>
* There will be as many unique subscription objects per message listener class as there are message handlers
* defined in the message listeners class hierarchy.
* <p/>
* This class uses the "single writer principle", so that the subscription are only MODIFIED by a single thread,
* but are READ by X number of threads (in a safe way). This uses object thread visibility/publication to work.
*
* @author bennidi
* @author dorkbox, llc
* Date: 2/2/15
*/
@SuppressWarnings("Duplicates")
public final
class SubscriptionAsm extends Subscription {
private final AsmInvocation invocation;
private final MethodAccess handlerAccess;
private final int methodIndex;
public
SubscriptionAsm(final Class<?> listenerClass, final MessageHandler handler) {
// we use ASM here
super(listenerClass, handler);
AsmInvocation invocation = new AsmReflectiveInvocation();
if (handler.isSynchronized()) {
invocation = new AsmSynchronizedInvocation(invocation);
}
this.invocation = invocation;
// we use ASM here
Method method = handler.getMethod();
this.handlerAccess = MethodAccess.get(method.getDeclaringClass());
this.methodIndex = this.handlerAccess.getIndex(method.getName(), handler.getHandledMessages());
}
/**
* @return true if messages were published
*/
public
boolean publish(final Object message) throws Throwable {
final MethodAccess handler = this.handlerAccess;
final int handleIndex = this.methodIndex;
final AsmInvocation invocation = this.invocation;
boolean hasSubs = false;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
hasSubs = true;
listener = current.getValue();
current = current.next();
invocation.invoke(listener, handler, handleIndex, message);
}
return hasSubs;
}
/**
* @return true if messages were published
*/
public
boolean publish(final Object message1, final Object message2) throws Throwable {
final MethodAccess handler = this.handlerAccess;
final int handleIndex = this.methodIndex;
final AsmInvocation invocation = this.invocation;
boolean hasSubs = false;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
hasSubs = true;
listener = current.getValue();
current = current.next();
invocation.invoke(listener, handler, handleIndex, message1, message2);
}
return hasSubs;
}
/**
* @return true if messages were published
*/
public
boolean publish(final Object message1, final Object message2, final Object message3) throws Throwable {
final MethodAccess handler = this.handlerAccess;
final int handleIndex = this.methodIndex;
final AsmInvocation invocation = this.invocation;
boolean hasSubs = false;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
hasSubs = true;
listener = current.getValue();
current = current.next();
invocation.invoke(listener, handler, handleIndex, message1, message2, message3);
}
return hasSubs;
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.subscription.reflection;
import java.lang.reflect.Method;
/**
* A handler invocation encapsulates the logic that is used to invoke a single
* message handler to process a given message.
*
* A handler invocation might come in different flavours and can be composed
* of various independent invocations by means of delegation (-> decorator pattern)
*
* If an exception is thrown during handler invocation it is wrapped and propagated
* as a publication error
*
* @author bennidi
* Date: 11/23/12
* @author dorkbox, llc
* Date: 2/2/15
*/
public interface ReflectionInvocation {
/**
* Invoke the message delivery logic of this handler
*
* @param listener The listener that will receive the message. This can be a reference to a method object
* from the java reflection api or any other wrapper that can be used to invoke the handler
* @param message The message to be delivered to the handler. This can be any object compatible with the object
* type that the handler consumes
* @param handler The handler (method) that will be called via reflection
*/
void invoke(Object listener, Method handler, Object message) throws Throwable;
/**
* Invoke the message delivery logic of this handler
*
* @param listener The listener that will receive the message. This can be a reference to a method object
* from the java reflection api or any other wrapper that can be used to invoke the handler
* @param message1 The message to be delivered to the handler. This can be any object compatible with the object
* type that the handler consumes
* @param handler The handler (method) that will be called via reflection
*/
void invoke(Object listener, Method handler, Object message1, Object message2) throws Throwable;
/**
* Invoke the message delivery logic of this handler
*
* @param listener The listener that will receive the message. This can be a reference to a method object
* from the java reflection api or any other wrapper that can be used to invoke the handler
* @param message1 The message to be delivered to the handler. This can be any object compatible with the object
* type that the handler consumes
* @param handler The handler (method) that will be called via reflection
*/
void invoke(Object listener, Method handler, Object message1, Object message2, Object message3) throws Throwable;
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.subscription.reflection;
import java.lang.reflect.Method;
/**
* Uses reflection to invoke a message handler for a given message.
*
* @author bennidi
* Date: 11/23/12
* @author dorkbox, llc
* Date: 2/2/15
*/
public
class ReflectionReflectiveInvocation implements ReflectionInvocation {
public
ReflectionReflectiveInvocation() {
super();
}
@Override
public
void invoke(final Object listener, final Method handler, final Object message) throws Throwable {
handler.invoke(listener, message);
}
@Override
public
void invoke(final Object listener, final Method handler, final Object message1, final Object message2) throws Throwable {
handler.invoke(listener, message1, message2);
}
@Override
public
void invoke(final Object listener, final Method handler, final Object message1, final Object message2, final Object message3) throws Throwable {
handler.invoke(listener, message1, message2, message3);
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.subscription.reflection;
import java.lang.reflect.Method;
/**
* Synchronizes message handler invocations for all handlers that specify @Synchronized
*
* @author bennidi
* Date: 3/31/13
* @author dorkbox, llc
* Date: 2/2/15
*/
public
class ReflectionSynchronizedInvocation implements ReflectionInvocation {
private ReflectionInvocation delegate;
public
ReflectionSynchronizedInvocation(ReflectionInvocation delegate) {
this.delegate = delegate;
}
@Override
public
void invoke(final Object listener, final Method handler, final Object message) throws Throwable {
synchronized (listener) {
this.delegate.invoke(listener, handler, message);
}
}
@Override
public
void invoke(final Object listener, Method handler, final Object message1, final Object message2) throws Throwable {
synchronized (listener) {
this.delegate.invoke(listener, handler, message1, message2);
}
}
@Override
public
void invoke(final Object listener, final Method handler, final Object message1, final Object message2, final Object message3) throws Throwable {
synchronized (listener) {
this.delegate.invoke(listener, handler, message1, message2, message3);
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.subscription.reflection;
import dorkbox.util.messagebus.common.MessageHandler;
import dorkbox.util.messagebus.subscription.SubMaker;
import dorkbox.util.messagebus.subscription.Subscription;
public
class SubMakerReflection implements SubMaker {
public
SubMakerReflection() {
}
@Override
public
Subscription create(final Class<?> listenerClass, final MessageHandler handler) {
return new SubscriptionReflection(listenerClass, handler);
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.subscription.reflection;
import dorkbox.util.messagebus.common.Entry;
import dorkbox.util.messagebus.common.MessageHandler;
import dorkbox.util.messagebus.subscription.Subscription;
import java.lang.reflect.Method;
/**
* A subscription is a container that manages exactly one message handler of all registered
* message listeners of the same class, i.e. all subscribed instances (excluding subclasses) of a message
* will be referenced in the subscription created for a message.
* <p/>
* There will be as many unique subscription objects per message listener class as there are message handlers
* defined in the message listeners class hierarchy.
* <p/>
* This class uses the "single writer principle", so that the subscription are only MODIFIED by a single thread,
* but are READ by X number of threads (in a safe way). This uses object thread visibility/publication to work.
*
* @author bennidi
* @author dorkbox, llc
* Date: 2/2/15
*/
@SuppressWarnings("Duplicates")
public final
class SubscriptionReflection extends Subscription {
private final Method method;
private final ReflectionInvocation invocation;
public
SubscriptionReflection(final Class<?> listenerClass, final MessageHandler handler) {
// we use "normal java" here
super(listenerClass, handler);
ReflectionInvocation invocation = new ReflectionReflectiveInvocation();
if (handler.isSynchronized()) {
invocation = new ReflectionSynchronizedInvocation(invocation);
}
this.invocation = invocation;
method = handler.getMethod();
}
/**
* @return true if messages were published
*/
public
boolean publish(final Object message) throws Throwable {
final Method method = this.method;
final ReflectionInvocation invocation = this.invocation;
boolean hasSubs = false;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
hasSubs = true;
listener = current.getValue();
current = current.next();
invocation.invoke(listener, method, message);
}
return hasSubs;
}
/**
* @return true if messages were published
*/
public
boolean publish(final Object message1, final Object message2) throws Throwable {
final Method method = this.method;
final ReflectionInvocation invocation = this.invocation;
boolean hasSubs = false;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
hasSubs = true;
listener = current.getValue();
current = current.next();
invocation.invoke(listener, method, message1, message2);
}
return hasSubs;
}
/**
* @return true if messages were published
*/
public
boolean publish(final Object message1, final Object message2, final Object message3) throws Throwable {
final Method method = this.method;
final ReflectionInvocation invocation = this.invocation;
boolean hasSubs = false;
Entry current = headREF.get(this);
Object listener;
while (current != null) {
hasSubs = true;
listener = current.getValue();
current = current.next();
invocation.invoke(listener, method, message1, message2, message3);
}
return hasSubs;
}
}

View File

@ -1,22 +1,43 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.common.thread.NamedThreadFactory;
import dorkbox.util.messagebus.common.NamedThreadFactory;
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.error.PublicationError;
import dorkbox.util.messagebus.publication.Publisher;
import dorkbox.util.messagebus.subscription.Subscription;
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
/**
* This is similar to the disruptor, however the downside of this implementation is that, while faster than the no-gc version, it
* generates garbage (while the disruptor version does not).
*
* Basically, the disruptor is fast + noGC.
*
* @author dorkbox, llc Date: 2/3/16
*/
public
class AsyncABQ implements Synchrony {
private final ErrorHandlingSupport errorHandler;
private final ArrayBlockingQueue<Object> dispatchQueue;
private final ArrayBlockingQueue<MessageHolder> dispatchQueue;
private final Collection<Thread> threads;
/**
@ -31,8 +52,7 @@ class AsyncABQ implements Synchrony {
final Publisher publisher,
final Synchrony syncPublication) {
this.errorHandler = errorHandler;
this.dispatchQueue = new ArrayBlockingQueue<Object>(1024);
this.dispatchQueue = new ArrayBlockingQueue<MessageHolder>(1024);
this.threads = new ArrayDeque<Thread>(numberOfThreads);
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
@ -43,52 +63,79 @@ class AsyncABQ implements Synchrony {
@Override
public
void run() {
final ArrayBlockingQueue<?> IN_QUEUE = AsyncABQ.this.dispatchQueue;
final ArrayBlockingQueue<MessageHolder> IN_QUEUE = AsyncABQ.this.dispatchQueue;
final Publisher publisher1 = publisher;
final Synchrony syncPublication1 = syncPublication;
final ErrorHandlingSupport errorHandler1 = errorHandler;
MessageHolder event = null;
int messageType = MessageType.ONE;
Object message1 = null;
Object message2 = null;
Object message3 = null;
while (!AsyncABQ.this.shuttingDown) {
try {
//noinspection InfiniteLoopStatement
while (true) {
final Object take = IN_QUEUE.take();
publisher1.publish(syncPublication1, take);
event = IN_QUEUE.take();
messageType = event.type;
message1 = event.message1;
message2 = event.message2;
message3 = event.message3;
switch (messageType) {
case MessageType.ONE: {
publisher1.publish(syncPublication1, message1);
break;
}
case MessageType.TWO: {
publisher1.publish(syncPublication1, message1, message2);
break;
}
case MessageType.THREE: {
publisher1.publish(syncPublication1, message3, message1, message2);
break;
}
}
} catch (InterruptedException e) {
if (!AsyncABQ.this.shuttingDown) {
// Integer type = (Integer) MultiNode.lpMessageType(node);
// switch (type) {
// case 1: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node)));
// break;
// }
// case 2: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node),
// MultiNode.lpItem2(node)));
// break;
// }
// case 3: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node),
// MultiNode.lpItem2(node),
// MultiNode.lpItem3(node)));
// break;
// }
// default: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node)));
// }
// }
switch (messageType) {
case MessageType.ONE: {
PublicationError publicationError = new PublicationError()
.setMessage("Thread interrupted while processing message")
.setCause(e);
if (event != null) {
publicationError.setPublishedObject(message1);
}
errorHandler1.handlePublicationError(publicationError);
break;
}
case MessageType.TWO: {
PublicationError publicationError = new PublicationError()
.setMessage("Thread interrupted while processing message")
.setCause(e);
if (event != null) {
publicationError.setPublishedObject(message1, message2);
}
errorHandler1.handlePublicationError(publicationError);
break;
}
case MessageType.THREE: {
PublicationError publicationError = new PublicationError()
.setMessage("Thread interrupted while processing message")
.setCause(e);
if (event != null) {
publicationError.setPublishedObject(message1, message2, message3);
}
errorHandler1.handlePublicationError(publicationError);
break;
}
}
}
}
}
@ -100,27 +147,28 @@ class AsyncABQ implements Synchrony {
}
}
// unfortunately, this isn't as friendly to GC as the disruptor is...
public
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
this.dispatchQueue.put(message1);
MessageHolder take = new MessageHolder();
take.type = MessageType.ONE;
take.subscriptions = subscriptions;
take.message1 = message1;
this.dispatchQueue.put(take);
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2));
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
}
@Override
public
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2, message3));
}
public

View File

@ -0,0 +1,213 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.common.NamedThreadFactory;
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.error.PublicationError;
import dorkbox.util.messagebus.publication.Publisher;
import dorkbox.util.messagebus.subscription.Subscription;
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
/**
* This is similar in behavior to the disruptor in that it does not generate garbage, however the downside of this implementation is it is
* slow, but faster than other messagebus implementations.
*
* Basically, the disruptor is fast + noGC.
*
* @author dorkbox, llc Date: 2/3/16
*/
public
class AsyncABQ_noGc implements Synchrony {
private final ArrayBlockingQueue<MessageHolder> dispatchQueue;
// have two queues to prevent garbage, So we pull off one queue to add to another queue and when done, we put it back
private final ArrayBlockingQueue<MessageHolder> gcQueue;
private final Collection<Thread> threads;
/**
* Notifies the consumers during shutdown, that it's on purpose.
*/
private volatile boolean shuttingDown = false;
public
AsyncABQ_noGc(final int numberOfThreads,
final ErrorHandlingSupport errorHandler,
final Publisher publisher,
final Synchrony syncPublication) {
this.dispatchQueue = new ArrayBlockingQueue<MessageHolder>(1024);
this.gcQueue = new ArrayBlockingQueue<MessageHolder>(1024);
// this is how we prevent garbage
for (int i = 0; i < 1024; i++) {
gcQueue.add(new MessageHolder());
}
this.threads = new ArrayDeque<Thread>(numberOfThreads);
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
for (int i = 0; i < numberOfThreads; i++) {
// each thread will run forever and process incoming message publication requests
Runnable runnable = new Runnable() {
@Override
public
void run() {
final ArrayBlockingQueue<MessageHolder> IN_QUEUE = AsyncABQ_noGc.this.dispatchQueue;
final ArrayBlockingQueue<MessageHolder> OUT_QUEUE = AsyncABQ_noGc.this.gcQueue;
final Publisher publisher1 = publisher;
final Synchrony syncPublication1 = syncPublication;
final ErrorHandlingSupport errorHandler1 = errorHandler;
MessageHolder event = null;
int messageType = MessageType.ONE;
Object message1 = null;
Object message2 = null;
Object message3 = null;
while (!AsyncABQ_noGc.this.shuttingDown) {
try {
event = IN_QUEUE.take();
messageType = event.type;
message1 = event.message1;
message2 = event.message2;
message3 = event.message3;
OUT_QUEUE.put(event);
switch (messageType) {
case MessageType.ONE: {
publisher1.publish(syncPublication1, message1);
break;
}
case MessageType.TWO: {
publisher1.publish(syncPublication1, message1, message2);
break;
}
case MessageType.THREE: {
publisher1.publish(syncPublication1, message3, message1, message2);
break;
}
}
} catch (InterruptedException e) {
if (!AsyncABQ_noGc.this.shuttingDown) {
switch (messageType) {
case MessageType.ONE: {
PublicationError publicationError = new PublicationError()
.setMessage("Thread interrupted while processing message")
.setCause(e);
if (event != null) {
publicationError.setPublishedObject(message1);
}
errorHandler1.handlePublicationError(publicationError);
break;
}
case MessageType.TWO: {
PublicationError publicationError = new PublicationError()
.setMessage("Thread interrupted while processing message")
.setCause(e);
if (event != null) {
publicationError.setPublishedObject(message1, message2);
}
errorHandler1.handlePublicationError(publicationError);
break;
}
case MessageType.THREE: {
PublicationError publicationError = new PublicationError()
.setMessage("Thread interrupted while processing message")
.setCause(e);
if (event != null) {
publicationError.setPublishedObject(message1, message2, message3);
}
errorHandler1.handlePublicationError(publicationError);
break;
}
}
}
}
}
}
};
Thread runner = threadFactory.newThread(runnable);
this.threads.add(runner);
}
}
// unfortunately, this isn't as friendly to GC as the disruptor is...
public
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
MessageHolder take = gcQueue.take();
take.type = MessageType.ONE;
take.subscriptions = subscriptions;
take.message1 = message1;
this.dispatchQueue.put(take);
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2));
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2, message3));
}
public
void start() {
if (shuttingDown) {
throw new Error("Unable to restart the MessageBus");
}
for (Thread t : this.threads) {
t.start();
}
}
public
void shutdown() {
this.shuttingDown = true;
for (Thread t : this.threads) {
t.interrupt();
}
}
public
boolean hasPendingMessages() {
return !this.dispatchQueue.isEmpty();
}
}

View File

@ -1,3 +1,18 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony;
import com.lmax.disruptor.LiteBlockingWaitStrategy;
@ -8,14 +23,13 @@ import com.lmax.disruptor.SequenceBarrier;
import com.lmax.disruptor.Sequencer;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.WorkProcessor;
import dorkbox.util.messagebus.common.thread.NamedThreadFactory;
import dorkbox.util.messagebus.common.NamedThreadFactory;
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.publication.Publisher;
import dorkbox.util.messagebus.publication.disruptor.EventBusFactory;
import dorkbox.util.messagebus.publication.disruptor.MessageHandler;
import dorkbox.util.messagebus.publication.disruptor.MessageHolder;
import dorkbox.util.messagebus.publication.disruptor.MessageType;
import dorkbox.util.messagebus.publication.disruptor.PublicationExceptionHandler;
import dorkbox.util.messagebus.synchrony.disruptor.EventBusFactory;
import dorkbox.util.messagebus.synchrony.disruptor.MessageHandler;
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
import dorkbox.util.messagebus.synchrony.disruptor.PublicationExceptionHandler;
import dorkbox.util.messagebus.subscription.Subscription;
import java.util.concurrent.ExecutorService;
@ -23,6 +37,9 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author dorkbox, llc Date: 2/3/16
*/
public
class AsyncDisruptor implements Synchrony {
@ -130,12 +147,6 @@ class AsyncDisruptor implements Synchrony {
}
@Override
public
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
}
// gets the sequences used for processing work
private
Sequence[] getSequences() {

View File

@ -1,148 +0,0 @@
package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.common.thread.NamedThreadFactory;
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.publication.Publisher;
import dorkbox.util.messagebus.subscription.Subscription;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.concurrent.LinkedTransferQueue;
/**
*
*/
public
class AsyncLTQ implements Synchrony {
private final ErrorHandlingSupport errorHandler;
private final LinkedTransferQueue<Object> dispatchQueue;
private final Collection<Thread> threads;
/**
* Notifies the consumers during shutdown, that it's on purpose.
*/
private volatile boolean shuttingDown = false;
public
AsyncLTQ(final int numberOfThreads,
final ErrorHandlingSupport errorHandler,
final Publisher publisher,
final Synchrony syncPublication) {
this.errorHandler = errorHandler;
this.dispatchQueue = new LinkedTransferQueue<Object>();
this.threads = new ArrayDeque<Thread>(numberOfThreads);
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
for (int i = 0; i < numberOfThreads; i++) {
// each thread will run forever and process incoming message publication requests
Runnable runnable = new Runnable() {
@Override
public
void run() {
LinkedTransferQueue<?> IN_QUEUE = AsyncLTQ.this.dispatchQueue;
final Publisher publisher1 = publisher;
final Synchrony syncPublication1 = syncPublication;
while (!AsyncLTQ.this.shuttingDown) {
try {
//noinspection InfiniteLoopStatement
while (true) {
final Object take = IN_QUEUE.take();
publisher1.publish(syncPublication1, take);
}
} catch (InterruptedException e) {
if (!AsyncLTQ.this.shuttingDown) {
// Integer type = (Integer) MultiNode.lpMessageType(node);
// switch (type) {
// case 1: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node)));
// break;
// }
// case 2: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node),
// MultiNode.lpItem2(node)));
// break;
// }
// case 3: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node),
// MultiNode.lpItem2(node),
// MultiNode.lpItem3(node)));
// break;
// }
// default: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node)));
// }
// }
}
}
}
}
};
Thread runner = threadFactory.newThread(runnable);
this.threads.add(runner);
}
}
public
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
this.dispatchQueue.transfer(message1);
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
}
@Override
public
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
}
public
void start() {
if (shuttingDown) {
throw new Error("Unable to restart the MessageBus");
}
for (Thread t : this.threads) {
t.start();
}
}
public
void shutdown() {
this.shuttingDown = true;
for (Thread t : this.threads) {
t.interrupt();
}
}
public
boolean hasPendingMessages() {
return !this.dispatchQueue.isEmpty();
}
}

View File

@ -1,184 +0,0 @@
package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.common.thread.NamedThreadFactory;
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.publication.Publisher;
import dorkbox.util.messagebus.subscription.Subscription;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.concurrent.LinkedTransferQueue;
/**
*
*/
public
class AsyncMisc implements Synchrony {
/**
* Notifies the consumers during shutdown, that it's on purpose.
*/
private volatile boolean shuttingDown = false;
// private final LinkedBlockingQueue<Object> dispatchQueue;
// private final ArrayBlockingQueue<Object> dispatchQueue;
private final LinkedTransferQueue<Object> dispatchQueue;
private final Collection<Thread> threads;
public
AsyncMisc(final int numberOfThreads,
final ErrorHandlingSupport errorHandler,
final Publisher publisher,
final Synchrony syncPublication) {
// this.dispatchQueue = new LinkedBlockingQueue<Object>(1024);
// this.dispatchQueue = new ArrayBlockingQueue<Object>(1024);
this.dispatchQueue = new LinkedTransferQueue<Object>();
this.threads = new ArrayDeque<Thread>(numberOfThreads);
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
for (int i = 0; i < numberOfThreads; i++) {
// each thread will run forever and process incoming message publication requests
Runnable runnable = new Runnable() {
@Override
public
void run() {
// LinkedBlockingQueue<?> IN_QUEUE = MessageBus.this.dispatchQueue;
// ArrayBlockingQueue<?> IN_QUEUE = MessageBus.this.dispatchQueue;
LinkedTransferQueue<?> IN_QUEUE = AsyncMisc.this.dispatchQueue;
// MultiNode node = new MultiNode();
while (!AsyncMisc.this.shuttingDown) {
try {
//noinspection InfiniteLoopStatement
while (true) {
// IN_QUEUE.take(node);
final Object take = IN_QUEUE.take();
// Integer type = (Integer) MultiNode.lpMessageType(node);
// switch (type) {
// case 1: {
// publish(take);
// break;
// }
// case 2: {
// publish(MultiNode.lpItem1(node), MultiNode.lpItem2(node));
// break;
// }
// case 3: {
// publish(MultiNode.lpItem1(node), MultiNode.lpItem2(node), MultiNode.lpItem3(node));
// break;
// }
// default: {
// publish(MultiNode.lpItem1(node));
// }
// }
}
} catch (InterruptedException e) {
if (!AsyncMisc.this.shuttingDown) {
// Integer type = (Integer) MultiNode.lpMessageType(node);
// switch (type) {
// case 1: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node)));
// break;
// }
// case 2: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node),
// MultiNode.lpItem2(node)));
// break;
// }
// case 3: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node),
// MultiNode.lpItem2(node),
// MultiNode.lpItem3(node)));
// break;
// }
// default: {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Thread interrupted while processing message")
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node)));
// }
// }
}
}
}
}
};
Thread runner = threadFactory.newThread(runnable);
this.threads.add(runner);
}
}
public
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
// try {
// this.dispatchQueue.transfer(message);
//// this.dispatchQueue.put(message);
// } catch (Exception e) {
// errorHandler.handlePublicationError(new PublicationError().setMessage(
// "Error while adding an asynchronous message").setCause(e).setPublishedObject(message));
// }
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1);
}
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
}
@Override
public
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
}
public
void start() {
if (shuttingDown) {
throw new Error("Unable to restart the MessageBus");
}
for (Thread t : this.threads) {
t.start();
}
}
public
void shutdown() {
this.shuttingDown = true;
for (Thread t : this.threads) {
t.interrupt();
}
}
public
boolean hasPendingMessages() {
return !this.dispatchQueue.isEmpty();
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.subscription.Subscription;
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
/**
* @author dorkbox, llc Date: 2/2/15
*/
public
class MessageHolder {
public int type = MessageType.ONE;
public Subscription[] subscriptions;
public Object message1 = null;
public Object message2 = null;
public Object message3 = null;
public
MessageHolder() {}
public
MessageHolder(Subscription[] subscriptions, Object message1) {
this.subscriptions = subscriptions;
this.message1 = message1;
}
public
MessageHolder(Subscription[] subscriptions, Object message1, Object message2) {
type = MessageType.TWO;
this.subscriptions = subscriptions;
this.message1 = message1;
this.message2 = message2;
}
public
MessageHolder(Subscription[] subscriptions, Object message1, Object message2, Object message3) {
type = MessageType.THREE;
this.subscriptions = subscriptions;
this.message1 = message1;
this.message2 = message2;
this.message3 = message3;
}
}

View File

@ -1,12 +1,31 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.subscription.Subscription;
/**
* @author dorkbox, llc Date: 2/2/15
*/
public
class Sync implements Synchrony {
public
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
Subscription sub;
boolean hasSubs = false;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1);
@ -16,19 +35,21 @@ class Sync implements Synchrony {
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2);
}
}
@Override
public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
}
@Override
public
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
Subscription sub;
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2, message3);
}
}
@Override

View File

@ -1,16 +1,30 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.subscription.Subscription;
/**
*
* @author dorkbox, llc Date: 2/3/16
*/
public
interface Synchrony {
void publish(final Subscription[] subscriptions, Object message1) throws Throwable;
void publish(final Subscription[] subscriptions, Object message1, Object message2) throws Throwable ;
void publish(final Subscription[] subscriptions, Object message1, Object message2, Object message3) throws Throwable ;
void publish(final Subscription[] subscriptions, Object[] messages) throws Throwable ;
void start();
void shutdown();

View File

@ -0,0 +1,35 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony.disruptor;
import com.lmax.disruptor.EventFactory;
import dorkbox.util.messagebus.synchrony.MessageHolder;
/**
* @author dorkbox, llc
* Date: 2/2/15
*/
public class EventBusFactory implements EventFactory<MessageHolder> {
public EventBusFactory() {
}
@Override
public
MessageHolder newInstance() {
return new MessageHolder();
}
}

View File

@ -1,7 +1,23 @@
package dorkbox.util.messagebus.publication.disruptor;
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony.disruptor;
import com.lmax.disruptor.LifecycleAware;
import com.lmax.disruptor.WorkHandler;
import dorkbox.util.messagebus.synchrony.MessageHolder;
import dorkbox.util.messagebus.synchrony.Synchrony;
import dorkbox.util.messagebus.publication.Publisher;
@ -47,11 +63,6 @@ class MessageHandler implements WorkHandler<MessageHolder>, LifecycleAware {
this.publisher.publish(syncPublication, message3, message1, message2);
return;
}
case MessageType.ARRAY: {
Object[] messages = event.messages;
this.publisher.publish(syncPublication, messages);
return;
}
}
}

View File

@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.publication.disruptor;
package dorkbox.util.messagebus.synchrony.disruptor;
/**
* @author dorkbox, llc Date: 2/2/15
*/
public final class MessageType {
public static final int ONE = 1;
public static final int TWO = 2;
public static final int THREE = 3;
public static final int ARRAY = 4;
private MessageType() {
}

View File

@ -1,9 +1,27 @@
package dorkbox.util.messagebus.publication.disruptor;
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.synchrony.disruptor;
import com.lmax.disruptor.ExceptionHandler;
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.error.PublicationError;
/**
* @author dorkbox, llc Date: 2/3/16
*/
public final class PublicationExceptionHandler<T> implements ExceptionHandler<T> {
private final ErrorHandlingSupport errorHandler;

View File

@ -19,7 +19,6 @@ import com.esotericsoftware.kryo.util.IdentityMap;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public final
@ -77,7 +76,6 @@ class ClassUtils {
if (isArray) {
for (int i = 0; i < length; i++) {
c = superTypes[i];
c = getArrayClass(c);
if (c != clazz) {
@ -143,91 +141,4 @@ class ClassUtils {
this.arrayCache.clear();
this.superClassesCache.clear();
}
public static
<T> ArrayList<T> findCommon(final T[] arrayOne, final T[] arrayTwo) {
T[] arrayToHash;
T[] arrayToSearch;
final int size1 = arrayOne.length;
final int size2 = arrayTwo.length;
final int hashSize;
final int searchSize;
if (size1 < size2) {
hashSize = size1;
searchSize = size2;
arrayToHash = arrayOne;
arrayToSearch = arrayTwo;
}
else {
hashSize = size2;
searchSize = size1;
arrayToHash = arrayTwo;
arrayToSearch = arrayOne;
}
final ArrayList<T> intersection = new ArrayList<T>(searchSize);
final HashSet<T> hashedArray = new HashSet<T>();
for (int i = 0; i < hashSize; i++) {
T t = arrayToHash[i];
hashedArray.add(t);
}
for (int i = 0; i < searchSize; i++) {
T t = arrayToSearch[i];
if (hashedArray.contains(t)) {
intersection.add(t);
}
}
return intersection;
}
public static
<T> ArrayList<T> findCommon(final ArrayList<T> arrayOne, final ArrayList<T> arrayTwo) {
ArrayList<T> arrayToHash;
ArrayList<T> arrayToSearch;
final int size1 = arrayOne.size();
final int size2 = arrayTwo.size();
final int hashSize;
final int searchSize;
if (size1 < size2) {
hashSize = size1;
searchSize = size2;
arrayToHash = arrayOne;
arrayToSearch = arrayTwo;
}
else {
hashSize = size2;
searchSize = size1;
arrayToHash = arrayTwo;
arrayToSearch = arrayOne;
}
ArrayList<T> intersection = new ArrayList<T>(searchSize);
HashSet<T> hashedArray = new HashSet<T>();
for (int i = 0; i < hashSize; i++) {
T t = arrayToHash.get(i);
hashedArray.add(t);
}
for (int i = 0; i < searchSize; i++) {
T t = arrayToSearch.get(i);
if (hashedArray.contains(t)) {
intersection.add(t);
}
}
return intersection;
}
}

View File

@ -16,7 +16,7 @@
package dorkbox.util.messagebus.utils;
import com.esotericsoftware.kryo.util.IdentityMap;
import dorkbox.util.messagebus.common.HashMapTree;
import dorkbox.util.messagebus.common.ClassTree;
import dorkbox.util.messagebus.subscription.Subscription;
import dorkbox.util.messagebus.subscription.SubscriptionManager;
@ -32,7 +32,7 @@ class SubscriptionUtils {
// keeps track of all subscriptions of the super classes of a message type.
private volatile IdentityMap<Class<?>, Subscription[]> superClassSubscriptions;
private final HashMapTree<Class<?>, ArrayList<Subscription>> superClassSubscriptionsMulti;
private final ClassTree<Class<?>> superClassSubscriptionsMulti;
public
@ -43,7 +43,7 @@ class SubscriptionUtils {
// superClassSubscriptions keeps track of all subscriptions of super classes. SUB/UNSUB dumps it, so it is recreated dynamically.
// it's a hit on SUB/UNSUB, but improves performance of handlers
this.superClassSubscriptions = new IdentityMap<Class<?>, Subscription[]>(8, loadFactor);
this.superClassSubscriptionsMulti = new HashMapTree<Class<?>, ArrayList<Subscription>>();
this.superClassSubscriptionsMulti = new ClassTree<Class<?>>();
}
public
@ -80,7 +80,7 @@ class SubscriptionUtils {
// for (int j = 0; j < superSubLength; j++) {
// sub = superSubs[j];
//
// if (sub.getHandler().acceptsSubtypes()) {
// if (sub.getHandlerAccess().acceptsSubtypes()) {
// subsAsList.add(sub);
// }
// }
@ -123,61 +123,62 @@ class SubscriptionUtils {
*/
public
ArrayList<Subscription> getSuperSubscriptions(final Class<?> clazz1, final Class<?> clazz2, final SubscriptionManager subManager) {
// whenever our subscriptions change, this map is cleared.
final HashMapTree<Class<?>, ArrayList<Subscription>> cached = this.superClassSubscriptionsMulti;
// // whenever our subscriptions change, this map is cleared.
// final MapTree<Class<?>, ArrayList<Subscription>> cached = this.superClassSubscriptionsMulti;
//
// ArrayList<Subscription> subs = cached.get(clazz1, clazz2);
//
// if (subs == null) {
// // types was not empty, so collect subscriptions for each type and collate them
//
// // save the subscriptions
// final Class<?>[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response
// final Class<?>[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response
//
// Class<?> superClass1;
// Class<?> superClass2;
// ArrayList<Subscription> superSubs;
// Subscription sub;
//
// final int length1 = superClasses1.length;
// final int length2 = superClasses2.length;
//
// subs = new ArrayList<Subscription>(length1 + length2);
//
// for (int i = 0; i < length1; i++) {
// superClass1 = superClasses1[i];
//
// // only go over subtypes
// if (superClass1 == clazz1) {
// continue;
// }
//
// for (int j = 0; j < length2; j++) {
// superClass2 = superClasses2[j];
//
// // only go over subtypes
// if (superClass2 == clazz2) {
// continue;
// }
//
// superSubs = subManager.getExactAsArray(superClass1, superClass2);
// if (superSubs != null) {
// for (int k = 0; k < superSubs.size(); k++) {
// sub = superSubs.get(k);
//
// if (sub.getHandlerAccess().acceptsSubtypes()) {
// subs.add(sub);
// }
// }
// }
// }
// }
// subs.trimToSize();
// cached.put(subs, clazz1, clazz2);
// }
ArrayList<Subscription> subs = cached.get(clazz1, clazz2);
if (subs == null) {
// types was not empty, so collect subscriptions for each type and collate them
// save the subscriptions
final Class<?>[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response
final Class<?>[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response
Class<?> superClass1;
Class<?> superClass2;
ArrayList<Subscription> superSubs;
Subscription sub;
final int length1 = superClasses1.length;
final int length2 = superClasses2.length;
subs = new ArrayList<Subscription>(length1 + length2);
for (int i = 0; i < length1; i++) {
superClass1 = superClasses1[i];
// only go over subtypes
if (superClass1 == clazz1) {
continue;
}
for (int j = 0; j < length2; j++) {
superClass2 = superClasses2[j];
// only go over subtypes
if (superClass2 == clazz2) {
continue;
}
superSubs = subManager.getExactAsArray(superClass1, superClass2);
if (superSubs != null) {
for (int k = 0; k < superSubs.size(); k++) {
sub = superSubs.get(k);
if (sub.getHandler().acceptsSubtypes()) {
subs.add(sub);
}
}
}
}
}
subs.trimToSize();
cached.put(subs, clazz1, clazz2);
}
return subs;
// return subs;
return null;
}
/**
@ -190,72 +191,73 @@ class SubscriptionUtils {
public
ArrayList<Subscription> getSuperSubscriptions(final Class<?> clazz1, final Class<?> clazz2, final Class<?> clazz3,
final SubscriptionManager subManager) {
// whenever our subscriptions change, this map is cleared.
final HashMapTree<Class<?>, ArrayList<Subscription>> local = this.superClassSubscriptionsMulti;
ArrayList<Subscription> subs = local.get(clazz1, clazz2, clazz3);
if (subs == null) {
// types was not empty, so collect subscriptions for each type and collate them
// save the subscriptions
final Class<?>[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response
final Class<?>[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response
final Class<?>[] superClasses3 = this.superClass.getSuperClasses(clazz3); // never returns null, cached response
Class<?> superClass1;
Class<?> superClass2;
Class<?> superClass3;
ArrayList<Subscription> superSubs;
Subscription sub;
final int length1 = superClasses1.length;
final int length2 = superClasses2.length;
final int length3 = superClasses3.length;
subs = new ArrayList<Subscription>(length1 + length2 + length3);
for (int i = 0; i < length1; i++) {
superClass1 = superClasses1[i];
// only go over subtypes
if (superClass1 == clazz1) {
continue;
}
for (int j = 0; j < length2; j++) {
superClass2 = superClasses2[j];
// only go over subtypes
if (superClass2 == clazz2) {
continue;
}
for (int k = 0; k < length3; k++) {
superClass3 = superClasses3[j];
// only go over subtypes
if (superClass3 == clazz3) {
continue;
}
superSubs = subManager.getExactAsArray(superClass1, superClass2, superClass3);
if (superSubs != null) {
for (int m = 0; m < superSubs.size(); m++) {
sub = superSubs.get(m);
if (sub.getHandler().acceptsSubtypes()) {
subs.add(sub);
}
}
}
}
}
}
subs.trimToSize();
local.put(subs, clazz1, clazz2, clazz3);
}
return subs;
// // whenever our subscriptions change, this map is cleared.
// final MapTree<Class<?>, ArrayList<Subscription>> local = this.superClassSubscriptionsMulti;
//
// ArrayList<Subscription> subs = local.get(clazz1, clazz2, clazz3);
//
// if (subs == null) {
// // types was not empty, so collect subscriptions for each type and collate them
//
// // save the subscriptions
// final Class<?>[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response
// final Class<?>[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response
// final Class<?>[] superClasses3 = this.superClass.getSuperClasses(clazz3); // never returns null, cached response
//
// Class<?> superClass1;
// Class<?> superClass2;
// Class<?> superClass3;
// ArrayList<Subscription> superSubs;
// Subscription sub;
//
// final int length1 = superClasses1.length;
// final int length2 = superClasses2.length;
// final int length3 = superClasses3.length;
//
// subs = new ArrayList<Subscription>(length1 + length2 + length3);
//
// for (int i = 0; i < length1; i++) {
// superClass1 = superClasses1[i];
//
// // only go over subtypes
// if (superClass1 == clazz1) {
// continue;
// }
//
// for (int j = 0; j < length2; j++) {
// superClass2 = superClasses2[j];
//
// // only go over subtypes
// if (superClass2 == clazz2) {
// continue;
// }
//
// for (int k = 0; k < length3; k++) {
// superClass3 = superClasses3[j];
//
// // only go over subtypes
// if (superClass3 == clazz3) {
// continue;
// }
//
// superSubs = subManager.getExactAsArray(superClass1, superClass2, superClass3);
// if (superSubs != null) {
// for (int m = 0; m < superSubs.size(); m++) {
// sub = superSubs.get(m);
//
// if (sub.getHandlerAccess().acceptsSubtypes()) {
// subs.add(sub);
// }
// }
// }
// }
// }
// }
// subs.trimToSize();
// local.put(subs, clazz1, clazz2, clazz3);
// }
//
// return subs;
return null;
}
}

View File

@ -1,224 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.messagebus.utils;
import dorkbox.util.messagebus.common.HashMapTree;
import dorkbox.util.messagebus.common.MessageHandler;
import dorkbox.util.messagebus.subscription.Subscription;
import dorkbox.util.messagebus.subscription.SubscriptionManager;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public final
class VarArgUtils {
private final Map<Class<?>, Subscription[]> varArgSubscriptionsSingle;
private final HashMapTree<Class<?>, ArrayList<Subscription>> varArgSubscriptionsMulti;
private final Map<Class<?>, Subscription[]> varArgSuperSubscriptionsSingle;
private final HashMapTree<Class<?>, ArrayList<Subscription>> varArgSuperSubscriptionsMulti;
private final ClassUtils superClassUtils;
public
VarArgUtils(final ClassUtils superClassUtils, final float loadFactor, final int numberOfThreads) {
this.superClassUtils = superClassUtils;
this.varArgSubscriptionsSingle = new ConcurrentHashMap<Class<?>, Subscription[]>(16, loadFactor, numberOfThreads);
this.varArgSubscriptionsMulti = new HashMapTree<Class<?>, ArrayList<Subscription>>();
this.varArgSuperSubscriptionsSingle = new ConcurrentHashMap<Class<?>, Subscription[]>(16, loadFactor, numberOfThreads);
this.varArgSuperSubscriptionsMulti = new HashMapTree<Class<?>, ArrayList<Subscription>>();
}
public
void clear() {
this.varArgSubscriptionsSingle.clear();
this.varArgSubscriptionsMulti.clear();
this.varArgSuperSubscriptionsSingle.clear();
this.varArgSuperSubscriptionsMulti.clear();
}
// CAN NOT RETURN NULL
// check to see if the messageType can convert/publish to the "array" version, without the hit to JNI
// and then, returns the array'd version subscriptions
public
Subscription[] getVarArgSubscriptions(final Class<?> messageClass, final SubscriptionManager subManager) {
// whenever our subscriptions change, this map is cleared.
final Map<Class<?>, Subscription[]> local = this.varArgSubscriptionsSingle;
Subscription[] varArgSubs = local.get(messageClass);
if (varArgSubs == null) {
// this gets (and caches) our array type. This is never cleared.
final Class<?> arrayVersion = this.superClassUtils.getArrayClass(messageClass);
final Subscription[] subs = subManager.getSubs(arrayVersion);
if (subs != null) {
final int length = subs.length;
final ArrayList<Subscription> varArgSubsAsList = new ArrayList<Subscription>(length);
Subscription sub;
for (int i = 0; i < length; i++) {
sub = subs[i];
if (sub.getHandler().acceptsVarArgs()) {
varArgSubsAsList.add(sub);
}
}
varArgSubs = new Subscription[0];
varArgSubsAsList.toArray(varArgSubs);
local.put(messageClass, varArgSubs);
}
else {
varArgSubs = new Subscription[0];
}
}
return varArgSubs;
}
// CAN NOT RETURN NULL
// 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 superclass subscriptions
public
Subscription[] getVarArgSuperSubscriptions(final Class<?> messageClass, final SubscriptionManager subManager) {
// whenever our subscriptions change, this map is cleared.
final Map<Class<?>, Subscription[]> local = this.varArgSuperSubscriptionsSingle;
Subscription[] varArgSuperSubs = local.get(messageClass);
if (varArgSuperSubs == null) {
// this gets (and caches) our array type. This is never cleared.
final Class<?> arrayVersion = this.superClassUtils.getArrayClass(messageClass);
final Class<?>[] types = this.superClassUtils.getSuperClasses(arrayVersion);
final int typesLength = types.length;
varArgSuperSubs = new Subscription[typesLength];
if (typesLength == 0) {
local.put(messageClass, varArgSuperSubs);
return varArgSuperSubs;
}
// there are varArgs super classes for this messageClass
Class<?> type;
Subscription sub;
Subscription[] subs;
int length;
MessageHandler handlerMetadata;
for (int i = 0; i < typesLength; i++) {
type = types[i];
subs = subManager.getSubs(type);
if (subs != null) {
length = subs.length;
final ArrayList<Subscription> varArgSuperSubsAsList = new ArrayList<Subscription>(length);
for (int j = 0; j < length; j++) {
sub = subs[j];
handlerMetadata = sub.getHandler();
if (handlerMetadata.acceptsSubtypes() && handlerMetadata.acceptsVarArgs()) {
varArgSuperSubsAsList.add(sub);
}
}
varArgSuperSubs = new Subscription[varArgSuperSubsAsList.size()];
varArgSuperSubsAsList.toArray(varArgSuperSubs);
}
else {
varArgSuperSubs = new Subscription[0];
}
}
local.put(messageClass, varArgSuperSubs);
}
return varArgSuperSubs;
}
// CAN NOT RETURN NULL
// 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 superclass subscriptions
public
Subscription[] getVarArgSuperSubscriptions(final Class<?> messageClass1, final Class<?> messageClass2, final SubscriptionManager subManager) {
// whenever our subscriptions change, this map is cleared.
final HashMapTree<Class<?>, ArrayList<Subscription>> local = this.varArgSuperSubscriptionsMulti;
ArrayList<Subscription> subs = local.get(messageClass1, messageClass2);
// if (subs == null) {
// // the message class types are not the same, so look for a common superClass varArg subscription.
// // this is to publish to object[] (or any class[]) handler that is common among all superTypes of the messages
// final ArrayList<Subscription> varargSuperSubscriptions1 = getVarArgSuperSubscriptions_List(messageClass1, subManager);
// final ArrayList<Subscription> varargSuperSubscriptions2 = getVarArgSuperSubscriptions_List(messageClass2, subManager);
//
// subs = ClassUtils.findCommon(varargSuperSubscriptions1, varargSuperSubscriptions2);
//
// subs.trimToSize();
// local.put(subs, messageClass1, messageClass2);
// }
final Subscription[] returnedSubscriptions = new Subscription[subs.size()];
subs.toArray(returnedSubscriptions);
return returnedSubscriptions;
}
// CAN NOT RETURN NULL
// 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 superclass subscriptions
public
Subscription[] getVarArgSuperSubscriptions(final Class<?> messageClass1, final Class<?> messageClass2, final Class<?> messageClass3,
final SubscriptionManager subManager) {
// whenever our subscriptions change, this map is cleared.
final HashMapTree<Class<?>, ArrayList<Subscription>> local = this.varArgSuperSubscriptionsMulti;
ArrayList<Subscription> subs = local.get(messageClass1, messageClass2, messageClass3);
// if (subs == null) {
// // the message class types are not the same, so look for a common superClass varArg subscription.
// // this is to publish to object[] (or any class[]) handler that is common among all superTypes of the messages
// final ArrayList<Subscription> varargSuperSubscriptions1 = getVarArgSuperSubscriptions_List(messageClass1, subManager);
// final ArrayList<Subscription> varargSuperSubscriptions2 = getVarArgSuperSubscriptions_List(messageClass2, subManager);
// final ArrayList<Subscription> varargSuperSubscriptions3 = getVarArgSuperSubscriptions_List(messageClass3, subManager);
//
// subs = ClassUtils.findCommon(varargSuperSubscriptions1, varargSuperSubscriptions2);
// subs = ClassUtils.findCommon(subs, varargSuperSubscriptions3);
//
// subs.trimToSize();
// local.put(subs, messageClass1, messageClass2, messageClass3);
// }
final Subscription[] returnedSubscriptions = new Subscription[subs.size()];
subs.toArray(returnedSubscriptions);
return returnedSubscriptions;
}
}

View File

@ -1,25 +0,0 @@
package dorkbox.util.messagebus.perfTests;
@SuppressWarnings("Duplicates")
public class MpmcArrayQueue extends Base_BlockingQueue {
public static final int REPETITIONS = 50 * 1000 * 100;
private static final int bestRunsToAverage = 4;
private static final int runs = 10;
private static final int warmups = 3;
public static void main(final String[] args) throws Exception {
System.out.format("reps: %,d %s: \n", REPETITIONS, MpmcArrayQueue.class.getSimpleName());
for (int concurrency = 1; concurrency < 5; concurrency++) {
final org.jctools.queues.MpmcArrayQueue queue = new org.jctools.queues.MpmcArrayQueue(1 << 17);
final Integer initialValue = Integer.valueOf(777);
new MpmcArray_NonBlock().run(REPETITIONS, concurrency, concurrency, warmups, runs, bestRunsToAverage, false, queue,
initialValue);
}
}
static
class MpmcArray_NonBlock extends Base_Queue<Integer> {}
}