Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.aeron.test.driver.CTestMediaDriver Maven / Gradle / Ivy
Go to download
Efficient reliable UDP unicast, UDP multicast, and IPC transport protocol.
/*
* 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.test.driver;
import io.aeron.Aeron;
import io.aeron.AeronCounters;
import io.aeron.CommonContext;
import io.aeron.driver.*;
import io.aeron.protocol.HeaderFlyweight;
import io.aeron.test.SystemTestConfig;
import io.aeron.test.Tests;
import org.agrona.IoUtil;
import org.agrona.LangUtil;
import org.agrona.SystemUtil;
import org.agrona.collections.MutableInteger;
import org.agrona.collections.Object2ObjectHashMap;
import org.agrona.concurrent.AgentInvoker;
import org.agrona.concurrent.status.CountersReader;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.TimeUnit;
public final class CTestMediaDriver implements TestMediaDriver
{
private static final String UDP_CHANNEL_OUTGOING_INTERCEPTORS_ENV_VAR = "AERON_UDP_CHANNEL_OUTGOING_INTERCEPTORS";
private static final String UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR = "AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS";
private static final File NULL_FILE = SystemUtil.isWindows() ? new File("NUL") : new File("/dev/null");
private static final Map, String> C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE =
new IdentityHashMap<>();
private static final ThreadLocal>> C_DRIVER_ADDITIONAL_ENV_VARS =
ThreadLocal.withInitial(IdentityHashMap::new);
private static final Collection JOINABLE_ENV_VARS = Arrays.asList(
UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, UDP_CHANNEL_OUTGOING_INTERCEPTORS_ENV_VAR);
static
{
C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(
DefaultMulticastFlowControlSupplier.class, "aeron_max_multicast_flow_control_strategy_supplier");
C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(
MaxMulticastFlowControlSupplier.class, "aeron_max_multicast_flow_control_strategy_supplier");
C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(
MinMulticastFlowControlSupplier.class, "aeron_min_flow_control_strategy_supplier");
C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(
DefaultUnicastFlowControlSupplier.class, "aeron_unicast_flow_control_strategy_supplier");
C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(
TaggedMulticastFlowControlSupplier.class, "aeron_tagged_flow_control_strategy_supplier");
}
private final Process aeronMediaDriverProcess;
private final MediaDriver.Context context;
private final DriverOutputConsumer driverOutputConsumer;
private final File stdoutFile;
private final File stderrFile;
private Aeron.Context aeronContext;
private CountersReader countersReader;
private boolean isClosed = false;
private CTestMediaDriver(
final Process aeronMediaDriverProcess,
final MediaDriver.Context context,
final DriverOutputConsumer driverOutputConsumer,
final File stdoutFile,
final File stderrFile)
{
this.aeronMediaDriverProcess = aeronMediaDriverProcess;
this.context = context;
this.driverOutputConsumer = driverOutputConsumer;
this.stdoutFile = stdoutFile;
this.stderrFile = stderrFile;
}
public void close()
{
if (isClosed)
{
return;
}
awaitSendersAndReceiversClosed();
isClosed = true;
Exception error = null;
try
{
if (null != aeronContext)
{
aeronContext.close();
}
}
catch (final Exception ex)
{
error = ex;
}
try
{
final ExitStatus exitStatus = terminateDriver();
if (null != driverOutputConsumer)
{
driverOutputConsumer.exitCode(
context.aeronDirectoryName(), exitStatus.exitCode, exitStatus.exitMessage);
}
}
catch (final Exception ex)
{
if (null == error)
{
error = ex;
}
else
{
error.addSuppressed(ex);
}
}
if (null != error)
{
LangUtil.rethrowUnchecked(error);
}
}
private void awaitSendersAndReceiversClosed()
{
if (!SystemTestConfig.DRIVER_AWAIT_COUNTER_CLOSE)
{
return;
}
final MutableInteger counterCount = new MutableInteger();
final CountersReader.MetaData metaData = (counterId, typeId, keyBuffer, label) ->
{
if (AeronCounters.DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID == typeId ||
AeronCounters.DRIVER_SEND_CHANNEL_STATUS_TYPE_ID == typeId)
{
counterCount.increment();
}
};
final long deadlineMs = System.currentTimeMillis() + 15_000;
do
{
counterCount.set(0);
counters().forEach(metaData);
Tests.checkInterruptStatus();
Tests.yield();
}
while (0 != counterCount.get() && System.currentTimeMillis() < deadlineMs);
}
public void cleanup()
{
if (NULL_FILE != stdoutFile)
{
IoUtil.delete(stdoutFile, true);
}
if (NULL_FILE != stderrFile)
{
IoUtil.delete(stderrFile, true);
}
}
public CountersReader counters()
{
if (null == countersReader)
{
aeronContext = new Aeron.Context()
.aeronDirectoryName(context.aeronDirectoryName())
.keepAliveIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))
.conclude();
countersReader = new CountersReader(
aeronContext.countersMetaDataBuffer(), aeronContext.countersValuesBuffer(), StandardCharsets.US_ASCII);
}
return countersReader;
}
@SuppressWarnings("methodlength")
public static CTestMediaDriver launch(
final MediaDriver.Context context,
final boolean withAeronDir,
final DriverOutputConsumer driverOutputConsumer)
{
final String aeronMediaDriverPath = System.getProperty(TestMediaDriver.AERONMD_PATH_PROP_NAME);
final File aeronBinary = new File(aeronMediaDriverPath);
if (!aeronBinary.exists())
{
throw new RuntimeException("Unable to find native media driver binary: " + aeronBinary.getAbsolutePath());
}
context.concludeAeronDirectory();
IoUtil.ensureDirectoryExists(context.aeronDirectory().getParentFile(), "Aeron C Media Driver directory");
final HashMap environment = new HashMap<>();
if (withAeronDir)
{
environment.put("AERON_DIR", context.aeronDirectoryName());
}
environment.put("AERON_CLIENT_LIVENESS_TIMEOUT", String.valueOf(context.clientLivenessTimeoutNs()));
environment.put("AERON_IMAGE_LIVENESS_TIMEOUT", String.valueOf(context.imageLivenessTimeoutNs()));
environment.put("AERON_DRIVER_TERMINATION_VALIDATOR", "allow");
environment.put("AERON_DIR_DELETE_ON_START", Boolean.toString(context.dirDeleteOnStart()));
environment.put("AERON_DIR_DELETE_ON_SHUTDOWN", Boolean.toString(context.dirDeleteOnShutdown()));
environment.put("AERON_TERM_BUFFER_SPARSE_FILE", Boolean.toString(context.termBufferSparseFile()));
environment.put("AERON_TERM_BUFFER_LENGTH", String.valueOf(context.publicationTermBufferLength()));
environment.put("AERON_IPC_TERM_BUFFER_LENGTH", String.valueOf(context.ipcTermBufferLength()));
environment.put(
"AERON_PUBLICATION_UNBLOCK_TIMEOUT", String.valueOf(context.publicationUnblockTimeoutNs()));
environment.put(
"AERON_PUBLICATION_CONNECTION_TIMEOUT", String.valueOf(context.publicationConnectionTimeoutNs()));
environment.put("AERON_SPIES_SIMULATE_CONNECTION", Boolean.toString(context.spiesSimulateConnection()));
environment.put("AERON_PERFORM_STORAGE_CHECKS", Boolean.toString(context.performStorageChecks()));
if (null != context.threadingMode())
{
environment.put("AERON_THREADING_MODE", context.threadingMode().name());
}
environment.put("AERON_TIMER_INTERVAL", String.valueOf(context.timerIntervalNs()));
environment.put("AERON_UNTETHERED_RESTING_TIMEOUT", String.valueOf(context.untetheredRestingTimeoutNs()));
environment.put(
"AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT", String.valueOf(context.untetheredWindowLimitTimeoutNs()));
if (null != context.receiverGroupTag())
{
environment.put("AERON_RECEIVER_GROUP_TAG", context.receiverGroupTag().toString());
}
environment.put("AERON_FLOW_CONTROL_GROUP_TAG", String.valueOf(context.flowControlGroupTag()));
environment.put(
"AERON_FLOW_CONTROL_GROUP_MIN_SIZE", String.valueOf(context.flowControlGroupMinSize()));
environment.put("AERON_PRINT_CONFIGURATION", "true");
if (null != context.resolverName())
{
environment.put("AERON_DRIVER_RESOLVER_NAME", context.resolverName());
}
if (null != context.resolverInterface())
{
environment.put("AERON_DRIVER_RESOLVER_INTERFACE", context.resolverInterface());
environment.put("AERON_NAME_RESOLVER_SUPPLIER", "driver");
}
if (null != context.resolverBootstrapNeighbor())
{
environment.put("AERON_DRIVER_RESOLVER_BOOTSTRAP_NEIGHBOR", context.resolverBootstrapNeighbor());
}
environment.put("AERON_SOCKET_SO_RCVBUF", String.valueOf(context.socketRcvbufLength()));
environment.put("AERON_SOCKET_SO_SNDBUF", String.valueOf(context.socketSndbufLength()));
environment.put("AERON_RCV_INITIAL_WINDOW_LENGTH", String.valueOf(context.initialWindowLength()));
environment.put("AERON_PUBLICATION_UNBLOCK_TIMEOUT", String.valueOf(context.publicationUnblockTimeoutNs()));
final NameResolver nameResolver = context.nameResolver();
if (nameResolver instanceof RedirectingNameResolver)
{
final String csvConfiguration = ((RedirectingNameResolver)nameResolver).csvConfiguration();
environment.put("AERON_NAME_RESOLVER_SUPPLIER", "csv_table");
environment.put("AERON_NAME_RESOLVER_INIT_ARGS", csvConfiguration);
}
environment.put("AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD", String.valueOf(context.conductorCycleThresholdNs()));
environment.put("AERON_DRIVER_SENDER_CYCLE_THRESHOLD", String.valueOf(context.senderCycleThresholdNs()));
environment.put("AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD", String.valueOf(context.receiverCycleThresholdNs()));
environment.put("AERON_DRIVER_NAME_RESOLVER_THRESHOLD", String.valueOf(context.nameResolverThresholdNs()));
environment.put("AERON_DRIVER_ASYNC_EXECUTOR_THREADS", String.valueOf(context.asyncTaskExecutorThreads()));
final String senderWildcardPortRange = context.senderWildcardPortRange();
if (null != senderWildcardPortRange)
{
environment.put("AERON_SENDER_WILDCARD_PORT_RANGE", senderWildcardPortRange);
}
final String receiverWildcardPortRange = context.receiverWildcardPortRange();
if (null != receiverWildcardPortRange)
{
environment.put("AERON_RECEIVER_WILDCARD_PORT_RANGE", receiverWildcardPortRange);
}
environment.put("AERON_ENABLE_EXPERIMENTAL_FEATURES", String.valueOf(context.enableExperimentalFeatures()));
environment.put("AERON_DRIVER_STREAM_SESSION_LIMIT", String.valueOf(context.streamSessionLimit()));
setFlowControlStrategy(environment, context);
setLogging(environment);
setTransportSecurity(environment);
setAdditionalEnvVars(environment, getAdditionalEnvVarsMap(context));
try
{
File stdoutFile = NULL_FILE;
File stderrFile = NULL_FILE;
final ProcessBuilder pb = new ProcessBuilder(aeronBinary.getAbsolutePath());
if (null != driverOutputConsumer)
{
stdoutFile = Files.createTempFile(context.aeronDirectory().getName() + "-driver-", ".out").toFile();
final String tmpName = stdoutFile.getName().substring(0, stdoutFile.getName().length() - 4) + ".err";
stderrFile = new File(stdoutFile.getParent(), tmpName);
driverOutputConsumer.outputFiles(context.aeronDirectoryName(), stdoutFile, stderrFile);
driverOutputConsumer.environmentVariables(context.aeronDirectoryName(), environment);
}
pb.environment().putAll(environment);
pb.redirectOutput(stdoutFile).redirectError(stderrFile);
final Process process = pb.start();
Thread.yield();
return new CTestMediaDriver(process, context, driverOutputConsumer, stdoutFile, stderrFile);
}
catch (final IOException ex)
{
LangUtil.rethrowUnchecked(ex);
return null;
}
}
public MediaDriver.Context context()
{
return context;
}
public String aeronDirectoryName()
{
return context.aeronDirectoryName();
}
public AgentInvoker sharedAgentInvoker()
{
throw new UnsupportedOperationException("Not supported in C media driver");
}
public static void enableRandomLossOnReceive(
final MediaDriver.Context context,
final double rate,
final long seed,
final boolean loseDataMessages,
final boolean loseControlMessages)
{
int receiveMessageTypeMask = 0;
receiveMessageTypeMask |= loseDataMessages ? 1 << HeaderFlyweight.HDR_TYPE_DATA : 0;
receiveMessageTypeMask |= loseControlMessages ? 1 << HeaderFlyweight.HDR_TYPE_SM : 0;
receiveMessageTypeMask |= loseControlMessages ? 1 << HeaderFlyweight.HDR_TYPE_NAK : 0;
receiveMessageTypeMask |= loseControlMessages ? 1 << HeaderFlyweight.HDR_TYPE_RTTM : 0;
final Object2ObjectHashMap lossTransportEnv = new Object2ObjectHashMap<>();
final String interceptor = "loss";
final String lossArgs = "rate=" + rate +
"|seed=" + seed +
"|recv-msg-mask=0x" + Integer.toHexString(receiveMessageTypeMask);
lossTransportEnv.put(UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, interceptor);
lossTransportEnv.put("AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS", lossArgs);
getAdditionalEnvVarsMap(context).putAll(lossTransportEnv);
}
public static void enableFixedLossOnReceive(
final MediaDriver.Context context,
final int termId,
final int termOffset,
final int length)
{
final Object2ObjectHashMap fixedLossTransportEnv = new Object2ObjectHashMap<>();
final String interceptor = "fixed-loss";
final String fixedLossArgs = "term-id=" + termId +
"|term-offset=" + termOffset +
"|length=" + length;
fixedLossTransportEnv.put(UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, interceptor);
fixedLossTransportEnv.put("AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_FIXED_LOSS_ARGS", fixedLossArgs);
getAdditionalEnvVarsMap(context).putAll(fixedLossTransportEnv);
}
public static void enableMultiGapLossOnReceive(
final MediaDriver.Context context,
final int termId,
final int gapRadix,
final int gapLength,
final int totalGaps)
{
final Object2ObjectHashMap multiGapLossTransportEnv = new Object2ObjectHashMap<>();
final String interceptor = "multi-gap-loss";
final String multiGapLossArgs = "term-id=" + termId +
"|gap-radix=" + gapRadix +
"|gap-length=" + gapLength +
"|total-gaps=" + totalGaps;
multiGapLossTransportEnv.put(UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, interceptor);
multiGapLossTransportEnv.put("AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MULTI_GAP_LOSS_ARGS", multiGapLossArgs);
getAdditionalEnvVarsMap(context).putAll(multiGapLossTransportEnv);
}
public static void dontCoalesceNaksOnReceiverByDefault(final MediaDriver.Context context)
{
getAdditionalEnvVarsMap(context).put("AERON_NAK_UNICAST_DELAY", "0");
}
public static Map getAdditionalEnvVarsMap(final MediaDriver.Context context)
{
return C_DRIVER_ADDITIONAL_ENV_VARS.get().computeIfAbsent(context, c -> new IdentityHashMap<>());
}
private static void setLogging(final Map environment)
{
environment.put("AERON_EVENT_LOG", System.getProperty(
"aeron.event.log",
"admin"));
environment.put("AERON_EVENT_LOG_DISABLE", System.getProperty("aeron.event.log.disable", ""));
final String driverAgentPath = System.getProperty(DRIVER_AGENT_PATH_PROP_NAME);
if (null == driverAgentPath)
{
return;
}
final File driverAgent = new File(driverAgentPath);
if (!driverAgent.exists())
{
throw new RuntimeException(
"Unable to find driver agent file at: " + DRIVER_AGENT_PATH_PROP_NAME + "=" + driverAgentPath);
}
}
private static void setTransportSecurity(final HashMap environment)
{
final String atsLibPath = (String)System.getProperties().get(ATS_LIBRARY_PATH_PROP_NAME);
if (null != atsLibPath && !atsLibPath.isEmpty())
{
IoUtil.checkFileExists(new File(atsLibPath), ATS_LIBRARY_PATH_PROP_NAME);
environment.put("AERON_DRIVER_DYNAMIC_LIBRARIES", atsLibPath);
environment.put(
UDP_CHANNEL_OUTGOING_INTERCEPTORS_ENV_VAR, "aeron_transport_security_channel_interceptor_load");
environment.put(
UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, "aeron_transport_security_channel_interceptor_load");
final String atsConfDir = System.getProperty(ATS_LIBRARY_CONF_PATH_PROP_NAME);
if (null != atsConfDir)
{
environment.put("AERON_TRANSPORT_SECURITY_CONF_DIR", atsConfDir);
}
final String atsConfFile = System.getProperty(ATS_LIBRARY_CONF_FILE_PROP_NAME);
if (null != atsConfFile)
{
environment.put("AERON_TRANSPORT_SECURITY_CONF_FILE", atsConfFile);
}
}
}
private static void setAdditionalEnvVars(
final HashMap environment,
final Map additionalEnvVars)
{
additionalEnvVars.forEach(
(k, v) ->
{
final String existingValue = environment.putIfAbsent(k, v);
if (null != existingValue)
{
if (JOINABLE_ENV_VARS.contains(k))
{
environment.put(k, existingValue + "," + v);
}
else
{
throw new RuntimeException(
"Variable: " + k + " is already specified as: " + existingValue + " cannot set to: " + v);
}
}
});
}
private static void setFlowControlStrategy(final Map environment, final MediaDriver.Context context)
{
final FlowControlSupplier multicastFlowControlSupplier = context.multicastFlowControlSupplier();
final String multicastFlowControlStrategyName = getFlowControlStrategyName(multicastFlowControlSupplier);
if (null != multicastFlowControlStrategyName)
{
environment.put("AERON_MULTICAST_FLOWCONTROL_SUPPLIER", multicastFlowControlStrategyName);
}
else if (null != multicastFlowControlSupplier)
{
throw new RuntimeException("No equivalent C multicast flow control strategy for: " +
multicastFlowControlSupplier.getClass());
}
final FlowControlSupplier unicastFlowControlSupplier = context.unicastFlowControlSupplier();
final String unicastFlowControlStrategyName = getFlowControlStrategyName(unicastFlowControlSupplier);
if (null != unicastFlowControlStrategyName)
{
environment.put("AERON_UNICAST_FLOWCONTROL_SUPPLIER", unicastFlowControlStrategyName);
}
else if (null != unicastFlowControlSupplier)
{
throw new RuntimeException("No equivalent C unicast flow control strategy for: " +
unicastFlowControlSupplier.getClass());
}
}
private static String getFlowControlStrategyName(final FlowControlSupplier flowControlSupplier)
{
return null == flowControlSupplier ?
null : C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.get(flowControlSupplier.getClass());
}
private static final class ExitStatus
{
private final int exitCode;
private final String exitMessage;
private ExitStatus(final int exitCode, final String exitMessage)
{
this.exitCode = exitCode;
this.exitMessage = exitMessage;
}
}
private ExitStatus terminateDriver()
{
boolean isInterrupted = false;
boolean requestTermination = true;
try
{
try
{
final int exitCode = aeronMediaDriverProcess.exitValue();
String exitMessage = "Process exited early";
if (!SystemUtil.isWindows())
{
final int exitSignal = (0x7F & exitCode); // Essentially the same on Linux and macOS.
if (0 != exitSignal)
{
exitMessage += " - signal " + exitSignal;
}
}
return new ExitStatus(exitCode, exitMessage);
}
catch (final IllegalThreadStateException ignore)
{
}
while (true)
{
isInterrupted |= Thread.interrupted();
try
{
if (requestTermination)
{
requestTermination = false;
if (requestDriverTermination() && aeronMediaDriverProcess.waitFor(10, TimeUnit.SECONDS))
{
return new ExitStatus(aeronMediaDriverProcess.exitValue(), "Process shutdown cleanly");
}
}
aeronMediaDriverProcess.destroyForcibly().waitFor(5, TimeUnit.SECONDS);
final int exitCode = aeronMediaDriverProcess.exitValue();
return new ExitStatus(exitCode, "Process destroyed forcibly");
}
catch (final InterruptedException ex)
{
isInterrupted = true;
}
}
}
finally
{
if (isInterrupted)
{
Thread.currentThread().interrupt();
}
}
}
private boolean requestDriverTermination()
{
try
{
return CommonContext.requestDriverTermination(new File(context.aeronDirectoryName()), null, 0, 0);
}
catch (final Exception ex)
{
return false;
}
}
}