
io.aeron.cluster.service.ClusteredServiceContainer Maven / Gradle / Ivy
Show all versions of aeron-all Show documentation
/*
* Copyright 2014-2024 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.cluster.service;
import io.aeron.*;
import io.aeron.archive.client.AeronArchive;
import io.aeron.cluster.AppVersionValidator;
import io.aeron.cluster.client.ClusterException;
import io.aeron.cluster.codecs.mark.ClusterComponentType;
import io.aeron.cluster.codecs.mark.MarkFileHeaderEncoder;
import io.aeron.config.Config;
import io.aeron.config.DefaultType;
import io.aeron.driver.DutyCycleTracker;
import io.aeron.driver.status.DutyCycleStallTracker;
import io.aeron.exceptions.ConcurrentConcludeException;
import io.aeron.exceptions.ConfigurationException;
import io.aeron.version.Versioned;
import org.agrona.*;
import org.agrona.concurrent.*;
import org.agrona.concurrent.errors.DistinctErrorLog;
import org.agrona.concurrent.status.AtomicCounter;
import org.agrona.concurrent.status.StatusIndicator;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.Supplier;
import static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.*;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater;
import static org.agrona.SystemUtil.*;
/**
* Container for a service in the cluster managed by the Consensus Module. This is where business logic resides and
* loaded via {@link ClusteredServiceContainer.Configuration#SERVICE_CLASS_NAME_PROP_NAME} or
* {@link ClusteredServiceContainer.Context#clusteredService(ClusteredService)}.
*/
@Versioned
public final class ClusteredServiceContainer implements AutoCloseable
{
/**
* Launch the clustered service container and await a shutdown signal.
*
* @param args command line argument which is a list for properties files as URLs or filenames.
*/
public static void main(final String[] args)
{
loadPropertiesFiles(args);
try (ClusteredServiceContainer container = launch())
{
container.context().shutdownSignalBarrier().await();
System.out.println("Shutdown ClusteredServiceContainer...");
}
}
private final Context ctx;
private final AgentRunner serviceAgentRunner;
private ClusteredServiceContainer(final Context ctx)
{
this.ctx = ctx;
try
{
ctx.conclude();
}
catch (final Exception ex)
{
if (null != ctx.markFile)
{
ctx.markFile.signalFailedStart();
ctx.markFile.force();
}
ctx.close();
throw ex;
}
final ClusteredServiceAgent agent = new ClusteredServiceAgent(ctx);
serviceAgentRunner = new AgentRunner(ctx.idleStrategy(), ctx.errorHandler(), ctx.errorCounter(), agent);
}
/**
* Launch an ClusteredServiceContainer using a default configuration.
*
* @return a new instance of a ClusteredServiceContainer.
*/
public static ClusteredServiceContainer launch()
{
return launch(new Context());
}
/**
* Launch a ClusteredServiceContainer by providing a configuration context.
*
* @param ctx for the configuration parameters.
* @return a new instance of a ClusteredServiceContainer.
*/
public static ClusteredServiceContainer launch(final Context ctx)
{
final ClusteredServiceContainer clusteredServiceContainer = new ClusteredServiceContainer(ctx);
AgentRunner.startOnThread(clusteredServiceContainer.serviceAgentRunner, ctx.threadFactory());
return clusteredServiceContainer;
}
/**
* Get the {@link Context} that is used by this {@link ClusteredServiceContainer}.
*
* @return the {@link Context} that is used by this {@link ClusteredServiceContainer}.
*/
public Context context()
{
return ctx;
}
/**
* {@inheritDoc}
*/
public void close()
{
CloseHelper.close(serviceAgentRunner);
}
/**
* Configuration options for the consensus module and service container within a cluster.
*/
@Config(existsInC = false)
public static final class Configuration
{
/**
* Type of snapshot for this service.
*/
public static final long SNAPSHOT_TYPE_ID = 2;
/**
* Update interval for cluster mark file in nanoseconds.
*/
public static final long MARK_FILE_UPDATE_INTERVAL_NS = TimeUnit.SECONDS.toNanos(1);
/**
* Timeout in milliseconds to detect liveness.
*/
public static final long LIVENESS_TIMEOUT_MS = 10 * TimeUnit.NANOSECONDS.toMillis(MARK_FILE_UPDATE_INTERVAL_NS);
/**
* Property name for the identity of the cluster instance.
*/
@Config
public static final String CLUSTER_ID_PROP_NAME = "aeron.cluster.id";
/**
* Default identity for a clustered instance.
*/
@Config
public static final int CLUSTER_ID_DEFAULT = 0;
/**
* Identity for a clustered service. Services should be numbered from 0 and be contiguous.
*/
@Config
public static final String SERVICE_ID_PROP_NAME = "aeron.cluster.service.id";
/**
* Default identity for a clustered service.
*/
@Config
public static final int SERVICE_ID_DEFAULT = 0;
/**
* Name for a clustered service to be the role of the {@link Agent}.
*/
@Config
public static final String SERVICE_NAME_PROP_NAME = "aeron.cluster.service.name";
/**
* Name for a clustered service to be the role of the {@link Agent}.
*/
@Config
public static final String SERVICE_NAME_DEFAULT = "clustered-service";
/**
* Class name for dynamically loading a {@link ClusteredService}. This is used if
* {@link Context#clusteredService()} is not set.
*/
@Config(defaultType = DefaultType.STRING, defaultString = "")
public static final String SERVICE_CLASS_NAME_PROP_NAME = "aeron.cluster.service.class.name";
/**
* Channel to be used for log or snapshot replay on startup.
*/
@Config
public static final String REPLAY_CHANNEL_PROP_NAME = "aeron.cluster.replay.channel";
/**
* Default channel to be used for log or snapshot replay on startup.
*/
@Config
public static final String REPLAY_CHANNEL_DEFAULT = CommonContext.IPC_CHANNEL;
/**
* Stream id within a channel for the clustered log or snapshot replay.
*/
@Config
public static final String REPLAY_STREAM_ID_PROP_NAME = "aeron.cluster.replay.stream.id";
/**
* Default stream id for the log or snapshot replay within a channel.
*/
@Config
public static final int REPLAY_STREAM_ID_DEFAULT = 103;
/**
* Channel for control communications between the local consensus module and services.
*/
@Config
public static final String CONTROL_CHANNEL_PROP_NAME = "aeron.cluster.control.channel";
/**
* Default channel for communications between the local consensus module and services. This should be IPC.
*/
@Config
public static final String CONTROL_CHANNEL_DEFAULT = "aeron:ipc?term-length=128k";
/**
* Stream id within the control channel for communications from the consensus module to the services.
*/
@Config
public static final String SERVICE_STREAM_ID_PROP_NAME = "aeron.cluster.service.stream.id";
/**
* Default stream id within the control channel for communications from the consensus module.
*/
@Config
public static final int SERVICE_STREAM_ID_DEFAULT = 104;
/**
* Stream id within the control channel for communications from the services to the consensus module.
*/
@Config
public static final String CONSENSUS_MODULE_STREAM_ID_PROP_NAME = "aeron.cluster.consensus.module.stream.id";
/**
* Default stream id within a channel for communications from the services to the consensus module.
*/
@Config
public static final int CONSENSUS_MODULE_STREAM_ID_DEFAULT = 105;
/**
* Channel to be used for archiving snapshots.
*/
@Config
public static final String SNAPSHOT_CHANNEL_PROP_NAME = "aeron.cluster.snapshot.channel";
/**
* Default channel to be used for archiving snapshots.
*/
@Config
public static final String SNAPSHOT_CHANNEL_DEFAULT = "aeron:ipc?alias=snapshot";
/**
* Stream id within a channel for archiving snapshots.
*/
@Config
public static final String SNAPSHOT_STREAM_ID_PROP_NAME = "aeron.cluster.snapshot.stream.id";
/**
* Default stream id for the archived snapshots within a channel.
*/
@Config
public static final int SNAPSHOT_STREAM_ID_DEFAULT = 106;
/**
* Directory to use for the aeron cluster.
*/
@Config
public static final String CLUSTER_DIR_PROP_NAME = "aeron.cluster.dir";
/**
* Default directory to use for the aeron cluster.
*/
@Config
public static final String CLUSTER_DIR_DEFAULT = "aeron-cluster";
/**
* Directory to use for the aeron cluster services, will default to
* {@link io.aeron.cluster.ConsensusModule.Context#clusterDir()} if not specified.
*/
@Config(defaultType = DefaultType.STRING)
public static final String CLUSTER_SERVICES_DIR_PROP_NAME = "aeron.cluster.services.dir";
/**
* Directory to use for the Cluster component's mark file.
*/
@Config(defaultType = DefaultType.STRING, defaultString = "")
public static final String MARK_FILE_DIR_PROP_NAME = "aeron.cluster.mark.file.dir";
/**
* Length in bytes of the error buffer for the cluster container.
*/
@Config(id = "SERVICE_ERROR_BUFFER_LENGTH")
public static final String ERROR_BUFFER_LENGTH_PROP_NAME = "aeron.cluster.service.error.buffer.length";
/**
* Default length in bytes of the error buffer for the cluster container.
*/
@Config(id = "SERVICE_ERROR_BUFFER_LENGTH")
public static final int ERROR_BUFFER_LENGTH_DEFAULT = 1024 * 1024;
/**
* Is this a responding service to client requests property.
*/
@Config
public static final String RESPONDER_SERVICE_PROP_NAME = "aeron.cluster.service.responder";
/**
* Default to true that this a responding service to client requests.
*/
@Config
public static final boolean RESPONDER_SERVICE_DEFAULT = true;
/**
* Fragment limit to use when polling the log.
*/
@Config
public static final String LOG_FRAGMENT_LIMIT_PROP_NAME = "aeron.cluster.log.fragment.limit";
/**
* Default fragment limit for polling log.
*/
@Config
public static final int LOG_FRAGMENT_LIMIT_DEFAULT = 50;
/**
* Delegating {@link ErrorHandler} which will be first in the chain before delegating to the
* {@link Context#errorHandler()}.
*/
@Config(defaultType = DefaultType.STRING, defaultString = "")
public static final String DELEGATING_ERROR_HANDLER_PROP_NAME =
"aeron.cluster.service.delegating.error.handler";
/**
* Property name for threshold value for the container work cycle threshold to track
* for being exceeded.
*/
@Config(id = "SERVICE_CYCLE_THRESHOLD")
public static final String CYCLE_THRESHOLD_PROP_NAME = "aeron.cluster.service.cycle.threshold";
/**
* Default threshold value for the container work cycle threshold to track for being exceeded.
*/
@Config(
id = "SERVICE_CYCLE_THRESHOLD",
defaultType = DefaultType.LONG,
defaultLong = 1000L * 1000 * 1000)
public static final long CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000);
/**
* Property name for threshold value, which is used for tracking snapshot duration breaches.
*
* @since 1.44.0
*/
@Config
public static final String SNAPSHOT_DURATION_THRESHOLD_PROP_NAME = "aeron.cluster.service.snapshot.threshold";
/**
* Default threshold value, which is used for tracking snapshot duration breaches.
*
* @since 1.44.0
*/
@Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000)
public static final long SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000);
/**
* Counter type id for the cluster node role.
*/
public static final int CLUSTER_NODE_ROLE_TYPE_ID = AeronCounters.CLUSTER_NODE_ROLE_TYPE_ID;
/**
* Counter type id of the commit position.
*/
public static final int COMMIT_POSITION_TYPE_ID = AeronCounters.CLUSTER_COMMIT_POSITION_TYPE_ID;
/**
* Counter type id for the clustered service error count.
*/
public static final int CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID =
AeronCounters.CLUSTER_CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID;
/**
* The value {@link #CLUSTER_ID_DEFAULT} or system property {@link #CLUSTER_ID_PROP_NAME} if set.
*
* @return {@link #CLUSTER_ID_DEFAULT} or system property {@link #CLUSTER_ID_PROP_NAME} if set.
*/
public static int clusterId()
{
return Integer.getInteger(CLUSTER_ID_PROP_NAME, CLUSTER_ID_DEFAULT);
}
/**
* The value {@link #SERVICE_ID_DEFAULT} or system property {@link #SERVICE_ID_PROP_NAME} if set.
*
* @return {@link #SERVICE_ID_DEFAULT} or system property {@link #SERVICE_ID_PROP_NAME} if set.
*/
public static int serviceId()
{
return Integer.getInteger(SERVICE_ID_PROP_NAME, SERVICE_ID_DEFAULT);
}
/**
* The value {@link #SERVICE_NAME_DEFAULT} or system property {@link #SERVICE_NAME_PROP_NAME} if set.
*
* @return {@link #SERVICE_NAME_DEFAULT} or system property {@link #SERVICE_NAME_PROP_NAME} if set.
*/
public static String serviceName()
{
return System.getProperty(SERVICE_NAME_PROP_NAME, SERVICE_NAME_DEFAULT);
}
/**
* The value {@link #REPLAY_CHANNEL_DEFAULT} or system property {@link #REPLAY_CHANNEL_PROP_NAME} if set.
*
* @return {@link #REPLAY_CHANNEL_DEFAULT} or system property {@link #REPLAY_CHANNEL_PROP_NAME} if set.
*/
public static String replayChannel()
{
return System.getProperty(REPLAY_CHANNEL_PROP_NAME, REPLAY_CHANNEL_DEFAULT);
}
/**
* The value {@link #REPLAY_STREAM_ID_DEFAULT} or system property {@link #REPLAY_STREAM_ID_PROP_NAME}
* if set.
*
* @return {@link #REPLAY_STREAM_ID_DEFAULT} or system property {@link #REPLAY_STREAM_ID_PROP_NAME}
* if set.
*/
public static int replayStreamId()
{
return Integer.getInteger(REPLAY_STREAM_ID_PROP_NAME, REPLAY_STREAM_ID_DEFAULT);
}
/**
* The value {@link #CONTROL_CHANNEL_DEFAULT} or system property
* {@link #CONTROL_CHANNEL_PROP_NAME} if set.
*
* @return {@link #CONTROL_CHANNEL_DEFAULT} or system property
* {@link #CONTROL_CHANNEL_PROP_NAME} if set.
*/
public static String controlChannel()
{
return System.getProperty(CONTROL_CHANNEL_PROP_NAME, CONTROL_CHANNEL_DEFAULT);
}
/**
* The value {@link #CONSENSUS_MODULE_STREAM_ID_DEFAULT} or system property
* {@link #CONSENSUS_MODULE_STREAM_ID_PROP_NAME} if set.
*
* @return {@link #CONSENSUS_MODULE_STREAM_ID_DEFAULT} or system property
* {@link #CONSENSUS_MODULE_STREAM_ID_PROP_NAME} if set.
*/
public static int consensusModuleStreamId()
{
return Integer.getInteger(CONSENSUS_MODULE_STREAM_ID_PROP_NAME, CONSENSUS_MODULE_STREAM_ID_DEFAULT);
}
/**
* The value {@link #SERVICE_STREAM_ID_DEFAULT} or system property
* {@link #SERVICE_STREAM_ID_PROP_NAME} if set.
*
* @return {@link #SERVICE_STREAM_ID_DEFAULT} or system property
* {@link #SERVICE_STREAM_ID_PROP_NAME} if set.
*/
public static int serviceStreamId()
{
return Integer.getInteger(SERVICE_STREAM_ID_PROP_NAME, SERVICE_STREAM_ID_DEFAULT);
}
/**
* The value {@link #SNAPSHOT_CHANNEL_DEFAULT} or system property {@link #SNAPSHOT_CHANNEL_PROP_NAME} if set.
*
* @return {@link #SNAPSHOT_CHANNEL_DEFAULT} or system property {@link #SNAPSHOT_CHANNEL_PROP_NAME} if set.
*/
public static String snapshotChannel()
{
return System.getProperty(SNAPSHOT_CHANNEL_PROP_NAME, SNAPSHOT_CHANNEL_DEFAULT);
}
/**
* The value {@link #SNAPSHOT_STREAM_ID_DEFAULT} or system property {@link #SNAPSHOT_STREAM_ID_PROP_NAME}
* if set.
*
* @return {@link #SNAPSHOT_STREAM_ID_DEFAULT} or system property {@link #SNAPSHOT_STREAM_ID_PROP_NAME} if set.
*/
public static int snapshotStreamId()
{
return Integer.getInteger(SNAPSHOT_STREAM_ID_PROP_NAME, SNAPSHOT_STREAM_ID_DEFAULT);
}
/**
* Default {@link IdleStrategy} to be employed for cluster agents.
*/
@Config(id = "CLUSTER_IDLE_STRATEGY")
public static final String DEFAULT_IDLE_STRATEGY = "org.agrona.concurrent.BackoffIdleStrategy";
/**
* {@link IdleStrategy} to be employed for cluster agents.
*/
@Config
public static final String CLUSTER_IDLE_STRATEGY_PROP_NAME = "aeron.cluster.idle.strategy";
/**
* Property to configure if this node should take standby snapshots. The default for this property is
* false
.
*/
@Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false)
public static final String STANDBY_SNAPSHOT_ENABLED_PROP_NAME = "aeron.cluster.standby.snapshot.enabled";
/**
* Create a supplier of {@link IdleStrategy}s that will use the system property.
*
* @param controllableStatus if a {@link org.agrona.concurrent.ControllableIdleStrategy} is required.
* @return the new idle strategy
*/
public static Supplier idleStrategySupplier(final StatusIndicator controllableStatus)
{
return () ->
{
final String name = System.getProperty(CLUSTER_IDLE_STRATEGY_PROP_NAME, DEFAULT_IDLE_STRATEGY);
return io.aeron.driver.Configuration.agentIdleStrategy(name, controllableStatus);
};
}
/**
* The value {@link #CLUSTER_DIR_DEFAULT} or system property {@link #CLUSTER_DIR_PROP_NAME} if set.
*
* @return {@link #CLUSTER_DIR_DEFAULT} or system property {@link #CLUSTER_DIR_PROP_NAME} if set.
*/
public static String clusterDirName()
{
return System.getProperty(CLUSTER_DIR_PROP_NAME, CLUSTER_DIR_DEFAULT);
}
/**
* The value of system property {@link #CLUSTER_DIR_PROP_NAME} if set or null.
*
* @return {@link #CLUSTER_DIR_PROP_NAME} if set or null.
*/
public static String clusterServicesDirName()
{
return System.getProperty(CLUSTER_SERVICES_DIR_PROP_NAME);
}
/**
* Size in bytes of the error buffer in the mark file.
*
* @return length of error buffer in bytes.
* @see #ERROR_BUFFER_LENGTH_PROP_NAME
*/
public static int errorBufferLength()
{
return getSizeAsInt(ERROR_BUFFER_LENGTH_PROP_NAME, ERROR_BUFFER_LENGTH_DEFAULT);
}
/**
* The value {@link #RESPONDER_SERVICE_DEFAULT} or system property {@link #RESPONDER_SERVICE_PROP_NAME} if set.
*
* @return {@link #RESPONDER_SERVICE_DEFAULT} or system property {@link #RESPONDER_SERVICE_PROP_NAME} if set.
*/
public static boolean isRespondingService()
{
final String property = System.getProperty(RESPONDER_SERVICE_PROP_NAME);
if (null == property)
{
return RESPONDER_SERVICE_DEFAULT;
}
return "true".equals(property);
}
/**
* The value {@link #LOG_FRAGMENT_LIMIT_DEFAULT} or system property
* {@link #LOG_FRAGMENT_LIMIT_PROP_NAME} if set.
*
* @return {@link #LOG_FRAGMENT_LIMIT_DEFAULT} or system property
* {@link #LOG_FRAGMENT_LIMIT_PROP_NAME} if set.
*/
public static int logFragmentLimit()
{
return Integer.getInteger(LOG_FRAGMENT_LIMIT_PROP_NAME, LOG_FRAGMENT_LIMIT_DEFAULT);
}
/**
* Get threshold value for the container work cycle threshold to track for being exceeded.
*
* @return threshold value in nanoseconds.
*/
public static long cycleThresholdNs()
{
return getDurationInNanos(CYCLE_THRESHOLD_PROP_NAME, CYCLE_THRESHOLD_DEFAULT_NS);
}
/**
* Get threshold value, which is used for monitoring snapshot duration breaches of its predefined
* threshold.
*
* @return threshold value in nanoseconds.
*/
public static long snapshotDurationThresholdNs()
{
return getDurationInNanos(SNAPSHOT_DURATION_THRESHOLD_PROP_NAME, SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS);
}
/**
* Get the configuration value to determine if this node should take standby snapshots be enabled.
*
* @return configuration value for standby snapshots being enabled.
*/
public static boolean standbySnapshotEnabled()
{
return Boolean.getBoolean(STANDBY_SNAPSHOT_ENABLED_PROP_NAME);
}
/**
* Create a new {@link ClusteredService} based on the configured {@link #SERVICE_CLASS_NAME_PROP_NAME}.
*
* @return a new {@link ClusteredService} based on the configured {@link #SERVICE_CLASS_NAME_PROP_NAME}.
*/
public static ClusteredService newClusteredService()
{
final String className = System.getProperty(Configuration.SERVICE_CLASS_NAME_PROP_NAME);
if (null == className)
{
throw new ClusterException("either a instance or class name for the service must be provided");
}
try
{
return (ClusteredService)Class.forName(className).getConstructor().newInstance();
}
catch (final Exception ex)
{
LangUtil.rethrowUnchecked(ex);
return null;
}
}
/**
* Create a new {@link DelegatingErrorHandler} defined by {@link #DELEGATING_ERROR_HANDLER_PROP_NAME}.
*
* @return a new {@link DelegatingErrorHandler} defined by {@link #DELEGATING_ERROR_HANDLER_PROP_NAME} or
* null if property not set.
*/
public static DelegatingErrorHandler newDelegatingErrorHandler()
{
final String className = System.getProperty(Configuration.DELEGATING_ERROR_HANDLER_PROP_NAME);
if (null != className)
{
try
{
return (DelegatingErrorHandler)Class.forName(className).getConstructor().newInstance();
}
catch (final Exception ex)
{
LangUtil.rethrowUnchecked(ex);
}
}
return null;
}
/**
* Get the alternative directory to be used for storing the Cluster component's mark file.
*
* @return the directory to be used for storing the archive mark file.
*/
public static String markFileDir()
{
return System.getProperty(MARK_FILE_DIR_PROP_NAME);
}
}
/**
* The context will be owned by {@link ClusteredServiceAgent} after a successful
* {@link ClusteredServiceContainer#launch(Context)} and closed via {@link ClusteredServiceContainer#close()}.
*/
public static final class Context implements Cloneable
{
/**
* Using an integer because there is no support for boolean. 1 is concluded, 0 is not concluded.
*/
private static final AtomicIntegerFieldUpdater IS_CONCLUDED_UPDATER = newUpdater(
Context.class, "isConcluded");
private volatile int isConcluded;
private int appVersion = SemanticVersion.compose(0, 0, 1);
private int clusterId = Configuration.clusterId();
private int serviceId = Configuration.serviceId();
private String serviceName = System.getProperty(SERVICE_NAME_PROP_NAME);
private String replayChannel = Configuration.replayChannel();
private int replayStreamId = Configuration.replayStreamId();
private String controlChannel = Configuration.controlChannel();
private int consensusModuleStreamId = Configuration.consensusModuleStreamId();
private int serviceStreamId = Configuration.serviceStreamId();
private String snapshotChannel = Configuration.snapshotChannel();
private int snapshotStreamId = Configuration.snapshotStreamId();
private int errorBufferLength = Configuration.errorBufferLength();
private boolean isRespondingService = Configuration.isRespondingService();
private int logFragmentLimit = Configuration.logFragmentLimit();
private long cycleThresholdNs = Configuration.cycleThresholdNs();
private long snapshotDurationThresholdNs = Configuration.snapshotDurationThresholdNs();
private boolean standbySnapshotEnabled = Configuration.standbySnapshotEnabled();
private CountDownLatch abortLatch;
private ThreadFactory threadFactory;
private Supplier idleStrategySupplier;
private EpochClock epochClock;
private NanoClock nanoClock;
private DistinctErrorLog errorLog;
private ErrorHandler errorHandler;
private DelegatingErrorHandler delegatingErrorHandler;
private AtomicCounter errorCounter;
private CountedErrorHandler countedErrorHandler;
private AeronArchive.Context archiveContext;
private String clusterDirectoryName = Configuration.clusterDirName();
private File clusterDir;
private File markFileDir;
private String aeronDirectoryName = CommonContext.getAeronDirectoryName();
private Aeron aeron;
private DutyCycleTracker dutyCycleTracker;
private SnapshotDurationTracker snapshotDurationTracker;
private AppVersionValidator appVersionValidator;
private boolean ownsAeronClient;
private ClusteredService clusteredService;
private ShutdownSignalBarrier shutdownSignalBarrier;
private Runnable terminationHook;
private ClusterMarkFile markFile;
/**
* Perform a shallow copy of the object.
*
* @return a shallow copy of the object.
*/
public Context clone()
{
try
{
return (Context)super.clone();
}
catch (final CloneNotSupportedException ex)
{
throw new RuntimeException(ex);
}
}
/**
* Conclude configuration by setting up defaults when specifics are not provided.
*/
@SuppressWarnings("MethodLength")
public void conclude()
{
if (0 != IS_CONCLUDED_UPDATER.getAndSet(this, 1))
{
throw new ConcurrentConcludeException();
}
if (serviceId < 0 || serviceId > 127)
{
throw new ConfigurationException("service id outside allowed range (0-127): " + serviceId);
}
if (null == threadFactory)
{
threadFactory = Thread::new;
}
if (null == idleStrategySupplier)
{
idleStrategySupplier = Configuration.idleStrategySupplier(null);
}
if (null == appVersionValidator)
{
appVersionValidator = AppVersionValidator.SEMANTIC_VERSIONING_VALIDATOR;
}
if (null == epochClock)
{
epochClock = SystemEpochClock.INSTANCE;
}
if (null == nanoClock)
{
nanoClock = SystemNanoClock.INSTANCE;
}
if (null == clusterDir)
{
clusterDir = new File(clusterDirectoryName);
}
if (null == markFileDir)
{
final String dir = Configuration.markFileDir();
markFileDir = Strings.isEmpty(dir) ? clusterDir : new File(dir);
}
try
{
clusterDir = clusterDir.getCanonicalFile();
clusterDirectoryName = clusterDir.getAbsolutePath();
markFileDir = markFileDir.getCanonicalFile();
}
catch (final IOException e)
{
throw new UncheckedIOException(e);
}
IoUtil.ensureDirectoryExists(clusterDir, "cluster");
IoUtil.ensureDirectoryExists(markFileDir, "mark file");
if (null == markFile)
{
markFile = new ClusterMarkFile(
new File(markFileDir, ClusterMarkFile.markFilenameForService(serviceId)),
ClusterComponentType.CONTAINER, errorBufferLength, epochClock, LIVENESS_TIMEOUT_MS);
}
MarkFile.ensureMarkFileLink(
clusterDir,
new File(markFile.parentDirectory(), ClusterMarkFile.markFilenameForService(serviceId)),
ClusterMarkFile.linkFilenameForService(serviceId));
if (null == errorLog)
{
errorLog = new DistinctErrorLog(markFile.errorBuffer(), epochClock, US_ASCII);
}
errorHandler = CommonContext.setupErrorHandler(this.errorHandler, errorLog);
if (null == delegatingErrorHandler)
{
delegatingErrorHandler = Configuration.newDelegatingErrorHandler();
if (null != delegatingErrorHandler)
{
delegatingErrorHandler.next(errorHandler);
errorHandler = delegatingErrorHandler;
}
}
else
{
delegatingErrorHandler.next(errorHandler);
errorHandler = delegatingErrorHandler;
}
if (Strings.isEmpty(serviceName))
{
serviceName = "clustered-service-" + clusterId + "-" + serviceId;
}
if (null == aeron)
{
aeron = Aeron.connect(
new Aeron.Context()
.aeronDirectoryName(aeronDirectoryName)
.errorHandler(errorHandler)
.subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)
.awaitingIdleStrategy(YieldingIdleStrategy.INSTANCE)
.epochClock(epochClock)
.clientName(serviceName));
ownsAeronClient = true;
}
if (!(aeron.context().subscriberErrorHandler() instanceof RethrowingErrorHandler))
{
throw new ClusterException("Aeron client must use a RethrowingErrorHandler");
}
final ExpandableArrayBuffer tempBuffer = new ExpandableArrayBuffer();
if (null == errorCounter)
{
errorCounter = ClusterCounters.allocateServiceErrorCounter(aeron, tempBuffer, clusterId, serviceId);
}
if (null == countedErrorHandler)
{
countedErrorHandler = new CountedErrorHandler(errorHandler, errorCounter);
if (ownsAeronClient)
{
aeron.context().errorHandler(countedErrorHandler);
}
}
if (null == dutyCycleTracker)
{
dutyCycleTracker = new DutyCycleStallTracker(
ClusterCounters.allocateServiceCounter(
aeron,
tempBuffer,
"Cluster container max cycle time in ns",
AeronCounters.CLUSTER_CLUSTERED_SERVICE_MAX_CYCLE_TIME_TYPE_ID,
clusterId,
serviceId),
ClusterCounters.allocateServiceCounter(
aeron,
tempBuffer,
"Cluster container work cycle time exceeded count: threshold=" + cycleThresholdNs + "ns",
AeronCounters.CLUSTER_CLUSTERED_SERVICE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID,
clusterId,
serviceId),
cycleThresholdNs);
}
if (null == snapshotDurationTracker)
{
snapshotDurationTracker = new SnapshotDurationTracker(
ClusterCounters.allocateServiceCounter(
aeron,
tempBuffer,
"Clustered service max snapshot duration in ns",
AeronCounters.CLUSTERED_SERVICE_MAX_SNAPSHOT_DURATION_TYPE_ID,
clusterId,
serviceId
),
ClusterCounters.allocateServiceCounter(
aeron,
tempBuffer,
"Clustered service max snapshot duration exceeded count: threshold=" +
snapshotDurationThresholdNs,
AeronCounters.CLUSTERED_SERVICE_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID,
clusterId,
serviceId
),
snapshotDurationThresholdNs);
}
if (null == archiveContext)
{
archiveContext = new AeronArchive.Context()
.controlRequestChannel(AeronArchive.Configuration.localControlChannel())
.controlResponseChannel(AeronArchive.Configuration.localControlChannel())
.controlRequestStreamId(AeronArchive.Configuration.localControlStreamId());
}
if (!archiveContext.controlRequestChannel().startsWith(CommonContext.IPC_CHANNEL))
{
throw new ClusterException("local archive control must be IPC");
}
if (!archiveContext.controlResponseChannel().startsWith(CommonContext.IPC_CHANNEL))
{
throw new ClusterException("local archive control must be IPC");
}
archiveContext
.aeron(aeron)
.ownsAeronClient(false)
.lock(NoOpLock.INSTANCE)
.errorHandler(countedErrorHandler);
if (null == shutdownSignalBarrier)
{
shutdownSignalBarrier = new ShutdownSignalBarrier();
}
if (null == terminationHook)
{
terminationHook = () -> shutdownSignalBarrier.signalAll();
}
if (null == clusteredService)
{
clusteredService = Configuration.newClusteredService();
}
abortLatch = new CountDownLatch(aeron.conductorAgentInvoker() == null ? 1 : 0);
concludeMarkFile();
if (io.aeron.driver.Configuration.printConfigurationOnStart())
{
System.out.println(this);
}
}
/**
* Has the context had the {@link #conclude()} method called.
*
* @return true of the {@link #conclude()} method has been called.
*/
public boolean isConcluded()
{
return 1 == isConcluded;
}
/**
* User assigned application version which appended to the log as the appVersion in new leadership events.
*
* This can be validated using {@link org.agrona.SemanticVersion} to ensure only application nodes of the same
* major version communicate with each other.
*
* @param appVersion for user application.
* @return this for a fluent API.
*/
public Context appVersion(final int appVersion)
{
this.appVersion = appVersion;
return this;
}
/**
* User assigned application version which appended to the log as the appVersion in new leadership events.
*
* This can be validated using {@link org.agrona.SemanticVersion} to ensure only application nodes of the same
* major version communicate with each other.
*
* @return appVersion for user application.
*/
public int appVersion()
{
return appVersion;
}
/**
* User assigned application version validator implementation used to check version compatibility.
*
* The default validator uses {@link org.agrona.SemanticVersion} semantics.
*
* @param appVersionValidator for user application.
* @return this for fluent API.
*/
public Context appVersionValidator(final AppVersionValidator appVersionValidator)
{
this.appVersionValidator = appVersionValidator;
return this;
}
/**
* User assigned application version validator implementation used to check version compatibility.
*
* The default is to use {@link org.agrona.SemanticVersion} major version for checking compatibility.
*
* @return AppVersionValidator in use.
*/
public AppVersionValidator appVersionValidator()
{
return appVersionValidator;
}
/**
* Set the id for this cluster instance. This must match with the Consensus Module.
*
* @param clusterId for this clustered instance.
* @return this for a fluent API
* @see Configuration#CLUSTER_ID_PROP_NAME
*/
public Context clusterId(final int clusterId)
{
this.clusterId = clusterId;
return this;
}
/**
* Get the id for this cluster instance. This must match with the Consensus Module.
*
* @return the id for this cluster instance.
* @see Configuration#CLUSTER_ID_PROP_NAME
*/
@Config
public int clusterId()
{
return clusterId;
}
/**
* Set the id for this clustered service. Services should be numbered from 0 and be contiguous.
*
* @param serviceId for this clustered service.
* @return this for a fluent API
* @see Configuration#SERVICE_ID_PROP_NAME
*/
public Context serviceId(final int serviceId)
{
this.serviceId = serviceId;
return this;
}
/**
* Get the id for this clustered service. Services should be numbered from 0 and be contiguous.
*
* @return the id for this clustered service.
* @see Configuration#SERVICE_ID_PROP_NAME
*/
@Config
public int serviceId()
{
return serviceId;
}
/**
* Set the name for a clustered service to be the {@link Agent#roleName()} for the {@link Agent}.
*
* @param serviceName for a clustered service to be the role for the {@link Agent}.
* @return this for a fluent API.
* @see Configuration#SERVICE_NAME_PROP_NAME
*/
public Context serviceName(final String serviceName)
{
this.serviceName = serviceName;
return this;
}
/**
* Get the name for a clustered service to be the {@link Agent#roleName()} for the {@link Agent}.
*
* @return the name for a clustered service to be the role of the {@link Agent}.
* @see Configuration#SERVICE_NAME_PROP_NAME
*/
@Config
public String serviceName()
{
return serviceName;
}
/**
* Set the channel parameter for the cluster log and snapshot replay channel.
*
* @param channel parameter for the cluster log replay channel.
* @return this for a fluent API.
* @see Configuration#REPLAY_CHANNEL_PROP_NAME
*/
public Context replayChannel(final String channel)
{
replayChannel = channel;
return this;
}
/**
* Get the channel parameter for the cluster log and snapshot replay channel.
*
* @return the channel parameter for the cluster replay channel.
* @see Configuration#REPLAY_CHANNEL_PROP_NAME
*/
@Config
public String replayChannel()
{
return replayChannel;
}
/**
* Set the stream id for the cluster log and snapshot replay channel.
*
* @param streamId for the cluster log replay channel.
* @return this for a fluent API
* @see Configuration#REPLAY_STREAM_ID_PROP_NAME
*/
public Context replayStreamId(final int streamId)
{
replayStreamId = streamId;
return this;
}
/**
* Get the stream id for the cluster log and snapshot replay channel.
*
* @return the stream id for the cluster log replay channel.
* @see Configuration#REPLAY_STREAM_ID_PROP_NAME
*/
@Config
public int replayStreamId()
{
return replayStreamId;
}
/**
* Set the channel parameter for bidirectional communications between the consensus module and services.
*
* @param channel parameter for sending messages to the Consensus Module.
* @return this for a fluent API.
* @see Configuration#CONTROL_CHANNEL_PROP_NAME
*/
public Context controlChannel(final String channel)
{
controlChannel = channel;
return this;
}
/**
* Get the channel parameter for bidirectional communications between the consensus module and services.
*
* @return the channel parameter for sending messages to the Consensus Module.
* @see Configuration#CONTROL_CHANNEL_PROP_NAME
*/
@Config
public String controlChannel()
{
return controlChannel;
}
/**
* Set the stream id for communications from the consensus module and to the services.
*
* @param streamId for communications from the consensus module and to the services.
* @return this for a fluent API
* @see Configuration#SERVICE_STREAM_ID_PROP_NAME
*/
public Context serviceStreamId(final int streamId)
{
serviceStreamId = streamId;
return this;
}
/**
* Get the stream id for communications from the consensus module and to the services.
*
* @return the stream id for communications from the consensus module and to the services.
* @see Configuration#SERVICE_STREAM_ID_PROP_NAME
*/
@Config
public int serviceStreamId()
{
return serviceStreamId;
}
/**
* Set the stream id for communications from the services to the consensus module.
*
* @param streamId for communications from the services to the consensus module.
* @return this for a fluent API
* @see Configuration#CONSENSUS_MODULE_STREAM_ID_PROP_NAME
*/
public Context consensusModuleStreamId(final int streamId)
{
consensusModuleStreamId = streamId;
return this;
}
/**
* Get the stream id for communications from the services to the consensus module.
*
* @return the stream id for communications from the services to the consensus module.
* @see Configuration#CONSENSUS_MODULE_STREAM_ID_PROP_NAME
*/
@Config
public int consensusModuleStreamId()
{
return consensusModuleStreamId;
}
/**
* Set the channel parameter for snapshot recordings.
*
* @param channel parameter for snapshot recordings
* @return this for a fluent API.
* @see Configuration#SNAPSHOT_CHANNEL_PROP_NAME
*/
public Context snapshotChannel(final String channel)
{
snapshotChannel = channel;
return this;
}
/**
* Get the channel parameter for snapshot recordings.
*
* @return the channel parameter for snapshot recordings.
* @see Configuration#SNAPSHOT_CHANNEL_PROP_NAME
*/
@Config
public String snapshotChannel()
{
return snapshotChannel;
}
/**
* Set the stream id for snapshot recordings.
*
* @param streamId for snapshot recordings.
* @return this for a fluent API
* @see Configuration#SNAPSHOT_STREAM_ID_PROP_NAME
*/
public Context snapshotStreamId(final int streamId)
{
snapshotStreamId = streamId;
return this;
}
/**
* Get the stream id for snapshot recordings.
*
* @return the stream id for snapshot recordings.
* @see Configuration#SNAPSHOT_STREAM_ID_PROP_NAME
*/
@Config
public int snapshotStreamId()
{
return snapshotStreamId;
}
/**
* Set if this a service that responds to client requests.
*
* @param isRespondingService true if this service responds to client requests, otherwise false.
* @return this for a fluent API.
* @see Configuration#RESPONDER_SERVICE_PROP_NAME
*/
public Context isRespondingService(final boolean isRespondingService)
{
this.isRespondingService = isRespondingService;
return this;
}
/**
* Set the fragment limit to be used when polling the log {@link Subscription}.
*
* @param logFragmentLimit for this clustered service.
* @return this for a fluent API
* @see Configuration#LOG_FRAGMENT_LIMIT_DEFAULT
*/
public Context logFragmentLimit(final int logFragmentLimit)
{
this.logFragmentLimit = logFragmentLimit;
return this;
}
/**
* Get the fragment limit to be used when polling the log {@link Subscription}.
*
* @return the fragment limit to be used when polling the log {@link Subscription}.
* @see Configuration#LOG_FRAGMENT_LIMIT_PROP_NAME
*/
@Config
public int logFragmentLimit()
{
return logFragmentLimit;
}
/**
* Is this a service that responds to client requests?
*
* @return true if this service responds to client requests, otherwise false.
* @see Configuration#RESPONDER_SERVICE_PROP_NAME
*/
@Config(id = "RESPONDER_SERVICE")
public boolean isRespondingService()
{
return isRespondingService;
}
/**
* Get the thread factory used for creating threads.
*
* @return thread factory used for creating threads.
*/
public ThreadFactory threadFactory()
{
return threadFactory;
}
/**
* Set the thread factory used for creating threads.
*
* @param threadFactory used for creating threads
* @return this for a fluent API.
*/
public Context threadFactory(final ThreadFactory threadFactory)
{
this.threadFactory = threadFactory;
return this;
}
/**
* Provides an {@link IdleStrategy} supplier for the idle strategy for the agent duty cycle.
*
* @param idleStrategySupplier supplier for the idle strategy for the agent duty cycle.
* @return this for a fluent API.
*/
public Context idleStrategySupplier(final Supplier idleStrategySupplier)
{
this.idleStrategySupplier = idleStrategySupplier;
return this;
}
/**
* Get a new {@link IdleStrategy} based on configured supplier.
*
* @return a new {@link IdleStrategy} based on configured supplier.
*/
@Config(id = "CLUSTER_IDLE_STRATEGY")
public IdleStrategy idleStrategy()
{
return idleStrategySupplier.get();
}
/**
* Set the {@link EpochClock} to be used for tracking wall clock time when interacting with the container.
*
* @param clock {@link EpochClock} to be used for tracking wall clock time when interacting with the container.
* @return this for a fluent API.
*/
public Context epochClock(final EpochClock clock)
{
this.epochClock = clock;
return this;
}
/**
* Get the {@link EpochClock} to used for tracking wall clock time within the container.
*
* @return the {@link EpochClock} to used for tracking wall clock time within the container.
*/
public EpochClock epochClock()
{
return epochClock;
}
/**
* Get the {@link ErrorHandler} to be used by the {@link ClusteredServiceContainer}.
*
* @return the {@link ErrorHandler} to be used by the {@link ClusteredServiceContainer}.
*/
public ErrorHandler errorHandler()
{
return errorHandler;
}
/**
* Set the {@link ErrorHandler} to be used by the {@link ClusteredServiceContainer}.
*
* @param errorHandler the error handler to be used by the {@link ClusteredServiceContainer}.
* @return this for a fluent API
*/
public Context errorHandler(final ErrorHandler errorHandler)
{
this.errorHandler = errorHandler;
return this;
}
/**
* Get the {@link DelegatingErrorHandler} to be used by the {@link ClusteredServiceContainer} which will
* delegate to {@link #errorHandler()} as next in the chain.
*
* @return the {@link DelegatingErrorHandler} to be used by the {@link ClusteredServiceContainer}.
* @see Configuration#DELEGATING_ERROR_HANDLER_PROP_NAME
*/
@Config
public DelegatingErrorHandler delegatingErrorHandler()
{
return delegatingErrorHandler;
}
/**
* Set the {@link DelegatingErrorHandler} to be used by the {@link ClusteredServiceContainer} which will
* delegate to {@link #errorHandler()} as next in the chain.
*
* @param delegatingErrorHandler the error handler to be used by the {@link ClusteredServiceContainer}.
* @return this for a fluent API
* @see Configuration#DELEGATING_ERROR_HANDLER_PROP_NAME
*/
public Context delegatingErrorHandler(final DelegatingErrorHandler delegatingErrorHandler)
{
this.delegatingErrorHandler = delegatingErrorHandler;
return this;
}
/**
* Get the error counter that will record the number of errors the container has observed.
*
* @return the error counter that will record the number of errors the container has observed.
*/
public AtomicCounter errorCounter()
{
return errorCounter;
}
/**
* Set the error counter that will record the number of errors the cluster node has observed.
*
* @param errorCounter the error counter that will record the number of errors the cluster node has observed.
* @return this for a fluent API.
*/
public Context errorCounter(final AtomicCounter errorCounter)
{
this.errorCounter = errorCounter;
return this;
}
/**
* Non-default for context.
*
* @param countedErrorHandler to override the default.
* @return this for a fluent API.
*/
public Context countedErrorHandler(final CountedErrorHandler countedErrorHandler)
{
this.countedErrorHandler = countedErrorHandler;
return this;
}
/**
* The {@link #errorHandler()} that will increment {@link #errorCounter()} by default.
*
* @return {@link #errorHandler()} that will increment {@link #errorCounter()} by default.
*/
public CountedErrorHandler countedErrorHandler()
{
return countedErrorHandler;
}
/**
* Set the top level Aeron directory used for communication between the Aeron client and Media Driver.
*
* @param aeronDirectoryName the top level Aeron directory.
* @return this for a fluent API.
*/
public Context aeronDirectoryName(final String aeronDirectoryName)
{
this.aeronDirectoryName = aeronDirectoryName;
return this;
}
/**
* Get the top level Aeron directory used for communication between the Aeron client and Media Driver.
*
* @return The top level Aeron directory.
*/
public String aeronDirectoryName()
{
return aeronDirectoryName;
}
/**
* An {@link Aeron} client for the container.
*
* @return {@link Aeron} client for the container
*/
public Aeron aeron()
{
return aeron;
}
/**
* Provide an {@link Aeron} client for the container
*
* If not provided then one will be created.
*
* @param aeron client for the container
* @return this for a fluent API.
*/
public Context aeron(final Aeron aeron)
{
this.aeron = aeron;
return this;
}
/**
* Does this context own the {@link #aeron()} client and this takes responsibility for closing it?
*
* @param ownsAeronClient does this context own the {@link #aeron()} client.
* @return this for a fluent API.
*/
public Context ownsAeronClient(final boolean ownsAeronClient)
{
this.ownsAeronClient = ownsAeronClient;
return this;
}
/**
* Does this context own the {@link #aeron()} client and this takes responsibility for closing it?
*
* @return does this context own the {@link #aeron()} client and this takes responsibility for closing it?
*/
public boolean ownsAeronClient()
{
return ownsAeronClient;
}
/**
* The service this container holds.
*
* @return service this container holds.
*/
@Config(id = "SERVICE_CLASS_NAME")
public ClusteredService clusteredService()
{
return clusteredService;
}
/**
* Set the service this container is to hold.
*
* @param clusteredService this container is to hold.
* @return this for fluent API.
*/
public Context clusteredService(final ClusteredService clusteredService)
{
this.clusteredService = clusteredService;
return this;
}
/**
* Set the context that should be used for communicating with the local Archive.
*
* @param archiveContext that should be used for communicating with the local Archive.
* @return this for a fluent API.
*/
public Context archiveContext(final AeronArchive.Context archiveContext)
{
this.archiveContext = archiveContext;
return this;
}
/**
* Get the context that should be used for communicating with the local Archive.
*
* @return the context that should be used for communicating with the local Archive.
*/
public AeronArchive.Context archiveContext()
{
return archiveContext;
}
/**
* Set the directory name to use for the consensus module directory.
*
* @param clusterDirectoryName to use.
* @return this for a fluent API.
* @see Configuration#CLUSTER_DIR_PROP_NAME
*/
public Context clusterDirectoryName(final String clusterDirectoryName)
{
this.clusterDirectoryName = clusterDirectoryName;
return this;
}
/**
* The directory name to use for the cluster directory.
*
* @return directory name for the cluster directory.
* @see Configuration#CLUSTER_DIR_PROP_NAME
*/
@Config(id = "CLUSTER_DIR")
public String clusterDirectoryName()
{
return clusterDirectoryName;
}
/**
* Set the directory to use for the cluster directory.
*
* @param clusterDir to use.
* @return this for a fluent API.
* @see ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME
*/
public Context clusterDir(final File clusterDir)
{
this.clusterDir = clusterDir;
return this;
}
/**
* The directory used for the cluster directory.
*
* @return directory for the cluster directory.
* @see ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME
*/
public File clusterDir()
{
return clusterDir;
}
/**
* Get the directory in which the ClusteredServiceContainer will store mark file (i.e. {@code
* cluster-mark-service-0.dat}). It defaults to {@link #clusterDir()} if it is not set explicitly via the {@link
* ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME}.
*
* @return the directory in which the ClusteredServiceContainer will store mark file (i.e.
* {@code cluster-mark-service-0.dat}).
* @see ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME
* @see #clusterDir()
*/
@Config
public File markFileDir()
{
return markFileDir;
}
/**
* Set the directory in which the ClusteredServiceContainer will store mark file (i.e. {@code
* cluster-mark-service-0.dat}).
*
* @param markFileDir the directory in which the ClusteredServiceContainer will store mark file (i.e. {@code
* cluster-mark-service-0.dat}).
* @return this for a fluent API.
*/
public ClusteredServiceContainer.Context markFileDir(final File markFileDir)
{
this.markFileDir = markFileDir;
return this;
}
/**
* Set the {@link ShutdownSignalBarrier} that can be used to shut down a clustered service.
*
* @param barrier that can be used to shut down a clustered service.
* @return this for a fluent API.
*/
public Context shutdownSignalBarrier(final ShutdownSignalBarrier barrier)
{
shutdownSignalBarrier = barrier;
return this;
}
/**
* Get the {@link ShutdownSignalBarrier} that can be used to shut down a clustered service.
*
* @return the {@link ShutdownSignalBarrier} that can be used to shut down a clustered service.
*/
public ShutdownSignalBarrier shutdownSignalBarrier()
{
return shutdownSignalBarrier;
}
/**
* Set the {@link Runnable} that is called when container is instructed to terminate.
*
* @param terminationHook that can be used to terminate a service container.
* @return this for a fluent API.
*/
public Context terminationHook(final Runnable terminationHook)
{
this.terminationHook = terminationHook;
return this;
}
/**
* Get the {@link Runnable} that is called when container is instructed to terminate.
*
* The default action is to call signal on the {@link #shutdownSignalBarrier()}.
*
* @return the {@link Runnable} that can be used to terminate a service container.
*/
public Runnable terminationHook()
{
return terminationHook;
}
/**
* Set the {@link ClusterMarkFile} in use.
*
* @param markFile to use.
* @return this for a fluent API.
*/
public Context clusterMarkFile(final ClusterMarkFile markFile)
{
this.markFile = markFile;
return this;
}
/**
* The {@link ClusterMarkFile} in use.
*
* @return {@link ClusterMarkFile} in use.
*/
public ClusterMarkFile clusterMarkFile()
{
return markFile;
}
/**
* Set the error buffer length in bytes to use.
*
* @param errorBufferLength in bytes to use.
* @return this for a fluent API.
*/
public Context errorBufferLength(final int errorBufferLength)
{
this.errorBufferLength = errorBufferLength;
return this;
}
/**
* The error buffer length in bytes.
*
* @return error buffer length in bytes.
*/
@Config(id = "SERVICE_ERROR_BUFFER_LENGTH")
public int errorBufferLength()
{
return errorBufferLength;
}
/**
* Set the {@link DistinctErrorLog} in use.
*
* @param errorLog to use.
* @return this for a fluent API.
*/
public Context errorLog(final DistinctErrorLog errorLog)
{
this.errorLog = errorLog;
return this;
}
/**
* The {@link DistinctErrorLog} in use.
*
* @return {@link DistinctErrorLog} in use.
*/
public DistinctErrorLog errorLog()
{
return errorLog;
}
/**
* The {@link NanoClock} as a source of time in nanoseconds for measuring duration.
*
* @return the {@link NanoClock} as a source of time in nanoseconds for measuring duration.
*/
public NanoClock nanoClock()
{
return nanoClock;
}
/**
* The {@link NanoClock} as a source of time in nanoseconds for measuring duration.
*
* @param clock to be used.
* @return this for a fluent API.
*/
public Context nanoClock(final NanoClock clock)
{
nanoClock = clock;
return this;
}
/**
* Set a threshold for the container work cycle time which when exceed it will increment the
* counter.
*
* @param thresholdNs value in nanoseconds
* @return this for fluent API.
* @see Configuration#CYCLE_THRESHOLD_PROP_NAME
* @see Configuration#CYCLE_THRESHOLD_DEFAULT_NS
*/
public Context cycleThresholdNs(final long thresholdNs)
{
this.cycleThresholdNs = thresholdNs;
return this;
}
/**
* Threshold for the container work cycle time which when exceed it will increment the
* counter.
*
* @return threshold to track for the container work cycle time.
*/
@Config(id = "SERVICE_CYCLE_THRESHOLD")
public long cycleThresholdNs()
{
return cycleThresholdNs;
}
/**
* Set a duty cycle tracker to be used for tracking the duty cycle time of the container.
*
* @param dutyCycleTracker to use for tracking.
* @return this for fluent API.
*/
public Context dutyCycleTracker(final DutyCycleTracker dutyCycleTracker)
{
this.dutyCycleTracker = dutyCycleTracker;
return this;
}
/**
* The duty cycle tracker used to track the container duty cycle.
*
* @return the duty cycle tracker.
*/
public DutyCycleTracker dutyCycleTracker()
{
return dutyCycleTracker;
}
/**
* Set a threshold for snapshot duration which when exceeded will result in a counter increment.
*
* @param thresholdNs value in nanoseconds.
* @return this for fluent API.
* @see Configuration#SNAPSHOT_DURATION_THRESHOLD_PROP_NAME
* @see Configuration#SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS
* @since 1.44.0
*/
public Context snapshotDurationThresholdNs(final long thresholdNs)
{
this.snapshotDurationThresholdNs = thresholdNs;
return this;
}
/**
* Threshold for snapshot duration which when exceeded will result in a counter increment.
*
* @return threshold value in nanoseconds.
* @since 1.44.0
*/
@Config
public long snapshotDurationThresholdNs()
{
return snapshotDurationThresholdNs;
}
/**
* Set snapshot duration tracker used for monitoring snapshot duration.
*
* @param snapshotDurationTracker snapshot duration tracker.
* @return this for fluent API.
* @since 1.44.0
*/
public Context snapshotDurationTracker(final SnapshotDurationTracker snapshotDurationTracker)
{
this.snapshotDurationTracker = snapshotDurationTracker;
return this;
}
/**
* Get snapshot duration tracker used for monitoring snapshot duration.
*
* @return snapshot duration tracker.
* @since 1.44.0
*/
public SnapshotDurationTracker snapshotDurationTracker()
{
return snapshotDurationTracker;
}
/**
* Delete the cluster container directory.
*/
public void deleteDirectory()
{
if (null != clusterDir)
{
IoUtil.delete(clusterDir, false);
}
}
/**
* Indicates if this node should take standby snapshots
*
* @return true
if this should take standby snapshots, false
otherwise.
* @see ClusteredServiceContainer.Configuration#STANDBY_SNAPSHOT_ENABLED_PROP_NAME
* @see ClusteredServiceContainer.Configuration#standbySnapshotEnabled()
*/
@Config
public boolean standbySnapshotEnabled()
{
return standbySnapshotEnabled;
}
/**
* Indicates if this node should take standby snapshots
*
* @param standbySnapshotEnabled if this node should take standby snapshots.
* @return this for a fluent API.
* @see ClusteredServiceContainer.Configuration#STANDBY_SNAPSHOT_ENABLED_PROP_NAME
* @see ClusteredServiceContainer.Configuration#standbySnapshotEnabled()
*/
public ClusteredServiceContainer.Context standbySnapshotEnabled(final boolean standbySnapshotEnabled)
{
this.standbySnapshotEnabled = standbySnapshotEnabled;
return this;
}
/**
* Close the context and free applicable resources.
*
* If {@link #ownsAeronClient()} is true then the {@link #aeron()} client will be closed.
*/
public void close()
{
final ErrorHandler errorHandler = countedErrorHandler();
if (ownsAeronClient)
{
CloseHelper.close(errorHandler, aeron);
}
CloseHelper.close(errorHandler, markFile);
}
CountDownLatch abortLatch()
{
return abortLatch;
}
private void concludeMarkFile()
{
ClusterMarkFile.checkHeaderLength(
aeron.context().aeronDirectoryName(),
controlChannel(),
null,
serviceName,
null);
final MarkFileHeaderEncoder encoder = markFile.encoder();
encoder
.archiveStreamId(archiveContext.controlRequestStreamId())
.serviceStreamId(serviceStreamId)
.consensusModuleStreamId(consensusModuleStreamId)
.ingressStreamId(Aeron.NULL_VALUE)
.memberId(Aeron.NULL_VALUE)
.serviceId(serviceId)
.clusterId(clusterId)
.aeronDirectory(aeron.context().aeronDirectoryName())
.controlChannel(controlChannel)
.ingressChannel(null)
.serviceName(serviceName)
.authenticator(null);
markFile.updateActivityTimestamp(epochClock.time());
markFile.signalReady();
markFile.force();
}
/**
* {@inheritDoc}
*/
public String toString()
{
return "ClusteredServiceContainer.Context" +
"\n{" +
"\n isConcluded=" + isConcluded() +
"\n ownsAeronClient=" + ownsAeronClient +
"\n aeronDirectoryName='" + aeronDirectoryName + '\'' +
"\n aeron=" + aeron +
"\n archiveContext=" + archiveContext +
"\n clusterDirectoryName='" + clusterDirectoryName + '\'' +
"\n clusterDir=" + clusterDir +
"\n appVersion=" + appVersion +
"\n clusterId=" + clusterId +
"\n serviceId=" + serviceId +
"\n serviceName='" + serviceName + '\'' +
"\n replayChannel='" + replayChannel + '\'' +
"\n replayStreamId=" + replayStreamId +
"\n controlChannel='" + controlChannel + '\'' +
"\n consensusModuleStreamId=" + consensusModuleStreamId +
"\n serviceStreamId=" + serviceStreamId +
"\n snapshotChannel='" + snapshotChannel + '\'' +
"\n snapshotStreamId=" + snapshotStreamId +
"\n errorBufferLength=" + errorBufferLength +
"\n isRespondingService=" + isRespondingService +
"\n logFragmentLimit=" + logFragmentLimit +
"\n abortLatch=" + abortLatch +
"\n threadFactory=" + threadFactory +
"\n idleStrategySupplier=" + idleStrategySupplier +
"\n epochClock=" + epochClock +
"\n errorLog=" + errorLog +
"\n errorHandler=" + errorHandler +
"\n delegatingErrorHandler=" + delegatingErrorHandler +
"\n errorCounter=" + errorCounter +
"\n countedErrorHandler=" + countedErrorHandler +
"\n clusteredService=" + clusteredService +
"\n shutdownSignalBarrier=" + shutdownSignalBarrier +
"\n terminationHook=" + terminationHook +
"\n cycleThresholdNs=" + cycleThresholdNs +
"\n dutyCyleTracker=" + dutyCycleTracker +
"\n snapshotDurationThresholdNs=" + snapshotDurationThresholdNs +
"\n snapshotDurationTracker=" + snapshotDurationTracker +
"\n markFile=" + markFile +
"\n}";
}
}
}