All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.messageBus.MessageBus Maven / Gradle / Ivy

/*
 * 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.messageBus;

import dorkbox.messageBus.dispatch.Dispatch;
import dorkbox.messageBus.dispatch.DispatchExact;
import dorkbox.messageBus.dispatch.DispatchExactWithSuperTypes;
import dorkbox.messageBus.error.ErrorHandler;
import dorkbox.messageBus.error.IPublicationErrorHandler;
import dorkbox.messageBus.publication.ConversantDisruptor;
import dorkbox.messageBus.publication.DirectInvocation;
import dorkbox.messageBus.publication.LmaxDisruptor;
import dorkbox.messageBus.publication.Publisher;
import dorkbox.messageBus.subscription.SubscriptionManager;

/**
 * A message bus offers facilities for publishing messages to the message handlers of registered listeners.
 * 

* * Because the message bus keeps track of classes that are subscribed and published, reloading the classloader means that you will need to * SHUTDOWN the messagebus when you unload the classloader, and then re-subscribe relevant classes when you reload the classes. *

* * Messages can be published synchronously or asynchronously and may be of any type that is a valid sub type of the type parameter T. * Message handlers can be invoked synchronously or asynchronously depending on their configuration. Thus, there * are two notions of synchronicity / asynchronicity. One on the caller side, e.g. the invocation of the message publishing * methods. The second on the handler side, e.g. whether the handler is invoked in the same or a different thread. * *

* Each message publication is isolated from all other running publications such that it does not interfere with them. * Hence, the bus generally expects message handlers to be stateless as it may invoke them concurrently if multiple * messages publish published asynchronously. If handlers are stateful and not thread-safe they can be marked to be invoked * in a synchronized fashion using @Synchronized annotation * *

* A listener is any object that defines at least one message handler and that has been subscribed to at least * one message bus. A message handler can be any method that accepts exactly one parameter (the message) and is marked * as a message handler using the @Handler annotation. * *

* By default, the bus uses weak references to all listeners such that registered listeners do not need to * be explicitly unregistered to be eligible for garbage collection. Dead (garbage collected) listeners are * removed on-the-fly as messages publish dispatched. This can be changed using the @Listener annotation. * *

* Generally message handlers will be invoked in inverse sequence of subscription but any * client using this bus should not rely on this assumption. The basic contract of the bus is that it will deliver * a specific message exactly once to each of the respective message handlers. * *

* Messages are dispatched to all listeners that accept the type or supertype of the dispatched message. * *

* Subscribed message handlers are available to all pending message publications that have not yet started processing. * Any message listener may only be subscribed once -> subsequent subscriptions of an already subscribed message listener * will be silently ignored) * *

* Removing a listener (unsubscribing) means removing all subscribed message handlers of that listener. This remove operation * immediately takes effect and on all running dispatch processes -> A removed listener (a listener * is considered removed after the remove(Object) call returned) will under no circumstances receive any message publications. * Any running message publication that has not yet delivered the message to the removed listener will not see the listener * after the remove operation completed. * *

* NOTE: Generic type parameters of messages will not be taken into account, e.g. a List will * publish dispatched to all message handlers that take an instance of List as their parameter * * * * See this post for insight on how it operates: http://psy-lob-saw.blogspot.com/2012/12/atomiclazyset-is-performance-win-for.html * TLDR: we use single-writer-principle + lazySet/get for major performance * * @author bennidi * Date: 2/8/12 * @author dorkbox, llc * Date: 2/2/15 */ @SuppressWarnings("WeakerAccess") public class MessageBus { /** * Gets the version number. */ public static String getVersion() { return "2.7"; } /** * Always return at least 1 thread */ private static int getMinNumberOfThreads(final int numberOfThreads) { if (numberOfThreads < 1) { return 1; } return numberOfThreads; } static { // Add this project to the updates system, which verifies this class + UUID + version information dorkbox.updates.Updates.INSTANCE.add(MessageBus.class, "3e5ed233229c41359cabeb56e32b16d8", getVersion()); } private final Dispatch dispatch; private final ErrorHandler errorHandler = new ErrorHandler(); private final DispatchMode dispatchMode; private final SubscriptionManager subscriptionManager; private final AsyncPublicationMode publicationMode; private final SubscriptionMode subscriptionMode; private final int numberOfThreads; private final Publisher syncPublisher; private final Publisher asyncPublisher; /** * Will permit subType matching for matching what subscription handles which message *

* Will use half of CPUs available for dispatching async messages *

* Will use the Conversant Disruptor as the blocking queue implementation for asynchronous message publication *

* Will use half of CPUs available for dispatching async messages */ public MessageBus() { this(Runtime.getRuntime().availableProcessors()/2); } /** * Will permit subType matching for matching what subscription handles which message *

* Will use half of CPUs available for dispatching async messages *

* Will use the Conversant Disruptor as the blocking queue implementation for asynchronous message publication * * @param numberOfThreads how many threads to use for dispatching async messages */ public MessageBus(final int numberOfThreads) { this(DispatchMode.ExactWithSuperTypes, SubscriptionMode.StrongReferences, numberOfThreads); } /** * Will use half of CPUs available for dispatching async messages *

* Will use the LMAX Disruptor as the blocking queue implementation for asynchronous publication * * @param dispatchMode Specifies which Dispatch Mode (Exact or ExactWithSuperTypes) to allow what subscription hierarchies receive the publication of a message. * @param subscriptionMode Specifies which Subscription Mode Mode (Strong or Weak) to change how subscription handlers are saved internally. */ public MessageBus(final DispatchMode dispatchMode, final SubscriptionMode subscriptionMode) { this(dispatchMode, subscriptionMode, Runtime.getRuntime().availableProcessors()/2); } /** * Will use the LMAX Disruptor as the blocking queue implementation for asynchronous publication * * @param dispatchMode Specifies which Dispatch Mode (Exact or ExactWithSuperTypes) to allow what subscription hierarchies receive the publication of a message. * @param subscriptionMode Specifies which Subscription Mode Mode (Strong or Weak) to change how subscription handlers are saved internally. * @param numberOfThreads how many threads to use for dispatching async messages */ public MessageBus(final DispatchMode dispatchMode, final SubscriptionMode subscriptionMode, final int numberOfThreads) { this(dispatchMode, subscriptionMode, AsyncPublicationMode.LmaxDisruptor, numberOfThreads); } /** * @param dispatchMode Specifies which Dispatch Mode (Exact or ExactWithSuperTypes) to allow what subscription hierarchies receive the publication of a message. * @param subscriptionMode Specifies which Subscription Mode (Strong or Weak) to change how subscription handlers are saved internally. * @param publicationMode Specifies which Publication Mode (LMAX or Conversant disruptors) for executing messages asynchronously. * @param numberOfThreads how many threads to use for dispatching async messages */ public MessageBus(final DispatchMode dispatchMode, final SubscriptionMode subscriptionMode, final AsyncPublicationMode publicationMode, final int numberOfThreads) { this.dispatchMode = dispatchMode; this.subscriptionMode = subscriptionMode; this.publicationMode = publicationMode; // make sure there are ALWAYS at least 1 thread, even if (by accident) 0 threads were specified because of rounding errors. int minNumberOfThreads = getMinNumberOfThreads(numberOfThreads); this.numberOfThreads = minNumberOfThreads; // Will subscribe and publish using all provided parameters in the method signature (for subscribe), and arguments (for publish) this.subscriptionManager = new SubscriptionManager(subscriptionMode); if (dispatchMode == DispatchMode.Exact) { this.dispatch = new DispatchExact(); } else { this.dispatch = new DispatchExactWithSuperTypes(); } syncPublisher = new DirectInvocation(); if (publicationMode == AsyncPublicationMode.LmaxDisruptor) { asyncPublisher = new LmaxDisruptor(minNumberOfThreads, errorHandler); } else { asyncPublisher = new ConversantDisruptor(minNumberOfThreads); } } /** * Creates a copy of this messagebus - BUT - instead of making a copy of everything, it shares the async thread executor. *

* This will permit unique subscriptions. *

* Only the original messagebus can shutdown the thread executor */ private MessageBus(final MessageBus messageBus) { this.dispatchMode = messageBus.dispatchMode; this.subscriptionMode = messageBus.subscriptionMode; this.publicationMode = messageBus.publicationMode; this.numberOfThreads = messageBus.numberOfThreads; // Will subscribe and publish using all provided parameters in the method signature (for subscribe), and arguments (for publish) this.subscriptionManager = new SubscriptionManager(subscriptionMode); this.dispatch = messageBus.dispatch; this.syncPublisher = messageBus.syncPublisher; // we have to make sure that calling .shutdown() DOES NOT shutdown the thread executor for these! if (publicationMode == AsyncPublicationMode.LmaxDisruptor) { asyncPublisher = new LmaxDisruptor((LmaxDisruptor) messageBus.asyncPublisher) { @Override public void shutdown() { // do nothing for a clone! } }; } else { asyncPublisher = new ConversantDisruptor((ConversantDisruptor) messageBus.asyncPublisher) { @Override public void shutdown() { // do nothing for a clone! } }; } } /** * Clones the this MessageBus, sharing the same configurations and executor for Async Publications. *

* This will permit unique subscriptions. *

* Calling {@link #shutdown()} on this new MessageBus will NOT shutdown the thread executor. Only the ORIGINAL * MessageBus can shut it down. */ public MessageBus cloneWithSharedExecutor() { return new MessageBus(this); } /** * Subscribe all handlers of the given listener. Any listener is only subscribed once and * subsequent subscriptions of an already subscribed listener will be silently ignored */ public void subscribe(final Object listener) { subscriptionManager.subscribe(listener); } /** * Immediately remove all registered message handlers (if any) of the given listener. *

* When this call returns all handlers have effectively been removed and will not * receive any messages (provided that running publications/iterators in other threads * have not yet obtained a reference to the listener) *

* A call to this method passing any object that is not subscribed will not have any effect and is silently ignored. */ public void unsubscribe(final Object listener) { subscriptionManager.unsubscribe(listener); } /** * Synchronously publish a message to all registered listeners. *

* 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 subscription handlers of all registered listeners have been notified (invoked) of the message. */ public void publish(final Object message) { dispatch.publish(syncPublisher, errorHandler, subscriptionManager, message); } /** * Synchronously publish TWO 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 subscription handlers of all registered listeners have been notified (invoked) of the message. */ public void publish(final Object message1, final Object message2) { dispatch.publish(syncPublisher, errorHandler, subscriptionManager, message1, message2); } /** * Synchronously publish THREE 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 subscription handlers of all registered listeners have been notified (invoked) of the message. */ public void publish(final Object message1, final Object message2, final Object message3) { dispatch.publish(syncPublisher, errorHandler, subscriptionManager, message1, message2, message3); } /** * Asynchronously publish the message 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. *

* This call returns immediately. */ public void publishAsync(final Object message) { dispatch.publish(asyncPublisher, errorHandler, subscriptionManager, message); } /** * Asynchronously publish TWO 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. *

* This call returns immediately. */ public void publishAsync(final Object message1, final Object message2) { dispatch.publish(asyncPublisher, errorHandler, subscriptionManager, message1, message2); } /** * Asynchronously publish THREE 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. *

* This call returns immediately. */ public void publishAsync(final Object message1, final Object message2, final Object message3) { dispatch.publish(asyncPublisher, errorHandler, subscriptionManager, message1, message2, message3); } /** * Publication errors may occur at various points of time during message delivery. A handler may throw an exception, * may not be accessible due to security constraints or is not annotated properly. *

* In any of all possible cases a publication error is created and passed to each of the registered error handlers. * A call to this method will add the given error handler to the chain */ public void addErrorHandler(final IPublicationErrorHandler errorHandler) { this.errorHandler.addErrorHandler(errorHandler); } /** * Check whether any asynchronous message publications are pending to be processed. *

* Because of the nature of MULTI-THREADED, ASYNCHRONOUS environments, it is ** MORE THAN LIKELY ** this will not be an accurate reflection of the current state. * * @return true if there are still message publications waiting to be processed. */ public final boolean hasPendingMessages() { return asyncPublisher.hasPendingMessages(); } /** * Shutdown the bus such that it will stop delivering asynchronous messages. Executor service and * other internally used threads will be shutdown gracefully. *

* After calling shutdown it is not safe to further use the message bus. */ public void shutdown() { this.subscriptionManager.shutdown(); this.asyncPublisher.shutdown(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy