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 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. parameters > 1 & varity arguments.
*Many* features from the original MBassador have been removed, specifically the ONLY things to remain for a handler are *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), 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 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) 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. * Will publish to listeners with this exact message signature, as well as listeners that match the super class types signatures.
*/ */
ExactWithSuperTypes, 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.Publisher;
import dorkbox.util.messagebus.publication.PublisherExact; import dorkbox.util.messagebus.publication.PublisherExact;
import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypes; import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypes;
import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypesAndVarity;
import dorkbox.util.messagebus.subscription.SubscriptionManager; 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.AsyncDisruptor;
import dorkbox.util.messagebus.synchrony.Sync; import dorkbox.util.messagebus.synchrony.Sync;
import dorkbox.util.messagebus.synchrony.Synchrony; import dorkbox.util.messagebus.synchrony.Synchrony;
@ -37,6 +38,36 @@ import dorkbox.util.messagebus.synchrony.Synchrony;
*/ */
public public
class MessageBus implements IMessageBus { 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 ErrorHandlingSupport errorHandler;
private final SubscriptionManager subscriptionManager; 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 public
MessageBus() { 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 * @param numberOfThreads how many threads to use for dispatching async messages
*/ */
@ -74,6 +105,7 @@ class MessageBus implements IMessageBus {
MessageBus(final PublishMode publishMode) { MessageBus(final PublishMode publishMode) {
this(publishMode, Runtime.getRuntime().availableProcessors()); this(publishMode, Runtime.getRuntime().availableProcessors());
} }
/** /**
* @param publishMode Specifies which publishMode to operate the publication of messages. * @param publishMode Specifies which publishMode to operate the publication of messages.
* @param numberOfThreads how many threads to use for dispatching async messages * @param numberOfThreads how many threads to use for dispatching async messages
@ -96,17 +128,26 @@ class MessageBus implements IMessageBus {
break; break;
case ExactWithSuperTypes: case ExactWithSuperTypes:
default:
publisher = new PublisherExactWithSuperTypes(errorHandler, subscriptionManager); publisher = new PublisherExactWithSuperTypes(errorHandler, subscriptionManager);
break; break;
case ExactWithSuperTypesAndVarity:
default:
publisher = new PublisherExactWithSuperTypesAndVarity(errorHandler, subscriptionManager);
} }
syncPublication = new Sync(); 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); publisher.publish(syncPublication, message1, message2, message3);
} }
@Override
public
void publish(final Object[] messages) {
publisher.publish(syncPublication, messages);
}
@Override @Override
public public
void publishAsync(final Object message) { 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 @Override
public final public final
boolean hasPendingMessages() { 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); 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 * 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 * 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. * return.
*/ */
void publishAsync(Object message1, Object message2, Object message3); 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 public
@interface Handler { @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 * Define whether or not the handler accepts sub types of the message type it declares in its
* signature. * 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; package dorkbox.util.messagebus.common;
abstract class pad0<T> { // not thread-safe! must be synchronized in enclosing context
volatile long z0, z1, z2, z4, z5, z6 = 7L; public class Entry {
} private Object value;
private Entry next;
private Entry prev;
abstract class item1<T> extends pad0<T>{ public Entry(Object value, Entry next) {
Entry<T> next; if (next != null) {
} this.next = next;
next.prev = this;
}
abstract class pad1<T> extends item1<T> { this.value = value;
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;
} }
protected Entry() {
}
// not thread-safe! must be synchronized in enclosing context
@Override
public void remove() { public void remove() {
if (this.prev != null) { if (this.prev != null) {
this.prev.next = this.next; this.prev.next = this.next;
@ -62,13 +45,16 @@ public abstract class Entry<T> extends pad2<T> implements ISetEntry<T> {
//predecessor = null; //predecessor = null;
} }
@Override public Entry next() {
public Entry<T> next() {
return this.next; return this.next;
} }
@Override
public void clear() { public void clear() {
this.next = null; 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; package dorkbox.util.messagebus.common;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.util.messagebus.annotations.Handler; import dorkbox.util.messagebus.annotations.Handler;
import dorkbox.util.messagebus.annotations.Synchronized; import dorkbox.util.messagebus.annotations.Synchronized;
import dorkbox.util.messagebus.utils.ReflectionUtils; import dorkbox.util.messagebus.utils.ReflectionUtils;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; 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 * 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]); return finalMethods.toArray(new MessageHandler[0]);
} }
private final MethodAccess handler; private final Method method;
private final int methodIndex;
private final Class<?>[] handledMessages; private final Class<?>[] handledMessages;
private final boolean acceptsSubtypes; private final boolean acceptsSubtypes;
private final Class<?> varArgClass;
private final boolean isSynchronized; private final boolean isSynchronized;
public private
MessageHandler(Method handler, Handler handlerConfig) { MessageHandler(final Method method, final Handler config) {
super(); if (method == null) {
throw new IllegalArgumentException("The message method configuration may not be null");
if (handler == null) {
throw new IllegalArgumentException("The message handler configuration may not be null");
} }
Class<?>[] handledMessages = handler.getParameterTypes(); this.method = method;
this.acceptsSubtypes = config.acceptSubtypes();
this.handler = MethodAccess.get(handler.getDeclaringClass()); this.handledMessages = method.getParameterTypes();
this.methodIndex = this.handler.getIndex(handler.getName(), handledMessages); this.isSynchronized = ReflectionUtils.getAnnotation(method, Synchronized.class) != null;
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;
}
} }
public final public final
@ -143,14 +127,11 @@ class MessageHandler {
return this.isSynchronized; return this.isSynchronized;
} }
public final
MethodAccess getHandler() {
return this.handler;
}
public final public final
int getMethodIndex() { Method getMethod() {
return this.methodIndex; return this.method;
} }
public final public final
@ -158,31 +139,15 @@ class MessageHandler {
return this.handledMessages; return this.handledMessages;
} }
public final
Class<?> getVarArgClass() {
return this.varArgClass;
}
public final public final
boolean acceptsSubtypes() { boolean acceptsSubtypes() {
return this.acceptsSubtypes; return this.acceptsSubtypes;
} }
public final
boolean acceptsVarArgs() {
return this.varArgClass != null;
}
@Override @Override
public final public final
int hashCode() { int hashCode() {
final int prime = 31; return this.method.hashCode();
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;
} }
@Override @Override
@ -197,24 +162,8 @@ class MessageHandler {
if (getClass() != obj.getClass()) { if (getClass() != obj.getClass()) {
return false; return false;
} }
MessageHandler other = (MessageHandler) obj; MessageHandler other = (MessageHandler) obj;
if (this.acceptsSubtypes != other.acceptsSubtypes) { return this.method.equals(other.method);
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;
} }
} }

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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package dorkbox.util.messagebus.common.thread; package dorkbox.util.messagebus.common;
import java.lang.management.RuntimeMXBean; import java.lang.management.RuntimeMXBean;
import java.util.List; import java.util.List;
@ -68,7 +68,7 @@ class NamedThreadFactory implements ThreadFactory {
else { else {
try { try {
value = Integer.parseInt(stackSize.substring(4)); 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);
void publish(final Synchrony synchrony, Object message1, Object message2); 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 message1, Object message2, Object message3);
void publish(final Synchrony synchrony, Object[] messages);
} }

View File

@ -44,24 +44,14 @@ class PublisherExact implements Publisher {
// Run subscriptions // Run subscriptions
if (subscriptions != null) { if (subscriptions != null) {
Subscription sub; synchrony.publish(subscriptions, message1);
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1);
}
} }
else { else {
// Dead Event must EXACTLY MATCH (no subclasses) // Dead Event must EXACTLY MATCH (no subclasses)
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
if (deadSubscriptions != null) { if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1); synchrony.publish(deadSubscriptions, new DeadMessage(message1));
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
} }
} }
} catch (Throwable e) { } catch (Throwable e) {
@ -78,33 +68,18 @@ class PublisherExact implements Publisher {
final Class<?> messageClass1 = message1.getClass(); final Class<?> messageClass1 = message1.getClass();
final Class<?> messageClass2 = message2.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 final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null
// lock.unlockRead(stamp);
// Run subscriptions // Run subscriptions
if (subscriptions != null) { if (subscriptions != null) {
Subscription sub; synchrony.publish(subscriptions, message1, message2);
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2);
}
} }
else { else {
// Dead Event must EXACTLY MATCH (no subclasses) // Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) { if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2); synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2));
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
} }
} }
} catch (Throwable e) { } catch (Throwable e) {
@ -122,33 +97,18 @@ class PublisherExact implements Publisher {
final Class<?> messageClass2 = message2.getClass(); final Class<?> messageClass2 = message2.getClass();
final Class<?> messageClass3 = message3.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 final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null
// lock.unlockRead(stamp);
// Run subscriptions // Run subscriptions
if (subscriptions != null) { if (subscriptions != null) {
Subscription sub; synchrony.publish(subscriptions, message1, message2, message3);
for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
sub.publish(message1, message2, message3);
}
} }
else { else {
// Dead Event must EXACTLY MATCH (no subclasses) // Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) { if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2, message3); synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2, message3));
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
} }
} }
} catch (Throwable e) { } catch (Throwable e) {
@ -157,10 +117,4 @@ class PublisherExact implements Publisher {
.setPublishedObject(message1, message2, message3)); .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); synchrony.publish(subscriptions, message1);
} }
// Run superClasses // Run superSubscriptions
final Subscription[] superSubscriptions = subManager.getSuperSubs(message1Class); // can return null final Subscription[] superSubscriptions = subManager.getSuperSubs(message1Class); // NOT return null
if (superSubscriptions != null) { if (superSubscriptions.length > 0) {
hasSubs = true; hasSubs = true;
synchrony.publish(superSubscriptions, message1); synchrony.publish(superSubscriptions, message1);
} }
@ -80,34 +80,31 @@ class PublisherExactWithSuperTypes implements Publisher {
try { try {
final Class<?> messageClass1 = message1.getClass(); final Class<?> messageClass1 = message1.getClass();
final Class<?> messageClass2 = message2.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 // Run subscriptions
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null
if (subscriptions != null) { if (subscriptions != null) {
Subscription sub; hasSubs = true;
for (int i = 0; i < subscriptions.length; i++) { synchrony.publish(subscriptions, message1, message2);
sub = subscriptions[i];
sub.publish(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) // Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) { if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2); synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2));
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
} }
} }
} catch (Throwable e) { } catch (Throwable e) {
@ -124,36 +121,31 @@ class PublisherExactWithSuperTypes implements Publisher {
final Class<?> messageClass1 = message1.getClass(); final Class<?> messageClass1 = message1.getClass();
final Class<?> messageClass2 = message2.getClass(); final Class<?> messageClass2 = message2.getClass();
final Class<?> messageClass3 = message3.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 // Run subscriptions
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null
if (subscriptions != null) { if (subscriptions != null) {
Subscription sub; hasSubs = true;
for (int i = 0; i < subscriptions.length; i++) { synchrony.publish(subscriptions, message1, message2, message3);
sub = subscriptions[i];
sub.publish(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) // Dead Event must EXACTLY MATCH (no subclasses)
// stamp = lock.readLock();
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
// lock.unlockRead(stamp);
if (deadSubscriptions != null) { if (deadSubscriptions != null) {
final DeadMessage deadMessage = new DeadMessage(message1, message2, message3); synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2, message3));
Subscription sub;
for (int i = 0; i < deadSubscriptions.length; i++) {
sub = deadSubscriptions[i];
sub.publish(deadMessage);
}
} }
} }
} catch (Throwable e) { } catch (Throwable e) {
@ -162,10 +154,4 @@ class PublisherExactWithSuperTypes implements Publisher {
.setPublishedObject(message1, message2, message3)); .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 * Copyright 2016 dorkbox, llc
*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -38,12 +16,8 @@
package dorkbox.util.messagebus.subscription; package dorkbox.util.messagebus.subscription;
import com.esotericsoftware.kryo.util.IdentityMap; import com.esotericsoftware.kryo.util.IdentityMap;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.util.messagebus.common.Entry; import dorkbox.util.messagebus.common.Entry;
import dorkbox.util.messagebus.common.MessageHandler; 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.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 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, * 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. * 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 * @author dorkbox, llc
* Date: 2/2/15 * Date: 2/3/16
*/ */
public final public abstract
class Subscription { class Subscription {
private static final AtomicInteger ID_COUNTER = new AtomicInteger(); private static final AtomicInteger ID_COUNTER = new AtomicInteger();
public final int ID = ID_COUNTER.getAndIncrement(); 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 // the handler's metadata -> for each handler in a listener, a unique subscription context is created
private final MessageHandler handler; private final MessageHandler handler;
private final IHandlerInvocation invocation;
// Recommended for best performance while adhering to the "single writer principle". Must be static-final // 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, AtomicReferenceFieldUpdater.newUpdater(Subscription.class,
Entry.class, Entry.class,
"head"); "head");
// This is only touched by a single thread! // This is only touched by a single thread!
private final IdentityMap<Object, Entry> entries; // maintain a map of entries for FAST lookup during unsubscribe. 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) // 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 volatile Entry head; // reference to the first element
protected
public
Subscription(final Class<?> listenerClass, final MessageHandler handler) { Subscription(final Class<?> listenerClass, final MessageHandler handler) {
this.listenerClass = listenerClass; this.listenerClass = listenerClass;
this.handler = handler; this.handler = handler;
IHandlerInvocation invocation = new ReflectiveHandlerInvocation(); entries = new IdentityMap<Object, Entry>(32, SubscriptionManager.LOAD_FACTOR);
if (handler.isSynchronized()) {
invocation = new SynchronizedHandlerInvocation(invocation);
}
this.invocation = invocation;
entries = new IdentityMap<>(32, SubscriptionManager.LOAD_FACTOR);
if (handler.acceptsSubtypes()) { 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 // super-subscriptions to clear when we sub/unsub
} }
} }
// called on shutdown for GC purposes // called on shutdown for GC purposes
public void clear() { public final
void clear() {
this.entries.clear(); this.entries.clear();
this.head.clear(); this.head.clear();
} }
// only used in unit tests to verify that the subscription manager is working correctly // only used in unit tests to verify that the subscription manager is working correctly
public public final
Class<?> getListenerClass() { Class<?> getListenerClass() {
return listenerClass; return listenerClass;
} }
public public final
MessageHandler getHandler() { MessageHandler getHandler() {
return handler; return handler;
} }
public public final
void subscribe(final Object listener) { void subscribe(final Object listener) {
// single writer principle! // single writer principle!
Entry head = headREF.get(this); Entry head = headREF.get(this);
@ -145,7 +109,7 @@ class Subscription {
/** /**
* @return TRUE if the element was removed * @return TRUE if the element was removed
*/ */
public public final
boolean unsubscribe(final Object listener) { boolean unsubscribe(final Object listener) {
Entry entry = entries.get(listener); Entry entry = entries.get(listener);
if (entry == null || entry.getValue() == null) { if (entry == null || entry.getValue() == null) {
@ -174,68 +138,38 @@ class Subscription {
/** /**
* only used in unit tests * only used in unit tests
*/ */
public public final
int size() { int size() {
return this.entries.size; return this.entries.size;
} }
public /**
void publish(final Object message) throws Throwable { * @return true if messages were published
final MethodAccess handler = this.handler.getHandler(); */
final int handleIndex = this.handler.getMethodIndex(); public abstract
final IHandlerInvocation invocation = this.invocation; boolean publish(final Object message) throws Throwable;
Entry current = headREF.get(this); /**
Object listener; * @return true if messages were published
while (current != null) { */
listener = current.getValue(); public abstract
current = current.next(); boolean publish(final Object message1, final Object message2) throws Throwable;
invocation.invoke(listener, handler, handleIndex, message); /**
} * @return true if messages were published
} */
public abstract
public boolean publish(final Object message1, final Object message2, final Object message3) throws Throwable;
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);
}
}
@Override @Override
public public final
int hashCode() { int hashCode() {
return this.ID; return this.ID;
} }
@Override @Override
public public final
boolean equals(final Object obj) { boolean equals(final Object obj) {
if (this == obj) { if (this == obj) {
return true; return true;

View File

@ -16,22 +16,23 @@
package dorkbox.util.messagebus.subscription; package dorkbox.util.messagebus.subscription;
import com.esotericsoftware.kryo.util.IdentityMap; import com.esotericsoftware.kryo.util.IdentityMap;
import dorkbox.util.messagebus.MessageBus;
import dorkbox.util.messagebus.common.ClassTree; import dorkbox.util.messagebus.common.ClassTree;
import dorkbox.util.messagebus.common.MessageHandler; import dorkbox.util.messagebus.common.MessageHandler;
import dorkbox.util.messagebus.common.MultiClass; import dorkbox.util.messagebus.common.MultiClass;
import dorkbox.util.messagebus.error.ErrorHandlingSupport; 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.ClassUtils;
import dorkbox.util.messagebus.utils.SubscriptionUtils; import dorkbox.util.messagebus.utils.SubscriptionUtils;
import dorkbox.util.messagebus.utils.VarArgUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 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 * 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. * The subscription managers responsibility is to consistently handle and synchronize the message listener subscription process.
* It provides fast lookup of existing subscriptions when another instance of an already known * It provides fast lookup of existing subscriptions when another instance of an already known
* listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines * listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines
@ -44,10 +45,12 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public final public final
class SubscriptionManager { class SubscriptionManager {
public static final float LOAD_FACTOR = 0.8F; 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 // 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 // ONLY used by SUB/UNSUB
// remember already processed classes that do not contain any message handlers // 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 // once a collection of subscriptions is stored it does not change
private final IdentityMap<Class<?>, Subscription[]> subsPerListener; private final IdentityMap<Class<?>, Subscription[]> subsPerListener;
// We perpetually KEEP the types registered here, and just change what is sub/unsub // We perpetually KEEP the types registered here, and just change what is sub/unsub
// all subscriptions of a message type. // 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. // keeps track of all subscriptions of the super classes of a message type.
private volatile IdentityMap<Class<?>, Subscription[]> subsSuperSingle; 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 // 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 // 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. // 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 ErrorHandlingSupport errorHandler;
private final SubscriptionUtils subUtils; private final SubscriptionUtils subUtils;
private final VarArgUtils varArgUtils;
private final ClassTree<Class<?>> classTree; 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; private final ClassUtils classUtils;
@ -113,16 +105,6 @@ class SubscriptionManager {
IdentityMap.class, IdentityMap.class,
"subsSuperSingle"); "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 //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 // 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) // correct type too)
@ -131,30 +113,27 @@ class SubscriptionManager {
SubscriptionManager(final int numberOfThreads, final ErrorHandlingSupport errorHandler) { SubscriptionManager(final int numberOfThreads, final ErrorHandlingSupport errorHandler) {
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
if (MessageBus.useAsmForDispatch) {
this.subMaker = new SubMakerAsm();
}
else {
this.subMaker = new SubMakerReflection();
}
classUtils = new ClassUtils(SubscriptionManager.LOAD_FACTOR); classUtils = new ClassUtils(SubscriptionManager.LOAD_FACTOR);
// modified ONLY during SUB/UNSUB // modified ONLY during SUB/UNSUB
nonListeners = new IdentityMap<Class<?>, Boolean>(16, LOAD_FACTOR); 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); subsSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
subsMulti = new IdentityMap<MultiClass, Subscription[]>(32, LOAD_FACTOR); subsMulti = new IdentityMap<MultiClass, Subscription[]>(32, LOAD_FACTOR);
subsSuperSingle = new IdentityMap<Class<?>, 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 public
@ -174,18 +153,14 @@ class SubscriptionManager {
} }
} }
this.nonListeners.clear(); this.nonListeners.clear();
this.subsPerListener.clear(); this.subsPerListener.clear();
this.subsSingle.clear(); this.subsSingle.clear();
this.subsSuperSingle.clear(); this.subsSuperSingle.clear();
this.subsVaritySingle.clear();
this.subsSuperVaritySingle.clear();
this.classTree.clear(); this.classTree.clear();
this.classUtils.shutdown(); this.classUtils.shutdown();
} }
@ -223,201 +198,158 @@ class SubscriptionManager {
// access a snapshot of the subscriptions (single-writer-principle) // access a snapshot of the subscriptions (single-writer-principle)
final IdentityMap<Class<?>, Subscription[]> singleSubs = subsSingleREF.get(this); final IdentityMap<Class<?>, Subscription[]> singleSubs = subsSingleREF.get(this);
// final IdentityMap<MultiClass, Subscription[]> multiSubs = subsMultiREF.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);
Subscription subscription; Subscription subscription;
MessageHandler messageHandler; MessageHandler messageHandler;
Class<?>[] messageHandlerTypes; Class<?>[] messageHandlerTypes;
int messageHandlerTypesSize;
MultiClass multiClass;
Class<?> handlerType; 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++) { for (int i = 0; i < handlersSize; i++) {
// THE HANDLER IS THE SAME FOR ALL SUBSCRIPTIONS OF THE SAME TYPE!
messageHandler = messageHandlers[i]; messageHandler = messageHandlers[i];
// is this handler able to accept var args? subscription = subMaker.create(listenerClass, messageHandler);
// if (messageHandler.getVarArgClass() != null) { subscription.subscribe(listener); // register this callback listener to this subscription
// varArgPossibility.lazySet(true); subscriptions[i] = subscription;
// }
// now create a list of subscriptions for this specific handlerType (but don't add anything yet). // register for publication
// we only store things based on the FIRST type (for lookup) then parse the rest of the types during publication
messageHandlerTypes = messageHandler.getHandledMessages(); messageHandlerTypes = messageHandler.getHandledMessages();
// final int handlerSize = messageHandlerTypes.length; messageHandlerTypesSize = messageHandlerTypes.length;
// switch (handlerSize) {
// case 0: { switch (messageHandlerTypesSize) {
// // if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed) case 0: {
// // This is the SAME THING as having Void as a parameter!! // if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed)
// handlerType = Void.class; // 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); // makes this subscription visible for publication
// } final Subscription[] newSubs;
// break; Subscription[] currentSubs = singleSubs.get(handlerType);
// } if (currentSubs != null) {
// case 1: { 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]; handlerType = messageHandlerTypes[0];
if (!singleSubs.containsKey(handlerType)) { // makes this subscription visible for publication
// this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added final Subscription[] newSubs;
singleSubs.put(handlerType, SUBSCRIPTIONS); 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 singleSubs.put(handlerType, newSubs);
subscription = new Subscription(listenerClass, messageHandler);
subscriptions[i] = subscription; 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) // activates this sub for sub/unsub (only used by the subscription writer thread)
subsPerListener.put(listenerClass, subscriptions); 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) // save this snapshot back to the original (single writer principle)
subsSingleREF.lazySet(this, singleSubs); subsSingleREF.lazySet(this, singleSubs);
// subsMultiREF.lazySet(this, multiSubs); subsMultiREF.lazySet(this, multiSubs);
// subsSuperSingleREF.lazySet(this, localSuperSubs);
// subsVaritySingleREF.lazySet(this, localVaritySubs);
// subsSuperVaritySingleREF.lazySet(this, localSuperVaritySubs);
// only dump the super subscritions if it is a COMPLETELY NEW subscription. If it's not new, then the heirarchy isn't // only dump the super subscriptions if it is a COMPLETELY NEW subscription.
// changing for super subscriptions // If it's not new, then the hierarchy isn't changing for super subscriptions
subsSuperSingleREF.lazySet(this, new IdentityMap(32)); final IdentityMap<Class<?>, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this);
localSuperSubs.clear();
subsSuperSingleREF.lazySet(this, localSuperSubs);
} }
else { else {
// subscriptions already exist and must only be updated // subscriptions already exist and must only be updated
@ -475,7 +407,7 @@ class SubscriptionManager {
// for (int i = 0; i < length; i++) { // for (int i = 0; i < length; i++) {
// sub = arraySubs[i]; // sub = arraySubs[i];
// //
// if (sub.getHandler().acceptsVarArgs()) { // if (sub.getHandlerAccess().acceptsVarArgs()) {
// varArgSubsAsList.add(sub); // varArgSubsAsList.add(sub);
// } // }
// } // }
@ -501,7 +433,7 @@ class SubscriptionManager {
// for (int j = 0; j < superSubLength; j++) { // for (int j = 0; j < superSubLength; j++) {
// sub = superSubs[j]; // sub = superSubs[j];
// //
// if (sub.getHandler().acceptsSubtypes()) { // if (sub.getHandlerAccess().acceptsSubtypes()) {
// subsAsList.add(sub); // subsAsList.add(sub);
// } // }
// } // }
@ -517,28 +449,6 @@ class SubscriptionManager {
public
AtomicBoolean getVarArgPossibility() {
return varArgPossibility;
}
public
VarArgUtils getVarArgUtils() {
return varArgUtils;
}
// can return null // can return null
public public
Subscription[] getSubs(final Class<?> messageClass) { Subscription[] getSubs(final Class<?> messageClass) {
@ -574,57 +484,54 @@ class SubscriptionManager {
} }
// can NOT return null
// can return null
public public
Subscription[] getSuperSubs(final Class<?> messageClass) { 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; Subscription[] subscriptions = localSuperSubs.get(messageClass);
final ArrayList<Subscription> subsAsList = new ArrayList<Subscription>(length); // 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; final IdentityMap<Class<?>, Subscription[]> localSubs = subsSingleREF.get(this);
Subscription sub;
Subscription[] superSubs;
boolean hasSubs = false;
// walks through all of the subscriptions that might exist for super types, and if applicable, save them Class<?> superClass;
for (int i = 0; i < length; i++) { Subscription sub;
superClass = superClasses[i]; Subscription[] superSubs;
superSubs = localSubs.get(superClass);
if (superSubs != null) { // walks through all of the subscriptions that might exist for super types, and if applicable, save them
int superSubLength = superSubs.length; for (int i = 0; i < length; i++) {
for (int j = 0; j < superSubLength; j++) { superClass = superClasses[i];
sub = superSubs[j]; superSubs = localSubs.get(superClass);
if (sub.getHandler().acceptsSubtypes()) { if (superSubs != null) {
subsAsList.add(sub); int superSubLength = superSubs.length;
hasSubs = true; 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);
} }
return subscriptions;
// 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);
} }
// can return null // can return null

View File

@ -35,7 +35,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package dorkbox.util.messagebus.dispatch; package dorkbox.util.messagebus.subscription.asm;
import com.esotericsoftware.reflectasm.MethodAccess; import com.esotericsoftware.reflectasm.MethodAccess;
@ -54,7 +54,7 @@ import com.esotericsoftware.reflectasm.MethodAccess;
* @author dorkbox, llc * @author dorkbox, llc
* Date: 2/2/15 * Date: 2/2/15
*/ */
public interface IHandlerInvocation { public interface AsmInvocation {
/** /**
* Invoke the message delivery logic of this handler * 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 * @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; 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package dorkbox.util.messagebus.dispatch; package dorkbox.util.messagebus.subscription.asm;
import com.esotericsoftware.reflectasm.MethodAccess; import com.esotericsoftware.reflectasm.MethodAccess;
@ -48,10 +48,10 @@ import com.esotericsoftware.reflectasm.MethodAccess;
* Date: 2/2/15 * Date: 2/2/15
*/ */
public public
class ReflectiveHandlerInvocation implements IHandlerInvocation { class AsmReflectiveInvocation implements AsmInvocation {
public public
ReflectiveHandlerInvocation() { AsmReflectiveInvocation() {
super(); super();
} }
@ -63,21 +63,13 @@ class ReflectiveHandlerInvocation implements IHandlerInvocation {
@Override @Override
public public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2) void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2) throws Throwable {
throws Throwable {
handler.invoke(listener, methodIndex, message1, message2); handler.invoke(listener, methodIndex, message1, message2);
} }
@Override @Override
public public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2, void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2, final Object message3) throws Throwable {
final Object message3) throws Throwable {
handler.invoke(listener, methodIndex, message1, message2, message3); 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package dorkbox.util.messagebus.dispatch; package dorkbox.util.messagebus.subscription.asm;
import com.esotericsoftware.reflectasm.MethodAccess; import com.esotericsoftware.reflectasm.MethodAccess;
@ -48,12 +48,12 @@ import com.esotericsoftware.reflectasm.MethodAccess;
* Date: 2/2/15 * Date: 2/2/15
*/ */
public public
class SynchronizedHandlerInvocation implements IHandlerInvocation { class AsmSynchronizedInvocation implements AsmInvocation {
private IHandlerInvocation delegate; private AsmInvocation delegate;
public public
SynchronizedHandlerInvocation(IHandlerInvocation delegate) { AsmSynchronizedInvocation(AsmInvocation delegate) {
this.delegate = delegate; this.delegate = delegate;
} }
@ -67,8 +67,7 @@ class SynchronizedHandlerInvocation implements IHandlerInvocation {
@Override @Override
public public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2) void invoke(final Object listener, final MethodAccess handler, int methodIndex, final Object message1, final Object message2) throws Throwable {
throws Throwable {
synchronized (listener) { synchronized (listener) {
this.delegate.invoke(listener, handler, methodIndex, message1, message2); this.delegate.invoke(listener, handler, methodIndex, message1, message2);
} }
@ -76,18 +75,9 @@ class SynchronizedHandlerInvocation implements IHandlerInvocation {
@Override @Override
public public
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2, void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2, final Object message3) throws Throwable {
final Object message3) throws Throwable {
synchronized (listener) { synchronized (listener) {
this.delegate.invoke(listener, handler, methodIndex, message1, message2, message3); 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; 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.ErrorHandlingSupport;
import dorkbox.util.messagebus.error.PublicationError;
import dorkbox.util.messagebus.publication.Publisher; import dorkbox.util.messagebus.publication.Publisher;
import dorkbox.util.messagebus.subscription.Subscription; import dorkbox.util.messagebus.subscription.Subscription;
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue; 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 public
class AsyncABQ implements Synchrony { class AsyncABQ implements Synchrony {
private final ErrorHandlingSupport errorHandler; private final ArrayBlockingQueue<MessageHolder> dispatchQueue;
private final ArrayBlockingQueue<Object> dispatchQueue;
private final Collection<Thread> threads; private final Collection<Thread> threads;
/** /**
@ -31,8 +52,7 @@ class AsyncABQ implements Synchrony {
final Publisher publisher, final Publisher publisher,
final Synchrony syncPublication) { final Synchrony syncPublication) {
this.errorHandler = errorHandler; this.dispatchQueue = new ArrayBlockingQueue<MessageHolder>(1024);
this.dispatchQueue = new ArrayBlockingQueue<Object>(1024);
this.threads = new ArrayDeque<Thread>(numberOfThreads); this.threads = new ArrayDeque<Thread>(numberOfThreads);
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus"); final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
@ -43,52 +63,79 @@ class AsyncABQ implements Synchrony {
@Override @Override
public public
void run() { void run() {
final ArrayBlockingQueue<?> IN_QUEUE = AsyncABQ.this.dispatchQueue; final ArrayBlockingQueue<MessageHolder> IN_QUEUE = AsyncABQ.this.dispatchQueue;
final Publisher publisher1 = publisher; final Publisher publisher1 = publisher;
final Synchrony syncPublication1 = syncPublication; 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) { while (!AsyncABQ.this.shuttingDown) {
try { try {
//noinspection InfiniteLoopStatement event = IN_QUEUE.take();
while (true) { messageType = event.type;
final Object take = IN_QUEUE.take(); message1 = event.message1;
publisher1.publish(syncPublication1, take); 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) { } catch (InterruptedException e) {
if (!AsyncABQ.this.shuttingDown) { if (!AsyncABQ.this.shuttingDown) {
// Integer type = (Integer) MultiNode.lpMessageType(node); switch (messageType) {
// switch (type) { case MessageType.ONE: {
// case 1: { PublicationError publicationError = new PublicationError()
// errorHandler.handlePublicationError(new PublicationError().setMessage( .setMessage("Thread interrupted while processing message")
// "Thread interrupted while processing message") .setCause(e);
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node))); if (event != null) {
// break; publicationError.setPublishedObject(message1);
// } }
// case 2: {
// errorHandler.handlePublicationError(new PublicationError().setMessage( errorHandler1.handlePublicationError(publicationError);
// "Thread interrupted while processing message") break;
// .setCause(e) }
// .setPublishedObject(MultiNode.lpItem1(node), case MessageType.TWO: {
// MultiNode.lpItem2(node))); PublicationError publicationError = new PublicationError()
// break; .setMessage("Thread interrupted while processing message")
// } .setCause(e);
// case 3: {
// errorHandler.handlePublicationError(new PublicationError().setMessage( if (event != null) {
// "Thread interrupted while processing message") publicationError.setPublishedObject(message1, message2);
// .setCause(e) }
// .setPublishedObject(MultiNode.lpItem1(node),
// MultiNode.lpItem2(node), errorHandler1.handlePublicationError(publicationError);
// MultiNode.lpItem3(node))); break;
// break; }
// } case MessageType.THREE: {
// default: { PublicationError publicationError = new PublicationError()
// errorHandler.handlePublicationError(new PublicationError().setMessage( .setMessage("Thread interrupted while processing message")
// "Thread interrupted while processing message") .setCause(e);
// .setCause(e)
// .setPublishedObject(MultiNode.lpItem1(node))); 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 public
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable { 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 @Override
public public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable { void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2));
} }
@Override @Override
public public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable { 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));
}
@Override
public
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
} }
public 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; package dorkbox.util.messagebus.synchrony;
import com.lmax.disruptor.LiteBlockingWaitStrategy; import com.lmax.disruptor.LiteBlockingWaitStrategy;
@ -8,14 +23,13 @@ import com.lmax.disruptor.SequenceBarrier;
import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.Sequencer;
import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.WorkProcessor; 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.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.publication.Publisher; import dorkbox.util.messagebus.publication.Publisher;
import dorkbox.util.messagebus.publication.disruptor.EventBusFactory; import dorkbox.util.messagebus.synchrony.disruptor.EventBusFactory;
import dorkbox.util.messagebus.publication.disruptor.MessageHandler; import dorkbox.util.messagebus.synchrony.disruptor.MessageHandler;
import dorkbox.util.messagebus.publication.disruptor.MessageHolder; import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
import dorkbox.util.messagebus.publication.disruptor.MessageType; import dorkbox.util.messagebus.synchrony.disruptor.PublicationExceptionHandler;
import dorkbox.util.messagebus.publication.disruptor.PublicationExceptionHandler;
import dorkbox.util.messagebus.subscription.Subscription; import dorkbox.util.messagebus.subscription.Subscription;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -23,6 +37,9 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
/**
* @author dorkbox, llc Date: 2/3/16
*/
public public
class AsyncDisruptor implements Synchrony { 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 // gets the sequences used for processing work
private private
Sequence[] getSequences() { 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; package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.subscription.Subscription; import dorkbox.util.messagebus.subscription.Subscription;
/**
* @author dorkbox, llc Date: 2/2/15
*/
public public
class Sync implements Synchrony { class Sync implements Synchrony {
public public
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable { void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
Subscription sub; Subscription sub;
boolean hasSubs = false;
for (int i = 0; i < subscriptions.length; i++) { for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i]; sub = subscriptions[i];
sub.publish(message1); sub.publish(message1);
@ -16,19 +35,21 @@ class Sync implements Synchrony {
@Override @Override
public public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable { 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 @Override
public public
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable { void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
Subscription sub;
} for (int i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
@Override sub.publish(message1, message2, message3);
public }
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
} }
@Override @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; package dorkbox.util.messagebus.synchrony;
import dorkbox.util.messagebus.subscription.Subscription; import dorkbox.util.messagebus.subscription.Subscription;
/** /**
* * @author dorkbox, llc Date: 2/3/16
*/ */
public public
interface Synchrony { interface Synchrony {
void publish(final Subscription[] subscriptions, Object message1) throws Throwable; 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) throws Throwable ;
void publish(final Subscription[] subscriptions, Object message1, Object message2, Object message3) 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 start();
void shutdown(); 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.LifecycleAware;
import com.lmax.disruptor.WorkHandler; import com.lmax.disruptor.WorkHandler;
import dorkbox.util.messagebus.synchrony.MessageHolder;
import dorkbox.util.messagebus.synchrony.Synchrony; import dorkbox.util.messagebus.synchrony.Synchrony;
import dorkbox.util.messagebus.publication.Publisher; import dorkbox.util.messagebus.publication.Publisher;
@ -47,11 +63,6 @@ class MessageHandler implements WorkHandler<MessageHolder>, LifecycleAware {
this.publisher.publish(syncPublication, message3, message1, message2); this.publisher.publish(syncPublication, message3, message1, message2);
return; 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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 final class MessageType {
public static final int ONE = 1; public static final int ONE = 1;
public static final int TWO = 2; public static final int TWO = 2;
public static final int THREE = 3; public static final int THREE = 3;
public static final int ARRAY = 4;
private MessageType() { 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 com.lmax.disruptor.ExceptionHandler;
import dorkbox.util.messagebus.error.ErrorHandlingSupport; import dorkbox.util.messagebus.error.ErrorHandlingSupport;
import dorkbox.util.messagebus.error.PublicationError; import dorkbox.util.messagebus.error.PublicationError;
/**
* @author dorkbox, llc Date: 2/3/16
*/
public final class PublicationExceptionHandler<T> implements ExceptionHandler<T> { public final class PublicationExceptionHandler<T> implements ExceptionHandler<T> {
private final ErrorHandlingSupport errorHandler; private final ErrorHandlingSupport errorHandler;

View File

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