diff --git a/README.md b/README.md index 45e3ff8..a8ad13e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ MessageBus ========== -The MessageBus is a fork from MBassador, and it is a high-performance, very-low GC, custom distribution that supports method signature +The MessageBus is a fork from MBassador, and it is a high-performance, zero GC, custom distribution that supports method signature parameters > 1 & varity arguments. *Many* features from the original MBassador have been removed, specifically the ONLY things to remain for a handler are @@ -10,7 +10,8 @@ parameters > 1 & varity arguments. Additionally, the bus *must* explicitly be started now (because of errorhandling when starting the disruptor), ie: ```.start()```, and conversely ```.shutdown()``` is necessary to shutdown the disruptor/thread pool. During the distillation -process, the API has changed, and the only way to publish now is to actually call ```bus.publish()``` or ```bus.publishAsync()```. +process, the API has changed, and the only way to publish now is to actually call ```bus.publish()``` or ```bus.publishAsync()```, of +note, the asynchronous publication of messages is not in a guaranteed order. The largest change however, is the ability to publish N-number of objects. A single object (or all-matching-types, when more than one) diff --git a/src/dorkbox/util/messagebus/IMessageBus.java b/src/dorkbox/util/messagebus/IMessageBus.java index 55ec8a4..a253e0e 100644 --- a/src/dorkbox/util/messagebus/IMessageBus.java +++ b/src/dorkbox/util/messagebus/IMessageBus.java @@ -109,12 +109,6 @@ public interface IMessageBus extends PubSubSupport { * Will publish to listeners with this exact message signature, as well as listeners that match the super class types signatures. */ ExactWithSuperTypes, - - /** - * Will publish to listeners with this exact message signature, as well as listeners that match the super class types signatures. - * and to listeners that have matching varity arguments. (ie: a listener that matches Object[], will accept messages of type Object) - */ - ExactWithSuperTypesAndVarity, } /** diff --git a/src/dorkbox/util/messagebus/MessageBus.java b/src/dorkbox/util/messagebus/MessageBus.java index 2621a12..5a19c72 100644 --- a/src/dorkbox/util/messagebus/MessageBus.java +++ b/src/dorkbox/util/messagebus/MessageBus.java @@ -20,8 +20,9 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport; import dorkbox.util.messagebus.publication.Publisher; import dorkbox.util.messagebus.publication.PublisherExact; import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypes; -import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypesAndVarity; import dorkbox.util.messagebus.subscription.SubscriptionManager; +import dorkbox.util.messagebus.synchrony.AsyncABQ; +import dorkbox.util.messagebus.synchrony.AsyncABQ_noGc; import dorkbox.util.messagebus.synchrony.AsyncDisruptor; import dorkbox.util.messagebus.synchrony.Sync; import dorkbox.util.messagebus.synchrony.Synchrony; @@ -37,6 +38,36 @@ import dorkbox.util.messagebus.synchrony.Synchrony; */ public class MessageBus implements IMessageBus { + + public static boolean useDisruptorForAsyncPublish = true; + public static boolean useAsmForDispatch = true; + + public static boolean useNoGarbageVersionOfABQ = true; + + static { + // check to see if we can use ASM for method access (it's a LOT faster than reflection). By default, we use ASM. + if (useAsmForDispatch) { + // only bother checking if we are different that the defaults + try { + Class.forName("com.esotericsoftware.reflectasm.MethodAccess"); + } catch (Exception e) { + useAsmForDispatch = false; + } + } + + + // check to see if we can use the disruptor for publication (otherwise, we use native java). The disruptor is a lot faster, but + // not available on all platforms/JRE's because of it's use of UNSAFE. + if (useDisruptorForAsyncPublish) { + // only bother checking if we are different that the defaults + try { + Class.forName("com.lmax.disruptor.RingBuffer"); + } catch (Exception e) { + useDisruptorForAsyncPublish = false; + } + } + } + private final ErrorHandlingSupport errorHandler; private final SubscriptionManager subscriptionManager; @@ -47,7 +78,7 @@ class MessageBus implements IMessageBus { /** - * By default, will permit subTypes and Varity Argument matching, and will use half of CPUs available for dispatching async messages + * By default, will permit subType matching, and will use half of CPUs available for dispatching async messages */ public MessageBus() { @@ -55,7 +86,7 @@ class MessageBus implements IMessageBus { } /** - * By default, will permit subTypes and Varity Argument matching + * By default, will permit subType matching * * @param numberOfThreads how many threads to use for dispatching async messages */ @@ -74,6 +105,7 @@ class MessageBus implements IMessageBus { MessageBus(final PublishMode publishMode) { this(publishMode, Runtime.getRuntime().availableProcessors()); } + /** * @param publishMode Specifies which publishMode to operate the publication of messages. * @param numberOfThreads how many threads to use for dispatching async messages @@ -96,17 +128,26 @@ class MessageBus implements IMessageBus { break; case ExactWithSuperTypes: + default: publisher = new PublisherExactWithSuperTypes(errorHandler, subscriptionManager); break; - - case ExactWithSuperTypesAndVarity: - default: - publisher = new PublisherExactWithSuperTypesAndVarity(errorHandler, subscriptionManager); } syncPublication = new Sync(); -// asyncPublication = new PubAsync(numberOfThreads, errorHandler, publisher, syncPublication); - asyncPublication = new AsyncDisruptor(numberOfThreads, errorHandler, publisher, syncPublication); + + // the disruptor is preferred, but if it cannot be loaded -- we want to try to continue working, hence the use of ArrayBlockingQueue + if (useDisruptorForAsyncPublish) { + asyncPublication = new AsyncDisruptor(numberOfThreads, errorHandler, publisher, syncPublication); + } else { + if (useNoGarbageVersionOfABQ) { + // no garbage is created, but this is slow (but faster than other messagebus implementations) + asyncPublication = new AsyncABQ_noGc(numberOfThreads, errorHandler, publisher, syncPublication); + } + else { + // garbage is created, but this is fast + asyncPublication = new AsyncABQ(numberOfThreads, errorHandler, publisher, syncPublication); + } + } } /** @@ -160,12 +201,6 @@ class MessageBus implements IMessageBus { publisher.publish(syncPublication, message1, message2, message3); } - @Override - public - void publish(final Object[] messages) { - publisher.publish(syncPublication, messages); - } - @Override public void publishAsync(final Object message) { @@ -204,22 +239,6 @@ class MessageBus implements IMessageBus { // } } - @Override - public - void publishAsync(final Object[] messages) { -// if (messages != null) { -// try { -// this.dispatchQueue.transfer(messages, MessageType.ARRAY); -// } catch (Exception e) { -// errorHandler.handlePublicationError(new PublicationError().setMessage( -// "Error while adding an asynchronous message").setCause(e).setPublishedObject(messages)); -// } -// } -// else { -// throw new NullPointerException("Message cannot be null."); -// } - } - @Override public final boolean hasPendingMessages() { diff --git a/src/dorkbox/util/messagebus/MpmcMultiTransferArrayQueue.java b/src/dorkbox/util/messagebus/MpmcMultiTransferArrayQueue.java deleted file mode 100644 index 504a106..0000000 --- a/src/dorkbox/util/messagebus/MpmcMultiTransferArrayQueue.java +++ /dev/null @@ -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 { - 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 nodeThreadLocal = new ThreadLocal() { - @Override - protected - Object initialValue() { - return new MultiNode(); - } - }; - - private static final ThreadLocal randomThreadLocal = new ThreadLocal() { - @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 - *

- * Place an item on the queue, and wait as long as necessary for a corresponding consumer to take it. - *

- * The item can be a single object (MessageType.ONE), or an array object (MessageType.ARRAY) - *

- */ - 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 - *

- * 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 - *

- * 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 - *

- * 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. - *

- * This method does not depend on thread-local for node information, and so is more efficient. - *

- * 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); - } -} diff --git a/src/dorkbox/util/messagebus/MultiNode.java b/src/dorkbox/util/messagebus/MultiNode.java deleted file mode 100644 index 40741fb..0000000 --- a/src/dorkbox/util/messagebus/MultiNode.java +++ /dev/null @@ -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() { - } -} diff --git a/src/dorkbox/util/messagebus/PubSubSupport.java b/src/dorkbox/util/messagebus/PubSubSupport.java index 0057a26..36bb93c 100644 --- a/src/dorkbox/util/messagebus/PubSubSupport.java +++ b/src/dorkbox/util/messagebus/PubSubSupport.java @@ -90,14 +90,6 @@ public interface PubSubSupport { */ void publish(Object message1, Object message2, Object message3); - /** - * Synchronously publish AN ARRAY of messages to all registered listeners (that match the signature). This - * includes listeners defined for super types of the given message type, provided they are not configured - * to reject valid subtypes. The call returns when all matching handlers of all registered listeners have - * been notified (invoked) of the message. - */ - void publish(Object[] message); - /** * Publish the message asynchronously to all registered listeners (that match the signature). This includes * listeners defined for super types of the given message type, provided they are not configured to reject @@ -136,17 +128,4 @@ public interface PubSubSupport { * return. */ void publishAsync(Object message1, Object message2, Object message3); - - /** - * Publish AN ARRAY 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. - *

- *

- * 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); } diff --git a/src/dorkbox/util/messagebus/annotations/Handler.java b/src/dorkbox/util/messagebus/annotations/Handler.java index e8ba9b5..cadf5cd 100644 --- a/src/dorkbox/util/messagebus/annotations/Handler.java +++ b/src/dorkbox/util/messagebus/annotations/Handler.java @@ -54,14 +54,6 @@ import java.lang.annotation.*; public @interface Handler { - /** - * Define whether or not the handler accepts variable arguments it declares in its signature. - * VarArg "acceptance" means that the handler, handle(String... s), will accept a publication - * of ("s"), ("s", "s"), or (String[3]{"s", "s", "s"}). By default, handle(String... s) will - * only handle publications that are exactly an array (String[3]{"s"}) - */ - boolean acceptVarargs() default false; - /** * Define whether or not the handler accepts sub types of the message type it declares in its * signature. diff --git a/src/dorkbox/util/messagebus/annotations/Listener.java b/src/dorkbox/util/messagebus/annotations/Listener.java deleted file mode 100644 index f214787..0000000 --- a/src/dorkbox/util/messagebus/annotations/Listener.java +++ /dev/null @@ -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: - *

- * 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 {} diff --git a/src/dorkbox/util/messagebus/common/AbstractConcurrentSet.java b/src/dorkbox/util/messagebus/common/AbstractConcurrentSet.java deleted file mode 100644 index 62431a4..0000000 --- a/src/dorkbox/util/messagebus/common/AbstractConcurrentSet.java +++ /dev/null @@ -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 implements Set { - 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> entries; // maintain a map of entries for O(log n) lookup - - public volatile Entry head; // reference to the first element - volatile long z0, z1, z2, z4, z5, z6 = 7L; - - - protected - AbstractConcurrentSet(Map> entries) { - this.entries = entries; - } - - protected abstract - Entry createEntry(T value, Entry 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 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 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 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[] 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; - } - - -} diff --git a/src/dorkbox/util/messagebus/common/ClassTree.java b/src/dorkbox/util/messagebus/common/ClassTree.java new file mode 100644 index 0000000..d7204cb --- /dev/null +++ b/src/dorkbox/util/messagebus/common/ClassTree.java @@ -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 { + + + + public static int INITIAL_SIZE = 4; + public static float LOAD_FACTOR = 0.8F; + + private static + final ThreadLocal keyCache = new ThreadLocal() { + @Override + protected + IdentityMap initialValue() { + return new IdentityMap(INITIAL_SIZE, LOAD_FACTOR); + } + }; + + private static + final ThreadLocal valueCache = new ThreadLocal(); + + private AtomicReference children = new AtomicReference(); + private AtomicReference value = new AtomicReference(); + private AtomicInteger valueId = new AtomicInteger(Integer.MIN_VALUE); + + + @SuppressWarnings("unchecked") + public static IdentityMap> cast(Object o) { + return (IdentityMap>) 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 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 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 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 leaf = getOrCreateLeaf(keys[0]); + for (int i=1;i 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> 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 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(); + 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 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; + } +} diff --git a/src/dorkbox/util/messagebus/common/Entry.java b/src/dorkbox/util/messagebus/common/Entry.java index 7d690f9..0e06404 100644 --- a/src/dorkbox/util/messagebus/common/Entry.java +++ b/src/dorkbox/util/messagebus/common/Entry.java @@ -15,38 +15,21 @@ */ package dorkbox.util.messagebus.common; -abstract class pad0 { - volatile long z0, z1, z2, z4, z5, z6 = 7L; -} +// not thread-safe! must be synchronized in enclosing context +public class Entry { + private Object value; + private Entry next; + private Entry prev; -abstract class item1 extends pad0{ - Entry next; -} + public Entry(Object value, Entry next) { + if (next != null) { + this.next = next; + next.prev = this; + } -abstract class pad1 extends item1 { - volatile long z0, z1, z2, z4, z5, z6 = 7L; -} - -abstract class item2 extends pad1 { - Entry prev; -} - -abstract class pad2 extends item2 { - volatile long z0, z1, z2, z4, z5, z6 = 7L; -} - - -public abstract class Entry extends pad2 implements ISetEntry { - protected Entry(Entry next) { - this.next = next; - next.prev = this; + this.value = value; } - protected Entry() { - } - - // not thread-safe! must be synchronized in enclosing context - @Override public void remove() { if (this.prev != null) { this.prev.next = this.next; @@ -62,13 +45,16 @@ public abstract class Entry extends pad2 implements ISetEntry { //predecessor = null; } - @Override - public Entry next() { + public Entry next() { return this.next; } - @Override public void clear() { this.next = null; } + + public + Object getValue() { + return value; + } } diff --git a/src/dorkbox/util/messagebus/common/HashMapTree.java b/src/dorkbox/util/messagebus/common/HashMapTree.java deleted file mode 100644 index af02adc..0000000 --- a/src/dorkbox/util/messagebus/common/HashMapTree.java +++ /dev/null @@ -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 { - public static int INITIAL_SIZE = 4; - public static float LOAD_FACTOR = 0.8F; - - - private static - final ThreadLocal keyCache = new ThreadLocal() { - @Override - protected - Object initialValue() { - return new ConcurrentHashMap(INITIAL_SIZE, LOAD_FACTOR, 1); - } - }; - - private static - final ThreadLocal valueCache = new ThreadLocal() { - @Override - protected - Object initialValue() { - return new HashMapTree(); - } - }; - -// Map> - private AtomicReference children = new AtomicReference(); - private VALUE value; - - - - @SuppressWarnings("unchecked") - public static ConcurrentMap> cast(Object o) { - return (ConcurrentMap>) 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>> entrySet = this.children.entrySet(); -// for (Entry> 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 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 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 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 leaf = createLeaf(keys[0]); - for (int i=1;i createLeaf(KEY... keys) { - if (keys == null) { - return this; - } - - int length = keys.length; - - // have to put value into our children - HashMapTree leaf = createLeaf(keys[0]); - for (int i=1;i createLeaf(KEY key) { - if (key == null) { - return null; - } - - final Object cached = keyCache.get(); - final Object checked = children.get(); - ConcurrentMap> 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 tree = kids.putIfAbsent(key, (HashMapTree) cached2); - if (tree == null) { - // was absent - valueCache.set(new HashMapTree()); - return (HashMapTree) cached2; - } - else { - return tree; - } - } - - - - - - - ///////////////////////////////////////// - ///////////////////////////////////////// - ///////////////////////////////////////// - ///////////////////////////////////////// - - - public final VALUE get(KEY key) { - if (key == null) { - return null; - } - - HashMapTree 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 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 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 tree; - - // publish value from our children - tree = getLeaf(keys[0]); - - int size = keys.length; - for (int i=1;i getLeaf(KEY key) { - if (key == null) { - return null; - } - - HashMapTree tree; - - if (this.children == null) { - tree = null; - } else { - final ConcurrentMap> o = cast(this.children.get()); - tree = o.get(key); - } - - return tree; - } - - - public final HashMapTree getLeaf(KEY key1, KEY key2) { - HashMapTree tree; - - // publish value from our children - tree = getLeaf(key1); - if (tree != null) { - tree = tree.getLeaf(key2); - } - - return tree; - } - - public final HashMapTree getLeaf(KEY key1, KEY key2, KEY key3) { - HashMapTree 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 getLeaf(KEY... keys) { - int size = keys.length; - - if (size == 0) { - return null; - } - - HashMapTree tree; - // publish value from our children - tree = getLeaf(keys[0]); - - for (int i=1;iIS NOT safe to call outside of root.remove(...) - *

- * Removes a branch from the tree, and cleans up, if necessary - */ - public final void remove(KEY key) { - if (key != null) { - removeLeaf(key); - } - } - - - /** - * This IS NOT safe to call outside of root.remove(...) - *

- * 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 leaf; - if (this.children != null) { - leaf = getLeaf(key1); - - if (leaf != null) { - leaf.removeLeaf(key2); - - final ConcurrentMap> o = cast(this.children.get()); - o.remove(key1); - - if (o.isEmpty()) { - this.children.compareAndSet(o, null); - } - } - } - } - - /** - * This IS NOT safe to call outside of root.remove(...) - *

- * 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 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 IS NOT safe to call outside of root.remove(...) - *

- * 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> 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> kids = cast(this.children.get()); - if (kids != null) { -// HashMapTree 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; -// } -// } - } - } - } -} diff --git a/src/dorkbox/util/messagebus/common/ISetEntry.java b/src/dorkbox/util/messagebus/common/ISetEntry.java deleted file mode 100644 index c59cd50..0000000 --- a/src/dorkbox/util/messagebus/common/ISetEntry.java +++ /dev/null @@ -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 getValue(); - - // not thread-safe! must be synchronized in enclosing context - void remove(); - - ISetEntry next(); - - void clear(); -} diff --git a/src/dorkbox/util/messagebus/common/MessageHandler.java b/src/dorkbox/util/messagebus/common/MessageHandler.java index 092abe9..b63aeaf 100644 --- a/src/dorkbox/util/messagebus/common/MessageHandler.java +++ b/src/dorkbox/util/messagebus/common/MessageHandler.java @@ -37,14 +37,12 @@ */ package dorkbox.util.messagebus.common; -import com.esotericsoftware.reflectasm.MethodAccess; import dorkbox.util.messagebus.annotations.Handler; import dorkbox.util.messagebus.annotations.Synchronized; import dorkbox.util.messagebus.utils.ReflectionUtils; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; /** * Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains @@ -104,38 +102,24 @@ class MessageHandler { return finalMethods.toArray(new MessageHandler[0]); } - private final MethodAccess handler; - private final int methodIndex; + private final Method method; + private final Class[] handledMessages; private final boolean acceptsSubtypes; - private final Class varArgClass; private final boolean isSynchronized; - public - MessageHandler(Method handler, Handler handlerConfig) { - super(); - - if (handler == null) { - throw new IllegalArgumentException("The message handler configuration may not be null"); + private + MessageHandler(final Method method, final Handler config) { + if (method == null) { + throw new IllegalArgumentException("The message method configuration may not be null"); } - Class[] handledMessages = handler.getParameterTypes(); - - this.handler = MethodAccess.get(handler.getDeclaringClass()); - this.methodIndex = this.handler.getIndex(handler.getName(), handledMessages); - - this.acceptsSubtypes = handlerConfig.acceptSubtypes(); - this.isSynchronized = ReflectionUtils.getAnnotation(handler, Synchronized.class) != null; - this.handledMessages = handledMessages; - - if (handledMessages.length == 1 && handledMessages[0].isArray() && handlerConfig.acceptVarargs()) { - this.varArgClass = handledMessages[0].getComponentType(); - } - else { - this.varArgClass = null; - } + this.method = method; + this.acceptsSubtypes = config.acceptSubtypes(); + this.handledMessages = method.getParameterTypes(); + this.isSynchronized = ReflectionUtils.getAnnotation(method, Synchronized.class) != null; } public final @@ -143,14 +127,11 @@ class MessageHandler { return this.isSynchronized; } - public final - MethodAccess getHandler() { - return this.handler; - } + public final - int getMethodIndex() { - return this.methodIndex; + Method getMethod() { + return this.method; } public final @@ -158,31 +139,15 @@ class MessageHandler { return this.handledMessages; } - public final - Class getVarArgClass() { - return this.varArgClass; - } - public final boolean acceptsSubtypes() { return this.acceptsSubtypes; } - public final - boolean acceptsVarArgs() { - return this.varArgClass != null; - } - @Override public final int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (this.acceptsSubtypes ? 1231 : 1237); - result = prime * result + Arrays.hashCode(this.handledMessages); - result = prime * result + (this.handler == null ? 0 : this.handler.hashCode()); - result = prime * result + (this.isSynchronized ? 1231 : 1237); - return result; + return this.method.hashCode(); } @Override @@ -197,24 +162,8 @@ class MessageHandler { if (getClass() != obj.getClass()) { return false; } + MessageHandler other = (MessageHandler) obj; - if (this.acceptsSubtypes != other.acceptsSubtypes) { - return false; - } - if (!Arrays.equals(this.handledMessages, other.handledMessages)) { - return false; - } - if (this.handler == null) { - if (other.handler != null) { - return false; - } - } - else if (!this.handler.equals(other.handler)) { - return false; - } - if (this.isSynchronized != other.isSynchronized) { - return false; - } - return true; + return this.method.equals(other.method); } } diff --git a/src/dorkbox/util/messagebus/common/MultiClass.java b/src/dorkbox/util/messagebus/common/MultiClass.java new file mode 100644 index 0000000..f96fb1f --- /dev/null +++ b/src/dorkbox/util/messagebus/common/MultiClass.java @@ -0,0 +1,50 @@ +package dorkbox.util.messagebus.common; + +/** + * + */ +public +class MultiClass implements Comparable { + 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; + } +} diff --git a/src/dorkbox/util/messagebus/common/thread/NamedThreadFactory.java b/src/dorkbox/util/messagebus/common/NamedThreadFactory.java similarity index 97% rename from src/dorkbox/util/messagebus/common/thread/NamedThreadFactory.java rename to src/dorkbox/util/messagebus/common/NamedThreadFactory.java index ef19e6e..1aad502 100644 --- a/src/dorkbox/util/messagebus/common/thread/NamedThreadFactory.java +++ b/src/dorkbox/util/messagebus/common/NamedThreadFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.util.messagebus.common.thread; +package dorkbox.util.messagebus.common; import java.lang.management.RuntimeMXBean; import java.util.List; @@ -68,7 +68,7 @@ class NamedThreadFactory implements ThreadFactory { else { try { value = Integer.parseInt(stackSize.substring(4)); - } catch (Exception e) { + } catch (Exception ignored) { } } diff --git a/src/dorkbox/util/messagebus/common/StrongConcurrentSet.java b/src/dorkbox/util/messagebus/common/StrongConcurrentSet.java deleted file mode 100644 index 46d1bb6..0000000 --- a/src/dorkbox/util/messagebus/common/StrongConcurrentSet.java +++ /dev/null @@ -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. - *

- * - * @author bennidi - * Date: 2/12/12 - */ -public -class StrongConcurrentSet extends AbstractConcurrentSet { - - - public - StrongConcurrentSet() { - this(16, 0.75f); - } - - public - StrongConcurrentSet(int size, float loadFactor) { - this(new HashMap>(size, loadFactor)); - } - - public - StrongConcurrentSet(Map> entries) { - super(entries); - } - - @Override - public - Iterator iterator() { - return new Iterator() { - private ISetEntry 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 newCurrent = this.current.next(); - StrongConcurrentSet.this.remove(this.current.getValue()); - this.current = newCurrent; - } - }; - } - - @Override - protected - Entry createEntry(T value, Entry next) { - return next != null ? new StrongEntry(value, next) : new StrongEntry(value); - } - - - public static - class StrongEntry extends Entry { - - private T value; - - private - StrongEntry(T value, Entry next) { - super(next); - this.value = value; - } - - private - StrongEntry(T value) { - super(); - this.value = value; - } - - @Override - public - T getValue() { - return this.value; - } - } -} diff --git a/src/dorkbox/util/messagebus/common/StrongConcurrentSetV8.java b/src/dorkbox/util/messagebus/common/StrongConcurrentSetV8.java deleted file mode 100644 index 8ad70ec..0000000 --- a/src/dorkbox/util/messagebus/common/StrongConcurrentSetV8.java +++ /dev/null @@ -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 - *

