
net.sf.eBus.client.EClient Maven / Gradle / Ivy
//
// Copyright 2014 - 2016, 2020 Charles W. Rapp
//
// 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 net.sf.eBus.client;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import java.io.File;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.openhft.affinity.AffinityLock;
import net.sf.eBus.config.EConfigure;
import net.sf.eBus.config.EConfigure.DispatcherType;
import net.sf.eBus.config.ThreadAffinity;
import net.sf.eBus.config.ThreadAffinityConfigure;
import net.sf.eBus.config.ThreadAffinityConfigure.AffinityType;
import net.sf.eBus.config.ThreadType;
import net.sf.eBus.util.IndexPool;
/**
* {@code EClient} ties together eBus message routing and message
* processing. eBus views an application instance in three ways:
* 1) an implementation of one or more eBus interfaces
* (publisher, subscriber, requester, or replier), 2) the owner
* of open feeds and 3) the target of callback
* {@link Runnable tasks}. {@code EClient} stores the instance's
* open feeds and un-executed callback tasks.
*
* eBus has a client-oriented execution mode rather than
* task-oriented. When the client's task list is not empty, the
* {@code EClient} instance is placed on a run queue where it
* appears only once. {@code RQThread} threads remove
* clients from this run queue and execute the client's tasks.
* The {@code Dispatcher} continues executing the client's
* tasks until the client task queue or run quantum is exhausted.
* When the nanosecond run quantum reaches zero, the client is
* appended to the run queue if it has pending tasks.
* The run quantum reset to the configurable initial quantum
* value when a dispatcher thread acquires the client. Note: when
* the run quantum expires, eBus cannot preempt the client. It is
* possible for the client to run indefinitely, taking over the
* {@code Dispatcher} thread.
*
*
* {@code EClient} maintains a weak reference to the application
* instance. When eBus detects that the application instance is
* finalized, all feeds left open by the instance are closed. If
* the application instance is a replier, then all active
* requests will receive an error reply. The client's un-executed
* tasks are cleared.
*
* eBus Client Execution
* {@code EClient} maintains one or more {@code RQThread}
* groups where each group has a separate run queue. Each client
* is assigned to an executor group based on the encapsulated
* application class. If the application class is not assigned to
* a particular executor group, then the class is assigned to the
* default executor group. The executor groups are defined via
* {@link EConfigure configuration}. If no executors are defined,
* then there is one {@code Dispatcher} thread for each
* {@link Runtime#availableProcessors() core} using a single,
* blocking run queue. Executors block until there is an runnable
* {@code EClient} posted to group run queue. As stated
* previously, an {@code EClient} is assigned to only one client
* dispatcher thread at a time. This makes an {@code EClient}
* instance effectively single-threaded if that client
* is accessed only by dispatcher threads. While a client may be
* accessed by different dispatcher threads over time, it will be
* accessed by only one at any given moment in time.
*
* Each {@code EClient} is assigned a integer identifier which is
* guaranteed unique for the client lifespan. When the client
* is disposed, the client identifier is re-used and may be
* assigned to a new, future eBus client instance. An
* {@code EClient} is disposed when the encapsulated application
* instance is finalized.
*
*
* The reason for {@code EClient} is because eBus requires
* application objects to {@code implement} interfaces in order
* to interface with eBus, not {@code extend} an
* eBus abstract class. This class provides the functionality
* that an abstract client class would provide.
*
*
* @see EFeed
* @see ESubject
*
* @author Charles Rapp
*/
public final class EClient
extends WeakReference
implements Comparable
{
//---------------------------------------------------------------
// Enums.
//
/**
* An eBus client is either local to this JVM or in a remote
* JVM.
*/
public enum ClientLocation
{
/**
* Client resides in this JVM.
*/
LOCAL (0x1, "local"),
/**
* Client resides in another JVM.
*/
REMOTE (0x2, "remote");
//-----------------------------------------------------------
// Member data.
//
/**
* The location bit mask used with
* {@link EFeed.FeedScope}.
*/
public final int mask;
//-------------------------------------------------------
// Locals.
//
/**
* Human-readable text describing this zone.
*/
private final String mDescription;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a client location with for the given
* description.
* @param mask location bit mask.
* @param text client location description.
*/
private ClientLocation(final int mask,
final String text)
{
this.mask = mask;
this.mDescription = text;
} // end of ClientLocation(int, String)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
@Override
public String toString()
{
return (mDescription);
} // end of toString()
//
// end of Object Method Overrides.
//-------------------------------------------------------
} // end of enum ClientLocation
/**
* The eBus client state is independent of the
* {@link RunState} which tracks {@link #mTasks} status.
* This state tracks whether the eBus client's start-up or
* shutdown method was or is being executed.
*/
private enum ClientState
{
/**
* eBus client has not been started yet.
*
* Next state: {@code STARTING}
*
*/
NOT_STARTED,
/**
* eBus client start-up method is now being executed.
*
* Next state: {@code STARTED}
*
*/
STARTING,
/**
* eBus client is now started. This state is achieved
* regardless of whether the start-up method completed
* successfully or not.
*
* This is the initial state if the eBus client is
* registered due to opening a feed.
*
*
* Next state: {@code SHUTTING_DOWN}
*
*/
STARTED,
/**
* eBus client shutdown method is in-progress.
*
* Next state: NOT_STARTED
*
*/
SHUTTING_DOWN
} // end of ClientState
/**
* Defines the eBus client run states. The client run
* state changes as tasks are posted and removed from the
* client task queue.
*/
private enum RunState
{
/**
* The eBus client has no pending tasks. Will not
* be on the run queue.
*/
IDLE,
/**
* The eBus client has pending tasks. Will be on the
* run queue, waiting for a {@code RQThread}
* to execute its oldest pending task.
*/
READY,
/**
* The eBus client is processing its oldest task. May or
* may not have pending tasks. Once the processing is
* completed, the client is placed back on the run queue
* if it still has pending tasks.
*/
RUNNING,
/**
* The eBus client is defunct because the underlying
* application instance is finalized. Once the eBus
* client enters this state, it never leaves.
*/
DEFUNCT
} // end of enum RunState
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* The default dispatcher name is {@value}.
* The default dispatcher type is
* {@link ThreadType#BLOCKING blocking}.
*/
private static final String DEFAULT_DISPATCHER =
"__DEFAULT__";
/**
* The eBus client executor thread names are prefixed with
* "eBus:executor-".
*/
private static final String EBUS_THREAD_NAME_PREFIX =
"eBus:dispatcher-";
//-----------------------------------------------------------
// Statics.
//
/**
* Used to assign a unique 4-byte, signed integer index to
* each eBus client. This index is guaranteed to be unique
* for the client's lifespan only and may be re-used by
* multiple clients over the JVM execution.
*/
private static final IndexPool sClientPool;
/**
* Maps the eBus client instances to their matching
* {@link EClient} proxy.
*/
private static final List sClients;
/**
* When the referenced eBus target object is finalized, the
* {@code EClient} referencing the defunct target is placed
* on this reference queue for disposal.
*/
private static final ReferenceQueue super EObject> sGcQueue;
/**
* This thread uses the blocking
* {@link ReferenceQueue#remove()} to take the next defunct
* {@code EClient} from {@link #sGcQueue}, cleaning up after
* the finalized eBus client.
*/
private static final Thread sGcThread;
/**
* Maps the run queue name to its configuration information.
*/
private static final Map sRunQueues;
/**
* Maps an eBus client class to its assigned run queue.
*/
private static final Map, DispatcherInfo> sDispatchers;
/**
* The automatically loaded eBus configuration. Will be
* {@code null} if no eBus configuration file was specified
* in the command line.
*/
private static final EConfigure sEBusConfig;
/**
* If an eBus client class does not appear in
* {@link #sDispatchers}, then assign that class to the default
* run queue.
*/
private static DispatcherInfo sDefaultDispatcher;
/**
* Logging subsystem interface.
*/
private static final Logger sLogger;
// Static initialization block.
static
{
final String jsonFile =
System.getProperty(EConfigure.JSON_FILE_ENV);
EConfigure eConfig = null;
Map dispatchers =
Collections.emptyMap();
DispatcherInfo info;
int index;
String name;
sLogger = Logger.getLogger(EClient.class.getName());
sClientPool = new IndexPool();
sClients = new ArrayList<>();
sGcQueue = new ReferenceQueue<>();
sGcThread =
new Thread("eBus:finalizeThread")
{
@Override
public void run()
{
EClient proxy;
while (true)
{
try
{
// Get the next defunct eBus client.
proxy = (EClient) sGcQueue.remove();
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format(
"EClient: removing eBus client %d.",
proxy.clientId()));
}
// Clean up the proxy resources.
proxy.cleanUp();
// Remove the client from the list
// and return its unique index to the
// pool.
sClients.remove(proxy);
sClientPool.returnIndex(
proxy.clientId());
}
catch (InterruptedException interrupt)
{}
}
// The point of no return.
}
};
sGcThread.start();
if (!Strings.isNullOrEmpty(jsonFile))
{
eConfig = loadJsonFile(jsonFile);
dispatchers = eConfig.dispatchers();
}
sRunQueues = new HashMap<>();
sDispatchers = new HashMap<>();
// Are any of the configured Dispatchers set to default?
if (!containsDefault(dispatchers.values()))
{
// No. Add a default default Dispatcher
// configuration.
int numProcs =
(Runtime.getRuntime()).availableProcessors();
final EConfigure.DispatcherBuilder builder =
EConfigure.dispatcherBuilder();
dispatchers = new HashMap<>();
dispatchers.put(
DEFAULT_DISPATCHER,
builder.name(DEFAULT_DISPATCHER)
.dispatcherType(DispatcherType.EBUS)
.threadType(ThreadType.BLOCKING)
.spinLimit(0L)
.parkTime(Duration.ZERO)
.priority(Thread.NORM_PRIORITY)
.quantum(EConfigure.DEFAULT_QUANTUM)
.numberThreads(numProcs)
.isDefault(true)
.build());
}
for (EConfigure.Dispatcher dispatcher :
dispatchers.values())
{
info = new DispatcherInfo(dispatcher);
sRunQueues.put(dispatcher.name(), info);
// Is this the default run queue?
if (dispatcher.isDefault())
{
// Yes.
sDefaultDispatcher = info;
}
// No. Get the dispatcher classes and create the
// class-to-run queue mappings.
else
{
for (Class> clazz : dispatcher.classes())
{
sDispatchers.put(clazz, info);
}
}
// Start the eBus Dispatchers running.
for (index = 0;
index < dispatcher.numberThreads();
++index)
{
name =
(EBUS_THREAD_NAME_PREFIX +
dispatcher.name() +
"-" +
index);
(new RQThread(name,
index,
info.runQueue(),
dispatcher)).start();
}
// Drop the previous affinity lock since it is now
// no longer used.
RQThread.sPreviousLock = null;
}
sEBusConfig = eConfig;
if (eConfig != null)
{
// Open eBus servers and unicast and multicast
// connections.
try
{
EServer.configure(eConfig);
ERemoteApp.configure(eConfig);
EMulticastConnection.configure(eConfig);
}
catch (IOException ioex)
{
sLogger.log(
Level.WARNING,
"Failure to open eBus remote connections:",
ioex);
}
}
} // end of Static initialization block.
//-----------------------------------------------------------
// Locals.
//
/**
* Store away the target class for reporting purposes.
*/
private final Class> mTargetClass;
/**
* The unique 4-byte, signed integer identifier for this
* client. This identifier is unique for the client lifespan
* only and may be re-used by future client instances.
*/
private final int mClientId;
/**
* The client location is either local or remote.
*/
private final ClientLocation mLocation;
/**
* Use an {@link IndexPool} to assign unique identifiers to
* the client feeds. When a feed is closed, its identifier is
* returned to this pool.
*/
private final IndexPool mFeedIdPool;
/**
* Stores the eBus client's currently active feeds. If the
* client is finalized, all active feeds are immediately
* closed.
*/
private final List mFeeds;
/**
* Execute this task when starting up an eBus client.
*/
private final Runnable mStartupCallback;
/**
* Execute this task when shutting down an eBus client.
*/
private final Runnable mShutdownCallback;
/**
* Post this client when it is ready to run to this queue for
* later execution.
*/
private final Queue mRunQueue;
/**
* This client runs in this dispatcher.
*/
private final String mDispatcher;
/**
* An {@code EClient} may continue running on a
* {@link RQThread} as long as the client has pending
* tasks and up to this many nanoseconds.
*/
private final long mMaxQuantum;
/**
* The client's remaining run-time quantum. Initialized to
* {@link #mMaxQuantum} each time it is acquired by a
* {@link RQThread} thread.
*/
private long mQuantum;
/**
* Inverse of {@link #mQuantum} where total time client
* spends running.
*/
private long mRunTime;
//
// Executor data members.
//
/**
* The eBus client's pending callback tasks. If this queue is
* not empty and the client run state is not
* {@link RunState#RUNNING}, then this client will be in
* the dispatch table. When the encapsulated application
* instance is finalized, this queue is cleared.
*
* Set to {@code null} when another thread (like JavaFX GUI
* thread) is used to dispatch tasks.
*
*/
private final Queue mTasks;
/**
* This function points to the method for posting tasks to
* {@link #mTasks} or to a third-party thread for the purpose
* of executing the task.
*/
private final Consumer mDispatchHandle;
/**
* This client's current run state. This value is updated
* when new tasks are dispatched and when pending tasks are
* executed.
*/
private RunState mRunState;
/**
* Has this client been started yet or shutdown?
* Note: this data member must only be accessed from one of
* the client's run queue threads.
*/
private ClientState mClientState;
//
// Execution statistics.
//
/**
* Minimum nanoseconds spent processing messages.
*/
private long mMinimumRunTime;
/**
* Maximum nanoseconds spent processing messages.
*/
private long mMaximumRunTime;
/**
* Total nanoseconds spent processing messages.
*/
private long mTotalRunTime;
/**
* Number of times this client has been on core.
*/
private long mRunCount;
/**
* {@link #mMaxQuantum} overrun count by this client.
*/
private long mMaxQuantumOverrunCount;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new eBus client instance for the given
* application instance, executor run queue, and nanosecond
* execution quantum.
* @param target the application instance.
* @param clientId eBus client identifier.
* @param location this client is either local or remote.
* @param startCb client start up callback.
* @param shutdownCb client shut down callback.
* @param runQueue the assigned executor run queue. Will be
* {@code null} when a non-{@code null handle} is provided.
* @param handle use this function to post tasks to the
* dispatcher. Will be {@code null} when a
* non-{@code null runQueue} is provided.
* @param dispatcher Dispatcher's name.
* @param maxQuantum maximum nanosecond run quantum for this
* client.
* @param initialState eBus client initial state.
*/
@SuppressWarnings({"java:S107"})
private EClient(final EObject target,
final int clientId,
final ClientLocation location,
final Runnable startCb,
final Runnable shutdownCb,
final Queue runQueue,
final Consumer handle,
final String dispatcher,
final long maxQuantum,
final ClientState initialState)
{
super (target, sGcQueue);
mTargetClass = target.getClass();
mClientId = clientId;
mLocation = location;
mStartupCallback = startCb;
mShutdownCallback = shutdownCb;
mRunQueue = runQueue;
mDispatchHandle =
(runQueue != null ?
this::doDispatch :
handle);
mDispatcher = dispatcher;
mMaxQuantum = maxQuantum;
mQuantum = maxQuantum;
mRunTime = 0L;
mFeedIdPool = new IndexPool();
mFeeds = new ArrayList<>();
mTasks = new ArrayDeque<>();
mRunState = RunState.IDLE;
mClientState = initialState;
mMinimumRunTime = Long.MAX_VALUE;
mMaximumRunTime = Long.MIN_VALUE;
mTotalRunTime = 0L;
mRunCount = 0L;
mMaxQuantumOverrunCount = 0L;
} // end of EClient(...)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Comparable Interface Implementation.
//
/**
* Returns an integer value <, equal to, or > zero
* based on whether {@code this EClient} instance's
* identifier is <, equal to, or > {@code client}'s
* identifier.
* @param client comparison object.
* @return integer value <, equal to, or > zero.
*/
@Override
public int compareTo(final EClient client)
{
return (mClientId - client.clientId());
} // end of compareTo(EClient)
//
// end of Comparable Interface Implementation.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns {@code true} if {@code o} is a
* non-{@code null EClient} instance with the same client
* identifier as {@code this} instance; otherwise, returns
* {@code false}.
* @param o comparison object.
* @return {@code true} if the client identifiers are equal.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof EClient)
{
retcode = (mClientId == ((EClient) o).clientId());
}
return (retcode);
} // end of equals(Object)
/**
* Returns the unique client identifier as the hash code.
* @return client identifier.
*/
@Override
public int hashCode()
{
return (mClientId);
} // end of hashCode()
@Override
public String toString()
{
return ("Client-" + mClientId);
} // end of toString()
//
// end of Object Method Overrides.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get methods
//
/**
* Returns the target client. May return {@code null} if the
* target client is finalized.
* @return eBus client.
*/
public EObject target()
{
return (this.get());
} // end of target()
/**
* Returns the unique eBus client identifier. This identifier
* is guaranteed unique for the client lifespan only.
* @return eBus client identifier.
*/
public int clientId()
{
return (mClientId);
} // end of clientId()
/**
* Returns the client location.
* @return client location.
*/
public ClientLocation location()
{
return (mLocation);
} // end of location()
/**
* Returns maximum allowed time a client is allowed to
* process messages.
* @return maximum run quantum.
*/
public long maximumQuantum()
{
return (mMaxQuantum);
} // end of maximumQuantum()
/**
* Returns the auto-loaded eBus configuration. Will return
* {@code null} if no eBus configuration was automatically
* loaded.
* @return auto-loaded eBus configuration.
*/
public static EConfigure eBusConfig()
{
return (sEBusConfig);
} // end of eBusConfig()
/**
* Returns run time statistics immutable list for extant eBus
* objects. Returns an empty list if there are no currently
* registered eBus objects.
*
* The following is an example output of the returned list:
*
* ConnectionPublisher (id 0)
min run time: 1,364 nanos
max run time: 32,743,678 nanos
total run time: 34,189,949 nanos
run count: 4
avg run time: 8,547,487 nanos
dispatcher: general
max quantum: 100,000 nanos
quantum overruns: 3
MulticastConnectionPublisher (id 1)
min run time: 613 nanos
max run time: 751,792 nanos
total run time: 763,513 nanos
run count: 3
avg run time: 254,504 nanos
dispatcher: general
max quantum: 100,000 nanos
quantum overruns: 1
PingPong Main (id 2)
min run time: 10,541 nanos
max run time: 3,700,790 nanos
total run time: 3,711,331 nanos
run count: 2
avg run time: 1,855,665 nanos
dispatcher: general
max quantum: 100,000 nanos
quantum overruns: 1
Ping! Pong! Timer (id 3)
min run time: 1,260 nanos
max run time: 9,877,401 nanos
total run time: 10,195,402 nanos
run count: 5
avg run time: 2,039,080 nanos
dispatcher: general
max quantum: 100,000 nanos
quantum overruns: 2
Pinger (id 4)
min run time: 61 nanos
max run time: 33,913,494 nanos
total run time: 953,601,532 nanos
run count: 338,447
avg run time: 2,817 nanos
dispatcher: ping
max quantum: 100,000 nanos
quantum overruns: 179
Ponger (id 5)
min run time: 164 nanos
max run time: 4,439,180 nanos
total run time: 926,228,288 nanos
run count: 132,905
avg run time: 6,969 nanos
dispatcher: pong
max quantum: 100,000 nanos
quantum overruns: 235
* @return run time statistics list.
*/
public static List runTimeStats()
{
final ImmutableList.Builder builder =
ImmutableList.builder();
EObject eo;
synchronized (sClients)
{
for (EClient ec : sClients)
{
eo = ec.target();
if (eo != null)
{
builder.add(
new RunTimeStats(
eo.name(),
ec.mClientId,
ec.mMinimumRunTime,
ec.mMaximumRunTime,
ec.mTotalRunTime,
ec.mRunCount,
ec.mDispatcher,
ec.mMaxQuantum,
ec.mMaxQuantumOverrunCount));
}
}
}
return (builder.build());
} // end of runTimeStats()
/**
* Returns the target object class.
* @return target object class.
*/
/* package */ Class> targetClass()
{
return (mTargetClass);
} // end of targetClass()
/**
* Returns {@code true} if the client is local to this JVM
* and {@code false} if the client is a proxy for a remote
* eBus client.
* @return {@code true} if the client is local.
*/
/* package */ boolean isLocal()
{
return (mLocation == ClientLocation.LOCAL);
} // end of isLocal()
/**
* Returns the next available feed identifier. Returned
* values are ≥ zero. Feed identifiers are unique only
* within a given eBus client. Feed identifiers are re-used
* when a feed is closed.
* @return next client feed identifier.
*/
/* package */ int nextFeedId()
{
return (mFeedIdPool.nextIndex());
} // nextFeedId()
/**
* Returns the feed identifier to the pool for later re-use.
* @param feedId the no-longer-used feed identifier.
*/
/* package */ void returnFeedId(final int feedId)
{
mFeedIdPool.returnIndex(feedId);
} // end of returnFeedId(int)
/**
* Returns {@code true} if {@code client} is a known eBus
* client and {@code false} otherwise.
* @param client check if this object is a known eBus client.
* @return {@code true} if {@code client} is known to eBus.
*/
/* package */ static boolean hasClient(final EObject client)
{
boolean retcode = false;
synchronized (sClients)
{
final Iterator cit = sClients.iterator();
while (cit.hasNext() && !retcode)
{
retcode = (client == (cit.next()).get());
}
}
return (retcode);
} // end of hasClient(EObject)
/**
* Returns the eBus proxy for the given application object.
* Returns {@code null} if {@code client} is currently
* unknown to eBus.
* @param client search for this client's eBus proxy.
* @return eBus proxy for {@code client}.
*/
/* package */ static EClient findClient(final EObject client)
{
EClient retval = null;
synchronized (sClients)
{
final Iterator cit = sClients.iterator();
while (cit.hasNext() && retval == null)
{
retval = cit.next();
if (client != retval.get())
{
retval = null;
}
}
}
return (retval);
} // end of findClient(EObject)
/**
* Returns the set of all currently registered eBus clients.
* The returned set is an immutable copy of the actual client
* list.
* @return currently registered eBus clients.
*/
/* package */ static List getClients()
{
final ImmutableList.Builder builder =
ImmutableList.builder();
synchronized (sClients)
{
builder.addAll(sClients);
}
return (builder.build());
} // end of getClients();
/**
* Returns the current number of clients.
* @return current client count.
*/
/* package */ static int clientCount()
{
return (sClients.size());
} // end of clientCount()
/**
* Returns the default dispatcher name.
* @return default dispatcher name.
*/
/* package */ static String defaultDispatcher()
{
return (sDefaultDispatcher.name());
} // end of defaultDispatcher()
//
// end of Get methods.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Set methods.
//
/**
* Adds the specified feed to the client feed list.
* @param feed adds this feed to the client feed list.
*
* @see #addFeed(EAbstractMultikeyFeed)
* @see #removeFeed(EFeed)
*/
/* package */ synchronized void addFeed(final EFeed feed)
{
mFeeds.add(feed);
if (sLogger.isLoggable(Level.FINEST))
{
sLogger.finest(
String.format("%s: reference count %,d.",
mTargetClass.getName(),
mFeeds.size()));
}
} // end of addFeed(EFeed)
/**
* Removes the specified feed from the eBus client feed list.
* @param feed remove this feed from the eBus client feed
* list.
*
* @see #addFeed(EFeed)
* @see #removeFeed(EAbstractMultikeyFeed)
*/
/* package */ synchronized void removeFeed(final EFeed feed)
{
mFeeds.remove(feed);
if (sLogger.isLoggable(Level.FINEST))
{
sLogger.finest(
String.format("%s: reference count %,d.",
mTargetClass.getName(),
mFeeds.size()));
}
} // end of removeFeed()
//
// end of Set methods.
//-----------------------------------------------------------
/**
* Returns the eBus proxy for the given eBus client instance.
* If the proxy does not yet exist, then creates the proxy
* and places the proxy into the map. There is only one proxy
* created for each client independent of the number of eBus
* interfaces the client implements.
* @param client the eBus client.
* @param location {@link ClientLocation#LOCAL} if the client
* is local to this JVM and {@link ClientLocation#REMOTE} if
* it exists in a remote eBus application.
* @return the eBus client proxy.
*/
public static EClient findOrCreateClient(final EObject client,
final ClientLocation location)
{
EClient retval;
synchronized (sClients)
{
retval = findClient(client);
// Does this eBus client have a proxy?
if (retval == null)
{
// No, create it now.
// Find the dispatcher for this client.
final DispatcherInfo info =
findDispatcher(client);
// Use the default
retval =
new EClient(client,
sClientPool.nextIndex(),
location,
client::startup,
client::shutdown,
info.runQueue(),
info.dispatchHandle(),
info.name(),
info.maxQuantum(),
ClientState.STARTED);
sClients.add(retval);
if (sLogger.isLoggable(Level.FINE))
{
sLogger.fine(
String.format(
"EClient: created %s client %d -> %s",
location,
retval.clientId(),
client));
}
}
}
return (retval);
} // end of findOrCreateClient(EObject, ClientLocation)
/**
* Posts {@code task} to {@code client}'s task queue (this is
* the same task queue used for eBus message delivery). If
* the task queue was originally empty, then the client
* posted to its run queue for execution by a
* {@code RQThread}.
*
* If {@code client} is unknown to eBus, then a new
* {@code EClient} instance will be created with a
* {@link WeakReference weak reference} to {@code client}.
* {@code client} does not need to be an eBus
* publisher/ subscriber/requestor/replier.
*
* @param task post to the client task queue.
* @param client {@code task} is for this client.
* @throws NullPointerException
* if either {@code task} or {@code client} is {@code null}.
*/
public static void dispatch(final Runnable task,
final EObject client)
{
Objects.requireNonNull(task, "task is null");
Objects.requireNonNull(client, "client is null");
final EClient eClient =
findOrCreateClient(client, ClientLocation.LOCAL);
eClient.dispatch(task);
} // end of dispatch(Runnable, EObject)
/**
* Posts the task to the client execution queue. This is
* performed by the {@link Consumer} method referenced by the
* dispatch handle.
* @param task post this task to the client queue.
*/
/* package */ synchronized void dispatch(final Runnable task)
{
mDispatchHandle.accept(task);
} // end of dispatch(Runnable)
/**
* Performs the actual work of posting the task to the client
* execution queue. If the client task is initially empty,
* then this eBus client is posted to its assigned run queue.
* @param task post this task to the client queue.
*/
private void doDispatch(final Runnable task)
{
// Put the task on the queue.
if (!mTasks.offer(task))
{
sLogger.warning(
String.format(
"client %d: failed to add %s to task queue.",
mClientId,
(task.getClass()).getName()));
}
// If this client is currently idle, then change it
// to ready and post it to the dispatch table.
else if (mRunState == RunState.IDLE)
{
setState(RunState.READY);
mRunQueue.offer(this);
}
// Else if this client is currently ready, then
// nothing has changed.
// If this client is currently running, then when
// the task completes, the client will be posted
// back to the dispatch table.
} // end of doDispatch(Runnable)
/**
* Returns the next task from this client's task queue if the
* task queue is not empty and the client's quantum
* is not exhausted. Otherwise, returns {@code null}.
* @param timeUsed time most recently used for this client.
* Subtract this value from {@link #mQuantum} to get this
* client's remaining run time quantum.
* @return the client's next task or {@code null} if there
* are no more tasks or no more time to run tasks.
*/
private synchronized Runnable nextTask(final long timeUsed)
{
Runnable retval = null;
mQuantum -= timeUsed;
mRunTime += timeUsed;
// Is this client still alive?
if (mRunState == RunState.DEFUNCT)
{
// no-op.
}
// Are there any more tasks to run?
else if (mTasks.isEmpty())
{
// No tasks. The client is still active but idle.
setState(RunState.IDLE);
updateRunStats(mRunTime);
// Reset the quantum as well so we have a fresh start
// when the next task is posted.
mQuantum = mMaxQuantum;
mRunTime = 0L;
}
// Yes, there are more tasks to run.
// But is there time left to run the next task?
else if (mQuantum <= 0L)
{
// No, times up. Post this client to the back of the
// run queue, giving the other clients a chance.
setState(RunState.READY);
updateRunStats(mRunTime);
// Reset this client's quantum to the allowed
// maximum and run time to zero.
mQuantum = mMaxQuantum;
mRunTime = 0L;
// Offer this client up for later sacrifice.
mRunQueue.offer(this);
}
// Yes, there is task to run and time left to run it.
// Take the next task from the queue and set this client
// running.
else
{
setState(RunState.RUNNING);
retval = mTasks.poll();
}
return (retval);
} // end of nextTask(long)
/**
* Updates client run-time statistics based on the latest
* run.
* @param runTime latest nanosecond run-time.
*/
private void updateRunStats(final long runTime)
{
if (runTime < mMinimumRunTime)
{
mMinimumRunTime = runTime;
}
if (runTime > mMaximumRunTime)
{
mMaximumRunTime = runTime;
}
if (runTime > mMaxQuantum)
{
++mMaxQuantumOverrunCount;
}
mTotalRunTime += runTime;
++mRunCount;
} // end of updateRunStats(long)
/**
* Sets the client run state, logging this fact.
* @param nextState set the client run state to this value.
*/
private void setState(final RunState nextState)
{
mRunState = nextState;
if (sLogger.isLoggable(Level.FINEST))
{
sLogger.finest(
String.format(
"client %d: run state %s.",
mClientId,
mRunState));
}
} // end of setState(RunState)
/**
* Returns the dispatcher to which the client class is
* assigned. If the client class is not assigned to a
* specific dispatcher, then the default dispatcher is
* returned.
* @param client find the dispatcher for this eBus client.
* @return the client's assigned dispatcher.
*/
/* package */ static DispatcherInfo findDispatcher(final EObject client)
{
final Class> clazz = client.getClass();
return (sDispatchers.containsKey(clazz) ?
sDispatchers.get(clazz) :
sDefaultDispatcher);
} // end of findDispatcher(EObject)
/**
* Returns an eBus proxy for the given application object,
* assigning the client to the given eBus dispatcher. This
* method allows individual objects to be assigned to a
* dispatcher rather than by class.
* @param client the application object.
* @param location the client is either local or remote.
* @param info assign {@code client} to this dispatcher.
* @param startupCb {@code client} start-up method callback.
* @param shutdownCb [@code client} shutdown method callback.
* @return the newly added eBus client proxy.
* @throws IllegalStateException
* if {@code client} is currently on the client list.
*/
/* package */ static EClient addClient(final EObject client,
final ClientLocation location,
final DispatcherInfo info,
final Runnable startupCb,
final Runnable shutdownCb)
{
final EClient retval;
synchronized (sClients)
{
// Is client already known to eBus?
if (findClient(client) != null)
{
// Yes, so it cannot be added a second time.
throw (
new IllegalStateException(
"client already registered with eBus"));
}
retval =
new EClient(client,
sClientPool.nextIndex(),
location,
startupCb,
shutdownCb,
info.runQueue(),
info.dispatchHandle(),
info.name(),
info.maxQuantum(),
ClientState.NOT_STARTED);
sClients.add(retval);
if (sLogger.isLoggable(Level.FINE))
{
sLogger.fine(
String.format(
"EClient: created client %d -> %s",
retval.clientId(),
client));
}
}
return (retval);
} // end of addClient(EObject, ClientLocation, DispatcherInfo)
/**
* Returns the dispatcher information for the given name.
* Returns {@code null} if there is no dispatcher with that
* name.
* @param name dispatcher name.
* @return dispatcher information for that name.
*/
/* package */ static DispatcherInfo
findDispatcher(final String name)
{
return (sRunQueues.get(name));
} // end of findDispatcher(String)
/**
* Starts the given eBus clients by calling each client's
* {@link #mStartupCallback} method from within the client's
* run queue.
* @param clients start up these clients.
*/
@SuppressWarnings ("AccessingNonPublicFieldOfAnotherObject")
/* package */ static void startup(final List clients)
{
clients.forEach(
client ->
client.dispatch(
new StartStopTask(client,
client.mStartupCallback,
ClientState.NOT_STARTED,
ClientState.STARTING,
ClientState.STARTED)));
} // end of startup(List<>)
/**
* Shuts down the given eBus clients by calling each client's
* {@link #mShutdownCallback} method from within the client's
* run queue.
* @param clients shut down these clients.
*/
@SuppressWarnings ("AccessingNonPublicFieldOfAnotherObject")
/* package */ static void shutdown(final List clients)
{
clients.forEach(
client ->
client.dispatch(
new StartStopTask(client,
client.mShutdownCallback,
ClientState.STARTED,
ClientState.SHUTTING_DOWN,
ClientState.NOT_STARTED)));
} // end of shutdown(List<>)
/**
* When an eBus client is finalized, close any and all active
* feeds associated with that defunct client, dispose of
* all unexecuted tasks, and mark this client as defunct.
*/
private synchronized void cleanUp()
{
mFeeds.stream().forEach(feed -> feed.close());
// Remove this client from subject listeners - in case
// it is so registered.
ESubject.removeListener(this);
mFeeds.clear();
mTasks.clear();
setState(RunState.DEFUNCT);
} // end of removeClient(EClient)
/**
* Returns the Dispatcher run queue associated with the
* dispatcher and thread types.
* @param config Dispatcher configuration.
* @return Dispatcher run queue.
*/
private static Queue runQueue(final EConfigure.Dispatcher config)
{
final DispatcherType dType = config.dispatchType();
final Queue retval;
if (dType == DispatcherType.EBUS)
{
// If the thread type is blocking, then use a
// blocking queue.
if (config.runQueueType() == ThreadType.BLOCKING)
{
retval = new LinkedBlockingQueue<>();
}
// All other thread types use a non-blocking
// queue.
else
{
retval = new ConcurrentLinkedQueue<>();
}
}
else
{
retval = null;
}
return (retval);
} // end of runQueue(DispatcherType)
/**
* Returns {@code true} if {@code dispatchers} contains an
* entry marked as the default Dispatcher. Otherwise, returns
* {@code false}.
* @param dispatchers search collection for default
* Dispatcher.
* @return {@code true} if {@code dispatchers} contains a
* default.
*/
private static boolean
containsDefault(final Collection dispatchers)
{
final Iterator dit =
dispatchers.iterator();
boolean retcode = false;
while (dit.hasNext() && !retcode)
{
retcode = (dit.next()).isDefault();
}
return (retcode);
} // end of containsDefault(Collection<>)
/**
* Returns eBus configuration load from given JSON file.
* @param jsonFileName JSON configuration file name.
* @return eBus configuration.
* @throws ConfigException
* if {@code jsonFileName} is not a valid configuration
* file.
*/
private static EConfigure loadJsonFile(final String jsonFileName)
{
final File jsonFile = new File(jsonFileName);
final Config eBusConfig =
ConfigFactory.parseFile(jsonFile);
return (EConfigure.load(eBusConfig));
} // end of loadJsonFile(String)
//---------------------------------------------------------------
// Inner classes.
//
/**
* Contains run time statistics for a given
* {@link EObject eBus object}. These statistics are in
* nanoseconds and with respect to time spend on an run queue
* thread:
*
* -
* minimum run time,
*
* -
* maximum run time,
*
* -
* total run time,
*
* -
* average run time,
*
* -
* number of times posted to a run queue thread, and
*
* -
* number of times maximum quantum was exceeded.
*
*
*/
public static final class RunTimeStats
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* eBus object name.
*/
private final String mName;
/**
* eBus object client identifier.
*/
private final int mClientId;
/**
* Minimum nanoseconds spent processing messages.
*/
private final long mMinimumRunTime;
/**
* Maximum nanoseconds spent processing messages.
*/
private final long mMaximumRunTime;
/**
* Total nanoseconds spent processing messages.
*/
private final long mTotalRunTime;
/**
* Number of times this client has been on core.
*/
private final long mRunCount;
/**
* Dispatcher responsible for running this object.
*/
private final String mDispatcher;
/**
* Maximum allowed run time. Cannot be enforced because
* Java tasks cannot be preempted.
*/
private final long mMaxQuantum;
/**
* {@link #mMaxQuantum} overrun count by this client.
*/
private final long mMaxQuantumOverrunCount;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates run time statistics for named {@link EObject}.
* @param name eBus object name.
* @param clientId eBus object identifier.
* @param minRunTime minimum run time.
* @param maxRunTime maximum run time.
* @param totalRunTime total run time.
* @param runCount total run count.
* @param dispatcher Dispatcher name.
* @param maxQuantum maximum run time quantum.
* @param overrunCount quantum overrun count.
*/
private RunTimeStats(final String name,
final int clientId,
final long minRunTime,
final long maxRunTime,
final long totalRunTime,
final long runCount,
final String dispatcher,
final long maxQuantum,
final long overrunCount)
{
mName = (Strings.isNullOrEmpty(name) ?
EObject.NAME_NOT_SET :
name);
mClientId = clientId;
mMinimumRunTime = minRunTime;
mMaximumRunTime = maxRunTime;
mTotalRunTime = totalRunTime;
mRunCount = runCount;
mDispatcher = dispatcher;
mMaxQuantum = maxQuantum;
mMaxQuantumOverrunCount = overrunCount;
} // end of RunTimeStats(...)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns eBus object's run time statistics as text.
* @return textual representation of run time
* statistics.
*/
@Override
public String toString()
{
return (
String.format(
"%s (id %d)" +
"%n min run time: %,d nanos" +
"%n max run time: %,d nanos" +
"%n total run time: %,d nanos" +
"%n run count: %,d" +
"%n avg run time: %,d nanos" +
"%n dispatcher: %s" +
"%n max quantum: %,d nanos" +
"%nquantum overruns: %,d",
mName,
mClientId,
mMinimumRunTime,
mMaximumRunTime,
mTotalRunTime,
mRunCount,
(mTotalRunTime / mRunCount),
mDispatcher,
mMaxQuantum,
mMaxQuantumOverrunCount));
} // end of toString()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns eBus object name. Returned name is neither
* {@code null} or empty.
* @return eBus object name.
*/
public String name()
{
return (mName);
} // end of name()
/**
* Return eBus-assigned identifier for {@code EObject}.
* @return eBus client identifier.
*/
public int clientId()
{
return (mClientId);
} // end of clientId()
/**
* Returns minimum nanosecond run time client spent
* processing messages.
* @return minimum nanosecond run time.
*/
public long minimumRunTime()
{
return (mMinimumRunTime);
} // end of minimumRunTime()
/**
* Returns maximum nanosecond run time client spent
* processing messages.
* @return maximum nanosecond run time.
*/
public long maximumRunTime()
{
return (mMaximumRunTime);
} // end of maximumRunTime()
/**
* Returns total nanosecond run time client spent
* processing messages.
* @return total nanosecond run time.
*/
public long totalRunTime()
{
return (mTotalRunTime);
} // end of totalRunTime()
/**
* Returns number of times client spent processing
* messages.
* @return client run count.
*/
public long runCount()
{
return (mRunCount);
} // end of runCount()
/**
* Returns average nanosecond client run time per time on
* {@link EClient.RQThread}. Calculation is
* {@link #totalRunTime()} divided by
* {@link #runCount()}.
* @return average nanosecond run time.
*/
public long averageRunTime()
{
return (mTotalRunTime / mRunCount);
} // end of averageRunTime()
/**
* Returns object's Dispatcher's name.
* @return Dispatcher name.
*/
public String dispatcher()
{
return (mDispatcher);
} // end of dispatcher()
/**
* Returns maximum allowed time a client is allowed to
* process messages.
* @return maximum run quantum.
*/
public long maximumQuantum()
{
return (mMaxQuantum);
} // end of maximumQuantum()
/**
* Returns number of times client exceeded
* {@link #maximumQuantum()} run time limit.
* @return maximum quantum exceeded count.
*/
public long maximumQuantumOverrunCount()
{
return (mMaxQuantumOverrunCount);
} // end of maximumQuantumOverrunCount()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of class RunTimeStats
/**
* A Dispatcher run queue thread watches a given
* {@link java.util.concurrent.ConcurrentLinkedQueue run queue}
* for {@link EClient} instances ready to run, attempting to
* acquire the next ready client. When the thread
* successfully acquires a client, this thread has the client
* execute its pending tasks until either 1) the client has
* no more tasks or 2) the client exhausts its run quantum.
* Each run queue has a configurable run quantum (default is
* {@link EConfigure#DEFAULT_QUANTUM}). When a client
* exhausts this quantum and still has tasks to run, the
* client is placed at the end of the run queue and its
* quantum is replenished.
*
* A client instance is referenced by only one dispatcher
* thread at time. This means a client is effectively
* single-threaded at any given instance while it may be
* accessed by multiple, different dispatcher threads over
* time.
*
*
* Note: this only applies to eBus threads.
* Non-eBus threads may still access the client object at the
* same time.
*
*/
private static final class RQThread
extends Thread
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Statics.
//
/**
* If dispatcher has multiple run queue threads
* and defined thread affinity, then this is the
* affinity lock assigned to the previous run queue
* thread. This lock is used when affinity type is a
* CPU selection strategy.
*/
private static AffinityLock sPreviousLock = null;
//-------------------------------------------------------
// Locals.
//
/**
* This executor takes ready eBus clients from this run
* queue
*/
private final Queue mRunQueue;
/**
* Use this method to extract clients from
* {@link #mRunQueue}.
*/
private final PollInterface mPollMethod;
/**
* Spin limit used when {@link #spinSleepPoll()} or
* {@link #yieldingPoll()} poll method is used.
*/
private final long mSpinLimit;
/**
* Nanosecond park time used when
* {@link #spinSleepPoll()} poll method is used.
*/
private final long mParkTime;
/**
* Thread affinity configuration. Used to associate thread
* with a CPU. Set to {@code null} if the run queue
* thread has no CPU affinity.
*/
private final ThreadAffinityConfigure mAffinity;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private RQThread(final String name,
final int index,
final Queue runQueue,
final EConfigure.Dispatcher config)
{
super (name);
mRunQueue = runQueue;
mSpinLimit = config.spinLimit();
mParkTime = (config.parkTime()).toNanos();
mAffinity = findAffinity(index, config);
switch (config.runQueueType())
{
case BLOCKING:
mPollMethod = this::blockingPoll;
break;
case SPINNING:
mPollMethod = this::spinningPoll;
break;
case SPINPARK:
mPollMethod = this::spinSleepPoll;
break;
default:
mPollMethod = this::yieldingPoll;
}
this.setDaemon(true);
} // end of Dispatcher(String, int, Queue, Dispatcher)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Thread Method Overrides.
//
/**
* A client executor thread continues processing ready
* eBus clients until the JVM exits. On each iteration,
* this thread acquires the next available client. Then
* iterates over the client's task list and executing
* each task. This continues until the client has no
* more tasks or has used up its runtime quantum. The
* client is put back on the run queue (if the quantum
* is expired, the value is reset to the configured
* maximum) and this thread goes back to the loop's top
* and acquires the next available client.
*
* Because an eBus client is posted to only one, unique
* run queue and each {@code Dispatcher} thread works
* with only one run queue, once a dispatcher thread
* acquires an eBus client, it is guaranteed that no
* other dispatcher thread has access to the
* client. Therefore, from an eBus perspective, eBus
* client access is single-threaded.
*
*
* (Non-eBus threads may still access a client
* simultaneously as a dispatcher thread. This is an
* application design decision.)
*
*/
@SuppressWarnings ({"java:S2696", "java:S3776"})
@Override
public void run()
{
EClient client;
Runnable task;
long startTime;
long timeUsed;
// If run queue thread is configured for thread
// affinity then put that affinity into place here.
if (mAffinity != null)
{
// Is this a strategy affinity?
if (mAffinity.affinityType() ==
AffinityType.CPU_STRATEGIES)
{
// Yes. Use the previous lock when selecting
// this next lock.
sPreviousLock =
ThreadAffinity.acquireLock(
sPreviousLock, mAffinity);
}
// No. Apply the CPU selection independent of
// previous lock.
else
{
sPreviousLock =
ThreadAffinity.acquireLock(mAffinity);
}
}
if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format(
"%s: running.", this.getName()));
}
if (sLogger.isLoggable(Level.FINEST))
{
sLogger.finest(
String.format(
"%s: polling run queue %s.",
this.getName(),
mRunQueue));
}
// Keep going until perdition.
while (true)
{
// Get the next client.
if ((client = mPollMethod.poll()) != null)
{
timeUsed = 0L;
// Continue processing this client until it
// either runs out of tasks or out of time.
while ((task = client.nextTask(timeUsed)) != null)
{
// Yes, there is a task to perform.
// So, perform it.
if (sLogger.isLoggable(Level.FINEST))
{
sLogger.finest(
String.format(
"%s: executing client %d, %s.",
this.getName(),
client.clientId(),
(task.getClass()).getName()));
}
// eBus tasks catch any client-thrown
// exceptions, so a try-catch block is not
// needed here.
startTime = System.nanoTime();
task.run();
timeUsed = (System.nanoTime() - startTime);
// Do not reset the client to null here but
// continue processing the client's tasks
// until the client has not tasks or its
// quantum is used up.
}
}
}
// Point of no return.
} // end of run()
//
// end of Thread Method Overrides.
//-------------------------------------------------------
/**
* Returns the next available client from the run queue,
* blocking until the client arrives.
* @return next available client. Does not return
* {@code null}.
*/
private EClient blockingPoll()
{
EClient retval = null;
while (retval == null)
{
try
{
retval =
((LinkedBlockingQueue)
mRunQueue).take();
}
catch (InterruptedException interrupt)
{}
}
return (retval);
} // end of blockingPoll()
/**
* Actively spins calling
* {@link ConcurrentLinkedQueue#poll()} to extract the
* next available client from the run queue.
* @return next available client. Does not return
* {@code null}.
*/
private EClient spinningPoll()
{
EClient retval = null;
while (retval == null)
{
retval = mRunQueue.poll();
}
return (retval);
} // end of spinningPoll()
/**
* Spins a fixed number of times calling
* {@link ConcurrentLinkedQueue#poll()} to extract the
* next available client from the run queue. When the
* spin limit is reached, then parks for a fixed number
* of nanoseconds.
* @return next available client. Does not return
* {@code null}.
*/
public EClient spinSleepPoll()
{
long counter = mSpinLimit;
EClient retval = null;
while (retval == null)
{
// Spin limit reached?
if (counter == 0)
{
// Yes. Take a nap before continuing.
LockSupport.parkNanos(mParkTime);
counter = mSpinLimit;
}
retval = mRunQueue.poll();
--counter;
}
return (retval);
} // end of spinSleepPoll()
/**
* Spins a fixed number of times calling
* {@link ConcurrentLinkedQueue#poll()} to extract the
* next available client from the run queue. When the
* spin limit is reached, then this Dispatcher thread
* yields.
* @return next available client. Does not return
* {@code null}.
*/
public EClient yieldingPoll()
{
long counter = mSpinLimit;
EClient retval = null;
while (retval == null)
{
// Spin limit reached?
if (counter == 0)
{
// Yes. Take a nap before continuing.
LockSupport.park();
counter = mSpinLimit;
}
retval = mRunQueue.poll();
--counter;
}
return (retval);
} // end of yieldingPoll()
private static ThreadAffinityConfigure findAffinity(final int index,
final EConfigure.Dispatcher config)
{
final ThreadAffinityConfigure[] affinities =
config.affinity();
final int numAffinity =
(affinities == null ? 0 : affinities.length);
final ThreadAffinityConfigure retval;
// Are affinities specified?
if (numAffinity == 0)
{
// No. Return null affinity.
retval = null;
}
// Is the affinity count greater than this index?
else if (numAffinity > index)
{
// Yes. Use the thread affinity matching the
// thread index.
retval = affinities[index];
}
// No, there are fewer affinities than threads. Use
// the final affinity.
else
{
retval = affinities[numAffinity - 1];
}
return (retval);
} // end of findAffinity(int, Dispatcher)
} // end of class Dispatcher
/**
* This immutable class stores the dispatcher name,
* associated run queue, and maximum quantum for a given
* eBus client dispatcher. This class is used to create a new
* {@code EClient} instance.
*/
@SuppressWarnings ("PackageVisibleInnerClass")
/* package */ static final class DispatcherInfo
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* The unique dispatcher name.
*/
private final String mName;
/**
* The dispatcher run queue.
*/
private final Queue mRunQueue;
/**
* References the method used to post a task.
*/
private final Consumer mDispatchHandle;
/**
* The maximum nanosecond run quantum for this
* dispatcher.
*/
private final long mMaxQuantum;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates Dispatcher information for the given
* configuration.
* @param config Dispatcher configuration.
*/
private DispatcherInfo(final EConfigure.Dispatcher config)
{
mName = config.name();
mRunQueue = EClient.runQueue(config);
mDispatchHandle =
(config.dispatchType()).dispatchHandle();
mMaxQuantum = (config.quantum()).toNanos();
} // end of DispatcherInfo(EConfigure.Dispatcher)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns the dispatcher name.
* @return dispatcher name.
*/
public String name()
{
return (mName);
} // end of name()
/**
* Returns the dispatcher run queue.
* @return run queue.
*/
public Queue runQueue()
{
return (mRunQueue);
} // end of runQueue()
/**
* Returns the dispatch method handle.
* @return dispatch method handle.
*/
public Consumer dispatchHandle()
{
return (mDispatchHandle);
} // end of dispatchHandle()
/**
* Returns the dispatcher maximum nanosecond quantum.
* @return nanosecond time.
*/
public long maxQuantum()
{
return (mMaxQuantum);
} // end of maxQuantum()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of class DispatcherInfo
/**
* This runnable is used to either start up or shutdown
* the given client.
*/
private static final class StartStopTask
implements Runnable
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Execute {@link #mTask} if-and-only-if the client's
* current state matches {@link #mInitialState}.
*/
private final EClient mClient;
/**
* The client start-up or shutdown task.
*/
private final Runnable mTask;
/**
* A client's current state must match this state in
* order to execute {@link #mTask}.
*/
private final ClientState mInitialState;
/**
* Set the client state to this value just before
* executing {@link #mTask}.
*/
private final ClientState mIntermediateState;
/**
* Set the client state to this value just after
* executing {@link #mTask}, regardless of whether the
* task succeeds or fails.
*/
private final ClientState mFinalState;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private StartStopTask(final EClient client,
final Runnable task,
final ClientState initState,
final ClientState betweenState,
final ClientState finalState)
{
mClient = client;
mTask = task;
mInitialState = initState;
mIntermediateState = betweenState;
mFinalState = finalState;
} // end of StartStopTask(...)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Runnable Interface Implementation.
//
/**
* Executes each client's startup or shutdown method
* if the client is in the expected initial
* state.
*/
@Override
@SuppressWarnings ("AccessingNonPublicFieldOfAnotherObject")
public void run()
{
final EObject target = mClient.get();
if (target != null &&
mClient.mClientState == mInitialState)
{
mClient.mClientState = mIntermediateState;
if (sLogger.isLoggable(Level.FINE))
{
sLogger.fine(
String.format(
"%s client %d (%s).",
(mIntermediateState == ClientState.STARTING ?
"Starting" :
"Stopping"),
mClient.mClientId,
target.name()));
}
try
{
mTask.run();
}
catch (Exception jex)
{
sLogger.log(Level.WARNING,
"start-up/shutdown exception:",
jex);
}
mClient.mClientState = mFinalState;
}
} // end of run()
//
// end of Runnable Interface Implementation.
//-------------------------------------------------------
} // end of class StartStopTask
/**
* Allows various methods to be substituted for
* a {@link Queue#poll()}.
*
* @param the {@code Queue} item type.
*/
@FunctionalInterface
private interface PollInterface
{
/**
* Returns the item removed from the queue's head.
* @return the queue's head or {@code null} if queue is
* empty.
*/
T poll();
} // end of interface PollInterface
} // end of class EClient