/*
* Copyright 2014-2017 Real Logic Ltd.
*
* 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 io.aeron;
import io.aeron.exceptions.DriverTimeoutException;
import org.agrona.ErrorHandler;
import org.agrona.IoUtil;
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.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static java.nio.channels.FileChannel.MapMode.READ_ONLY;
import static org.agrona.IoUtil.mapExistingFile;
/**
* 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.
*/
public final class Aeron implements AutoCloseable
{
/**
* The Default handler for Aeron runtime exceptions.
* When a {@link io.aeron.exceptions.DriverTimeoutException} is encountered, this handler will
* exit the program.
*
* The error handler can be overridden by supplying an {@link Aeron.Context} with a custom handler.
*
* @see Aeron.Context#errorHandler(ErrorHandler)
*/
public static final ErrorHandler DEFAULT_ERROR_HANDLER =
(throwable) ->
{
throwable.printStackTrace();
if (throwable instanceof DriverTimeoutException)
{
System.err.printf(
"%n***%n*** Timeout from the MediaDriver - is it currently running? Exiting.%n***%n");
System.exit(-1);
}
};
/**
* Duration in milliseconds for which the client conductor will sleep between duty cycles.
*/
public static final long IDLE_SLEEP_MS = 16L;
/**
* Duration in nanoseconds for which the client conductor will sleep between duty cycles.
*/
public static final long IDLE_SLEEP_NS = TimeUnit.MILLISECONDS.toNanos(IDLE_SLEEP_MS);
/**
* Default interval between sending keepalive control messages to the driver.
*/
public static final long KEEPALIVE_INTERVAL_NS = TimeUnit.MILLISECONDS.toNanos(500);
/**
* Default interval that if exceeded between duty cycles the conductor will consider itself a zombie and suicide.
*/
public static final long INTER_SERVICE_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(10);
/**
* Timeout after which if no status messages have been received then a publication is considered not connected.
*/
public static final long PUBLICATION_CONNECTION_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
private final Lock clientLock;
private final ClientConductor conductor;
private final AgentRunner conductorRunner;
private final AgentInvoker conductorInvoker;
private final RingBuffer commandBuffer;
Aeron(final Context ctx)
{
ctx.conclude();
clientLock = ctx.clientLock();
commandBuffer = ctx.toDriverBuffer;
conductor = new ClientConductor(ctx);
if (ctx.useConductorAgentInvoker())
{
conductorInvoker = new AgentInvoker(ctx.errorHandler, null, conductor);
conductorRunner = null;
ctx.conductorAgentInvoker(conductorInvoker);
}
else
{
conductorInvoker = null;
conductorRunner = new AgentRunner(ctx.idleStrategy, ctx.errorHandler, null, conductor);
}
}
/**
* 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())
{
return aeron;
}
return aeron.start(ctx.threadFactory);
}
catch (final Exception ex)
{
ctx.close();
throw ex;
}
}
/**
* Clean up and release all Aeron internal resources and shutdown threads.
*/
public void close()
{
clientLock.lock();
try
{
if (null != conductorRunner)
{
conductorRunner.close();
}
else
{
conductorInvoker.close();
}
}
finally
{
clientLock.unlock();
}
}
/**
* Add a {@link Publication} for publishing messages to subscribers.
*
* @param channel for receiving the messages known to the media layer.
* @param streamId within the channel scope.
* @return the new Publication.
*/
public Publication addPublication(final String channel, final int streamId)
{
clientLock.lock();
try
{
return conductor.addPublication(channel, streamId);
}
finally
{
clientLock.unlock();
}
}
/**
* Add an {@link ExclusivePublication} for publishing messages to subscribers from a single thread.
*
* @param channel for receiving the messages known to the media layer.
* @param streamId within the channel scope.
* @return the new Publication.
*/
public ExclusivePublication addExclusivePublication(final String channel, final int streamId)
{
clientLock.lock();
try
{
return conductor.addExclusivePublication(channel, streamId);
}
finally
{
clientLock.unlock();
}
}
/**
* 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)
{
clientLock.lock();
try
{
return conductor.addSubscription(channel, streamId);
}
finally
{
clientLock.unlock();
}
}
/**
* 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)
{
clientLock.lock();
try
{
return conductor.addSubscription(channel, streamId, availableImageHandler, unavailableImageHandler);
}
finally
{
clientLock.unlock();
}
}
/**
* 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 (conductor.status() != ClientConductor.Status.ACTIVE)
{
throw new IllegalStateException("Client is closed");
}
return commandBuffer.nextCorrelationId();
}
/**
* Create and return a {@link CountersReader} for the Aeron media driver counters.
*
* @return new {@link CountersReader} for the Aeron media driver in use.
*/
public CountersReader countersReader()
{
if (conductor.status() != ClientConductor.Status.ACTIVE)
{
throw new IllegalStateException("Client is closed");
}
final Context ctx = conductor.context();
return new CountersReader(ctx.countersMetaDataBuffer(), ctx.countersValuesBuffer());
}
private Aeron start(final ThreadFactory threadFactory)
{
AgentRunner.startOnThread(conductorRunner, threadFactory);
return this;
}
/**
* This class provides configuration for the {@link Aeron} class 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.
*/
public static class Context extends CommonContext
{
private boolean useConductorAgentInvoker = false;
private AgentInvoker conductorAgentInvoker;
private AgentInvoker driverAgentInvoker;
private Lock clientLock;
private EpochClock epochClock;
private NanoClock nanoClock;
private IdleStrategy idleStrategy;
private CopyBroadcastReceiver toClientBuffer;
private RingBuffer toDriverBuffer;
private DriverProxy driverProxy;
private MappedByteBuffer cncByteBuffer;
private AtomicBuffer cncMetaDataBuffer;
private LogBuffersFactory logBuffersFactory;
private ErrorHandler errorHandler;
private AvailableImageHandler availableImageHandler;
private UnavailableImageHandler unavailableImageHandler;
private long keepAliveInterval = KEEPALIVE_INTERVAL_NS;
private long interServiceTimeout = 0;
private long publicationConnectionTimeout = PUBLICATION_CONNECTION_TIMEOUT_MS;
private FileChannel.MapMode imageMapMode;
private ThreadFactory threadFactory = Thread::new;
/**
* 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 Aeron.Context for method chaining.
*/
public Context conclude()
{
super.conclude();
if (null == clientLock)
{
clientLock = new ReentrantLock();
}
if (null == epochClock)
{
epochClock = new SystemEpochClock();
}
if (null == nanoClock)
{
nanoClock = new SystemNanoClock();
}
if (null == idleStrategy)
{
idleStrategy = new SleepingMillisIdleStrategy(IDLE_SLEEP_MS);
}
if (cncFile() != null)
{
connectToDriver();
}
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 (0 == interServiceTimeout)
{
interServiceTimeout = CncFileDescriptor.clientLivenessTimeout(cncMetaDataBuffer);
}
else
{
interServiceTimeout = INTER_SERVICE_TIMEOUT_NS;
}
if (null == logBuffersFactory)
{
logBuffersFactory = new MappedLogBuffersFactory();
}
if (null == errorHandler)
{
errorHandler = DEFAULT_ERROR_HANDLER;
}
if (null == imageMapMode)
{
imageMapMode = READ_ONLY;
}
if (null == driverProxy)
{
driverProxy = new DriverProxy(toDriverBuffer);
}
return this;
}
/**
* 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;
}
Context conductorAgentInvoker(final AgentInvoker conductorAgentInvoker)
{
this.conductorAgentInvoker = conductorAgentInvoker;
return this;
}
/**
* Get the {@link AgentInvoker} that is used to run the {@link ClientConductor}.
*
* @return the {@link AgentInvoker} that is used to run the {@link ClientConductor}.
*/
public AgentInvoker conductorAgentInvoker()
{
return conductorAgentInvoker;
}
/**
* 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 #conductorAgentInvoker(AgentInvoker)} 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 Aeron.Context for method chaining
*/
public Context epochClock(final EpochClock clock)
{
this.epochClock = clock;
return this;
}
/**
* Get the {@link java.time.Clock} used by the client for the epoch time in milliseconds.
*
* @return the {@link java.time.Clock} 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 Aeron.Context for method chaining
*/
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 IdleStrategy for the thread responsible for communicating with the Aeron Media Driver.
*
* @param idleStrategy Thread idle strategy for communication with the Media Driver.
* @return this Aeron.Context for method chaining.
*/
public Context idleStrategy(final IdleStrategy idleStrategy)
{
this.idleStrategy = idleStrategy;
return this;
}
/**
* Get the {@link IdleStrategy} employed by the client conductor thread.
*
* @return the {@link IdleStrategy} employed by the client conductor thread.
*/
public IdleStrategy idleStrategy()
{
return idleStrategy;
}
/**
* This method is used for testing and debugging.
*
* @param toClientBuffer Injected CopyBroadcastReceiver
* @return this Aeron.Context for method chaining.
*/
public 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;
}
/**
* This method is used for testing and debugging.
*
* @param toDriverBuffer Injected RingBuffer.
* @return this Aeron.Context for method chaining.
*/
public Context toDriverBuffer(final RingBuffer toDriverBuffer)
{
this.toDriverBuffer = toDriverBuffer;
return this;
}
/**
* 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 Aeron.Context for method chaining.
*/
public 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 Aeron.Context for method chaining.
*/
public 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 Aeron#DEFAULT_ERROR_HANDLER}.
*
* @param errorHandler Method to handle objects of type Throwable.
* @return this Aeron.Context for method chaining.
* @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.
*
* @return the error handler that will be called for errors reported back from the media driver.
*/
public ErrorHandler errorHandler()
{
return errorHandler;
}
/**
* Setup a default callback for when an {@link Image} is available.
*
* @param handler Callback method for handling available image notifications.
* @return this Aeron.Context for method chaining.
*/
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 Aeron.Context for method chaining.
*/
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;
}
/**
* 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 Aeron.Context for method chaining.
*/
public Context keepAliveInterval(final long value)
{
keepAliveInterval = 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 keepAliveInterval()
{
return keepAliveInterval;
}
/**
* 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 Aeron.Context for method chaining.
* @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.
*
* @param interServiceTimeout the timeout (ms) between service calls the to {@link ClientConductor} duty cycles.
* @return this Aeron.Context for method chaining.
*/
public Context interServiceTimeout(final long interServiceTimeout)
{
this.interServiceTimeout = 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 and {@link Image}s
* closed.
*
* This value is controlled by the driver and included in the CnC file.
*
* @return the timeout between service calls in nanoseconds.
*/
public long interServiceTimeout()
{
return interServiceTimeout;
}
/**
* @see CommonContext#aeronDirectoryName(String)
*/
public Context aeronDirectoryName(final String dirName)
{
super.aeronDirectoryName(dirName);
return this;
}
/**
* Set the amount of time, in milliseconds, that this client will use to determine if a {@link Publication}
* has active subscribers or not.
*
* @param value number of milliseconds.
* @return this Aeron.Context for method chaining.
*/
public Context publicationConnectionTimeout(final long value)
{
publicationConnectionTimeout = value;
return this;
}
/**
* Return the timeout, in milliseconds, that this client will use to determine if a {@link Publication}
* has active subscribers or not.
*
* @return timeout in milliseconds.
*/
public long publicationConnectionTimeout()
{
return publicationConnectionTimeout;
}
/**
* The file memory mapping mode for {@link Image}s.
*
* @param imageMapMode file memory mapping mode for {@link Image}s.
* @return this for a fluent API.
*/
public Context imageMapMode(final FileChannel.MapMode imageMapMode)
{
this.imageMapMode = imageMapMode;
return this;
}
/**
* The file memory mapping mode for {@link Image}s.
*
* @return the file memory mapping mode for {@link Image}s.
*/
public FileChannel.MapMode imageMapMode()
{
return imageMapMode;
}
/**
* 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 use 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()
{
IoUtil.unmap(cncByteBuffer);
cncByteBuffer = null;
super.close();
}
private void connectToDriver()
{
final long startTimeMs = epochClock.time();
final File cncFile = cncFile();
while (true)
{
while (!cncFile.exists())
{
if (epochClock.time() > (startTimeMs + driverTimeoutMs()))
{
throw new DriverTimeoutException("CnC file not found: " + cncFile.getName());
}
sleep(16);
}
cncByteBuffer = mapExistingFile(cncFile(), CncFileDescriptor.CNC_FILE);
cncMetaDataBuffer = CncFileDescriptor.createMetaDataBuffer(cncByteBuffer);
int cncVersion;
while (0 == (cncVersion = cncMetaDataBuffer.getInt(CncFileDescriptor.cncVersionOffset(0))))
{
if (epochClock.time() > (startTimeMs + driverTimeoutMs()))
{
throw new DriverTimeoutException("CnC file is created but not initialised.");
}
sleep(1);
}
if (CncFileDescriptor.CNC_VERSION != cncVersion)
{
throw new IllegalStateException("CnC file version not supported: version=" + cncVersion);
}
final ManyToOneRingBuffer ringBuffer = new ManyToOneRingBuffer(
CncFileDescriptor.createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer));
while (0 == ringBuffer.consumerHeartbeatTime())
{
if (epochClock.time() > (startTimeMs + driverTimeoutMs()))
{
throw new DriverTimeoutException("No driver heartbeat detected.");
}
sleep(1);
}
final long timeMs = epochClock.time();
if (ringBuffer.consumerHeartbeatTime() < (timeMs - driverTimeoutMs()))
{
if (timeMs > (startTimeMs + driverTimeoutMs()))
{
throw new DriverTimeoutException("No driver heartbeat detected.");
}
IoUtil.unmap(cncByteBuffer);
cncByteBuffer = null;
cncMetaDataBuffer = null;
sleep(100);
continue;
}
if (null == toDriverBuffer)
{
toDriverBuffer = ringBuffer;
}
break;
}
}
}
static void sleep(final long durationMs)
{
try
{
Thread.sleep(durationMs);
}
catch (final InterruptedException ignore)
{
Thread.interrupted();
}
}
}