- * - * @author dorkbox - * Date: 2/2/15 - */ -public class StrongConcurrentSetV8 extends StrongConcurrentSet { - - 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>(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>(size, loadFactor, stripeSize)); - } -} diff --git a/src/dorkbox/util/messagebus/common/WeakConcurrentSet.java b/src/dorkbox/util/messagebus/common/WeakConcurrentSet.java deleted file mode 100644 index b230a12..0000000 --- a/src/dorkbox/util/messagebus/common/WeakConcurrentSet.java +++ /dev/null @@ -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 extends AbstractConcurrentSet { - - - public - WeakConcurrentSet() { - super(new WeakHashMap>()); - } - - @Override - public - Iterator iterator() { - return new Iterator() { - - // the current list element of this iterator - // used to keep track of the iteration process - private ISetEntry 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 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 newCurrent = this.current.next(); - WeakConcurrentSet.this.remove(this.current.getValue()); - this.current = newCurrent; - } - }; - } - - @Override - protected - Entry createEntry(T value, Entry next) { - return next != null ? new WeakEntry(value, next) : new WeakEntry(value); - } - - - public static - class WeakEntry extends Entry { - - private WeakReference value; - - private - WeakEntry(T value, Entry next) { - super(next); - this.value = new WeakReference(value); - } - - private - WeakEntry(T value) { - super(); - this.value = new WeakReference(value); - } - - @Override - public - T getValue() { - return this.value.get(); - } - } -} diff --git a/src/dorkbox/util/messagebus/common/thread/ConcurrentLinkedQueue2.java b/src/dorkbox/util/messagebus/common/thread/ConcurrentLinkedQueue2.java deleted file mode 100644 index 5b65131..0000000 --- a/src/dorkbox/util/messagebus/common/thread/ConcurrentLinkedQueue2.java +++ /dev/null @@ -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 head of the queue is that element that has been on the - * queue the longest time. - * The tail 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. - * - *

This implementation employs an efficient non-blocking - * algorithm based on one described in Simple, - * Fast, and Practical Non-Blocking and Blocking Concurrent Queue - * Algorithms by Maged M. Michael and Michael L. Scott. - * - *

Iterators are weakly consistent, returning elements - * reflecting the state of the queue at some point at or since the - * creation of the iterator. They do not 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. - * - *

Beware that, unlike in most collections, the {@code size} method - * is NOT 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 not guaranteed - * to be performed atomically. For example, an iterator operating - * concurrently with an {@code addAll} operation might view only some - * of the added elements. - * - *

This class and its iterator implement all of the optional - * methods of the {@link Queue} and {@link Iterator} interfaces. - * - *

Memory consistency effects: As with other concurrent - * collections, actions in a thread prior to placing an object into a - * {@code ConcurrentLinkedQueue} - * happen-before - * actions subsequent to the access or removal of that element from - * the {@code ConcurrentLinkedQueue} in another thread. - * - *

This class is a member of the - * - * Java Collections Framework. - * - * @since 1.5 - * @author Doug Lea - * @param the type of elements held in this collection - */ -public class ConcurrentLinkedQueue2 extends AbstractQueue - implements Queue, 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 { - volatile E item; - volatile Node 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 val) { - UNSAFE.putOrderedObject(this, nextOffset, val); - } - - boolean casNext(Node cmp, Node 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 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 tail; - - /** - * Creates a {@code ConcurrentLinkedQueue} that is initially empty. - */ - public ConcurrentLinkedQueue2() { - head = tail = new Node(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 c) { - Node h = null, t = null; - for (E e : c) { - checkNotNull(e); - Node newNode = new Node(e); - if (h == null) - h = t = newNode; - else { - t.lazySetNext(newNode); - t = newNode; - } - } - if (h == null) - h = t = new Node(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 h, Node 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 succ(Node p) { - Node 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> quickLookup = new IdentityMap>(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 newNode = new Node(e); - - for (Node t = tail, p = t;;) { - Node 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 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 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 first() { - restartFromHead: - for (;;) { - for (Node 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}. - * - *

Beware that, unlike in most collections, this method is - * NOT 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 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 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 pred = null; - for (Node p = first(); p != null; p = succ(p)) { - E item = p.item; - if (item != null && - o.equals(item) && - p.casItem(item, null)) { - Node 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 c) { - if (c == this) - // As historically specified in AbstractQueue#addAll - throw new IllegalArgumentException(); - - // Copy c into a private chain of Nodes - Node beginningOfTheEnd = null, last = null; - for (E e : c) { - checkNotNull(e); - Node newNode = new Node(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 t = tail, p = t;;) { - Node 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. - * - *

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. - * - *

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 al = new ArrayList(); - for (Node 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. - * - *

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}. - * - *

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. - * - *

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}: - * - *

 {@code String[] y = x.toArray(new String[0]);}
- * - * 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[] toArray(T[] a) { - // try to use sent-in array - int k = 0; - Node 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 al = new ArrayList(); - for (Node 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). - * - *

The returned iterator is - * weakly consistent. - * - * @return an iterator over the elements in this queue in proper sequence - */ - public Iterator iterator() { - return new Itr(); - } - - private class Itr implements Iterator { - /** - * Next node to return item for. - */ - private Node 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 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 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 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 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 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 h = null, t = null; - Object item; - while ((item = s.readObject()) != null) { - @SuppressWarnings("unchecked") - Node newNode = new Node((E) item); - if (h == null) - h = t = newNode; - else { - t.lazySetNext(newNode); - t = newNode; - } - } - if (h == null) - h = t = new Node(null); - head = h; - tail = t; - } - - /** A customized variant of Spliterators.IteratorSpliterator */ - static final class CLQSpliterator implements Spliterator { - static final int MAX_BATCH = 1 << 25; // max batch array size; - final ConcurrentLinkedQueue2 queue; - Node current; // current node; null until initialized - int batch; // batch size for splits - boolean exhausted; // true when no more nodes - CLQSpliterator(ConcurrentLinkedQueue2 queue) { - this.queue = queue; - } - - public Spliterator trySplit() { - Node p; - final ConcurrentLinkedQueue2 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 action) { - Node p; - if (action == null) throw new NullPointerException(); - final ConcurrentLinkedQueue2 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 action) { - Node p; - if (action == null) throw new NullPointerException(); - final ConcurrentLinkedQueue2 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. - * - *

The returned spliterator is - * weakly consistent. - * - *

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 spliterator() { - return new CLQSpliterator(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 cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val); - } - - private boolean casHead(Node cmp, Node 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); - } - } -} diff --git a/src/dorkbox/util/messagebus/publication/Publisher.java b/src/dorkbox/util/messagebus/publication/Publisher.java index 49e4a7d..340625e 100644 --- a/src/dorkbox/util/messagebus/publication/Publisher.java +++ b/src/dorkbox/util/messagebus/publication/Publisher.java @@ -21,5 +21,4 @@ public interface Publisher { void publish(final Synchrony synchrony, Object message1); void publish(final Synchrony synchrony, Object message1, Object message2); void publish(final Synchrony synchrony, Object message1, Object message2, Object message3); - void publish(final Synchrony synchrony, Object[] messages); } diff --git a/src/dorkbox/util/messagebus/publication/PublisherExact.java b/src/dorkbox/util/messagebus/publication/PublisherExact.java index fe6d940..aae25b5 100644 --- a/src/dorkbox/util/messagebus/publication/PublisherExact.java +++ b/src/dorkbox/util/messagebus/publication/PublisherExact.java @@ -44,24 +44,14 @@ class PublisherExact implements Publisher { // Run subscriptions if (subscriptions != null) { - Subscription sub; - for (int i = 0; i < subscriptions.length; i++) { - sub = subscriptions[i]; - sub.publish(message1); - } + synchrony.publish(subscriptions, message1); } else { // Dead Event must EXACTLY MATCH (no subclasses) final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null if (deadSubscriptions != null) { - final DeadMessage deadMessage = new DeadMessage(message1); - - Subscription sub; - for (int i = 0; i < deadSubscriptions.length; i++) { - sub = deadSubscriptions[i]; - sub.publish(deadMessage); - } + synchrony.publish(deadSubscriptions, new DeadMessage(message1)); } } } catch (Throwable e) { @@ -78,33 +68,18 @@ class PublisherExact implements Publisher { final Class messageClass1 = message1.getClass(); final Class messageClass2 = message2.getClass(); -// final StampedLock lock = this.lock; -// long stamp = lock.readLock(); final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null -// lock.unlockRead(stamp); // Run subscriptions if (subscriptions != null) { - Subscription sub; - for (int i = 0; i < subscriptions.length; i++) { - sub = subscriptions[i]; - sub.publish(message1, message2); - } + synchrony.publish(subscriptions, message1, message2); } else { // Dead Event must EXACTLY MATCH (no subclasses) -// stamp = lock.readLock(); final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null -// lock.unlockRead(stamp); if (deadSubscriptions != null) { - final DeadMessage deadMessage = new DeadMessage(message1, message2); - - Subscription sub; - for (int i = 0; i < deadSubscriptions.length; i++) { - sub = deadSubscriptions[i]; - sub.publish(deadMessage); - } + synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2)); } } } catch (Throwable e) { @@ -122,33 +97,18 @@ class PublisherExact implements Publisher { final Class messageClass2 = message2.getClass(); final Class messageClass3 = message3.getClass(); -// final StampedLock lock = this.lock; -// long stamp = lock.readLock(); final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null -// lock.unlockRead(stamp); // Run subscriptions if (subscriptions != null) { - Subscription sub; - for (int i = 0; i < subscriptions.length; i++) { - sub = subscriptions[i]; - sub.publish(message1, message2, message3); - } + synchrony.publish(subscriptions, message1, message2, message3); } else { // Dead Event must EXACTLY MATCH (no subclasses) -// stamp = lock.readLock(); final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null -// lock.unlockRead(stamp); if (deadSubscriptions != null) { - final DeadMessage deadMessage = new DeadMessage(message1, message2, message3); - - Subscription sub; - for (int i = 0; i < deadSubscriptions.length; i++) { - sub = deadSubscriptions[i]; - sub.publish(deadMessage); - } + synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2, message3)); } } } catch (Throwable e) { @@ -157,10 +117,4 @@ class PublisherExact implements Publisher { .setPublishedObject(message1, message2, message3)); } } - - @Override - public - void publish(final Synchrony synchrony, final Object[] messages) { - publish(synchrony, (Object) messages); - } } diff --git a/src/dorkbox/util/messagebus/publication/PublisherExactWithSuperTypes.java b/src/dorkbox/util/messagebus/publication/PublisherExactWithSuperTypes.java index e5a6f33..e8a383b 100644 --- a/src/dorkbox/util/messagebus/publication/PublisherExactWithSuperTypes.java +++ b/src/dorkbox/util/messagebus/publication/PublisherExactWithSuperTypes.java @@ -51,9 +51,9 @@ class PublisherExactWithSuperTypes implements Publisher { synchrony.publish(subscriptions, message1); } - // Run superClasses - final Subscription[] superSubscriptions = subManager.getSuperSubs(message1Class); // can return null - if (superSubscriptions != null) { + // Run superSubscriptions + final Subscription[] superSubscriptions = subManager.getSuperSubs(message1Class); // NOT return null + if (superSubscriptions.length > 0) { hasSubs = true; synchrony.publish(superSubscriptions, message1); } @@ -80,34 +80,31 @@ class PublisherExactWithSuperTypes implements Publisher { try { final Class messageClass1 = message1.getClass(); final Class messageClass2 = message2.getClass(); + boolean hasSubs = false; -// final StampedLock lock = this.lock; -// long stamp = lock.readLock(); - final Subscription[] subscriptions = subManager.getExactAndSuper(messageClass1, messageClass2); // can return null -// lock.unlockRead(stamp); // Run subscriptions + final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null if (subscriptions != null) { - Subscription sub; - for (int i = 0; i < subscriptions.length; i++) { - sub = subscriptions[i]; - sub.publish(message1, message2); - } + hasSubs = true; + synchrony.publish(subscriptions, message1, message2); } - else { + + + // Run superSubscriptions + final Subscription[] superSubscriptions = subManager.getSuperSubs(messageClass1, messageClass2); // can return null + if (superSubscriptions != null) { + hasSubs = true; + synchrony.publish(superSubscriptions, message1, message2); + } + + + // Run dead message subscriptions + if (!hasSubs) { // Dead Event must EXACTLY MATCH (no subclasses) -// stamp = lock.readLock(); final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null -// lock.unlockRead(stamp); - if (deadSubscriptions != null) { - final DeadMessage deadMessage = new DeadMessage(message1, message2); - - Subscription sub; - for (int i = 0; i < deadSubscriptions.length; i++) { - sub = deadSubscriptions[i]; - sub.publish(deadMessage); - } + synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2)); } } } catch (Throwable e) { @@ -124,36 +121,31 @@ class PublisherExactWithSuperTypes implements Publisher { final Class messageClass1 = message1.getClass(); final Class messageClass2 = message2.getClass(); final Class messageClass3 = message3.getClass(); + boolean hasSubs = false; -// final StampedLock lock = this.lock; -// long stamp = lock.readLock(); - final Subscription[] subscriptions = subManager.getExactAndSuper(messageClass1, messageClass2, - messageClass3); // can return null -// lock.unlockRead(stamp); - // Run subscriptions + final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null if (subscriptions != null) { - Subscription sub; - for (int i = 0; i < subscriptions.length; i++) { - sub = subscriptions[i]; - sub.publish(message1, message2, message3); - } + hasSubs = true; + synchrony.publish(subscriptions, message1, message2, message3); } - else { + + + // Run superSubscriptions + final Subscription[] superSubscriptions = subManager.getSuperSubs(messageClass1, messageClass2, messageClass3); // can return null + if (superSubscriptions != null) { + hasSubs = true; + synchrony.publish(superSubscriptions, message1, message2, message3); + } + + + // Run dead message subscriptions + if (!hasSubs) { // Dead Event must EXACTLY MATCH (no subclasses) -// stamp = lock.readLock(); final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null -// lock.unlockRead(stamp); - if (deadSubscriptions != null) { - final DeadMessage deadMessage = new DeadMessage(message1, message2, message3); - - Subscription sub; - for (int i = 0; i < deadSubscriptions.length; i++) { - sub = deadSubscriptions[i]; - sub.publish(deadMessage); - } + synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2, message3)); } } } catch (Throwable e) { @@ -162,10 +154,4 @@ class PublisherExactWithSuperTypes implements Publisher { .setPublishedObject(message1, message2, message3)); } } - - @Override - public - void publish(final Synchrony synchrony, final Object[] messages) { - publish(synchrony, (Object) messages); - } } diff --git a/src/dorkbox/util/messagebus/publication/PublisherExactWithSuperTypesAndVarity.java b/src/dorkbox/util/messagebus/publication/PublisherExactWithSuperTypesAndVarity.java deleted file mode 100644 index cfcabf6..0000000 --- a/src/dorkbox/util/messagebus/publication/PublisherExactWithSuperTypesAndVarity.java +++ /dev/null @@ -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); - } -} diff --git a/src/dorkbox/util/messagebus/publication/disruptor/EventBusFactory.java b/src/dorkbox/util/messagebus/publication/disruptor/EventBusFactory.java deleted file mode 100644 index 3331bda..0000000 --- a/src/dorkbox/util/messagebus/publication/disruptor/EventBusFactory.java +++ /dev/null @@ -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 { - - public EventBusFactory() { - } - - @Override - public - MessageHolder newInstance() { - return new MessageHolder(); - } -} diff --git a/src/dorkbox/util/messagebus/publication/disruptor/MessageHolder.java b/src/dorkbox/util/messagebus/publication/disruptor/MessageHolder.java deleted file mode 100644 index efbd54e..0000000 --- a/src/dorkbox/util/messagebus/publication/disruptor/MessageHolder.java +++ /dev/null @@ -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() {} -} diff --git a/src/dorkbox/util/messagebus/subscription/SubMaker.java b/src/dorkbox/util/messagebus/subscription/SubMaker.java new file mode 100644 index 0000000..3dfe0ff --- /dev/null +++ b/src/dorkbox/util/messagebus/subscription/SubMaker.java @@ -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); +} diff --git a/src/dorkbox/util/messagebus/subscription/Subscription.java b/src/dorkbox/util/messagebus/subscription/Subscription.java index f2fdca1..742028d 100644 --- a/src/dorkbox/util/messagebus/subscription/Subscription.java +++ b/src/dorkbox/util/messagebus/subscription/Subscription.java @@ -1,27 +1,5 @@ /* - * Copyright 2012 Benjamin Diedrichsen - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * - * Copyright 2015 dorkbox, llc + * Copyright 2016 dorkbox, llc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,12 +16,8 @@ package dorkbox.util.messagebus.subscription; import com.esotericsoftware.kryo.util.IdentityMap; -import com.esotericsoftware.reflectasm.MethodAccess; import dorkbox.util.messagebus.common.Entry; import dorkbox.util.messagebus.common.MessageHandler; -import dorkbox.util.messagebus.dispatch.IHandlerInvocation; -import dorkbox.util.messagebus.dispatch.ReflectiveHandlerInvocation; -import dorkbox.util.messagebus.dispatch.SynchronizedHandlerInvocation; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; @@ -59,11 +33,10 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; * This class uses the "single writer principle", so that the subscription are only MODIFIED by a single thread, * but are READ by X number of threads (in a safe way). This uses object thread visibility/publication to work. * - * @author bennidi * @author dorkbox, llc - * Date: 2/2/15 + * Date: 2/3/16 */ -public final +public abstract class Subscription { private static final AtomicInteger ID_COUNTER = new AtomicInteger(); public final int ID = ID_COUNTER.getAndIncrement(); @@ -74,62 +47,53 @@ class Subscription { // the handler's metadata -> for each handler in a listener, a unique subscription context is created private final MessageHandler handler; - private final IHandlerInvocation invocation; // Recommended for best performance while adhering to the "single writer principle". Must be static-final - private static final AtomicReferenceFieldUpdater headREF = + protected static final AtomicReferenceFieldUpdater headREF = AtomicReferenceFieldUpdater.newUpdater(Subscription.class, Entry.class, "head"); - // This is only touched by a single thread! private final IdentityMap entries; // maintain a map of entries for FAST lookup during unsubscribe. // this is still inside the single-writer, and can use the same techniques as subscription manager (for thread safe publication) public volatile Entry head; // reference to the first element - - public + protected Subscription(final Class listenerClass, final MessageHandler handler) { this.listenerClass = listenerClass; + this.handler = handler; - IHandlerInvocation invocation = new ReflectiveHandlerInvocation(); - if (handler.isSynchronized()) { - invocation = new SynchronizedHandlerInvocation(invocation); - } - - this.invocation = invocation; - - entries = new IdentityMap<>(32, SubscriptionManager.LOAD_FACTOR); + entries = new IdentityMap(32, SubscriptionManager.LOAD_FACTOR); if (handler.acceptsSubtypes()) { - // keep a list of "super-class" messages that access this. This is updated by multiple threads. This is so we know WHAT + // TODO keep a list of "super-class" messages that access this. This is updated by multiple threads. This is so we know WHAT // super-subscriptions to clear when we sub/unsub - } } // called on shutdown for GC purposes - public void clear() { + public final + void clear() { this.entries.clear(); this.head.clear(); } // only used in unit tests to verify that the subscription manager is working correctly - public + public final Class getListenerClass() { return listenerClass; } - public + public final MessageHandler getHandler() { return handler; } - public + public final void subscribe(final Object listener) { // single writer principle! Entry head = headREF.get(this); @@ -145,7 +109,7 @@ class Subscription { /** * @return TRUE if the element was removed */ - public + public final boolean unsubscribe(final Object listener) { Entry entry = entries.get(listener); if (entry == null || entry.getValue() == null) { @@ -174,68 +138,38 @@ class Subscription { /** * only used in unit tests */ - public + public final int size() { return this.entries.size; } - public - void publish(final Object message) throws Throwable { - final MethodAccess handler = this.handler.getHandler(); - final int handleIndex = this.handler.getMethodIndex(); - final IHandlerInvocation invocation = this.invocation; + /** + * @return true if messages were published + */ + public abstract + boolean publish(final Object message) throws Throwable; - Entry current = headREF.get(this); - Object listener; - while (current != null) { - listener = current.getValue(); - current = current.next(); + /** + * @return true if messages were published + */ + public abstract + boolean publish(final Object message1, final Object message2) throws Throwable; - invocation.invoke(listener, handler, handleIndex, message); - } - } - - public - void publish(final Object message1, final Object message2) throws Throwable { - final MethodAccess handler = this.handler.getHandler(); - final int handleIndex = this.handler.getMethodIndex(); - final IHandlerInvocation invocation = this.invocation; - - Entry current = headREF.get(this); - Object listener; - while (current != null) { - listener = current.getValue(); - current = current.next(); - - invocation.invoke(listener, handler, handleIndex, message1, message2); - } - } - - public - void publish(final Object message1, final Object message2, final Object message3) throws Throwable { - final MethodAccess handler = this.handler.getHandler(); - final int handleIndex = this.handler.getMethodIndex(); - final IHandlerInvocation invocation = this.invocation; - - Entry current = headREF.get(this); - Object listener; - while (current != null) { - listener = current.getValue(); - current = current.next(); - - invocation.invoke(listener, handler, handleIndex, message1, message2, message3); - } - } + /** + * @return true if messages were published + */ + public abstract + boolean publish(final Object message1, final Object message2, final Object message3) throws Throwable; @Override - public + public final int hashCode() { return this.ID; } @Override - public + public final boolean equals(final Object obj) { if (this == obj) { return true; diff --git a/src/dorkbox/util/messagebus/subscription/SubscriptionManager.java b/src/dorkbox/util/messagebus/subscription/SubscriptionManager.java index 786914c..9cb8dcd 100644 --- a/src/dorkbox/util/messagebus/subscription/SubscriptionManager.java +++ b/src/dorkbox/util/messagebus/subscription/SubscriptionManager.java @@ -16,22 +16,23 @@ package dorkbox.util.messagebus.subscription; import com.esotericsoftware.kryo.util.IdentityMap; +import dorkbox.util.messagebus.MessageBus; import dorkbox.util.messagebus.common.ClassTree; import dorkbox.util.messagebus.common.MessageHandler; import dorkbox.util.messagebus.common.MultiClass; import dorkbox.util.messagebus.error.ErrorHandlingSupport; +import dorkbox.util.messagebus.subscription.asm.SubMakerAsm; +import dorkbox.util.messagebus.subscription.reflection.SubMakerReflection; import dorkbox.util.messagebus.utils.ClassUtils; import dorkbox.util.messagebus.utils.SubscriptionUtils; -import dorkbox.util.messagebus.utils.VarArgUtils; import java.util.ArrayList; import java.util.Arrays; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** * Permits subscriptions with a varying length of parameters as the signature, which must be match by the publisher for it to be accepted - */ -/** + * + * * The subscription managers responsibility is to consistently handle and synchronize the message listener subscription process. * It provides fast lookup of existing subscriptions when another instance of an already known * listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines @@ -44,10 +45,12 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public final class SubscriptionManager { public static final float LOAD_FACTOR = 0.8F; - public static final Subscription[] SUBSCRIPTIONS = new Subscription[0]; // TODO: during startup, pre-calculate the number of subscription listeners and x2 to save as subsPerListener expected max size + // controls if we use java reflection or ASM to access methods during publication + private final SubMaker subMaker; + // ONLY used by SUB/UNSUB // remember already processed classes that do not contain any message handlers @@ -59,7 +62,6 @@ class SubscriptionManager { // once a collection of subscriptions is stored it does not change private final IdentityMap, Subscription[]> subsPerListener; - // We perpetually KEEP the types registered here, and just change what is sub/unsub // all subscriptions of a message type. @@ -69,12 +71,6 @@ class SubscriptionManager { // keeps track of all subscriptions of the super classes of a message type. private volatile IdentityMap, Subscription[]> subsSuperSingle; - // keeps track of all subscriptions of varity (var-arg) classes of a message type - private volatile IdentityMap, Subscription[]> subsVaritySingle; - - // keeps track of all subscriptions of super-class varity (var-arg) classes of a message type - private volatile IdentityMap, Subscription[]> subsSuperVaritySingle; - // In order to force the "Single writer principle" on subscribe & unsubscribe, they are within WRITE LOCKS. They could be dispatched // to another thread, however we do NOT want them asynchronous - as publish() should ALWAYS succeed if a correct subscribe() is // called before. A WriteLock doesn't perform any better here than synchronized does. @@ -86,13 +82,9 @@ class SubscriptionManager { private final ErrorHandlingSupport errorHandler; private final SubscriptionUtils subUtils; - private final VarArgUtils varArgUtils; private final ClassTree> classTree; - // shortcut publication if we know there is no possibility of varArg (ie: a method that has an array as arguments) - private final AtomicBoolean varArgPossibility = new AtomicBoolean(false); - private final ClassUtils classUtils; @@ -113,16 +105,6 @@ class SubscriptionManager { IdentityMap.class, "subsSuperSingle"); - private static final AtomicReferenceFieldUpdater subsVaritySingleREF = - AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class, - IdentityMap.class, - "subsVaritySingle"); - - private static final AtomicReferenceFieldUpdater subsSuperVaritySingleREF = - AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class, - IdentityMap.class, - "subsSuperVaritySingle"); - //NOTE for multiArg, can use the memory address concatenated with other ones and then just put it in the 'single" map (convert single to // use this too). it would likely have to be longs no idea what to do for arrays?? (arrays should verify all the elements are the // correct type too) @@ -131,30 +113,27 @@ class SubscriptionManager { SubscriptionManager(final int numberOfThreads, final ErrorHandlingSupport errorHandler) { this.errorHandler = errorHandler; + if (MessageBus.useAsmForDispatch) { + this.subMaker = new SubMakerAsm(); + } + else { + this.subMaker = new SubMakerReflection(); + } + classUtils = new ClassUtils(SubscriptionManager.LOAD_FACTOR); // modified ONLY during SUB/UNSUB nonListeners = new IdentityMap, Boolean>(16, LOAD_FACTOR); - subsPerListener = new IdentityMap<>(32, LOAD_FACTOR); + subsPerListener = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); subsSingle = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); subsMulti = new IdentityMap(32, LOAD_FACTOR); subsSuperSingle = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); - subsVaritySingle = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); - subsSuperVaritySingle = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); - - - - this.classTree = new ClassTree>(); - - 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>(); + subUtils = new SubscriptionUtils(classUtils, LOAD_FACTOR, numberOfThreads); } public @@ -174,18 +153,14 @@ class SubscriptionManager { } } - this.nonListeners.clear(); this.subsPerListener.clear(); this.subsSingle.clear(); this.subsSuperSingle.clear(); - this.subsVaritySingle.clear(); - this.subsSuperVaritySingle.clear(); this.classTree.clear(); - this.classUtils.shutdown(); } @@ -223,201 +198,158 @@ class SubscriptionManager { // access a snapshot of the subscriptions (single-writer-principle) final IdentityMap, Subscription[]> singleSubs = subsSingleREF.get(this); -// final IdentityMap multiSubs = subsMultiREF.get(this); - -// final IdentityMap, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this); -// final IdentityMap, Subscription[]> localVaritySubs = subsVaritySingleREF.get(this); -// final IdentityMap, Subscription[]> localSuperVaritySubs = subsSuperVaritySingleREF.get(this); + final IdentityMap multiSubs = subsMultiREF.get(this); Subscription subscription; MessageHandler messageHandler; Class[] messageHandlerTypes; + int messageHandlerTypesSize; + + MultiClass multiClass; Class handlerType; - // Prepare all of the subscriptions + // Prepare all of the subscriptions and add for publication AND subscribe since the data structures are consistent for (int i = 0; i < handlersSize; i++) { - // THE HANDLER IS THE SAME FOR ALL SUBSCRIPTIONS OF THE SAME TYPE! messageHandler = messageHandlers[i]; - // is this handler able to accept var args? -// if (messageHandler.getVarArgClass() != null) { -// varArgPossibility.lazySet(true); -// } + subscription = subMaker.create(listenerClass, messageHandler); + subscription.subscribe(listener); // register this callback listener to this subscription + subscriptions[i] = subscription; - // now create a list of subscriptions for this specific handlerType (but don't add anything yet). - // we only store things based on the FIRST type (for lookup) then parse the rest of the types during publication + // register for publication messageHandlerTypes = messageHandler.getHandledMessages(); -// final int handlerSize = messageHandlerTypes.length; -// switch (handlerSize) { -// case 0: { -// // if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed) -// // This is the SAME THING as having Void as a parameter!! -// handlerType = Void.class; -// -// if (!singleSubs.containsKey(handlerType)) { -// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added -// singleSubs.put(handlerType, SUBSCRIPTIONS); -// } -// break; -// } -// case 1: { + messageHandlerTypesSize = messageHandlerTypes.length; + + switch (messageHandlerTypesSize) { + case 0: { + // if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed) + // This is the SAME THING as having Void as a parameter!! + handlerType = Void.class; + + + // makes this subscription visible for publication + final Subscription[] newSubs; + Subscription[] currentSubs = singleSubs.get(handlerType); + if (currentSubs != null) { + final int currentLength = currentSubs.length; + + // add the new subscription to the array + newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); + newSubs[currentLength] = subscription; + } else { + newSubs = new Subscription[1]; + newSubs[0] = subscription; + } + + singleSubs.put(handlerType, newSubs); + break; + } + + case 1: { handlerType = messageHandlerTypes[0]; - if (!singleSubs.containsKey(handlerType)) { - // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added - singleSubs.put(handlerType, SUBSCRIPTIONS); + // makes this subscription visible for publication + final Subscription[] newSubs; + Subscription[] currentSubs = singleSubs.get(handlerType); + if (currentSubs != null) { + final int currentLength = currentSubs.length; + + // add the new subscription to the array + newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); + newSubs[currentLength] = subscription; + } else { + newSubs = new Subscription[1]; + newSubs[0] = subscription; } -// break; -// } -// case 2: { -// final MultiClass multiClass = classTree.get(messageHandlerTypes[0], -// messageHandlerTypes[1]); -// -// if (!multiSubs.containsKey(multiClass)) { -// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added -// multiSubs.put(multiClass, SUBSCRIPTIONS); -// } -// break; -// } -// case 3: { -// final MultiClass multiClass = classTree.get(messageHandlerTypes[0], -// messageHandlerTypes[1], -// messageHandlerTypes[2]); -// -// if (!multiSubs.containsKey(multiClass)) { -// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added -// multiSubs.put(multiClass, SUBSCRIPTIONS); -// } -// break; -// } -// default: { -// final MultiClass multiClass = classTree.get(messageHandlerTypes); -// -// if (!multiSubs.containsKey(multiClass)) { -// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added -// multiSubs.put(multiClass, SUBSCRIPTIONS); -// } -// break; -// } -// } - // create the subscription. This can be thrown away if the subscription succeeds in another thread - subscription = new Subscription(listenerClass, messageHandler); - subscriptions[i] = subscription; + singleSubs.put(handlerType, newSubs); + + break; + } + + case 2: { + multiClass = classTree.get(messageHandlerTypes[0], messageHandlerTypes[1]); + + // makes this subscription visible for publication + final Subscription[] newSubs; + Subscription[] currentSubs = multiSubs.get(multiClass); + + if (currentSubs != null) { + final int currentLength = currentSubs.length; + + // add the new subscription to the array + newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); + newSubs[currentLength] = subscription; + } else { + newSubs = new Subscription[1]; + newSubs[0] = subscription; + } + + multiSubs.put(multiClass, newSubs); + break; + } + + case 3: { + multiClass = classTree.get(messageHandlerTypes[0], messageHandlerTypes[1], messageHandlerTypes[2]); + + // makes this subscription visible for publication + final Subscription[] newSubs; + Subscription[] currentSubs = multiSubs.get(multiClass); + + if (currentSubs != null) { + final int currentLength = currentSubs.length; + + // add the new subscription to the array + newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); + newSubs[currentLength] = subscription; + } else { + newSubs = new Subscription[1]; + newSubs[0] = subscription; + } + + multiSubs.put(multiClass, newSubs); + break; + } + + default: { + multiClass = classTree.get(messageHandlerTypes); + + // makes this subscription visible for publication + final Subscription[] newSubs; + Subscription[] currentSubs = multiSubs.get(multiClass); + + if (currentSubs != null) { + final int currentLength = currentSubs.length; + + // add the new subscription to the array + newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); + newSubs[currentLength] = subscription; + } else { + newSubs = new Subscription[1]; + newSubs[0] = subscription; + } + + multiSubs.put(multiClass, newSubs); + break; + } + } } - // now subsPerMessageSingle has a unique list of subscriptions for a specific handlerType, and MAY already have subscriptions - // activates this sub for sub/unsub (only used by the subscription writer thread) subsPerListener.put(listenerClass, subscriptions); - - // add for publication AND subscribe since the data structures are consistent - for (int i = 0; i < handlersSize; i++) { - subscription = subscriptions[i]; - subscription.subscribe(listener); // register this callback listener to this subscription - - // THE HANDLER IS THE SAME FOR ALL SUBSCRIPTIONS OF THE SAME TYPE! - messageHandler = messageHandlers[i]; - - // register for publication - messageHandlerTypes = messageHandler.getHandledMessages(); -// final int handlerSize = messageHandlerTypes.length; -// -// switch (handlerSize) { -// case 0: { -// handlerType = Void.class; -// -// // makes this subscription visible for publication -// final Subscription[] currentSubs = singleSubs.get(handlerType); -// final int currentLength = currentSubs.length; -// -// // add the new subscription to the array -// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); -// newSubs[currentLength] = subscription; -// singleSubs.put(handlerType, newSubs); -// break; -// } -// -// case 1: { - handlerType = messageHandlerTypes[0]; - - // makes this subscription visible for publication - final Subscription[] currentSubs = singleSubs.get(handlerType); - final int currentLength = currentSubs.length; - - // add the new subscription to the array - final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); - newSubs[currentLength] = subscription; - singleSubs.put(handlerType, newSubs); - - - // update the varity/super types - // registerExtraSubs(handlerType, singleSubs, localSuperSubs, localVaritySubs); - -// break; -// } -// -// case 2: { -// final MultiClass multiClass = classTree.get(messageHandlerTypes[0], -// messageHandlerTypes[1]); -// // makes this subscription visible for publication -// final Subscription[] currentSubs = multiSubs.get(multiClass); -// final int currentLength = currentSubs.length; -// -// // add the new subscription to the array -// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); -// newSubs[currentLength] = subscription; -// multiSubs.put(multiClass, newSubs); -// -// break; -// } -// -// case 3: { -// final MultiClass multiClass = classTree.get(messageHandlerTypes[0], -// messageHandlerTypes[1], -// messageHandlerTypes[2]); -// // makes this subscription visible for publication -// final Subscription[] currentSubs = multiSubs.get(multiClass); -// final int currentLength = currentSubs.length; -// -// // add the new subscription to the array -// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); -// newSubs[currentLength] = subscription; -// multiSubs.put(multiClass, newSubs); -// -// break; -// } -// -// default: { -// final MultiClass multiClass = classTree.get(messageHandlerTypes); -// // makes this subscription visible for publication -// final Subscription[] currentSubs = multiSubs.get(multiClass); -// final int currentLength = currentSubs.length; -// -// // add the new subscription to the array -// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); -// newSubs[currentLength] = subscription; -// multiSubs.put(multiClass, newSubs); -// -// break; -// } -// } - } - // save this snapshot back to the original (single writer principle) subsSingleREF.lazySet(this, singleSubs); -// subsMultiREF.lazySet(this, multiSubs); -// subsSuperSingleREF.lazySet(this, localSuperSubs); -// subsVaritySingleREF.lazySet(this, localVaritySubs); -// subsSuperVaritySingleREF.lazySet(this, localSuperVaritySubs); + subsMultiREF.lazySet(this, multiSubs); - // only dump the super subscritions if it is a COMPLETELY NEW subscription. If it's not new, then the heirarchy isn't - // changing for super subscriptions - subsSuperSingleREF.lazySet(this, new IdentityMap(32)); + // only dump the super subscriptions if it is a COMPLETELY NEW subscription. + // If it's not new, then the hierarchy isn't changing for super subscriptions + final IdentityMap, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this); + localSuperSubs.clear(); + subsSuperSingleREF.lazySet(this, localSuperSubs); } else { // subscriptions already exist and must only be updated @@ -475,7 +407,7 @@ class SubscriptionManager { // for (int i = 0; i < length; i++) { // sub = arraySubs[i]; // -// if (sub.getHandler().acceptsVarArgs()) { +// if (sub.getHandlerAccess().acceptsVarArgs()) { // varArgSubsAsList.add(sub); // } // } @@ -501,7 +433,7 @@ class SubscriptionManager { // for (int j = 0; j < superSubLength; j++) { // sub = superSubs[j]; // -// if (sub.getHandler().acceptsSubtypes()) { +// if (sub.getHandlerAccess().acceptsSubtypes()) { // subsAsList.add(sub); // } // } @@ -517,28 +449,6 @@ class SubscriptionManager { - - - - - - - - - - - - - public - AtomicBoolean getVarArgPossibility() { - return varArgPossibility; - } - - public - VarArgUtils getVarArgUtils() { - return varArgUtils; - } - // can return null public Subscription[] getSubs(final Class messageClass) { @@ -574,57 +484,54 @@ class SubscriptionManager { } - - - - // can return null + // can NOT return null public Subscription[] getSuperSubs(final Class messageClass) { - final Class[] superClasses = this.classUtils.getSuperClasses(messageClass); // never returns null, cached response + // The subscriptions that are remembered here DO NOT CHANGE (only the listeners inside them change). + // if we subscribe a NEW LISTENER super/child class -- THEN these subscriptions change! + // we also DO NOT care about duplicates (since they will be the same anyways) + final IdentityMap, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this); - final int length = superClasses.length; - final ArrayList subsAsList = new ArrayList(length); + Subscription[] subscriptions = localSuperSubs.get(messageClass); + // the only time this is null, is when subscriptions DO NOT exist, and they haven't been calculated. Otherwise, if they are + // calculated and do not exist - this will be an empty array. + if (subscriptions == null) { + final Class[] superClasses = this.classUtils.getSuperClasses(messageClass); // never returns null, cached response - final IdentityMap, Subscription[]> localSubs = subsSingleREF.get(this); + final int length = superClasses.length; + final ArrayList subsAsList = new ArrayList(length); - Class superClass; - Subscription sub; - Subscription[] superSubs; - boolean hasSubs = false; + final IdentityMap, Subscription[]> localSubs = subsSingleREF.get(this); - // walks through all of the subscriptions that might exist for super types, and if applicable, save them - for (int i = 0; i < length; i++) { - superClass = superClasses[i]; - superSubs = localSubs.get(superClass); + Class superClass; + Subscription sub; + Subscription[] superSubs; - if (superSubs != null) { - int superSubLength = superSubs.length; - for (int j = 0; j < superSubLength; j++) { - sub = superSubs[j]; + // walks through all of the subscriptions that might exist for super types, and if applicable, save them + for (int i = 0; i < length; i++) { + superClass = superClasses[i]; + superSubs = localSubs.get(superClass); - if (sub.getHandler().acceptsSubtypes()) { - subsAsList.add(sub); - hasSubs = true; + if (superSubs != null) { + int superSubLength = superSubs.length; + for (int j = 0; j < superSubLength; j++) { + sub = superSubs[j]; + + if (sub.getHandler().acceptsSubtypes()) { + subsAsList.add(sub); + } } } } + + // subsAsList now contains ALL of the super-class subscriptions. + subscriptions = subsAsList.toArray(new Subscription[0]); + localSuperSubs.put(messageClass, subscriptions); + + subsSuperSingleREF.lazySet(this, localSuperSubs); } - - // subsAsList now contains ALL of the super-class subscriptions. - if (hasSubs) { - return subsAsList.toArray(new Subscription[0]); - } - else { - // TODO: shortcut out if there are no handlers that accept subtypes - return null; - } - - -// IT IS CRITICAL TO REMEMBER: The subscriptions that are remembered here DO NOT CHANGE (only the listeners inside them change). if we -// subscribe a super/child class -- THEN these subscriptions change! - -// return (Subscription[]) subsSuperSingleREF.get(this).get(messageClass); + return subscriptions; } // can return null diff --git a/src/dorkbox/util/messagebus/dispatch/IHandlerInvocation.java b/src/dorkbox/util/messagebus/subscription/asm/AsmInvocation.java similarity index 85% rename from src/dorkbox/util/messagebus/dispatch/IHandlerInvocation.java rename to src/dorkbox/util/messagebus/subscription/asm/AsmInvocation.java index e6f204a..f69f604 100644 --- a/src/dorkbox/util/messagebus/dispatch/IHandlerInvocation.java +++ b/src/dorkbox/util/messagebus/subscription/asm/AsmInvocation.java @@ -35,7 +35,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.util.messagebus.dispatch; +package dorkbox.util.messagebus.subscription.asm; import com.esotericsoftware.reflectasm.MethodAccess; @@ -54,7 +54,7 @@ import com.esotericsoftware.reflectasm.MethodAccess; * @author dorkbox, llc * Date: 2/2/15 */ -public interface IHandlerInvocation { +public interface AsmInvocation { /** * Invoke the message delivery logic of this handler @@ -88,15 +88,4 @@ public interface IHandlerInvocation { * @param handler The handler (method) that will be called via reflection */ void invoke(Object listener, MethodAccess handler, int methodIndex, Object message1, Object message2, Object message3) throws Throwable; - - /** - * Invoke the message delivery logic of this handler - * - * @param listener The listener that will receive the message. This can be a reference to a method object - * from the java reflection api or any other wrapper that can be used to invoke the handler - * @param messages The message to be delivered to the handler. This can be any object compatible with the object - * type that the handler consumes - * @param handler The handler (method) that will be called via reflection - */ - void invoke(Object listener, MethodAccess handler, int methodIndex, Object... messages) throws Throwable; } diff --git a/src/dorkbox/util/messagebus/dispatch/ReflectiveHandlerInvocation.java b/src/dorkbox/util/messagebus/subscription/asm/AsmReflectiveInvocation.java similarity index 77% rename from src/dorkbox/util/messagebus/dispatch/ReflectiveHandlerInvocation.java rename to src/dorkbox/util/messagebus/subscription/asm/AsmReflectiveInvocation.java index a93e6f0..7a0209c 100644 --- a/src/dorkbox/util/messagebus/dispatch/ReflectiveHandlerInvocation.java +++ b/src/dorkbox/util/messagebus/subscription/asm/AsmReflectiveInvocation.java @@ -35,7 +35,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.util.messagebus.dispatch; +package dorkbox.util.messagebus.subscription.asm; import com.esotericsoftware.reflectasm.MethodAccess; @@ -48,10 +48,10 @@ import com.esotericsoftware.reflectasm.MethodAccess; * Date: 2/2/15 */ public -class ReflectiveHandlerInvocation implements IHandlerInvocation { +class AsmReflectiveInvocation implements AsmInvocation { public - ReflectiveHandlerInvocation() { + AsmReflectiveInvocation() { super(); } @@ -63,21 +63,13 @@ class ReflectiveHandlerInvocation implements IHandlerInvocation { @Override public - void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2) - throws Throwable { + void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2) throws Throwable { handler.invoke(listener, methodIndex, message1, message2); } @Override public - void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2, - final Object message3) throws Throwable { + void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2, final Object message3) throws Throwable { handler.invoke(listener, methodIndex, message1, message2, message3); } - - @Override - public - void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object... messages) throws Throwable { - handler.invoke(listener, methodIndex, messages); - } } diff --git a/src/dorkbox/util/messagebus/dispatch/SynchronizedHandlerInvocation.java b/src/dorkbox/util/messagebus/subscription/asm/AsmSynchronizedInvocation.java similarity index 76% rename from src/dorkbox/util/messagebus/dispatch/SynchronizedHandlerInvocation.java rename to src/dorkbox/util/messagebus/subscription/asm/AsmSynchronizedInvocation.java index 1d53e7d..82e59c5 100644 --- a/src/dorkbox/util/messagebus/dispatch/SynchronizedHandlerInvocation.java +++ b/src/dorkbox/util/messagebus/subscription/asm/AsmSynchronizedInvocation.java @@ -35,7 +35,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.util.messagebus.dispatch; +package dorkbox.util.messagebus.subscription.asm; import com.esotericsoftware.reflectasm.MethodAccess; @@ -48,12 +48,12 @@ import com.esotericsoftware.reflectasm.MethodAccess; * Date: 2/2/15 */ public -class SynchronizedHandlerInvocation implements IHandlerInvocation { +class AsmSynchronizedInvocation implements AsmInvocation { - private IHandlerInvocation delegate; + private AsmInvocation delegate; public - SynchronizedHandlerInvocation(IHandlerInvocation delegate) { + AsmSynchronizedInvocation(AsmInvocation delegate) { this.delegate = delegate; } @@ -67,8 +67,7 @@ class SynchronizedHandlerInvocation implements IHandlerInvocation { @Override public - void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2) - throws Throwable { + void invoke(final Object listener, final MethodAccess handler, int methodIndex, final Object message1, final Object message2) throws Throwable { synchronized (listener) { this.delegate.invoke(listener, handler, methodIndex, message1, message2); } @@ -76,18 +75,9 @@ class SynchronizedHandlerInvocation implements IHandlerInvocation { @Override public - void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2, - final Object message3) throws Throwable { + void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2, final Object message3) throws Throwable { synchronized (listener) { this.delegate.invoke(listener, handler, methodIndex, message1, message2, message3); } } - - @Override - public - void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object... messages) throws Throwable { - synchronized (listener) { - this.delegate.invoke(listener, handler, methodIndex, messages); - } - } } diff --git a/src/dorkbox/util/messagebus/subscription/asm/SubMakerAsm.java b/src/dorkbox/util/messagebus/subscription/asm/SubMakerAsm.java new file mode 100644 index 0000000..db3583d --- /dev/null +++ b/src/dorkbox/util/messagebus/subscription/asm/SubMakerAsm.java @@ -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); + } +} diff --git a/src/dorkbox/util/messagebus/subscription/asm/SubscriptionAsm.java b/src/dorkbox/util/messagebus/subscription/asm/SubscriptionAsm.java new file mode 100644 index 0000000..938a1cc --- /dev/null +++ b/src/dorkbox/util/messagebus/subscription/asm/SubscriptionAsm.java @@ -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. + *

+ * There will be as many unique subscription objects per message listener class as there are message handlers + * defined in the message listeners class hierarchy. + *

+ * 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; + } +} diff --git a/src/dorkbox/util/messagebus/subscription/reflection/ReflectionInvocation.java b/src/dorkbox/util/messagebus/subscription/reflection/ReflectionInvocation.java new file mode 100644 index 0000000..08553e6 --- /dev/null +++ b/src/dorkbox/util/messagebus/subscription/reflection/ReflectionInvocation.java @@ -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; +} diff --git a/src/dorkbox/util/messagebus/subscription/reflection/ReflectionReflectiveInvocation.java b/src/dorkbox/util/messagebus/subscription/reflection/ReflectionReflectiveInvocation.java new file mode 100644 index 0000000..a305a16 --- /dev/null +++ b/src/dorkbox/util/messagebus/subscription/reflection/ReflectionReflectiveInvocation.java @@ -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); + } +} diff --git a/src/dorkbox/util/messagebus/subscription/reflection/ReflectionSynchronizedInvocation.java b/src/dorkbox/util/messagebus/subscription/reflection/ReflectionSynchronizedInvocation.java new file mode 100644 index 0000000..81890c0 --- /dev/null +++ b/src/dorkbox/util/messagebus/subscription/reflection/ReflectionSynchronizedInvocation.java @@ -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); + } + } +} diff --git a/src/dorkbox/util/messagebus/subscription/reflection/SubMakerReflection.java b/src/dorkbox/util/messagebus/subscription/reflection/SubMakerReflection.java new file mode 100644 index 0000000..e6c228b --- /dev/null +++ b/src/dorkbox/util/messagebus/subscription/reflection/SubMakerReflection.java @@ -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); + } +} diff --git a/src/dorkbox/util/messagebus/subscription/reflection/SubscriptionReflection.java b/src/dorkbox/util/messagebus/subscription/reflection/SubscriptionReflection.java new file mode 100644 index 0000000..dfceea0 --- /dev/null +++ b/src/dorkbox/util/messagebus/subscription/reflection/SubscriptionReflection.java @@ -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. + *

