io.aeron.Aeron Maven / Gradle / Ivy
Show all versions of aeron-client Show documentation
/*
* Copyright 2014-2021 Real Logic Limited.
*
* 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
*
* https://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 io.aeron;
import io.aeron.exceptions.AeronException;
import io.aeron.exceptions.ConcurrentConcludeException;
import io.aeron.exceptions.ConfigurationException;
import io.aeron.exceptions.DriverTimeoutException;
import io.aeron.logbuffer.FragmentHandler;
import org.agrona.*;
import org.agrona.concurrent.*;
import org.agrona.concurrent.broadcast.BroadcastReceiver;
import org.agrona.concurrent.broadcast.CopyBroadcastReceiver;
import org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;
import org.agrona.concurrent.ringbuffer.RingBuffer;
import org.agrona.concurrent.status.CountersReader;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.NoSuchFileException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static java.nio.channels.FileChannel.MapMode.READ_WRITE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater;
import static org.agrona.SystemUtil.getDurationInNanos;
/**
* Aeron entry point for communicating to the Media Driver for creating {@link Publication}s and {@link Subscription}s.
* Use an {@link Aeron.Context} to configure the Aeron object.
*
* A client application requires only one Aeron object per Media Driver.
*
* Note: If {@link Aeron.Context#errorHandler(ErrorHandler)} is not set and a {@link DriverTimeoutException}
* occurs then the process will face the wrath of {@link System#exit(int)}.
* See {@link Aeron.Configuration#DEFAULT_ERROR_HANDLER}.
*/
public class Aeron implements AutoCloseable
{
/**
* Used to represent a null value for when some value is not yet set.
*/
public static final int NULL_VALUE = -1;
/**
* Using an integer because there is no support for boolean. 1 is closed, 0 is not closed.
*/
private static final AtomicIntegerFieldUpdater IS_CLOSED_UPDATER = newUpdater(Aeron.class, "isClosed");
private volatile int isClosed;
private final long clientId;
private final ClientConductor conductor;
private final RingBuffer commandBuffer;
private final AgentInvoker conductorInvoker;
private final AgentRunner conductorRunner;
private final Context ctx;
Aeron(final Context ctx)
{
try
{
ctx.conclude();
this.ctx = ctx;
clientId = ctx.clientId();
commandBuffer = ctx.toDriverBuffer();
conductor = new ClientConductor(ctx, this);
if (ctx.useConductorAgentInvoker())
{
conductorInvoker = new AgentInvoker(ctx.errorHandler(), null, conductor);
conductorRunner = null;
}
else
{
conductorInvoker = null;
conductorRunner = new AgentRunner(ctx.idleStrategy(), ctx.errorHandler(), null, conductor);
}
}
catch (final ConcurrentConcludeException ex)
{
throw ex;
}
catch (final Exception ex)
{
CloseHelper.quietClose(ctx::close);
throw ex;
}
}
/**
* Create an Aeron instance and connect to the media driver with a default {@link Context}.
*
* Threads required for interacting with the media driver are created and managed within the Aeron instance.
*
* @return the new {@link Aeron} instance connected to the Media Driver.
*/
public static Aeron connect()
{
return connect(new Context());
}
/**
* Create an Aeron instance and connect to the media driver.
*
* Threads required for interacting with the media driver are created and managed within the Aeron instance.
*
* If an exception occurs while trying to establish a connection then the {@link Context#close()} method
* will be called on the passed context.
*
* @param ctx for configuration of the client.
* @return the new {@link Aeron} instance connected to the Media Driver.
*/
public static Aeron connect(final Context ctx)
{
try
{
final Aeron aeron = new Aeron(ctx);
if (ctx.useConductorAgentInvoker())
{
aeron.conductorInvoker.start();
}
else
{
AgentRunner.startOnThread(aeron.conductorRunner, ctx.threadFactory());
}
return aeron;
}
catch (final ConcurrentConcludeException ex)
{
throw ex;
}
catch (final Exception ex)
{
ctx.close();
throw ex;
}
}
/**
* Print out the values from {@link #countersReader()} which can be useful for debugging.
*
* @param out to where the counters get printed.
*/
public void printCounters(final PrintStream out)
{
final CountersReader counters = countersReader();
counters.forEach((value, id, label) -> out.format("%3d: %,20d - %s%n", id, value, label));
}
/**
* Has the client been closed? If not then the CnC file may not be unmapped.
*
* @return true if the client has been explicitly closed otherwise false.
*/
public boolean isClosed()
{
return 1 == isClosed;
}
/**
* Get the {@link Aeron.Context} that is used by this client.
*
* @return the {@link Aeron.Context} that is use by this client.
*/
public Context context()
{
return ctx;
}
/**
* Get the client identity that has been allocated for communicating with the media driver.
*
* @return the client identity that has been allocated for communicating with the media driver.
*/
public long clientId()
{
return clientId;
}
/**
* Get the {@link AgentInvoker} for the client conductor.
*
* @return the {@link AgentInvoker} for the client conductor.
*/
public AgentInvoker conductorAgentInvoker()
{
return conductorInvoker;
}
/**
* Is the command still active for a given correlation id.
*
* @param correlationId to check if it is still active.
* @return true in the command is still in active processing or false if completed successfully or errored.
* @see Publication#asyncAddDestination(String)
* @see Subscription#asyncAddDestination(String)
* @see #hasActiveCommands()
*/
public boolean isCommandActive(final long correlationId)
{
return conductor.isCommandActive(correlationId);
}
/**
* Does the client have any active asynchronous commands?
*
* When close operations are performed on {@link Publication}s, {@link Subscription}s, and {@link Counter}s the
* commands are sent asynchronously to the driver. The client tracks active commands in case errors need to be
* reported. If you wish to wait for acknowledgement of close operations then wait for this method to return false.
*
* @return true if any commands are currently active otherwise false.
*/
public boolean hasActiveCommands()
{
return conductor.hasActiveCommands();
}
/**
* Clean up and release all Aeron client resources and shutdown conductor thread if not using
* {@link Context#useConductorAgentInvoker(boolean)}.
*
* This will close all currently open {@link Publication}s, {@link Subscription}s, and {@link Counter}s created
* from this client. To check for the command being acknowledged by the driver
*/
public void close()
{
if (IS_CLOSED_UPDATER.compareAndSet(this, 0, 1))
{
final ErrorHandler errorHandler = ctx.errorHandler();
if (null != conductorRunner)
{
CloseHelper.close(errorHandler, conductorRunner);
}
else
{
CloseHelper.close(errorHandler, conductorInvoker);
}
}
}
/**
* Add a {@link Publication} for publishing messages to subscribers. The publication returned is threadsafe.
*
* @param channel for sending the messages known to the media layer.
* @param streamId within the channel scope.
* @return a new {@link ConcurrentPublication}.
*/
public ConcurrentPublication addPublication(final String channel, final int streamId)
{
return conductor.addPublication(channel, streamId);
}
/**
* Add an {@link ExclusivePublication} for publishing messages to subscribers from a single thread.
*
* @param channel for sending the messages known to the media layer.
* @param streamId within the channel scope.
* @return a new {@link ExclusivePublication}.
*/
public ExclusivePublication addExclusivePublication(final String channel, final int streamId)
{
return conductor.addExclusivePublication(channel, streamId);
}
/**
* Asynchronously add a {@link Publication} for publishing messages to subscribers. The added publication returned
* is threadsafe.
*
* @param channel for sending the messages known to the media layer.
* @param streamId within the channel scope.
* @return the registration id of the publication which can be used to get the added publication.
* @see #getPublication(long)
*/
public long asyncAddPublication(final String channel, final int streamId)
{
return conductor.asyncAddPublication(channel, streamId);
}
/**
* Asynchronously add a {@link Publication} for publishing messages to subscribers from a single thread.
*
* @param channel for sending the messages known to the media layer.
* @param streamId within the channel scope.
* @return the registration id of the publication which can be used to get the added exclusive publication.
* @see #getExclusivePublication(long)
*/
public long asyncAddExclusivePublication(final String channel, final int streamId)
{
return conductor.asyncAddExclusivePublication(channel, streamId);
}
/**
* Get a {@link Publication} for publishing messages to subscribers. The publication returned is threadsafe.
*
* @param registrationId returned from {@link #asyncAddPublication(String, int)}.
* @return a new {@link ConcurrentPublication} when available otherwise null.
* @see #asyncAddPublication(String, int)
*/
public ConcurrentPublication getPublication(final long registrationId)
{
return conductor.getPublication(registrationId);
}
/**
* Get a single threaded {@link Publication} for publishing messages to subscribers.
*
* @param registrationId returned from {@link #asyncAddExclusivePublication(String, int)}.
* @return a new {@link ExclusivePublication} when available otherwise null.
* @see #asyncAddExclusivePublication(String, int)
*/
public ExclusivePublication getExclusivePublication(final long registrationId)
{
return conductor.getExclusivePublication(registrationId);
}
/**
* Add a new {@link Subscription} for subscribing to messages from publishers.
*
* The method will set up the {@link Subscription} to use the
* {@link Aeron.Context#availableImageHandler(AvailableImageHandler)} and
* {@link Aeron.Context#unavailableImageHandler(UnavailableImageHandler)} from the {@link Aeron.Context}.
*
* @param channel for receiving the messages known to the media layer.
* @param streamId within the channel scope.
* @return the {@link Subscription} for the channel and streamId pair.
*/
public Subscription addSubscription(final String channel, final int streamId)
{
return conductor.addSubscription(channel, streamId);
}
/**
* Add a new {@link Subscription} for subscribing to messages from publishers.
*
* This method will override the default handlers from the {@link Aeron.Context}, i.e.
* {@link Aeron.Context#availableImageHandler(AvailableImageHandler)} and
* {@link Aeron.Context#unavailableImageHandler(UnavailableImageHandler)}. Null values are valid and will
* result in no action being taken.
*
* @param channel for receiving the messages known to the media layer.
* @param streamId within the channel scope.
* @param availableImageHandler called when {@link Image}s become available for consumption. Null is valid if no
* action is to be taken.
* @param unavailableImageHandler called when {@link Image}s go unavailable for consumption. Null is valid if no
* action is to be taken.
* @return the {@link Subscription} for the channel and streamId pair.
*/
public Subscription addSubscription(
final String channel,
final int streamId,
final AvailableImageHandler availableImageHandler,
final UnavailableImageHandler unavailableImageHandler)
{
return conductor.addSubscription(channel, streamId, availableImageHandler, unavailableImageHandler);
}
/**
* Generate the next correlation id that is unique for the connected Media Driver.
*
* This is useful generating correlation identifiers for pairing requests with responses in a clients own
* application protocol.
*
* This method is thread safe and will work across processes that all use the same media driver.
*
* @return next correlation id that is unique for the Media Driver.
*/
public long nextCorrelationId()
{
if (1 == isClosed)
{
throw new AeronException("client is closed");
}
return commandBuffer.nextCorrelationId();
}
/**
* Get the {@link CountersReader} for the Aeron media driver counters.
*
* @return new {@link CountersReader} for the Aeron media driver in use.
*/
public CountersReader countersReader()
{
if (1 == isClosed)
{
throw new AeronException("client is closed");
}
return conductor.countersReader();
}
/**
* Allocate a counter on the media driver and return a {@link Counter} for it.
*
* The counter should be freed by calling {@link Counter#close()}.
*
* @param typeId for the counter.
* @param keyBuffer containing the optional key for the counter.
* @param keyOffset within the keyBuffer at which the key begins.
* @param keyLength of the key in the keyBuffer.
* @param labelBuffer containing the mandatory label for the counter. The label should not be length prefixed.
* @param labelOffset within the labelBuffer at which the label begins.
* @param labelLength of the label in the labelBuffer.
* @return the newly allocated counter.
* @see org.agrona.concurrent.status.CountersManager#allocate(int, DirectBuffer, int, int, DirectBuffer, int, int)
*/
public Counter addCounter(
final int typeId,
final DirectBuffer keyBuffer,
final int keyOffset,
final int keyLength,
final DirectBuffer labelBuffer,
final int labelOffset,
final int labelLength)
{
return conductor.addCounter(typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength);
}
/**
* Allocate a counter on the media driver and return a {@link Counter} for it.
*
* The counter should be freed by calling {@link Counter#close()}.
*
* @param typeId for the counter.
* @param label for the counter. It should be US-ASCII.
* @return the newly allocated counter.
* @see org.agrona.concurrent.status.CountersManager#allocate(String, int)
*/
public Counter addCounter(final int typeId, final String label)
{
return conductor.addCounter(typeId, label);
}
/**
* Add a handler to the list be called when {@link Counter}s become available.
*
* @param handler to be called when {@link Counter}s become available.
* @return registration id for the handler which can be used to remove it.
*/
public long addAvailableCounterHandler(final AvailableCounterHandler handler)
{
return conductor.addAvailableCounterHandler(handler);
}
/**
* Remove a previously added handler to the list be called when {@link Counter}s become available.
*
* @param registrationId to be removed which was returned from add method.
* @return true if found and removed otherwise false.
*/
public boolean removeAvailableCounterHandler(final long registrationId)
{
return conductor.removeAvailableCounterHandler(registrationId);
}
/**
* Remove a previously added handler to the list be called when {@link Counter}s become available.
*
* @param handler to be removed.
* @return true if found and removed otherwise false.
* @deprecated please use {@link #removeAvailableCounterHandler(long)}.
*/
@Deprecated
public boolean removeAvailableCounterHandler(final AvailableCounterHandler handler)
{
return conductor.removeAvailableCounterHandler(handler);
}
/**
* Add a handler to the list be called when {@link Counter}s become unavailable.
*
* @param handler to be called when {@link Counter}s become unavailable.
* @return registration id for the handler which can be used to remove it.
*/
public long addUnavailableCounterHandler(final UnavailableCounterHandler handler)
{
return conductor.addUnavailableCounterHandler(handler);
}
/**
* Remove a previously added handler to the list be called when {@link Counter}s become unavailable.
*
* @param registrationId to be removed which was returned from add method.
* @return true if found and removed otherwise false.
*/
public boolean removeUnavailableCounterHandler(final long registrationId)
{
return conductor.removeUnavailableCounterHandler(registrationId);
}
/**
* Remove a previously added handler to the list be called when {@link Counter}s become unavailable.
*
* @param handler to be removed.
* @return true if found and removed otherwise false.
* @deprecated please use {@link #removeUnavailableCounterHandler(long)}.
*/
@Deprecated
public boolean removeUnavailableCounterHandler(final UnavailableCounterHandler handler)
{
return conductor.removeUnavailableCounterHandler(handler);
}
/**
* Add a handler to the list be called when the Aeron client is closed.
*
* @param handler to be called when the Aeron client is closed.
* @return registration id for the handler which can be used to remove it.
*/
public long addCloseHandler(final Runnable handler)
{
return conductor.addCloseHandler(handler);
}
/**
* Remove a previously added handler to the list be called when the Aeron client is closed.
*
* @param registrationId of the handler from when it was added.
* @return true if found and removed otherwise false.
*/
public boolean removeCloseHandler(final long registrationId)
{
return conductor.removeCloseHandler(registrationId);
}
/**
* Remove a previously added handler to the list be called when the Aeron client is closed.
*
* @param handler to be removed.
* @return true if found and removed otherwise false.
* @deprecated please use {@link #removeCloseHandler(long)}.
*/
@Deprecated
public boolean removeCloseHandler(final Runnable handler)
{
return conductor.removeCloseHandler(handler);
}
/**
* Called by the {@link ClientConductor} if the client should be terminated due to timeout.
*/
void internalClose()
{
isClosed = 1;
}
/**
* Configuration options for the {@link Aeron} client.
*/
public static class Configuration
{
/**
* Duration in milliseconds for which the client conductor will sleep between duty cycles.
*/
static final long IDLE_SLEEP_MS = 16;
/**
* Duration in milliseconds for which the client will sleep when awaiting a response from the driver.
*/
static final long AWAITING_IDLE_SLEEP_MS = 1;
/**
* Duration in nanoseconds for which the client conductor will sleep between duty cycles.
*/
static final long IDLE_SLEEP_NS = TimeUnit.MILLISECONDS.toNanos(IDLE_SLEEP_MS);
/**
* Default interval between sending keepalive control messages to the driver.
*/
static final long KEEPALIVE_INTERVAL_NS = TimeUnit.MILLISECONDS.toNanos(500);
/**
* Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources
* such as memory mapped files.
*/
public static final String RESOURCE_LINGER_DURATION_PROP_NAME = "aeron.client.resource.linger.duration";
/**
* Default duration a resource should linger before deletion.
*/
public static final long RESOURCE_LINGER_DURATION_DEFAULT_NS = TimeUnit.SECONDS.toNanos(3);
/**
* Duration to linger on close so that publishers subscribers have time to notice closed resources.
* This value can be set to a few seconds if the application is likely to experience CPU starvation or
* long GC pauses.
*/
public static final String CLOSE_LINGER_DURATION_PROP_NAME = "aeron.client.close.linger.duration";
/**
* Default duration to linger on close so that publishers subscribers have time to notice closed resources.
*/
public static final long CLOSE_LINGER_DURATION_DEFAULT_NS = 0;
/**
* Should memory-mapped files be pre-touched so that they are already faulted into a process.
*
* Pre-touching files can result in it taking longer for resources to become available in
* return for avoiding later pauses due to page faults.
*/
public static final String PRE_TOUCH_MAPPED_MEMORY_PROP_NAME = "aeron.pre.touch.mapped.memory";
/**
* Default for if a memory-mapped filed should be pre-touched to fault it into a process.
*/
public static final boolean PRE_TOUCH_MAPPED_MEMORY_DEFAULT = false;
/**
* The Default handler for Aeron runtime exceptions.
* When a {@link DriverTimeoutException} is encountered, this handler will exit the program.
*
* The error handler can be overridden by supplying an {@link Context} with a custom handler.
*
* @see Context#errorHandler(ErrorHandler)
*/
public static final ErrorHandler DEFAULT_ERROR_HANDLER =
(throwable) ->
{
synchronized (System.err)
{
System.err.println(System.currentTimeMillis() + " Exception:");
throwable.printStackTrace(System.err);
}
if (throwable instanceof DriverTimeoutException)
{
System.err.printf(
"%n***%n*** timeout for the Media Driver - is it currently running? exiting%n***%n");
System.exit(-1);
}
};
/**
* Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources
* such as memory mapped files.
*
* @return duration in nanoseconds to wait before deleting an expired resource.
* @see #RESOURCE_LINGER_DURATION_PROP_NAME
*/
public static long resourceLingerDurationNs()
{
return getDurationInNanos(RESOURCE_LINGER_DURATION_PROP_NAME, RESOURCE_LINGER_DURATION_DEFAULT_NS);
}
/**
* Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources
* such as memory mapped files.
*
* @return duration in nanoseconds to wait before deleting an expired resource.
* @see #RESOURCE_LINGER_DURATION_PROP_NAME
*/
public static long closeLingerDurationNs()
{
return getDurationInNanos(CLOSE_LINGER_DURATION_PROP_NAME, CLOSE_LINGER_DURATION_DEFAULT_NS);
}
/**
* Should memory-mapped files be pre-touched so that they are already faulted into a process.
*
* @return true if memory mappings should be pre-touched, otherwise false.
* @see #PRE_TOUCH_MAPPED_MEMORY_PROP_NAME
*/
public static boolean preTouchMappedMemory()
{
final String value = System.getProperty(PRE_TOUCH_MAPPED_MEMORY_PROP_NAME);
if (null != value)
{
return Boolean.parseBoolean(value);
}
return PRE_TOUCH_MAPPED_MEMORY_DEFAULT;
}
}
/**
* Provides a means to override configuration for an {@link Aeron} client via the
* {@link Aeron#connect(Aeron.Context)} method and its overloads. It gives applications some control over
* the interactions with the Aeron Media Driver. It can also set up error handling as well as application
* callbacks for image information from the Media Driver.
*
* A number of the properties are for testing and should not be set by end users.
*
* Note: Do not reuse instances of the context across different {@link Aeron} clients.
*
* The context will be owned by {@link ClientConductor} after a successful
* {@link Aeron#connect(Context)} and closed via {@link Aeron#close()}.
*/
public static class Context extends CommonContext
{
private long clientId;
private boolean useConductorAgentInvoker = false;
private boolean preTouchMappedMemory = Configuration.preTouchMappedMemory();
private AgentInvoker driverAgentInvoker;
private Lock clientLock;
private EpochClock epochClock;
private NanoClock nanoClock;
private IdleStrategy idleStrategy;
private IdleStrategy awaitingIdleStrategy;
private CopyBroadcastReceiver toClientBuffer;
private RingBuffer toDriverBuffer;
private DriverProxy driverProxy;
private MappedByteBuffer cncByteBuffer;
private AtomicBuffer cncMetaDataBuffer;
private LogBuffersFactory logBuffersFactory;
private ErrorHandler errorHandler;
private ErrorHandler subscriberErrorHandler;
private AvailableImageHandler availableImageHandler;
private UnavailableImageHandler unavailableImageHandler;
private AvailableCounterHandler availableCounterHandler;
private UnavailableCounterHandler unavailableCounterHandler;
private Runnable closeHandler;
private long keepAliveIntervalNs = Configuration.KEEPALIVE_INTERVAL_NS;
private long interServiceTimeoutNs = 0;
private long resourceLingerDurationNs = Configuration.resourceLingerDurationNs();
private long closeLingerDurationNs = Configuration.closeLingerDurationNs();
private ThreadFactory threadFactory = Thread::new;
/**
* Perform a shallow copy of the object.
*
* @return a shallow copy of the object.
*/
public Context clone()
{
return (Context)super.clone();
}
/**
* This is called automatically by {@link Aeron#connect(Aeron.Context)} and its overloads.
* There is no need to call it from a client application. It is responsible for providing default
* values for options that are not individually changed through field setters.
*
* @return this for a fluent API.
*/
public Context conclude()
{
super.conclude();
if (null == clientLock)
{
clientLock = new ReentrantLock();
}
if (null == epochClock)
{
epochClock = SystemEpochClock.INSTANCE;
}
if (null == nanoClock)
{
nanoClock = SystemNanoClock.INSTANCE;
}
if (null == idleStrategy)
{
idleStrategy = new SleepingMillisIdleStrategy(Configuration.IDLE_SLEEP_MS);
}
if (null == awaitingIdleStrategy)
{
awaitingIdleStrategy = new SleepingMillisIdleStrategy(Configuration.AWAITING_IDLE_SLEEP_MS);
}
if (cncFile() != null)
{
connectToDriver();
}
interServiceTimeoutNs = CncFileDescriptor.clientLivenessTimeoutNs(cncMetaDataBuffer);
if (interServiceTimeoutNs <= keepAliveIntervalNs)
{
throw new ConfigurationException("interServiceTimeoutNs=" + interServiceTimeoutNs +
" <= keepAliveIntervalNs=" + keepAliveIntervalNs);
}
if (null == toDriverBuffer)
{
toDriverBuffer = new ManyToOneRingBuffer(
CncFileDescriptor.createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer));
}
if (null == toClientBuffer)
{
toClientBuffer = new CopyBroadcastReceiver(new BroadcastReceiver(
CncFileDescriptor.createToClientsBuffer(cncByteBuffer, cncMetaDataBuffer)));
}
if (countersMetaDataBuffer() == null)
{
countersMetaDataBuffer(
CncFileDescriptor.createCountersMetaDataBuffer(cncByteBuffer, cncMetaDataBuffer));
}
if (countersValuesBuffer() == null)
{
countersValuesBuffer(CncFileDescriptor.createCountersValuesBuffer(cncByteBuffer, cncMetaDataBuffer));
}
if (null == logBuffersFactory)
{
logBuffersFactory = new MappedLogBuffersFactory();
}
if (null == errorHandler)
{
errorHandler = Configuration.DEFAULT_ERROR_HANDLER;
}
if (null == subscriberErrorHandler)
{
subscriberErrorHandler = errorHandler;
}
if (null == driverProxy)
{
clientId = toDriverBuffer.nextCorrelationId();
driverProxy = new DriverProxy(toDriverBuffer, clientId);
}
return this;
}
/**
* Get the client identity that has been allocated for communicating with the media driver.
*
* @return the client identity that has been allocated for communicating with the media driver.
*/
public long clientId()
{
return clientId;
}
/**
* Should an {@link AgentInvoker} be used for running the {@link ClientConductor} rather than run it on
* a thread with a {@link AgentRunner}.
*
* @param useConductorAgentInvoker use {@link AgentInvoker} be used for running the {@link ClientConductor}?
* @return this for a fluent API.
*/
public Context useConductorAgentInvoker(final boolean useConductorAgentInvoker)
{
this.useConductorAgentInvoker = useConductorAgentInvoker;
return this;
}
/**
* Should an {@link AgentInvoker} be used for running the {@link ClientConductor} rather than run it on
* a thread with a {@link AgentRunner}.
*
* @return true if the {@link ClientConductor} will be run with an {@link AgentInvoker} otherwise false.
*/
public boolean useConductorAgentInvoker()
{
return useConductorAgentInvoker;
}
/**
* Should mapped-memory be pre-touched to avoid soft page faults.
*
* @param preTouchMappedMemory true if mapped-memory should be pre-touched otherwise false.
* @return this for a fluent API.
* @see Configuration#PRE_TOUCH_MAPPED_MEMORY_PROP_NAME
*/
public Context preTouchMappedMemory(final boolean preTouchMappedMemory)
{
this.preTouchMappedMemory = preTouchMappedMemory;
return this;
}
/**
* Should mapped-memory be pre-touched to avoid soft page faults.
*
* @return true if mapped-memory should be pre-touched otherwise false.
* @see Configuration#PRE_TOUCH_MAPPED_MEMORY_PROP_NAME
*/
public boolean preTouchMappedMemory()
{
return preTouchMappedMemory;
}
/**
* Set the {@link AgentInvoker} for the Media Driver to be used while awaiting a synchronous response.
*
* Useful for when running on a low thread count scenario.
*
* @param driverAgentInvoker to be invoked while awaiting a response in the client.
* @return this for a fluent API.
*/
public Context driverAgentInvoker(final AgentInvoker driverAgentInvoker)
{
this.driverAgentInvoker = driverAgentInvoker;
return this;
}
/**
* Get the {@link AgentInvoker} that is used to run the Media Driver while awaiting a synchronous response.
*
* @return the {@link AgentInvoker} that is used for running the Media Driver.
*/
public AgentInvoker driverAgentInvoker()
{
return driverAgentInvoker;
}
/**
* The {@link Lock} that is used to provide mutual exclusion in the Aeron client.
*
* If the {@link #useConductorAgentInvoker()} is set and only one thread accesses the client
* then the lock can be set to {@link NoOpLock} to elide the lock overhead.
*
* @param lock that is used to provide mutual exclusion in the Aeron client.
* @return this for a fluent API.
*/
public Context clientLock(final Lock lock)
{
clientLock = lock;
return this;
}
/**
* Get the {@link Lock} that is used to provide mutual exclusion in the Aeron client.
*
* @return the {@link Lock} that is used to provide mutual exclusion in the Aeron client.
*/
public Lock clientLock()
{
return clientLock;
}
/**
* Set the {@link EpochClock} to be used for tracking wall clock time when interacting with the driver.
*
* @param clock {@link EpochClock} to be used for tracking wall clock time when interacting with the driver.
* @return this for a fluent API.
*/
public Context epochClock(final EpochClock clock)
{
this.epochClock = clock;
return this;
}
/**
* Get the {@link EpochClock} used by the client for the epoch time in milliseconds.
*
* @return the {@link EpochClock} used by the client for the epoch time in milliseconds.
*/
public EpochClock epochClock()
{
return epochClock;
}
/**
* Set the {@link NanoClock} to be used for tracking high resolution time.
*
* @param clock {@link NanoClock} to be used for tracking high resolution time.
* @return this for a fluent API.
*/
public Context nanoClock(final NanoClock clock)
{
this.nanoClock = clock;
return this;
}
/**
* Get the {@link NanoClock} to be used for tracking high resolution time.
*
* @return the {@link NanoClock} to be used for tracking high resolution time.
*/
public NanoClock nanoClock()
{
return nanoClock;
}
/**
* Provides an {@link IdleStrategy} for the thread responsible for the client duty cycle.
*
* @param idleStrategy Thread idle strategy for the client duty cycle.
* @return this for a fluent API.
*/
public Context idleStrategy(final IdleStrategy idleStrategy)
{
this.idleStrategy = idleStrategy;
return this;
}
/**
* Get the {@link IdleStrategy} employed by the client for the client duty cycle.
*
* @return the {@link IdleStrategy} employed by the client for the client duty cycle.
*/
public IdleStrategy idleStrategy()
{
return idleStrategy;
}
/**
* Provides an {@link IdleStrategy} to be used when awaiting a response from the Media Driver.
*
* @param idleStrategy Thread idle strategy for awaiting a response from the Media Driver.
* @return this for a fluent API.
*/
public Context awaitingIdleStrategy(final IdleStrategy idleStrategy)
{
this.awaitingIdleStrategy = idleStrategy;
return this;
}
/**
* The {@link IdleStrategy} to be used when awaiting a response from the Media Driver.
*
* This can be change to a {@link BusySpinIdleStrategy} or {@link YieldingIdleStrategy} for lower response time,
* especially for adding counters or releasing resources, at the expense of CPU usage.
*
* @return the {@link IdleStrategy} to be used when awaiting a response from the Media Driver.
*/
public IdleStrategy awaitingIdleStrategy()
{
return awaitingIdleStrategy;
}
/**
* This method is used for testing and debugging.
*
* @param toClientBuffer Injected CopyBroadcastReceiver
* @return this for a fluent API.
*/
Context toClientBuffer(final CopyBroadcastReceiver toClientBuffer)
{
this.toClientBuffer = toClientBuffer;
return this;
}
/**
* The buffer used for communicating from the media driver to the Aeron client.
*
* @return the buffer used for communicating from the media driver to the Aeron client.
*/
public CopyBroadcastReceiver toClientBuffer()
{
return toClientBuffer;
}
/**
* Get the {@link RingBuffer} used for sending commands to the media driver.
*
* @return the {@link RingBuffer} used for sending commands to the media driver.
*/
public RingBuffer toDriverBuffer()
{
return toDriverBuffer;
}
/**
* Set the proxy for communicating with the media driver.
*
* @param driverProxy for communicating with the media driver.
* @return this for a fluent API.
*/
Context driverProxy(final DriverProxy driverProxy)
{
this.driverProxy = driverProxy;
return this;
}
/**
* Get the proxy for communicating with the media driver.
*
* @return the proxy for communicating with the media driver.
*/
public DriverProxy driverProxy()
{
return driverProxy;
}
/**
* This method is used for testing and debugging.
*
* @param logBuffersFactory Injected LogBuffersFactory
* @return this for a fluent API.
*/
Context logBuffersFactory(final LogBuffersFactory logBuffersFactory)
{
this.logBuffersFactory = logBuffersFactory;
return this;
}
/**
* Get the factory for making log buffers.
*
* @return the factory for making log buffers.
*/
public LogBuffersFactory logBuffersFactory()
{
return logBuffersFactory;
}
/**
* Handle Aeron exceptions in a callback method. The default behavior is defined by
* {@link Configuration#DEFAULT_ERROR_HANDLER}. This is the error handler which will be used if an error occurs
* during the callback for poll operations such as {@link Subscription#poll(FragmentHandler, int)}.
*
* The error handler can be reset after {@link Aeron#connect()} and the latest version will always be used
* so that the boot-strapping process can be performed such as replacing the default one with a
* {@link CountedErrorHandler}.
*
* @param errorHandler Method to handle objects of type Throwable.
* @return this for a fluent API.
* @see io.aeron.exceptions.DriverTimeoutException
* @see io.aeron.exceptions.RegistrationException
*/
public Context errorHandler(final ErrorHandler errorHandler)
{
this.errorHandler = errorHandler;
return this;
}
/**
* Get the error handler that will be called for errors reported back from the media driver or during poll
* operations.
*
* @return the error handler that will be called for errors reported back from the media driver or poll
* operations.
*/
public ErrorHandler errorHandler()
{
return errorHandler;
}
/**
* The error handler which will be used if an error occurs during the callback for poll operations such as
* {@link Subscription#poll(FragmentHandler, int)}. The default will be {@link #errorHandler()} if not set.
*
* @param errorHandler Method to handle objects of type Throwable.
* @return this for a fluent API.
* @see io.aeron.exceptions.DriverTimeoutException
* @see io.aeron.exceptions.RegistrationException
*/
public Context subscriberErrorHandler(final ErrorHandler errorHandler)
{
this.subscriberErrorHandler = errorHandler;
return this;
}
/**
* This is the error handler which will be used if an error occurs during the callback for poll operations
* such as {@link Subscription#poll(FragmentHandler, int)}. The default will be {@link #errorHandler()} if not
* set. To have {@link Subscription#poll(FragmentHandler, int)} not delegate then set with
* {@link RethrowingErrorHandler}.
*
* @return the error handler that will be called for errors reported back from the media driver.
*/
public ErrorHandler subscriberErrorHandler()
{
return subscriberErrorHandler;
}
/**
* Setup a default callback for when an {@link Image} is available.
*
* @param handler Callback method for handling available image notifications.
* @return this for a fluent API.
*/
public Context availableImageHandler(final AvailableImageHandler handler)
{
this.availableImageHandler = handler;
return this;
}
/**
* Get the default callback handler for notifying when {@link Image}s become available.
*
* @return the callback handler for notifying when {@link Image}s become available.
*/
public AvailableImageHandler availableImageHandler()
{
return availableImageHandler;
}
/**
* Setup a default callback for when an {@link Image} is unavailable.
*
* @param handler Callback method for handling unavailable image notifications.
* @return this for a fluent API.
*/
public Context unavailableImageHandler(final UnavailableImageHandler handler)
{
this.unavailableImageHandler = handler;
return this;
}
/**
* Get the callback handler for when an {@link Image} is unavailable.
*
* @return the callback handler for when an {@link Image} is unavailable.
*/
public UnavailableImageHandler unavailableImageHandler()
{
return unavailableImageHandler;
}
/**
* Setup a callback for when a counter is available. This will be added to the list first before
* additional handler are added with {@link Aeron#addAvailableCounterHandler(AvailableCounterHandler)}.
*
* @param handler to be called for handling available counter notifications.
* @return this for a fluent API.
*/
public Context availableCounterHandler(final AvailableCounterHandler handler)
{
this.availableCounterHandler = handler;
return this;
}
/**
* Get the callback handler for when a counter is available.
*
* @return the callback handler for when a counter is available.
*/
public AvailableCounterHandler availableCounterHandler()
{
return availableCounterHandler;
}
/**
* Setup a callback for when a counter is unavailable. This will be added to the list first before
* additional handler are added with {@link Aeron#addUnavailableCounterHandler(UnavailableCounterHandler)}.
*
* @param handler to be called for handling unavailable counter notifications.
* @return this for a fluent API.
*/
public Context unavailableCounterHandler(final UnavailableCounterHandler handler)
{
this.unavailableCounterHandler = handler;
return this;
}
/**
* Get the callback handler for when a counter is unavailable.
*
* @return the callback handler for when a counter is unavailable.
*/
public UnavailableCounterHandler unavailableCounterHandler()
{
return unavailableCounterHandler;
}
/**
* Set a {@link Runnable} that is called when the client is closed by timeout or normal means.
*
* It is not safe to call any API functions from any threads after this hook is called. In addition, any
* in flight calls may still cause faults. Thus treating this as a hard error and terminate the process in
* this hook as soon as possible is recommended.
*
* @param handler that is called when the client is closed.
* @return this for a fluent API.
*/
public Context closeHandler(final Runnable handler)
{
this.closeHandler = handler;
return this;
}
/**
* Get the {@link Runnable} that is called when the client is closed by timeout or normal means.
*
* @return the {@link Runnable} that is called when the client is closed.
*/
public Runnable closeHandler()
{
return closeHandler;
}
/**
* Set the interval in nanoseconds for which the client will perform keep-alive operations.
*
* @param value the interval in nanoseconds for which the client will perform keep-alive operations.
* @return this for a fluent API.
*/
public Context keepAliveIntervalNs(final long value)
{
keepAliveIntervalNs = value;
return this;
}
/**
* Get the interval in nanoseconds for which the client will perform keep-alive operations.
*
* @return the interval in nanoseconds for which the client will perform keep-alive operations.
*/
public long keepAliveIntervalNs()
{
return keepAliveIntervalNs;
}
/**
* Set the amount of time, in milliseconds, that this client will wait until it determines the
* Media Driver is unavailable. When this happens a
* {@link io.aeron.exceptions.DriverTimeoutException} will be generated for the error handler.
*
* @param value Number of milliseconds.
* @return this for a fluent API.
* @see #errorHandler(ErrorHandler)
*/
public Context driverTimeoutMs(final long value)
{
super.driverTimeoutMs(value);
return this;
}
/**
* Set the timeout between service calls the to {@link ClientConductor} duty cycles in nanoseconds.
*
* Note: the method is used for testing only.
*
* @param interServiceTimeout the timeout (ns) between service calls the to {@link ClientConductor} duty cycle.
* @return this for a fluent API.
*/
Context interServiceTimeoutNs(final long interServiceTimeout)
{
this.interServiceTimeoutNs = interServiceTimeout;
return this;
}
/**
* Return the timeout between service calls to the duty cycle for the client.
*
* When exceeded, {@link #errorHandler()} will be called and the active {@link Publication}s, {@link Image}s,
* and {@link Counter}s will be closed.
*
* This value is controlled by the driver and included in the CnC file. It can be configured by adjusting
* the {@code aeron.client.liveness.timeout} property set on the media driver.
*
* @return the timeout in nanoseconds between service calls as an allowed maximum.
*/
public long interServiceTimeoutNs()
{
return CommonContext.checkDebugTimeout(interServiceTimeoutNs, TimeUnit.NANOSECONDS);
}
/**
* Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources
* such as memory mapped files.
*
* @param resourceLingerDurationNs to wait before deleting an expired resource.
* @return this for a fluent API.
* @see Configuration#RESOURCE_LINGER_DURATION_PROP_NAME
*/
public Context resourceLingerDurationNs(final long resourceLingerDurationNs)
{
this.resourceLingerDurationNs = resourceLingerDurationNs;
return this;
}
/**
* Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources
* such as memory mapped files.
*
* @return duration in nanoseconds to wait before deleting an expired resource.
* @see Configuration#RESOURCE_LINGER_DURATION_PROP_NAME
*/
public long resourceLingerDurationNs()
{
return resourceLingerDurationNs;
}
/**
* Duration to linger on closing to allow publishers and subscribers time to notice closed resources.
*
* This value can be increased from the default to a few seconds to better cope with long GC pauses
* or resource starved environments. Issues could manifest as seg faults using files after they have
* been unmapped from publishers or subscribers not noticing the close in a timely fashion.
*
* @param closeLingerDurationNs to wait before deleting resources when closing.
* @return this for a fluent API.
* @see Configuration#CLOSE_LINGER_DURATION_PROP_NAME
*/
public Context closeLingerDurationNs(final long closeLingerDurationNs)
{
this.closeLingerDurationNs = closeLingerDurationNs;
return this;
}
/**
* Duration to linger on closing to allow publishers and subscribers time to notice closed resources.
*
* This value can be increased from the default to a few seconds to better cope with long GC pauses
* or resource starved environments. Issues could manifest as seg faults using files after they have
* been unmapped from publishers or subscribers not noticing the close in a timely fashion.
*
* @return duration in nanoseconds to wait before deleting resources when closing.
* @see Configuration#CLOSE_LINGER_DURATION_PROP_NAME
*/
public long closeLingerDurationNs()
{
return closeLingerDurationNs;
}
/**
* {@inheritDoc}
*/
public Context aeronDirectoryName(final String dirName)
{
super.aeronDirectoryName(dirName);
return this;
}
/**
* Specify the thread factory to use when starting the conductor thread.
*
* @param threadFactory thread factory to construct the thread.
* @return this for a fluent API.
*/
public Context threadFactory(final ThreadFactory threadFactory)
{
this.threadFactory = threadFactory;
return this;
}
/**
* The thread factory to be used to construct the conductor thread.
*
* @return the specified thread factory or {@link Thread#Thread(Runnable)} if none is provided.
*/
public ThreadFactory threadFactory()
{
return threadFactory;
}
/**
* Clean up all resources that the client uses to communicate with the Media Driver.
*/
public void close()
{
BufferUtil.free(cncByteBuffer);
this.cncByteBuffer = null;
super.close();
}
/**
* {@inheritDoc}
*/
public String toString()
{
return "Aeron.Context" +
"\n{" +
"\n isConcluded=" + isConcluded() +
"\n aeronDirectory=" + aeronDirectory() +
"\n aeronDirectoryName='" + aeronDirectoryName() + '\'' +
"\n cncFile=" + cncFile() +
"\n countersMetaDataBuffer=" + countersMetaDataBuffer() +
"\n countersValuesBuffer=" + countersValuesBuffer() +
"\n driverTimeoutMs=" + driverTimeoutMs() +
"\n clientId=" + clientId +
"\n useConductorAgentInvoker=" + useConductorAgentInvoker +
"\n preTouchMappedMemory=" + preTouchMappedMemory +
"\n driverAgentInvoker=" + driverAgentInvoker +
"\n clientLock=" + clientLock +
"\n epochClock=" + epochClock +
"\n nanoClock=" + nanoClock +
"\n idleStrategy=" + idleStrategy +
"\n awaitingIdleStrategy=" + awaitingIdleStrategy +
"\n toClientBuffer=" + toClientBuffer +
"\n toDriverBuffer=" + toDriverBuffer +
"\n driverProxy=" + driverProxy +
"\n cncByteBuffer=" + cncByteBuffer +
"\n cncMetaDataBuffer=" + cncMetaDataBuffer +
"\n logBuffersFactory=" + logBuffersFactory +
"\n errorHandler=" + errorHandler +
"\n subscriberErrorHandler=" + subscriberErrorHandler +
"\n availableImageHandler=" + availableImageHandler +
"\n unavailableImageHandler=" + unavailableImageHandler +
"\n availableCounterHandler=" + availableCounterHandler +
"\n unavailableCounterHandler=" + unavailableCounterHandler +
"\n closeHandler=" + closeHandler +
"\n keepAliveIntervalNs=" + keepAliveIntervalNs +
"\n interServiceTimeoutNs=" + interServiceTimeoutNs +
"\n resourceLingerDurationNs=" + resourceLingerDurationNs +
"\n closeLingerDurationNs=" + closeLingerDurationNs +
"\n threadFactory=" + threadFactory +
"\n}";
}
private void connectToDriver()
{
final long deadlineMs = epochClock.time() + driverTimeoutMs();
final File cncFile = cncFile();
while (null == toDriverBuffer)
{
cncByteBuffer = waitForFileMapping(cncFile, epochClock, deadlineMs);
cncMetaDataBuffer = CncFileDescriptor.createMetaDataBuffer(cncByteBuffer);
int cncVersion;
while (0 == (cncVersion = cncMetaDataBuffer.getIntVolatile(CncFileDescriptor.cncVersionOffset(0))))
{
if (epochClock.time() > deadlineMs)
{
throw new DriverTimeoutException("CnC file is created but not initialised");
}
sleep(Configuration.AWAITING_IDLE_SLEEP_MS);
}
CncFileDescriptor.checkVersion(cncVersion);
if (SemanticVersion.minor(cncVersion) < SemanticVersion.minor(CncFileDescriptor.CNC_VERSION))
{
throw new AeronException("driverVersion=" + SemanticVersion.toString(cncVersion) +
" insufficient for clientVersion=" + SemanticVersion.toString(CncFileDescriptor.CNC_VERSION));
}
if (!CncFileDescriptor.isCncFileLengthSufficient(cncMetaDataBuffer, cncByteBuffer.capacity()))
{
BufferUtil.free(cncByteBuffer);
cncByteBuffer = null;
cncMetaDataBuffer = null;
sleep(Configuration.AWAITING_IDLE_SLEEP_MS);
continue;
}
final ManyToOneRingBuffer ringBuffer = new ManyToOneRingBuffer(
CncFileDescriptor.createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer));
while (0 == ringBuffer.consumerHeartbeatTime())
{
if (epochClock.time() > deadlineMs)
{
throw new DriverTimeoutException("no driver heartbeat detected");
}
sleep(Configuration.AWAITING_IDLE_SLEEP_MS);
}
final long timeMs = epochClock.time();
if (ringBuffer.consumerHeartbeatTime() < (timeMs - driverTimeoutMs()))
{
if (timeMs > deadlineMs)
{
throw new DriverTimeoutException("no driver heartbeat detected");
}
BufferUtil.free(cncByteBuffer);
cncByteBuffer = null;
cncMetaDataBuffer = null;
sleep(100);
continue;
}
toDriverBuffer = ringBuffer;
}
}
}
private static MappedByteBuffer waitForFileMapping(final File file, final EpochClock clock, final long deadlineMs)
{
while (true)
{
while (!file.exists() || file.length() < CncFileDescriptor.META_DATA_LENGTH)
{
if (clock.time() > deadlineMs)
{
throw new DriverTimeoutException("CnC file not created: " + file.getAbsolutePath());
}
sleep(Configuration.IDLE_SLEEP_MS);
}
try (FileChannel fileChannel = FileChannel.open(file.toPath(), READ, WRITE))
{
final long fileSize = fileChannel.size();
if (fileSize < CncFileDescriptor.META_DATA_LENGTH)
{
if (clock.time() > deadlineMs)
{
throw new DriverTimeoutException("CnC file is created but not populated");
}
fileChannel.close();
sleep(Configuration.IDLE_SLEEP_MS);
continue;
}
return fileChannel.map(READ_WRITE, 0, fileSize);
}
catch (final NoSuchFileException ignore)
{
}
catch (final IOException ex)
{
throw new AeronException("cannot open CnC file", ex);
}
}
}
static void sleep(final long durationMs)
{
try
{
Thread.sleep(durationMs);
}
catch (final InterruptedException ex)
{
Thread.currentThread().interrupt();
throw new AeronException("unexpected interrupt", ex);
}
}
}