+ * There will be as many unique subscription objects per message listener class as there are message handlers + * defined in the message listeners class hierarchy. + *

+ * 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; + } +} diff --git a/src/dorkbox/util/messagebus/synchrony/AsyncABQ.java b/src/dorkbox/util/messagebus/synchrony/AsyncABQ.java index a3d4458..c5d5136 100644 --- a/src/dorkbox/util/messagebus/synchrony/AsyncABQ.java +++ b/src/dorkbox/util/messagebus/synchrony/AsyncABQ.java @@ -1,22 +1,43 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package dorkbox.util.messagebus.synchrony; -import dorkbox.util.messagebus.common.thread.NamedThreadFactory; +import dorkbox.util.messagebus.common.NamedThreadFactory; import dorkbox.util.messagebus.error.ErrorHandlingSupport; +import dorkbox.util.messagebus.error.PublicationError; import dorkbox.util.messagebus.publication.Publisher; import dorkbox.util.messagebus.subscription.Subscription; +import dorkbox.util.messagebus.synchrony.disruptor.MessageType; import java.util.ArrayDeque; import java.util.Collection; import java.util.concurrent.ArrayBlockingQueue; /** + * This is similar to the disruptor, however the downside of this implementation is that, while faster than the no-gc version, it + * generates garbage (while the disruptor version does not). * + * Basically, the disruptor is fast + noGC. + * + * @author dorkbox, llc Date: 2/3/16 */ public class AsyncABQ implements Synchrony { - private final ErrorHandlingSupport errorHandler; - private final ArrayBlockingQueue dispatchQueue; + private final ArrayBlockingQueue dispatchQueue; private final Collection threads; /** @@ -31,8 +52,7 @@ class AsyncABQ implements Synchrony { final Publisher publisher, final Synchrony syncPublication) { - this.errorHandler = errorHandler; - this.dispatchQueue = new ArrayBlockingQueue(1024); + this.dispatchQueue = new ArrayBlockingQueue(1024); this.threads = new ArrayDeque(numberOfThreads); final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus"); @@ -43,52 +63,79 @@ class AsyncABQ implements Synchrony { @Override public void run() { - final ArrayBlockingQueue IN_QUEUE = AsyncABQ.this.dispatchQueue; + final ArrayBlockingQueue IN_QUEUE = AsyncABQ.this.dispatchQueue; final Publisher publisher1 = publisher; final Synchrony syncPublication1 = syncPublication; + final ErrorHandlingSupport errorHandler1 = errorHandler; + + MessageHolder event = null; + int messageType = MessageType.ONE; + Object message1 = null; + Object message2 = null; + Object message3 = null; while (!AsyncABQ.this.shuttingDown) { try { - //noinspection InfiniteLoopStatement - while (true) { - final Object take = IN_QUEUE.take(); - publisher1.publish(syncPublication1, take); + event = IN_QUEUE.take(); + messageType = event.type; + message1 = event.message1; + message2 = event.message2; + message3 = event.message3; + + switch (messageType) { + case MessageType.ONE: { + publisher1.publish(syncPublication1, message1); + break; + } + case MessageType.TWO: { + publisher1.publish(syncPublication1, message1, message2); + break; + } + case MessageType.THREE: { + publisher1.publish(syncPublication1, message3, message1, message2); + break; + } } } catch (InterruptedException e) { if (!AsyncABQ.this.shuttingDown) { -// Integer type = (Integer) MultiNode.lpMessageType(node); -// switch (type) { -// case 1: { -// errorHandler.handlePublicationError(new PublicationError().setMessage( -// "Thread interrupted while processing message") -// .setCause(e) -// .setPublishedObject(MultiNode.lpItem1(node))); -// break; -// } -// case 2: { -// errorHandler.handlePublicationError(new PublicationError().setMessage( -// "Thread interrupted while processing message") -// .setCause(e) -// .setPublishedObject(MultiNode.lpItem1(node), -// MultiNode.lpItem2(node))); -// break; -// } -// case 3: { -// errorHandler.handlePublicationError(new PublicationError().setMessage( -// "Thread interrupted while processing message") -// .setCause(e) -// .setPublishedObject(MultiNode.lpItem1(node), -// MultiNode.lpItem2(node), -// MultiNode.lpItem3(node))); -// break; -// } -// default: { -// errorHandler.handlePublicationError(new PublicationError().setMessage( -// "Thread interrupted while processing message") -// .setCause(e) -// .setPublishedObject(MultiNode.lpItem1(node))); -// } -// } + switch (messageType) { + case MessageType.ONE: { + PublicationError publicationError = new PublicationError() + .setMessage("Thread interrupted while processing message") + .setCause(e); + + if (event != null) { + publicationError.setPublishedObject(message1); + } + + errorHandler1.handlePublicationError(publicationError); + break; + } + case MessageType.TWO: { + PublicationError publicationError = new PublicationError() + .setMessage("Thread interrupted while processing message") + .setCause(e); + + if (event != null) { + publicationError.setPublishedObject(message1, message2); + } + + errorHandler1.handlePublicationError(publicationError); + break; + } + case MessageType.THREE: { + PublicationError publicationError = new PublicationError() + .setMessage("Thread interrupted while processing message") + .setCause(e); + + if (event != null) { + publicationError.setPublishedObject(message1, message2, message3); + } + + errorHandler1.handlePublicationError(publicationError); + break; + } + } } } } @@ -100,27 +147,28 @@ class AsyncABQ implements Synchrony { } } + // unfortunately, this isn't as friendly to GC as the disruptor is... + public void publish(final Subscription[] subscriptions, final Object message1) throws Throwable { - this.dispatchQueue.put(message1); + MessageHolder take = new MessageHolder(); + take.type = MessageType.ONE; + take.subscriptions = subscriptions; + take.message1 = message1; + + this.dispatchQueue.put(take); } @Override public void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable { - + this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2)); } @Override public void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable { - - } - - @Override - public - void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable { - + this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2, message3)); } public diff --git a/src/dorkbox/util/messagebus/synchrony/AsyncABQ_noGc.java b/src/dorkbox/util/messagebus/synchrony/AsyncABQ_noGc.java new file mode 100644 index 0000000..2567167 --- /dev/null +++ b/src/dorkbox/util/messagebus/synchrony/AsyncABQ_noGc.java @@ -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 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 gcQueue; + + + private final Collection 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(1024); + this.gcQueue = new ArrayBlockingQueue(1024); + + // this is how we prevent garbage + for (int i = 0; i < 1024; i++) { + gcQueue.add(new MessageHolder()); + } + + this.threads = new ArrayDeque(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 IN_QUEUE = AsyncABQ_noGc.this.dispatchQueue; + final ArrayBlockingQueue 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(); + } +} diff --git a/src/dorkbox/util/messagebus/synchrony/AsyncDisruptor.java b/src/dorkbox/util/messagebus/synchrony/AsyncDisruptor.java index 28b3f8b..291c5b4 100644 --- a/src/dorkbox/util/messagebus/synchrony/AsyncDisruptor.java +++ b/src/dorkbox/util/messagebus/synchrony/AsyncDisruptor.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package dorkbox.util.messagebus.synchrony; import com.lmax.disruptor.LiteBlockingWaitStrategy; @@ -8,14 +23,13 @@ import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.Sequencer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.WorkProcessor; -import dorkbox.util.messagebus.common.thread.NamedThreadFactory; +import dorkbox.util.messagebus.common.NamedThreadFactory; import dorkbox.util.messagebus.error.ErrorHandlingSupport; import dorkbox.util.messagebus.publication.Publisher; -import dorkbox.util.messagebus.publication.disruptor.EventBusFactory; -import dorkbox.util.messagebus.publication.disruptor.MessageHandler; -import dorkbox.util.messagebus.publication.disruptor.MessageHolder; -import dorkbox.util.messagebus.publication.disruptor.MessageType; -import dorkbox.util.messagebus.publication.disruptor.PublicationExceptionHandler; +import dorkbox.util.messagebus.synchrony.disruptor.EventBusFactory; +import dorkbox.util.messagebus.synchrony.disruptor.MessageHandler; +import dorkbox.util.messagebus.synchrony.disruptor.MessageType; +import dorkbox.util.messagebus.synchrony.disruptor.PublicationExceptionHandler; import dorkbox.util.messagebus.subscription.Subscription; import java.util.concurrent.ExecutorService; @@ -23,6 +37,9 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; +/** + * @author dorkbox, llc Date: 2/3/16 + */ public class AsyncDisruptor implements Synchrony { @@ -130,12 +147,6 @@ class AsyncDisruptor implements Synchrony { } - @Override - public - void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable { - - } - // gets the sequences used for processing work private Sequence[] getSequences() { diff --git a/src/dorkbox/util/messagebus/synchrony/AsyncLTQ.java b/src/dorkbox/util/messagebus/synchrony/AsyncLTQ.java deleted file mode 100644 index b82ab9a..0000000 --- a/src/dorkbox/util/messagebus/synchrony/AsyncLTQ.java +++ /dev/null @@ -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 dispatchQueue; - private final Collection 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(); - - this.threads = new ArrayDeque(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(); - } -} diff --git a/src/dorkbox/util/messagebus/synchrony/AsyncMisc.java b/src/dorkbox/util/messagebus/synchrony/AsyncMisc.java deleted file mode 100644 index 6abfed9..0000000 --- a/src/dorkbox/util/messagebus/synchrony/AsyncMisc.java +++ /dev/null @@ -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 dispatchQueue; -// private final ArrayBlockingQueue dispatchQueue; - private final LinkedTransferQueue dispatchQueue; - private final Collection threads; - - - public - AsyncMisc(final int numberOfThreads, - final ErrorHandlingSupport errorHandler, - final Publisher publisher, - final Synchrony syncPublication) { - -// this.dispatchQueue = new LinkedBlockingQueue(1024); -// this.dispatchQueue = new ArrayBlockingQueue(1024); - this.dispatchQueue = new LinkedTransferQueue(); - - this.threads = new ArrayDeque(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(); - } -} diff --git a/src/dorkbox/util/messagebus/synchrony/MessageHolder.java b/src/dorkbox/util/messagebus/synchrony/MessageHolder.java new file mode 100644 index 0000000..6ade7ad --- /dev/null +++ b/src/dorkbox/util/messagebus/synchrony/MessageHolder.java @@ -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; + } +} diff --git a/src/dorkbox/util/messagebus/synchrony/Sync.java b/src/dorkbox/util/messagebus/synchrony/Sync.java index 32611ea..d4d9f91 100644 --- a/src/dorkbox/util/messagebus/synchrony/Sync.java +++ b/src/dorkbox/util/messagebus/synchrony/Sync.java @@ -1,12 +1,31 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package dorkbox.util.messagebus.synchrony; import dorkbox.util.messagebus.subscription.Subscription; +/** + * @author dorkbox, llc Date: 2/2/15 + */ public class Sync implements Synchrony { public void publish(final Subscription[] subscriptions, final Object message1) throws Throwable { Subscription sub; + boolean hasSubs = false; for (int i = 0; i < subscriptions.length; i++) { sub = subscriptions[i]; sub.publish(message1); @@ -16,19 +35,21 @@ class Sync implements Synchrony { @Override public void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable { - + Subscription sub; + for (int i = 0; i < subscriptions.length; i++) { + sub = subscriptions[i]; + sub.publish(message1, message2); + } } @Override public void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable { - - } - - @Override - public - void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable { - + Subscription sub; + for (int i = 0; i < subscriptions.length; i++) { + sub = subscriptions[i]; + sub.publish(message1, message2, message3); + } } @Override diff --git a/src/dorkbox/util/messagebus/synchrony/Synchrony.java b/src/dorkbox/util/messagebus/synchrony/Synchrony.java index 3c46e3e..6de5eca 100644 --- a/src/dorkbox/util/messagebus/synchrony/Synchrony.java +++ b/src/dorkbox/util/messagebus/synchrony/Synchrony.java @@ -1,16 +1,30 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package dorkbox.util.messagebus.synchrony; import dorkbox.util.messagebus.subscription.Subscription; /** - * + * @author dorkbox, llc Date: 2/3/16 */ public interface Synchrony { void publish(final Subscription[] subscriptions, Object message1) throws Throwable; void publish(final Subscription[] subscriptions, Object message1, Object message2) throws Throwable ; void publish(final Subscription[] subscriptions, Object message1, Object message2, Object message3) throws Throwable ; - void publish(final Subscription[] subscriptions, Object[] messages) throws Throwable ; void start(); void shutdown(); diff --git a/src/dorkbox/util/messagebus/synchrony/disruptor/EventBusFactory.java b/src/dorkbox/util/messagebus/synchrony/disruptor/EventBusFactory.java new file mode 100644 index 0000000..d171b56 --- /dev/null +++ b/src/dorkbox/util/messagebus/synchrony/disruptor/EventBusFactory.java @@ -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 { + + public EventBusFactory() { + } + + @Override + public + MessageHolder newInstance() { + return new MessageHolder(); + } +} diff --git a/src/dorkbox/util/messagebus/publication/disruptor/MessageHandler.java b/src/dorkbox/util/messagebus/synchrony/disruptor/MessageHandler.java similarity index 72% rename from src/dorkbox/util/messagebus/publication/disruptor/MessageHandler.java rename to src/dorkbox/util/messagebus/synchrony/disruptor/MessageHandler.java index 636df1d..f9f951d 100644 --- a/src/dorkbox/util/messagebus/publication/disruptor/MessageHandler.java +++ b/src/dorkbox/util/messagebus/synchrony/disruptor/MessageHandler.java @@ -1,7 +1,23 @@ -package dorkbox.util.messagebus.publication.disruptor; +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util.messagebus.synchrony.disruptor; import com.lmax.disruptor.LifecycleAware; import com.lmax.disruptor.WorkHandler; +import dorkbox.util.messagebus.synchrony.MessageHolder; import dorkbox.util.messagebus.synchrony.Synchrony; import dorkbox.util.messagebus.publication.Publisher; @@ -47,11 +63,6 @@ class MessageHandler implements WorkHandler, LifecycleAware { this.publisher.publish(syncPublication, message3, message1, message2); return; } - case MessageType.ARRAY: { - Object[] messages = event.messages; - this.publisher.publish(syncPublication, messages); - return; - } } } diff --git a/src/dorkbox/util/messagebus/publication/disruptor/MessageType.java b/src/dorkbox/util/messagebus/synchrony/disruptor/MessageType.java similarity index 88% rename from src/dorkbox/util/messagebus/publication/disruptor/MessageType.java rename to src/dorkbox/util/messagebus/synchrony/disruptor/MessageType.java index 71a94fe..1a7fb86 100644 --- a/src/dorkbox/util/messagebus/publication/disruptor/MessageType.java +++ b/src/dorkbox/util/messagebus/synchrony/disruptor/MessageType.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.util.messagebus.publication.disruptor; +package dorkbox.util.messagebus.synchrony.disruptor; +/** + * @author dorkbox, llc Date: 2/2/15 + */ public final class MessageType { public static final int ONE = 1; public static final int TWO = 2; public static final int THREE = 3; - public static final int ARRAY = 4; private MessageType() { } diff --git a/src/dorkbox/util/messagebus/publication/disruptor/PublicationExceptionHandler.java b/src/dorkbox/util/messagebus/synchrony/disruptor/PublicationExceptionHandler.java similarity index 65% rename from src/dorkbox/util/messagebus/publication/disruptor/PublicationExceptionHandler.java rename to src/dorkbox/util/messagebus/synchrony/disruptor/PublicationExceptionHandler.java index 0a3ab61..00f8c67 100644 --- a/src/dorkbox/util/messagebus/publication/disruptor/PublicationExceptionHandler.java +++ b/src/dorkbox/util/messagebus/synchrony/disruptor/PublicationExceptionHandler.java @@ -1,9 +1,27 @@ -package dorkbox.util.messagebus.publication.disruptor; +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util.messagebus.synchrony.disruptor; import com.lmax.disruptor.ExceptionHandler; import dorkbox.util.messagebus.error.ErrorHandlingSupport; import dorkbox.util.messagebus.error.PublicationError; +/** + * @author dorkbox, llc Date: 2/3/16 + */ public final class PublicationExceptionHandler implements ExceptionHandler { private final ErrorHandlingSupport errorHandler; diff --git a/src/dorkbox/util/messagebus/utils/ClassUtils.java b/src/dorkbox/util/messagebus/utils/ClassUtils.java index 5fa8e5b..307045a 100644 --- a/src/dorkbox/util/messagebus/utils/ClassUtils.java +++ b/src/dorkbox/util/messagebus/utils/ClassUtils.java @@ -19,7 +19,6 @@ import com.esotericsoftware.kryo.util.IdentityMap; import java.lang.reflect.Array; import java.util.ArrayList; -import java.util.HashSet; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public final @@ -77,7 +76,6 @@ class ClassUtils { if (isArray) { for (int i = 0; i < length; i++) { c = superTypes[i]; - c = getArrayClass(c); if (c != clazz) { @@ -143,91 +141,4 @@ class ClassUtils { this.arrayCache.clear(); this.superClassesCache.clear(); } - - public static - ArrayList 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 intersection = new ArrayList(searchSize); - - final HashSet hashedArray = new HashSet(); - 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 - ArrayList findCommon(final ArrayList arrayOne, final ArrayList arrayTwo) { - - ArrayList arrayToHash; - ArrayList 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 intersection = new ArrayList(searchSize); - - HashSet hashedArray = new HashSet(); - 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; - } } diff --git a/src/dorkbox/util/messagebus/utils/SubscriptionUtils.java b/src/dorkbox/util/messagebus/utils/SubscriptionUtils.java index 330c3c8..c17d9fc 100644 --- a/src/dorkbox/util/messagebus/utils/SubscriptionUtils.java +++ b/src/dorkbox/util/messagebus/utils/SubscriptionUtils.java @@ -16,7 +16,7 @@ package dorkbox.util.messagebus.utils; import com.esotericsoftware.kryo.util.IdentityMap; -import dorkbox.util.messagebus.common.HashMapTree; +import dorkbox.util.messagebus.common.ClassTree; import dorkbox.util.messagebus.subscription.Subscription; import dorkbox.util.messagebus.subscription.SubscriptionManager; @@ -32,7 +32,7 @@ class SubscriptionUtils { // keeps track of all subscriptions of the super classes of a message type. private volatile IdentityMap, Subscription[]> superClassSubscriptions; - private final HashMapTree, ArrayList> superClassSubscriptionsMulti; + private final ClassTree> superClassSubscriptionsMulti; public @@ -43,7 +43,7 @@ class SubscriptionUtils { // superClassSubscriptions keeps track of all subscriptions of super classes. SUB/UNSUB dumps it, so it is recreated dynamically. // it's a hit on SUB/UNSUB, but improves performance of handlers this.superClassSubscriptions = new IdentityMap, Subscription[]>(8, loadFactor); - this.superClassSubscriptionsMulti = new HashMapTree, ArrayList>(); + this.superClassSubscriptionsMulti = new ClassTree>(); } public @@ -80,7 +80,7 @@ class SubscriptionUtils { // for (int j = 0; j < superSubLength; j++) { // sub = superSubs[j]; // -// if (sub.getHandler().acceptsSubtypes()) { +// if (sub.getHandlerAccess().acceptsSubtypes()) { // subsAsList.add(sub); // } // } @@ -123,61 +123,62 @@ class SubscriptionUtils { */ public ArrayList getSuperSubscriptions(final Class clazz1, final Class clazz2, final SubscriptionManager subManager) { - // whenever our subscriptions change, this map is cleared. - final HashMapTree, ArrayList> cached = this.superClassSubscriptionsMulti; +// // whenever our subscriptions change, this map is cleared. +// final MapTree, ArrayList> cached = this.superClassSubscriptionsMulti; +// +// ArrayList 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 superSubs; +// Subscription sub; +// +// final int length1 = superClasses1.length; +// final int length2 = superClasses2.length; +// +// subs = new ArrayList(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 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 superSubs; - Subscription sub; - - final int length1 = superClasses1.length; - final int length2 = superClasses2.length; - - subs = new ArrayList(length1 + length2); - - for (int i = 0; i < length1; i++) { - superClass1 = superClasses1[i]; - - // only go over subtypes - if (superClass1 == clazz1) { - continue; - } - - for (int j = 0; j < length2; j++) { - superClass2 = superClasses2[j]; - - // only go over subtypes - if (superClass2 == clazz2) { - continue; - } - - superSubs = subManager.getExactAsArray(superClass1, superClass2); - if (superSubs != null) { - for (int k = 0; k < superSubs.size(); k++) { - sub = superSubs.get(k); - - if (sub.getHandler().acceptsSubtypes()) { - subs.add(sub); - } - } - } - } - } - subs.trimToSize(); - cached.put(subs, clazz1, clazz2); - } - - return subs; +// return subs; + return null; } /** @@ -190,72 +191,73 @@ class SubscriptionUtils { public ArrayList getSuperSubscriptions(final Class clazz1, final Class clazz2, final Class clazz3, final SubscriptionManager subManager) { - // whenever our subscriptions change, this map is cleared. - final HashMapTree, ArrayList> local = this.superClassSubscriptionsMulti; - - ArrayList subs = local.get(clazz1, clazz2, clazz3); - - if (subs == null) { - // types was not empty, so collect subscriptions for each type and collate them - - // save the subscriptions - final Class[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response - final Class[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response - final Class[] superClasses3 = this.superClass.getSuperClasses(clazz3); // never returns null, cached response - - Class superClass1; - Class superClass2; - Class superClass3; - ArrayList superSubs; - Subscription sub; - - final int length1 = superClasses1.length; - final int length2 = superClasses2.length; - final int length3 = superClasses3.length; - - subs = new ArrayList(length1 + length2 + length3); - - for (int i = 0; i < length1; i++) { - superClass1 = superClasses1[i]; - - // only go over subtypes - if (superClass1 == clazz1) { - continue; - } - - for (int j = 0; j < length2; j++) { - superClass2 = superClasses2[j]; - - // only go over subtypes - if (superClass2 == clazz2) { - continue; - } - - for (int k = 0; k < length3; k++) { - superClass3 = superClasses3[j]; - - // only go over subtypes - if (superClass3 == clazz3) { - continue; - } - - superSubs = subManager.getExactAsArray(superClass1, superClass2, superClass3); - if (superSubs != null) { - for (int m = 0; m < superSubs.size(); m++) { - sub = superSubs.get(m); - - if (sub.getHandler().acceptsSubtypes()) { - subs.add(sub); - } - } - } - } - } - } - subs.trimToSize(); - local.put(subs, clazz1, clazz2, clazz3); - } - - return subs; +// // whenever our subscriptions change, this map is cleared. +// final MapTree, ArrayList> local = this.superClassSubscriptionsMulti; +// +// ArrayList subs = local.get(clazz1, clazz2, clazz3); +// +// if (subs == null) { +// // types was not empty, so collect subscriptions for each type and collate them +// +// // save the subscriptions +// final Class[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response +// final Class[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response +// final Class[] superClasses3 = this.superClass.getSuperClasses(clazz3); // never returns null, cached response +// +// Class superClass1; +// Class superClass2; +// Class superClass3; +// ArrayList superSubs; +// Subscription sub; +// +// final int length1 = superClasses1.length; +// final int length2 = superClasses2.length; +// final int length3 = superClasses3.length; +// +// subs = new ArrayList(length1 + length2 + length3); +// +// for (int i = 0; i < length1; i++) { +// superClass1 = superClasses1[i]; +// +// // only go over subtypes +// if (superClass1 == clazz1) { +// continue; +// } +// +// for (int j = 0; j < length2; j++) { +// superClass2 = superClasses2[j]; +// +// // only go over subtypes +// if (superClass2 == clazz2) { +// continue; +// } +// +// for (int k = 0; k < length3; k++) { +// superClass3 = superClasses3[j]; +// +// // only go over subtypes +// if (superClass3 == clazz3) { +// continue; +// } +// +// superSubs = subManager.getExactAsArray(superClass1, superClass2, superClass3); +// if (superSubs != null) { +// for (int m = 0; m < superSubs.size(); m++) { +// sub = superSubs.get(m); +// +// if (sub.getHandlerAccess().acceptsSubtypes()) { +// subs.add(sub); +// } +// } +// } +// } +// } +// } +// subs.trimToSize(); +// local.put(subs, clazz1, clazz2, clazz3); +// } +// +// return subs; + return null; } } diff --git a/src/dorkbox/util/messagebus/utils/VarArgUtils.java b/src/dorkbox/util/messagebus/utils/VarArgUtils.java deleted file mode 100644 index 0ecb426..0000000 --- a/src/dorkbox/util/messagebus/utils/VarArgUtils.java +++ /dev/null @@ -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, Subscription[]> varArgSubscriptionsSingle; - private final HashMapTree, ArrayList> varArgSubscriptionsMulti; - - private final Map, Subscription[]> varArgSuperSubscriptionsSingle; - private final HashMapTree, ArrayList> varArgSuperSubscriptionsMulti; - - private final ClassUtils superClassUtils; - - - public - VarArgUtils(final ClassUtils superClassUtils, final float loadFactor, final int numberOfThreads) { - - this.superClassUtils = superClassUtils; - - this.varArgSubscriptionsSingle = new ConcurrentHashMap, Subscription[]>(16, loadFactor, numberOfThreads); - this.varArgSubscriptionsMulti = new HashMapTree, ArrayList>(); - - this.varArgSuperSubscriptionsSingle = new ConcurrentHashMap, Subscription[]>(16, loadFactor, numberOfThreads); - this.varArgSuperSubscriptionsMulti = new HashMapTree, ArrayList>(); - } - - - 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, 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 varArgSubsAsList = new ArrayList(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, 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 varArgSuperSubsAsList = new ArrayList(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, ArrayList> local = this.varArgSuperSubscriptionsMulti; - - ArrayList 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 varargSuperSubscriptions1 = getVarArgSuperSubscriptions_List(messageClass1, subManager); -// final ArrayList 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, ArrayList> local = this.varArgSuperSubscriptionsMulti; - - ArrayList 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 varargSuperSubscriptions1 = getVarArgSuperSubscriptions_List(messageClass1, subManager); -// final ArrayList varargSuperSubscriptions2 = getVarArgSuperSubscriptions_List(messageClass2, subManager); -// final ArrayList 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; - } -} diff --git a/test/dorkbox/util/messagebus/perfTests/MpmcArrayQueue.java b/test/dorkbox/util/messagebus/perfTests/MpmcArrayQueue.java deleted file mode 100644 index c15c07f..0000000 --- a/test/dorkbox/util/messagebus/perfTests/MpmcArrayQueue.java +++ /dev/null @@ -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 {} -}