diff --git a/aeron-annotations/src/main/java/io/aeron/config/Config.java b/aeron-annotations/src/main/java/io/aeron/config/Config.java new file mode 100644 index 0000000000..82177d8f1e --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/Config.java @@ -0,0 +1,138 @@ +/* + * 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.config; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * Annotation to indicate this is a config option + */ +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.SOURCE) +public @interface Config +{ + /** + * Type is used to indicate whether the annotation is marking a property name or a default value + */ + enum Type + { + UNDEFINED, PROPERTY_NAME, DEFAULT + } + + /** + * @return what type of field is being annotated + */ + Type configType() default Type.UNDEFINED; + + /** + * @return the unique id that ties together all the usages of the annotation across fields/methods + */ + String id() default ""; + + /** + * @return the uri parameter (if any) associated with this option + */ + String uriParam() default ""; + + /** + * @return whether or not this config option exists in the C code + */ + boolean existsInC() default true; + + /** + * @return the expected C #define name that will be set with the env variable name for this option + */ + String expectedCEnvVarFieldName() default ""; + + /** + * @return the expected C env variable name for this option + */ + String expectedCEnvVar() default ""; + + /** + * @return the expected C #define name that will be set with the default value for this option + */ + String expectedCDefaultFieldName() default ""; + + /** + * @return the expected C default value for this option + */ + String expectedCDefault() default ""; + + /** + * @return whether to skip validation of the default in C + */ + boolean skipCDefaultValidation() default false; + + /** + * @return what's the type of default (string, int, etc...) + */ + DefaultType defaultType() default DefaultType.UNDEFINED; + + /** + * @return specify the default boolean, if defaultType is BOOLEAN + */ + boolean defaultBoolean() default false; + + /** + * @return specify the default int, if defaultType is INT + */ + int defaultInt() default 0; + + /** + * @return specify the default long, if defaultType is LONG + */ + long defaultLong() default 0; + + /** + * @return specify the default double, if defaultType is DOUBLE + */ + double defaultDouble() default 0.0; + + /** + * @return specify the default string, if defaultType is STRING + */ + String defaultString() default ""; + + /** + * @return specify a string that acts as a stand-in for the default value when generating documentation + */ + String defaultValueString() default ""; + + /** + * Used to indicate whether or not the default value is a time value + */ + enum IsTimeValue + { + UNDEFINED, TRUE, FALSE + } + + /** + * @return whether or not the default value is a time value + */ + IsTimeValue isTimeValue() default IsTimeValue.UNDEFINED; + + /** + * @return the time unit if the default value is a time value of some sort + */ + TimeUnit timeUnit() default TimeUnit.NANOSECONDS; + + /** + * @return whether or not this config option has a 'context' + */ + boolean hasContext() default true; +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java new file mode 100644 index 0000000000..9e275f4594 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -0,0 +1,80 @@ +/* + * 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.config; + +import java.io.Serializable; +import java.util.concurrent.TimeUnit; + +/** + * A handy class for storing data that gets serialized into json + */ +public class ConfigInfo implements Serializable +{ + private static final long serialVersionUID = 6600224566064248728L; + + public final String id; + public final ExpectedConfig expectations; + + /** + * @param id the unique identifier for this block o' config information + */ + public ConfigInfo(final String id) + { + this.id = id; + expectations = new ExpectedConfig(); + } + + public boolean foundPropertyName = false; + public boolean foundDefault = false; + + public String propertyNameDescription; + + public String propertyNameFieldName; + + public String propertyNameClassName; + + public String propertyName; + + public String defaultDescription; + + public String defaultFieldName; + + public String defaultClassName; + + public String defaultValue; + + public String defaultValueString; + + public DefaultType defaultValueType = DefaultType.UNDEFINED; + + public String overrideDefaultValue; + + public DefaultType overrideDefaultValueType = DefaultType.UNDEFINED; + + public String uriParam; + + public boolean hasContext = true; + + public String context; + + public String contextDescription; + + public Boolean isTimeValue; + + public TimeUnit timeUnit; + + public boolean deprecated = false; +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java new file mode 100644 index 0000000000..f870365037 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -0,0 +1,555 @@ +/* + * 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.config; + +import io.aeron.utility.ElementIO; +import io.aeron.utility.Processor; + +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.element.*; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.util.*; +import java.util.stream.Stream; + +/** + * ConfigOption processor + */ +@SupportedAnnotationTypes("io.aeron.config.Config") +public class ConfigProcessor extends Processor +{ + private static final String[] PROPERTY_NAME_SUFFIXES = new String[] {"_PROP_NAME"}; + + private static final String[] DEFAULT_SUFFIXES = new String[] {"_DEFAULT", "_DEFAULT_NS"}; + + private final Map typeConfigMap = new HashMap<>(); + + @Override + protected String getEnabledPropertyName() + { + return "aeron.build.configProcessor.enabled"; + } + + @Override + protected String getPrintNotesPropertyName() + { + return "aeron.build.configProcessor.printNotes"; + } + + @Override + protected String getFailOnErrorPropertyName() + { + return "aeron.build.configProcessor.failOnError"; + } + + /** + * {@inheritDoc} + */ + @Override + public void doProcess(final Set annotations, final RoundEnvironment roundEnv) + { + final Map configInfoMap = new HashMap<>(); + + for (final TypeElement annotation : annotations) + { + for (final Element element : roundEnv.getElementsAnnotatedWith(annotation)) + { + try + { + final ConfigInfo configInfo; + + if (element instanceof VariableElement) + { + configInfo = processElement(configInfoMap, (VariableElement)element); + } + else if (element instanceof ExecutableElement) + { + configInfo = processExecutableElement(configInfoMap, (ExecutableElement)element); + } + else if (element instanceof TypeElement) + { + processTypeElement((TypeElement)element); + configInfo = null; + } + else + { + configInfo = null; + } + + if (configInfo != null) + { + if (element.getAnnotation(Deprecated.class) != null) + { + configInfo.deprecated = true; + } + } + } + catch (final Exception e) + { + error("an error occurred processing an element: " + e.getMessage(), element); + e.printStackTrace(System.err); + } + } + } + + if (!configInfoMap.isEmpty()) + { + try + { + configInfoMap.forEach(this::applyTypeDefaults); + configInfoMap.forEach(this::deriveCExpectations); + configInfoMap.forEach(this::sanityCheck); + } + catch (final Exception e) + { + e.printStackTrace(System.err); + return; + } + + try + { + final FileObject resourceFile = processingEnv.getFiler() + .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "config-info.dat"); + + ElementIO.write(resourceFile, configInfoMap.values()); + } + catch (final Exception e) + { + e.printStackTrace(System.err); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "an error occurred while writing output: " + e.getMessage()); + } + } + } + + private ConfigInfo processElement(final Map configInfoMap, final VariableElement element) + { + final Config config = element.getAnnotation(Config.class); + + if (Objects.isNull(config)) + { + error("element found with no expected annotations", element); + return null; + } + + final String id; + final ConfigInfo configInfo; + final Object constantValue = element.getConstantValue(); + switch (getConfigType(element, config)) + { + case PROPERTY_NAME: + id = getConfigId(element, PROPERTY_NAME_SUFFIXES, config.id()); + configInfo = configInfoMap.computeIfAbsent(id, ConfigInfo::new); + if (configInfo.foundPropertyName) + { + error("duplicate config option info for id: " + id + ". Previous definition found at " + + configInfo.propertyNameClassName + ":" + configInfo.propertyNameFieldName, element); + return configInfo; + } + configInfo.foundPropertyName = true; + + configInfo.propertyNameFieldName = element.toString(); + configInfo.propertyNameClassName = element.getEnclosingElement().toString(); + configInfo.propertyNameDescription = getDocComment(element); + + if (constantValue instanceof String) + { + configInfo.propertyName = (String)constantValue; + } + else + { + error("Property names must be Strings", element); + } + break; + case DEFAULT: + id = getConfigId(element, DEFAULT_SUFFIXES, config.id()); + configInfo = configInfoMap.computeIfAbsent(id, ConfigInfo::new); + if (configInfo.foundDefault) + { + error("duplicate config default info for id: " + id + ". Previous definition found at " + + configInfo.defaultClassName + ":" + configInfo.defaultFieldName, element); + return configInfo; + } + configInfo.foundDefault = true; + + configInfo.defaultFieldName = element.toString(); + configInfo.defaultClassName = element.getEnclosingElement().toString(); + configInfo.defaultDescription = getDocComment(element); + + if (constantValue != null) + { + configInfo.defaultValue = constantValue.toString(); + configInfo.defaultValueType = + DefaultType.fromCanonicalName(constantValue.getClass().getCanonicalName()); + } + break; + default: + error("unable to determine config type", element); + return null; + } + + if (!config.uriParam().isEmpty()) + { + configInfo.uriParam = config.uriParam(); + } + + if (!config.hasContext()) + { + configInfo.hasContext = false; + } + + if (!config.defaultValueString().isEmpty()) + { + configInfo.defaultValueString = config.defaultValueString(); + } + + handleTimeValue(config, configInfo, id); + + handleDefaultTypeOverride(element, config, configInfo); + + handleCExpectations(element, configInfo, config); + + return configInfo; + } + + private static void handleTimeValue(final Config config, final ConfigInfo configInfo, final String id) + { + switch (config.isTimeValue()) + { + case UNDEFINED: + if (configInfo.isTimeValue == null) + { + configInfo.isTimeValue = + Stream.of("timeout", "backoff", "delay", "linger", "interval", "duration") + .anyMatch(k -> id.toLowerCase().contains(k)); + } + break; + case TRUE: + configInfo.isTimeValue = true; + break; + case FALSE: + // fall through + default: + configInfo.isTimeValue = false; + break; + } + + if (configInfo.isTimeValue) + { + // TODO make sure this is either seconds, milliseconds, microseconds, or nanoseconds + configInfo.timeUnit = config.timeUnit(); + } + } + + private void handleDefaultTypeOverride( + final VariableElement element, + final Config config, + final ConfigInfo configInfo) + { + if (DefaultType.isUndefined(config.defaultType())) + { + return; + } + + if (DefaultType.isUndefined(configInfo.defaultValueType)) + { + note("defaultType is set explicitly, rather than relying on a separately defined field", element); + + configInfo.overrideDefaultValueType = config.defaultType(); + switch (config.defaultType()) + { + case INT: + configInfo.overrideDefaultValue = "" + config.defaultInt(); + break; + case LONG: + configInfo.overrideDefaultValue = "" + config.defaultLong(); + break; + case DOUBLE: + configInfo.overrideDefaultValue = "" + config.defaultDouble(); + break; + case BOOLEAN: + configInfo.overrideDefaultValue = "" + config.defaultBoolean(); + break; + case STRING: + configInfo.overrideDefaultValue = config.defaultString(); + break; + default: + error("unhandled default type", element); + break; + } + } + else + { + error("defaultType specified twice", element); + } + } + + private void handleCExpectations(final VariableElement element, final ConfigInfo configInfo, final Config config) + { + final ExpectedCConfig c = configInfo.expectations.c; + + if (!config.existsInC()) + { + c.exists = false; + return; + } + + if (c.envVarFieldName == null && !config.expectedCEnvVarFieldName().isEmpty()) + { + note("expectedCEnvVarFieldName is set", element); + c.envVarFieldName = config.expectedCEnvVarFieldName(); + } + + if (c.envVar == null && !config.expectedCEnvVar().isEmpty()) + { + note("expectedCEnvVar is set", element); + c.envVar = config.expectedCEnvVar(); + } + + if (c.defaultFieldName == null && !config.expectedCDefaultFieldName().isEmpty()) + { + note("expectedCDefaultFieldName is set", element); + c.defaultFieldName = config.expectedCDefaultFieldName(); + } + + if (c.defaultValue == null && !config.expectedCDefault().isEmpty()) + { + note("expectedCDefault is set", element); + c.defaultValue = config.expectedCDefault(); + } + + if (config.skipCDefaultValidation()) + { + note("skipCDefaultValidation is set", element); + c.skipDefaultValidation = true; + } + } + + private ConfigInfo processExecutableElement( + final Map configInfoMap, final ExecutableElement element) + { + final Config config = element.getAnnotation(Config.class); + + if (Objects.isNull(config)) + { + error("element found with no expected annotations", element); + return null; + } + + final String id = getConfigId(element, config.id()); + final ConfigInfo configInfo = configInfoMap.computeIfAbsent(id, ConfigInfo::new); + + final String methodName = element.toString(); + final String enclosingElementName = element.getEnclosingElement().toString(); + + Element e = element.getEnclosingElement(); + while (e.getKind() != ElementKind.PACKAGE) + { + e = e.getEnclosingElement(); + } + + final String packageName = e.toString(); + + configInfo.context = enclosingElementName.substring(packageName.length() + 1) + "." + methodName; + configInfo.contextDescription = getDocComment(element); + + return configInfo; + } + + private void processTypeElement(final TypeElement element) + { + final Config config = element.getAnnotation(Config.class); + + if (Objects.isNull(config)) + { + error("element found with no expected annotations", element); + return; + } + + typeConfigMap.put(element.getQualifiedName().toString(), config); + } + + private Config.Type getConfigType(final VariableElement element, final Config config) + { + // use an explicitly configured type + if (config.configType() != Config.Type.UNDEFINED) + { + return config.configType(); + } + + if (element.toString().endsWith("_PROP_NAME")) + { + return Config.Type.PROPERTY_NAME; + } + + if (element.toString().contains("DEFAULT")) + { + return Config.Type.DEFAULT; + } + + return Config.Type.UNDEFINED; + } + + private String getConfigId(final ExecutableElement element, final String id) + { + final StringBuilder builder = new StringBuilder(); + + for (final char next: element.toString().toCharArray()) + { + if (Character.isLetter(next)) + { + if (Character.isUpperCase(next)) + { + builder.append("_"); + } + builder.append(Character.toUpperCase(next)); + } + else if (next == '(') + { + break; + } + } + + final String calculatedId = builder.toString().replace("_NS", ""); + + if (null != id && !id.isEmpty()) + { + if (id.equals(calculatedId)) + { + error("redundant id specified", element); + } + note("Config ID is overridden", element); + return id; + } + + return calculatedId; + } + + private String getConfigId(final VariableElement element, final String[] suffixes, final String id) + { + if (null != id && !id.isEmpty()) + { + note("Config ID is overridden", element); + return id; + } + + final String fieldName = element.toString(); + + for (final String suffix: suffixes) + { + if (fieldName.endsWith(suffix)) + { + return fieldName.substring(0, fieldName.length() - suffix.length()); + } + } + + error("unable to determine id for: " + fieldName, element); + + return fieldName; + } + + private void applyTypeDefaults(final String id, final ConfigInfo configInfo) + { + Optional.ofNullable(typeConfigMap.get(configInfo.propertyNameClassName)) + .filter(config -> !config.existsInC()) + .ifPresent(config -> configInfo.expectations.c.exists = false); + } + + private void deriveCExpectations(final String id, final ConfigInfo configInfo) + { + if (!configInfo.expectations.c.exists) + { + return; // skip it + } + + try + { + final ExpectedCConfig c = configInfo.expectations.c; + + if (Objects.isNull(c.envVar) && configInfo.foundPropertyName) + { + c.envVar = configInfo.propertyName.toUpperCase().replace('.', '_'); + } + + if (Objects.isNull(c.envVarFieldName)) + { + c.envVarFieldName = c.envVar + "_ENV_VAR"; + } + + if (Objects.isNull(c.defaultFieldName)) + { + c.defaultFieldName = c.envVar + "_DEFAULT"; + } + + if (DefaultType.isUndefined(configInfo.overrideDefaultValueType)) + { + if (Objects.isNull(c.defaultValue)) + { + c.defaultValue = configInfo.defaultValue; + } + + if (Objects.isNull(c.defaultValueType)) + { + c.defaultValueType = configInfo.defaultValueType; + } + } + else + { + if (Objects.isNull(c.defaultValue)) + { + c.defaultValue = configInfo.overrideDefaultValue; + } + + if (Objects.isNull(c.defaultValueType)) + { + c.defaultValueType = configInfo.overrideDefaultValueType; + } + } + } + catch (final Exception e) + { + error("an error occurred while deriving C config expectations for: " + id); + e.printStackTrace(System.err); + } + } + + private void sanityCheck(final String id, final ConfigInfo configInfo) + { + if (!configInfo.foundPropertyName) + { + insane(id, "no property name found"); + } + + if (configInfo.defaultValue == null && + configInfo.overrideDefaultValue == null && + configInfo.defaultValueString == null) + { + insane(id, "no default value found"); + } + + if (configInfo.hasContext && (configInfo.context == null || configInfo.context.isEmpty())) + { + note("Configuration (" + id + ") is missing context"); + } + } + + private void insane(final String id, final String errMsg) + { + error("Configuration (" + id + "): " + errMsg); + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java b/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java new file mode 100644 index 0000000000..0b15d6db4c --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java @@ -0,0 +1,88 @@ +/* + * 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.config; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Indicates the 'type' of the default field/value + */ +public enum DefaultType +{ + UNDEFINED("", "", false), + BOOLEAN("java.lang.Boolean", "Boolean", false), + INT("java.lang.Integer", "Integer", true), + LONG("java.lang.Long", "Long", true), + DOUBLE("java.lang.Double", "Double", true), + STRING("java.lang.String", "String", false); + + private static final Map BY_CANONICAL_NAME = new HashMap<>(); + + static + { + for (final DefaultType t : values()) + { + BY_CANONICAL_NAME.put(t.canonicalName, t); + } + } + + /** + * @param canonicalName the name of the java class + * @return the associated DefaultType + */ + public static DefaultType fromCanonicalName(final String canonicalName) + { + return BY_CANONICAL_NAME.getOrDefault(canonicalName, UNDEFINED); + } + + /** + * @param defaultType a DefaultType or null + * @return true if the type is null or if it's UNDEFINED, otherwise false + */ + public static boolean isUndefined(final DefaultType defaultType) + { + return Objects.isNull(defaultType) || UNDEFINED == defaultType; + } + + private final String canonicalName; + private final String simpleName; + private final boolean numeric; + + DefaultType(final String canonicalName, final String simpleName, final boolean numeric) + { + this.canonicalName = canonicalName; + this.simpleName = simpleName; + this.numeric = numeric; + } + + /** + * @return indicates whether or not the value is numeric (int or long) + */ + public boolean isNumeric() + { + return this.numeric; + } + + /** + * @return a simple name, for display purposes + */ + public String getSimpleName() + { + return this.simpleName; + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java new file mode 100644 index 0000000000..99900ea090 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java @@ -0,0 +1,40 @@ +/* + * 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.config; + +import java.io.Serializable; + +/** + * A handy class for storing expected C config info that can be serialized into json + */ +public class ExpectedCConfig implements Serializable +{ + private static final long serialVersionUID = -4549394851227986144L; + + public boolean exists = true; + + public String envVarFieldName; + + public String envVar; + + public String defaultFieldName; + + public String defaultValue; + + public DefaultType defaultValueType; + + public boolean skipDefaultValidation = false; +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java new file mode 100644 index 0000000000..eecd20e9c7 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java @@ -0,0 +1,32 @@ +/* + * 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.config; + +import java.io.Serializable; + +/** + */ +public class ExpectedConfig implements Serializable +{ + private static final long serialVersionUID = -2025994445988286324L; + + public final ExpectedCConfig c; + + ExpectedConfig() + { + c = new ExpectedCConfig(); + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java b/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java new file mode 100644 index 0000000000..d9580e662a --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java @@ -0,0 +1,263 @@ +/* + * 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.config.docgen; + +import io.aeron.config.ConfigInfo; +import io.aeron.config.DefaultType; + +import java.io.FileWriter; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +final class ConfigDocGenerator implements AutoCloseable +{ + static void generate(final List configInfoCollection, final String outputFilename) throws Exception + { + try (ConfigDocGenerator generator = new ConfigDocGenerator(outputFilename)) + { + generator.generateDoc(configInfoCollection); + } + } + + private final FileWriter writer; + + private ConfigDocGenerator(final String outputFile) throws Exception + { + writer = new FileWriter(outputFile); + } + + @Override + public void close() + { + try + { + writer.close(); + } + catch (final Exception e) + { + e.printStackTrace(System.err); + } + } + + private void generateDoc(final List configInfoCollection) throws Exception + { + for (final ConfigInfo configInfo: sort(configInfoCollection)) + { + writeHeader( + toHeaderString(configInfo.id) + + (configInfo.expectations.c.exists ? "" : " (***JAVA ONLY***)") + + (configInfo.deprecated ? " (***DEPRECATED***)" : "")); + write("Description", configInfo.propertyNameDescription); + write("Type", + (DefaultType.isUndefined(configInfo.overrideDefaultValueType) ? + configInfo.defaultValueType : + configInfo.overrideDefaultValueType).getSimpleName()); + writeCode("System Property", configInfo.propertyName); + if (configInfo.context != null && !configInfo.context.isEmpty()) + { + writeCode("Context", configInfo.context); + } + if (configInfo.contextDescription != null && !configInfo.contextDescription.isEmpty()) + { + write("Context Description", configInfo.contextDescription); + } + if (configInfo.uriParam != null && !configInfo.uriParam.isEmpty()) + { + writeCode("URI Param", configInfo.uriParam); + } + + if (configInfo.defaultDescription != null) + { + write("Default Description", configInfo.defaultDescription); + } + final String defaultValue = configInfo.overrideDefaultValue == null ? + (configInfo.defaultValue == null ? "" : configInfo.defaultValue) : + configInfo.overrideDefaultValue; + + write("Default", getDefaultString( + configInfo.defaultValueString == null ? defaultValue : configInfo.defaultValueString, + configInfo.isTimeValue, + configInfo.timeUnit)); + if (configInfo.isTimeValue == Boolean.TRUE) + { + write("Time Unit", configInfo.timeUnit.toString()); + } + + if (configInfo.expectations.c.exists) + { + writeCode("C Env Var", configInfo.expectations.c.envVar); + write("C Default", getDefaultString( + configInfo.expectations.c.defaultValue, + configInfo.isTimeValue, + configInfo.timeUnit)); + } + writeLine(); + } + } + + private List sort(final List config) + { + return config + .stream() + .sorted(Comparator.comparing(a -> a.id)) + .collect(Collectors.toList()); + } + + private void writeHeader(final String t) throws IOException + { + writeRow("", t); + writeLine(); + writeRow("---", "---"); + writeLine(); + } + + private void writeCode(final String a, final String b) throws IOException + { + write(a, "`" + b + "`"); + } + + private void write(final String a, final String b) throws IOException + { + writeRow("**" + a + "**", b.replaceAll("\n", " ").trim()); + writeLine(); + } + + private void writeLine() throws IOException + { + writer.write("\n"); + } + + private void writeRow(final String a, final String b) throws IOException + { + writer.write("| " + a + " | " + b + " |"); + } + + private String toHeaderString(final String t) + { + final StringBuilder builder = new StringBuilder(); + + char previous = '_'; + for (final char next: t.toCharArray()) + { + if (next == '_') + { + builder.append(' '); + } + else if (previous == '_') + { + builder.append(Character.toUpperCase(next)); + } + else + { + builder.append(Character.toLowerCase(next)); + } + previous = next; + } + return builder.toString(); + } + + private String getDefaultString( + final String defaultValue, + final boolean isTimeValue, + final TimeUnit timeUnit) throws Exception + { + if (defaultValue != null && !defaultValue.isEmpty() && defaultValue.chars().allMatch(Character::isDigit)) + { + final long defaultLong; + + try + { + defaultLong = Long.parseLong(defaultValue); + } + catch (final NumberFormatException nfe) + { + // This shouldn't be possible since we've already validated that every character is a digit + throw new Exception(nfe); + } + + final StringBuilder builder = new StringBuilder(); + + builder.append(defaultValue); + + if (defaultValue.length() > 3) + { + builder.append(" ("); + builder.append(DecimalFormat.getNumberInstance().format(defaultLong)); + builder.append(")"); + + int kCount = 0; + long remainingValue = defaultLong; + while (remainingValue % 1024 == 0) + { + kCount++; + remainingValue = remainingValue / 1024; + } + + if (kCount > 0 && remainingValue < 1024) + { + builder.append(" ("); + builder.append(remainingValue); + IntStream.range(0, kCount).forEach(i -> builder.append(" * 1024")); + builder.append(")"); + } + } + + if (isTimeValue) + { + int tCount = 0; + + long remaining = timeUnit.toNanos(defaultLong); + while (remaining % 1000 == 0 && tCount < 3) + { + tCount++; + remaining = remaining / 1000; + } + builder.append(" ("); + builder.append(remaining); + + switch (tCount) + { + case 0: + builder.append(" nano"); + break; + case 1: + builder.append(" micro"); + break; + case 2: + builder.append(" milli"); + break; + case 3: + builder.append(" "); + break; + } + builder.append("second"); + if (remaining != 1) + { + builder.append("s"); + } + builder.append(")"); + } + + return builder.toString(); + } + return defaultValue; + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java b/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java new file mode 100644 index 0000000000..325de5255b --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java @@ -0,0 +1,37 @@ +/* + * 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.config.docgen; + +import io.aeron.utility.ElementIO; + +/** + * A gradle task for generating config documentation + */ +public class GenerateConfigDocTask +{ + /** + * @param args + * Arg 0 should be the location of a config-info.xml file with a list of ConfigInfo objects + * Arg 1 should be the location of an output file where a .md file is to be written + * + * @throws Exception + * it sure does + */ + public static void main(final String[] args) throws Exception + { + ConfigDocGenerator.generate(ElementIO.read(args[0]), args[1]); + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/validation/Entry.java b/aeron-annotations/src/main/java/io/aeron/config/validation/Entry.java new file mode 100644 index 0000000000..1d74d8bddf --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Entry.java @@ -0,0 +1,55 @@ +/* + * 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.config.validation; + +import java.io.PrintStream; +import io.aeron.config.ConfigInfo; + +class Entry +{ + private final ConfigInfo configInfo; + final Validation envVarValidation; + final Validation defaultValidation; + + Entry(final ConfigInfo configInfo) + { + this.configInfo = configInfo; + this.envVarValidation = new Validation(); + this.defaultValidation = new Validation(); + } + + void printOn(final PrintStream out) + { + if (configInfo.expectations.c.exists) + { + out.println(configInfo.id); + envVarValidation.printOn(out); + defaultValidation.printOn(out); + } + else + { + out.println(configInfo.id + " -- SKIPPED"); + } + } + + void printFailuresOn(final PrintStream out) + { + if (configInfo.expectations.c.exists && (!envVarValidation.isValid() || !defaultValidation.isValid())) + { + printOn(out); + } + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java new file mode 100644 index 0000000000..cd4c7e5c27 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java @@ -0,0 +1,37 @@ +/* + * 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.config.validation; + +import io.aeron.utility.ElementIO; + +/** + * A gradle task for validating the C code looks as expected, based on the contents of the @Config java annotations + */ +public class ValidateConfigExpectationsTask +{ + /** + * @param args + * Arg 0 should be the location of a config-info.xml file with a list of ConfigInfo objects + * Arg 1 should be the location of the C source code + * + * @throws Exception + * it sure does + */ + public static void main(final String[] args) throws Exception + { + Validator.validate(ElementIO.read(args[0]), args[1]).printFailuresOn(System.err); + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java b/aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java new file mode 100644 index 0000000000..c16fa968bf --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java @@ -0,0 +1,75 @@ +/* + * 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.config.validation; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +class Validation +{ + private boolean valid = false; + + private String message; + + private ByteArrayOutputStream baOut; + + private PrintStream psOut; + + boolean isValid() + { + return valid; + } + + void close() + { + if (this.psOut != null) + { + this.psOut.close(); + } + } + + void valid(final String message) + { + this.valid = true; + this.message = message; + } + + void invalid(final String message) + { + this.valid = false; + this.message = message; + } + + PrintStream out() + { + if (this.psOut == null) + { + this.baOut = new ByteArrayOutputStream(); + this.psOut = new PrintStream(baOut); + } + + return psOut; + } + + void printOn(final PrintStream out) + { + out.println(" " + (this.valid ? "+" : "-") + " " + this.message); + if (this.psOut != null) + { + out.println(this.baOut); + } + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java new file mode 100644 index 0000000000..55a08a4cde --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java @@ -0,0 +1,87 @@ +/* + * 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.config.validation; + +import io.aeron.config.ConfigInfo; +import io.aeron.config.ExpectedCConfig; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +final class ValidationReport +{ + private final List entries; + + ValidationReport() + { + entries = new ArrayList<>(); + } + + void addEntry( + final ConfigInfo configInfo, + final BiConsumer validateCEnvVar, + final BiConsumer validateCDefault) + { + final Entry entry = new Entry(configInfo); + final ExpectedCConfig c = configInfo.expectations.c; + if (c.exists) + { + validate(validateCEnvVar, entry.envVarValidation, c); + + if (c.skipDefaultValidation) + { + entry.defaultValidation.valid("skipped"); + } + else + { + validate(validateCDefault, entry.defaultValidation, c); + } + } + entries.add(entry); + } + + private void validate( + final BiConsumer func, + final Validation validation, + final ExpectedCConfig c) + { + try + { + func.accept(validation, c); + } + catch (final Exception e) + { + validation.invalid(e.getMessage()); + e.printStackTrace(validation.out()); + } + finally + { + validation.close(); + } + } + + void printOn(final PrintStream out) + { + entries.forEach(entry -> entry.printOn(out)); + } + + void printFailuresOn(final PrintStream out) + { + entries.forEach(entry -> entry.printFailuresOn(out)); + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java new file mode 100644 index 0000000000..6bfa5d53c4 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -0,0 +1,223 @@ +/* + * 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.config.validation; + +import io.aeron.config.ConfigInfo; +import io.aeron.config.DefaultType; +import io.aeron.config.ExpectedCConfig; +import io.aeron.validation.Grep; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.util.Collection; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +final class Validator +{ + static ValidationReport validate( + final Collection configInfoCollection, + final String sourceDir) + { + return new Validator(sourceDir).validate(configInfoCollection).report; + } + + private final String sourceDir; + private final ScriptEngine scriptEngine; + private final ValidationReport report; + + private Validator(final String sourceDir) + { + this.sourceDir = sourceDir; + this.scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript"); + this.report = new ValidationReport(); + } + + private Validator validate(final Collection configInfoCollection) + { + configInfoCollection.forEach(this::validateCExpectations); + + // TODO look through C code in 'sourceDir' and check for anything marked '_ENV_VAR' that hasn't been processed. + // These will be 'C only' config options that need to be accounted for... + // ... perhaps in the Java code we can add @Config annotations to some static final fields that Java ignores. + + return this; + } + + private void validateCExpectations(final ConfigInfo configInfo) + { + report.addEntry(configInfo, this::validateCEnvVar, this::validateCDefault); + } + + private void validateCEnvVar(final Validation validation, final ExpectedCConfig c) + { + if (Objects.isNull(c.envVarFieldName)) + { + return; + } + + /* Expectations: + * #define AERON_OPTION_ENV_VAR "AERON_OPTION" + */ + final String pattern = "#define[ \t]+" + c.envVarFieldName + "[ \t]+\"" + c.envVar + "\""; + final Grep grep = Grep.execute(pattern, sourceDir); + if (grep.success()) + { + validation.valid("Expected Env Var found in " + grep.getFilenameAndLine()); + } + else + { + validation.invalid("Expected Env Var NOT found. `grep` command:\n" + grep.getCommandString()); + } + } + + private void validateCDefault(final Validation validation, final ExpectedCConfig c) + { + if (Objects.isNull(c.defaultFieldName)) + { + return; + } + + /* Expectations: + * #define AERON_OPTION_DEFAULT ("some_string") + * #define AERON_OPTION_DEFAULT (1234) + * #define AERON_OPTION_DEFAULT (10 * 1024) + * #define AERON_OPTION_DEFAULT (1024 * INT64_C(1000)) + * #define AERON_OPTION_DEFAULT false + * #define AERON_OPTION_DEFAULT (true) + */ + final String pattern = "#define[ \t]+" + c.defaultFieldName; + + final Grep grep = Grep.execute(pattern, sourceDir); + if (!grep.success()) + { + validation.invalid("Expected Default NOT found. `grep` command:\n" + grep.getCommandString()); + return; + } + final String location = grep.getFilenameAndLine(); + + final Matcher matcher = Pattern.compile(pattern + "(.*)$").matcher(grep.getOutput()); + if (!matcher.find()) + { + validation.invalid("Found Default but the pattern doesn't match at " + location); + return; + } + + final String originalFoundDefaultString = matcher.group(1).trim(); + + if (c.defaultValueType == DefaultType.STRING) + { + validateCDefaultString(validation, c, originalFoundDefaultString, location); + } + else if (c.defaultValueType == DefaultType.BOOLEAN) + { + validateCDefaultBoolean(validation, c, originalFoundDefaultString, location); + } + else if (c.defaultValueType.isNumeric()) + { + validateCDefaultNumeric(validation, c, originalFoundDefaultString, location); + } + else + { + validation.invalid("bad default type"); + } + } + + private void validateCDefaultString( + final Validation validation, + final ExpectedCConfig c, + final String originalFoundDefaultString, + final String location) + { + final String foundDefaultString = originalFoundDefaultString + .replaceFirst("^\\(", "") + .replaceFirst("\\)$", "") + .replaceFirst("^\"", "") + .replaceFirst("\"$", ""); + + if (foundDefaultString.equals(c.defaultValue)) + { + validation.valid("Expected Default (\"" + foundDefaultString + "\") found in " + location); + } + else + { + validation.invalid("Expected Default string doesn't match. Expected '" + c.defaultValue + + "' but found '" + foundDefaultString + "' in " + location); + } + } + + private void validateCDefaultBoolean( + final Validation validation, + final ExpectedCConfig c, + final String originalFoundDefaultString, + final String location) + { + final String foundDefaultString = originalFoundDefaultString + .replaceFirst("^\\(", "") + .replaceFirst("\\)$", ""); + + if (foundDefaultString.equals(c.defaultValue)) + { + validation.valid("Expected Default '" + foundDefaultString + "' found in " + location); + } + else + { + validation.invalid("boolean doesn't match: " + location); + } + } + + private void validateCDefaultNumeric( + final Validation validation, + final ExpectedCConfig c, + final String originalFoundDefaultString, + final String location) + { + final String foundDefaultString = originalFoundDefaultString + .replaceAll("INT64_C", "") + .replaceAll("UINT32_C", "") + .replaceAll("([0-9]+)LL", "$1") + .replaceAll("([0-9]+)L", "$1"); + + try + { + final String evaluatedFoundDefaultString = scriptEngine.eval( + "AERON_TERM_BUFFER_LENGTH_DEFAULT = (16 * 1024 * 1024);\n" + // this feels like a (very) bad idea + "(" + foundDefaultString + ").toFixed(0)" // avoid scientific notation + ).toString(); + + if (evaluatedFoundDefaultString.equals(c.defaultValue)) + { + validation.valid("Expected Default '" + foundDefaultString + "'" + + (foundDefaultString.equals(evaluatedFoundDefaultString) ? + "" : " (" + evaluatedFoundDefaultString + ")") + + " found in " + location); + } + else + { + validation.invalid("found " + foundDefaultString + + " (" + evaluatedFoundDefaultString + ") but expected " + c.defaultValue); + } + } + catch (final ScriptException e) + { + validation.invalid("Expected Default - unable to evaluate expression '" + + originalFoundDefaultString + "' in " + location); + e.printStackTrace(validation.out()); + } + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java b/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java new file mode 100644 index 0000000000..a4eb8bcd46 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java @@ -0,0 +1,39 @@ +/* + * 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.counter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to indicate this is an Aeron counter + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.SOURCE) +public @interface AeronCounter +{ + /** + * @return whether or not this counter exists in the C code + */ + boolean existsInC() default true; + + /** + * @return the name of the #define in C + */ + String expectedCName() default ""; +} diff --git a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java new file mode 100644 index 0000000000..f55cb059af --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java @@ -0,0 +1,51 @@ +/* + * 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.counter; + +import java.io.Serializable; + +/** + * A handy class for storing data that gets serialized into json + */ +public class CounterInfo implements Serializable +{ + private static final long serialVersionUID = -5863246029522577056L; + + public final String name; + + /** + */ + public CounterInfo() + { + this.name = null; + } + + /** + * @param name the name of the counter + */ + public CounterInfo(final String name) + { + this.name = name; + } + + public int id; + + public String counterDescription; + + public boolean existsInC = true; + + public String expectedCName; +} diff --git a/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java new file mode 100644 index 0000000000..88cd7906a5 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java @@ -0,0 +1,169 @@ +/* + * 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.counter; + +import io.aeron.utility.ElementIO; +import io.aeron.utility.Processor; + +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.element.*; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * AeronCounter processor + */ +@SupportedAnnotationTypes("io.aeron.counter.AeronCounter") +public class CounterProcessor extends Processor +{ + @Override + protected String getEnabledPropertyName() + { + return "aeron.build.counterProcessor.enabled"; + } + + @Override + protected String getPrintNotesPropertyName() + { + return "aeron.build.counterProcessor.printNotes"; + } + + @Override + protected String getFailOnErrorPropertyName() + { + return "aeron.build.counterProcessor.failOnError"; + } + + /** + * {@inheritDoc} + */ + @Override + public void doProcess(final Set annotations, final RoundEnvironment roundEnv) + { + final Map counterInfoMap = new HashMap<>(); + + for (final TypeElement annotation : annotations) + { + for (final Element element : roundEnv.getElementsAnnotatedWith(annotation)) + { + try + { + if (element instanceof VariableElement) + { + processElement(counterInfoMap, (VariableElement)element); + } + else + { + // TODO + } + } + catch (final Exception e) + { + error("an error occurred processing an element: " + e.getMessage(), element); + e.printStackTrace(System.err); + } + } + } + + if (!counterInfoMap.isEmpty()) + { + try + { + final FileObject resourceFile = processingEnv.getFiler() + .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "counter-info.dat"); + + ElementIO.write(resourceFile, counterInfoMap.values()); + } + catch (final Exception e) + { + e.printStackTrace(System.err); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "an error occurred while writing output: " + e.getMessage()); + } + } + } + + private void processElement(final Map counterInfoMap, final VariableElement element) + { + final AeronCounter counter = element.getAnnotation(AeronCounter.class); + + if (Objects.isNull(counter)) + { + error("element found with no expected annotations", element); + return; + } + + final Matcher matcher = Pattern.compile("^([A-Z_]+)_TYPE_ID$").matcher(element.toString()); + if (!matcher.find()) + { + error("unable to determine type and/or id", element); + return; + } + + final CounterInfo counterInfo = new CounterInfo(matcher.group(1)); + + if (null != counterInfoMap.put(counterInfo.name, counterInfo)) + { + error("duplicate counters found", element); + return; + } + + counterInfo.counterDescription = getDocComment(element); + + final Object constantValue = element.getConstantValue(); + if (constantValue instanceof Integer) + { + counterInfo.id = (Integer)constantValue; + } + else + { + error("Counter value must be an Integer", element); + } + + if (!counter.existsInC()) + { + note("Counter isn't expected to exist in C", element); + counterInfo.existsInC = false; + } + + if (counterInfo.existsInC) + { + final StringBuilder builder = new StringBuilder(); + + builder.append("AERON_COUNTER_"); + + if (counter.expectedCName().isEmpty()) + { + builder.append(counterInfo.name.replaceAll("^DRIVER_", "")); + } + else + { + note("Counter's C name is overridden", element); + + builder.append(counter.expectedCName()); + } + + builder.append("_TYPE_ID"); + + counterInfo.expectedCName = builder.toString(); + } + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java new file mode 100644 index 0000000000..3c76e36b14 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java @@ -0,0 +1,37 @@ +/* + * 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.counter.validation; + +import io.aeron.utility.ElementIO; + +/** + * A gradle task for validating C counters conform to expectations set by the AeronCounter annotation in java + */ +public class ValidateCounterExpectationsTask +{ + /** + * @param args + * Arg 0 should be the location of a counter-info.xml file with a list of CounterInfo objects + * Arg 1 should be the location of the C source code + * + * @throws Exception + * it sure does + */ + public static void main(final String[] args) throws Exception + { + Validator.validate(ElementIO.read(args[0]), args[1]).printFailuresOn(System.err); + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java new file mode 100644 index 0000000000..31ee2c1a3e --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java @@ -0,0 +1,82 @@ +/* + * 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.counter.validation; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +class Validation +{ + private final String name; + private boolean valid = false; + + private String message; + + private ByteArrayOutputStream baOut; + + private PrintStream psOut; + + Validation(final String name) + { + this.name = name; + } + + boolean isValid() + { + return valid; + } + + void close() + { + if (this.psOut != null) + { + this.psOut.close(); + } + } + + void valid(final String message) + { + this.valid = true; + this.message = message; + } + + void invalid(final String message) + { + this.valid = false; + this.message = message; + } + + PrintStream out() + { + if (this.psOut == null) + { + this.baOut = new ByteArrayOutputStream(); + this.psOut = new PrintStream(baOut); + } + + return psOut; + } + + void printOn(final PrintStream out) + { + out.println(name); + out.println(" " + (this.valid ? "+" : "-") + " " + this.message); + if (this.psOut != null) + { + out.println(this.baOut); + } + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidationReport.java b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidationReport.java new file mode 100644 index 0000000000..838ae63a34 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidationReport.java @@ -0,0 +1,89 @@ +/* + * 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.counter.validation; + +import io.aeron.counter.CounterInfo; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +final class ValidationReport +{ + private final List validations; + + ValidationReport() + { + validations = new ArrayList<>(); + } + + void addValidation( + final CounterInfo counterInfo, + final BiConsumer validateFunc) + { + final Validation validation = new Validation(counterInfo.name); + validate(validateFunc, validation, counterInfo); + validations.add(validation); + } + + void addValidation( + final boolean valid, + final String name, + final String message) + { + final Validation validation = new Validation(name); + if (valid) + { + validation.valid(message); + } + else + { + validation.invalid(message); + } + validations.add(validation); + } + + private void validate( + final BiConsumer func, + final Validation validation, + final CounterInfo c) + { + try + { + func.accept(validation, c); + } + catch (final Exception e) + { + validation.invalid(e.getMessage()); + e.printStackTrace(validation.out()); + } + finally + { + validation.close(); + } + } + + void printOn(final PrintStream out) + { + validations.forEach(validation -> validation.printOn(out)); + } + + void printFailuresOn(final PrintStream out) + { + validations.stream().filter(validation -> !validation.isValid()).forEach(validation -> validation.printOn(out)); + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java new file mode 100644 index 0000000000..e5e1e29975 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java @@ -0,0 +1,136 @@ +/* + * 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.counter.validation; + +import io.aeron.counter.CounterInfo; +import io.aeron.validation.Grep; + +import java.util.Collection; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +final class Validator +{ + static ValidationReport validate( + final Collection counterInfoCollection, + final String sourceDir) + { + return new Validator(sourceDir).validate(counterInfoCollection).report; + } + + private final String sourceDir; + private final ValidationReport report; + + private Validator(final String sourceDir) + { + this.sourceDir = sourceDir; + this.report = new ValidationReport(); + } + + private Validator validate(final Collection counterInfoCollection) + { + counterInfoCollection.forEach(this::validateCExpectations); + + identifyExtraCCounters(counterInfoCollection); + + return this; + } + + private void identifyExtraCCounters(final Collection counterInfoCollection) + { + final Pattern compiledPattern = Pattern.compile("#define[ \t]+([A-Z_]+)[ \t]+\\([0-9]+\\)"); + final List expectedCNames = counterInfoCollection + .stream() + .filter(counterInfo -> counterInfo.existsInC) + .map(counterInfo -> counterInfo.expectedCName) + .collect(Collectors.toList()); + + final String pattern = "#define[ \t]+AERON_COUNTER_([A-Z_]+)_TYPE_ID[ \t]+\\([0-9]+\\)"; + final Grep grep = Grep.execute(pattern, sourceDir); + + grep.forEach((fileAndLineNo, line) -> + { + final Matcher matcher = compiledPattern.matcher(line); + if (matcher.find()) + { + final String name = matcher.group(1); + + if (expectedCNames.stream().noneMatch(cName -> cName.equals(name))) + { + report.addValidation(false, name, + "Found C counter with no matching Java counter - " + fileAndLineNo); + } + } + else + { + System.err.println("malformed line: " + line); + } + }); + } + + private void validateCExpectations(final CounterInfo counterInfo) + { + if (counterInfo.existsInC) + { + report.addValidation(counterInfo, this::validate); + } + } + + private void validate(final Validation validation, final CounterInfo counterInfo) + { + /* Expectations: + * #define AERON_COUNTER_SOME_NAME (50) + */ + final String pattern = "#define[ \t]+" + counterInfo.expectedCName + "[ \t]+\\([0-9]+\\)"; + final Grep grep = Grep.execute(pattern, sourceDir); + if (grep.success()) + { + + final Matcher matcher = + Pattern.compile("#define[ \t]+[A-Z_]+[ \t]+\\(([0-9]+)\\)").matcher(grep.getOutput()); + if (matcher.find()) + { + final String id = matcher.group(1); + + try + { + if (counterInfo.id == Integer.parseInt(id)) + { + validation.valid("Expected ID found in " + grep.getFilenameAndLine()); + } + else + { + validation.invalid("Incorrect ID found. Expected: " + counterInfo.id + " but found: " + id); + } + } + catch (final NumberFormatException numberFormatException) + { + validation.invalid("Unable to parse ID. Expected a number but found: " + id); + } + } + else + { + validation.invalid("WHAT??"); + } + } + else + { + validation.invalid("Expected ID NOT found. `grep` command:\n" + grep.getCommandString()); + } + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java b/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java new file mode 100644 index 0000000000..80266fbbba --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java @@ -0,0 +1,58 @@ +/* + * 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.utility; + +import javax.tools.FileObject; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + */ +public class ElementIO +{ + /** + * @param elementsFilename the name of the filename that contains a list of objects + * @return a list of elements + * @param the type of elements - ConfigInfo or CounterInfo + * @throws Exception yup + */ + @SuppressWarnings("unchecked") + public static List read(final String elementsFilename) throws Exception + { + try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(elementsFilename)))) + { + return ((List)in.readObject()); + } + } + + /** + * @param resourceFile the destination file to write to + * @param elements a Collection of elements + * @throws Exception yup + */ + public static void write(final FileObject resourceFile, final Collection elements) throws Exception + { + try (ObjectOutputStream out = new ObjectOutputStream(resourceFile.openOutputStream())) + { + out.writeObject(new ArrayList<>(elements)); + } + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/utility/Processor.java b/aeron-annotations/src/main/java/io/aeron/utility/Processor.java new file mode 100644 index 0000000000..a7c67f135b --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/utility/Processor.java @@ -0,0 +1,119 @@ +/* + * 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.utility; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.*; +import javax.tools.Diagnostic; +import java.util.Set; + +/** + * abstract processor + */ +public abstract class Processor extends AbstractProcessor +{ + private boolean enabled = false; + + private boolean printNotes = false; + + private Diagnostic.Kind errorKind; + + /** + * {@inheritDoc} + */ + @Override + public SourceVersion getSupportedSourceVersion() + { + return SourceVersion.latest(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void init(final ProcessingEnvironment processingEnv) + { + enabled = System.getProperty(getEnabledPropertyName(), "true").equalsIgnoreCase("true"); + printNotes = System.getProperty(getPrintNotesPropertyName(), "false").equalsIgnoreCase("true"); + errorKind = + System.getProperty(getFailOnErrorPropertyName(), "false").equalsIgnoreCase("true") ? + Diagnostic.Kind.ERROR : + Diagnostic.Kind.NOTE; + super.init(processingEnv); + } + + protected abstract String getEnabledPropertyName(); + + protected abstract String getPrintNotesPropertyName(); + + protected abstract String getFailOnErrorPropertyName(); + + protected abstract void doProcess(Set annotations, RoundEnvironment roundEnv); + + @Override + public boolean process(final Set annotations, final RoundEnvironment roundEnv) + { + if (enabled) + { + doProcess(annotations, roundEnv); + } + + return false; + } + + protected String getDocComment(final Element element) + { + final String description = processingEnv.getElementUtils().getDocComment(element); + if (description == null) + { + error("no javadoc found", element); + return "NO DESCRIPTION FOUND"; + } + + return description.trim(); + } + + protected void error(final String errMsg) + { + error(errMsg, null); + } + + protected void error(final String errMsg, final Element element) + { + printMessage(errorKind, errMsg, element); + } + + protected void note(final String msg) + { + note(msg, null); + } + + protected void note(final String msg, final Element element) + { + if (printNotes) + { + printMessage(Diagnostic.Kind.NOTE, msg, element); + } + } + + private void printMessage(final Diagnostic.Kind kind, final String msg, final Element element) + { + processingEnv.getMessager().printMessage(kind, msg, element); + } +} diff --git a/aeron-annotations/src/main/java/io/aeron/validation/Grep.java b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java new file mode 100644 index 0000000000..5f051be354 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java @@ -0,0 +1,167 @@ +/* + * 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.validation; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +/** + * A utility class for running 'grep' + */ +public final class Grep +{ + /** + * @param pattern the regex pattern passed to grep + * @param sourceDir the base directory where the search should begin + * @return a Grep object with the results of the action + */ + public static Grep execute(final String pattern, final String sourceDir) + { + final String commandString = "grep -r -n -E '^" + pattern + "' " + sourceDir; + + try + { + // TODO make grep location configurable?? + final Process process = new ProcessBuilder() + .redirectErrorStream(true) + .command(new String[] {"/usr/bin/grep", "-r", "-n", "-E", "^" + pattern, sourceDir}) + .start(); + + final int exitCode = process.waitFor(); + + try ( + InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream()); + BufferedReader reader = new BufferedReader(inputStreamReader)) + { + return new Grep(commandString, exitCode, reader.lines().collect(Collectors.toList())); + } + } + catch (final Exception e) + { + return new Grep(commandString, e); + } + } + + private final String commandString; + + private final int exitCode; + + private final List lines; + + private final Exception e; + + private Grep(final String commandString, final Exception e) + { + this.commandString = commandString; + this.exitCode = -1; + this.lines = Collections.emptyList(); + this.e = e; + } + + private Grep(final String commandString, final int exitCode, final List lines) + { + this.commandString = commandString; + this.exitCode = exitCode; + this.lines = lines; + this.e = null; + } + + /** + * @return whether or not grep succeeded + */ + public boolean success() + { + return success(true); + } + + /** + * @param expectOneLine many of the usages expect only a single line to be found. + * if more than one are found, that counts as a failure + * @return whether or not grep succeeded + */ + public boolean success(final boolean expectOneLine) + { + if (this.e != null) + { + return false; + } + + if (this.exitCode != 0) + { + return false; + } + + return !expectOneLine || this.lines.size() == 1; + } + + /** + * @return the command string that was executed + */ + public String getCommandString() + { + return this.commandString; + } + + /** + * @return the filename and line number of the first line of output + */ + public String getFilenameAndLine() + { + return getFilenameAndLine(0); + } + + /** + * @param lineNumber specify the line of output + * @return the filename and line number of the specified line of output + */ + public String getFilenameAndLine(final int lineNumber) + { + final String[] pieces = this.lines.get(lineNumber).split(":"); + return pieces[0] + ":" + pieces[1]; + } + + /** + * @return the first line of output (minus the filename and line number) + */ + public String getOutput() + { + return getOutput(0); + } + + /** + * @param lineNumber specify the line of output + * @return the output of the specified line number (minus the filename and the line number) + */ + public String getOutput(final int lineNumber) + { + return this.lines.get(lineNumber).split(":")[2]; + } + + /** + * @param action a BiConsumer that consumes the filename/line number and output for each line of output + */ + public void forEach(final BiConsumer action) + { + for (int i = 0; i < lines.size(); i++) + { + action.accept(getFilenameAndLine(i), getOutput(i)); + } + } +} diff --git a/aeron-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/aeron-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor index b3f7af31a0..ccf1f91f4b 100644 --- a/aeron-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/aeron-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1,3 @@ -io.aeron.version.VersionProcessor \ No newline at end of file +io.aeron.version.VersionProcessor +io.aeron.config.ConfigProcessor +io.aeron.counter.CounterProcessor diff --git a/aeron-archive/src/main/java/io/aeron/archive/Archive.java b/aeron-archive/src/main/java/io/aeron/archive/Archive.java index 1b00643284..25426cd47a 100644 --- a/aeron-archive/src/main/java/io/aeron/archive/Archive.java +++ b/aeron-archive/src/main/java/io/aeron/archive/Archive.java @@ -20,6 +20,8 @@ import io.aeron.archive.checksum.Checksums; import io.aeron.archive.client.AeronArchive; import io.aeron.archive.client.ArchiveException; +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.AeronException; @@ -205,6 +207,7 @@ public static Archive launch(final Context ctx) *

* Details for the individual parameters can be found in the Javadoc for the {@link Context} setters. */ + @Config(existsInC = false) public static final class Configuration { /** @@ -220,16 +223,19 @@ public static final class Configuration /** * Default block length of data in a single IO operation during a recording or replay. */ + @Config public static final int FILE_IO_MAX_LENGTH_DEFAULT = 1024 * 1024; /** * Maximum length of a file IO operation for recording or replay. Must be a power of 2. */ + @Config public static final String FILE_IO_MAX_LENGTH_PROP_NAME = "aeron.archive.file.io.max.length"; /** * Directory in which the archive stores it files such as the catalog and recordings. */ + @Config public static final String ARCHIVE_DIR_PROP_NAME = "aeron.archive.dir"; /** @@ -237,17 +243,20 @@ public static final class Configuration * * @see #ARCHIVE_DIR_PROP_NAME */ + @Config public static final String ARCHIVE_DIR_DEFAULT = "aeron-archive"; /** * Alternative directory to store mark file (i.e. {@code archive-mark.dat}). */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String MARK_FILE_DIR_PROP_NAME = "aeron.archive.mark.file.dir"; /** * Recordings will be segmented on disk in files limited to the segment length which must be a multiple of * the term length for each stream. For lots of small recording this value may be reduced. */ + @Config public static final String SEGMENT_FILE_LENGTH_PROP_NAME = "aeron.archive.segment.file.length"; /** @@ -255,11 +264,13 @@ public static final class Configuration * * @see #SEGMENT_FILE_LENGTH_PROP_NAME */ + @Config public static final int SEGMENT_FILE_LENGTH_DEFAULT = 128 * 1024 * 1024; /** * Threshold below which the archive will reject new recording requests. */ + @Config public static final String LOW_STORAGE_SPACE_THRESHOLD_PROP_NAME = "aeron.archive.low.storage.space.threshold"; /** @@ -267,6 +278,7 @@ public static final class Configuration * * @see #LOW_STORAGE_SPACE_THRESHOLD_PROP_NAME */ + @Config public static final int LOW_STORAGE_SPACE_THRESHOLD_DEFAULT = SEGMENT_FILE_LENGTH_DEFAULT; /** @@ -277,6 +289,7 @@ public static final class Configuration *

  • 2 - sync file data + metadata.
  • * */ + @Config public static final String FILE_SYNC_LEVEL_PROP_NAME = "aeron.archive.file.sync.level"; /** @@ -284,6 +297,7 @@ public static final class Configuration * * @see #FILE_SYNC_LEVEL_PROP_NAME */ + @Config public static final int FILE_SYNC_LEVEL_DEFAULT = 0; /** @@ -294,6 +308,7 @@ public static final class Configuration *
  • 2 - sync file data + metadata.
  • * */ + @Config public static final String CATALOG_FILE_SYNC_LEVEL_PROP_NAME = "aeron.archive.catalog.file.sync.level"; /** @@ -301,26 +316,31 @@ public static final class Configuration * * @see #CATALOG_FILE_SYNC_LEVEL_PROP_NAME */ + @Config public static final int CATALOG_FILE_SYNC_LEVEL_DEFAULT = FILE_SYNC_LEVEL_DEFAULT; /** * What {@link ArchiveThreadingMode} should be used. */ + @Config(defaultType = DefaultType.STRING, defaultString = "DEDICATED") public static final String THREADING_MODE_PROP_NAME = "aeron.archive.threading.mode"; /** * Default {@link IdleStrategy} to be used for the archive {@link Agent}s when not busy. */ + @Config public static final String ARCHIVE_IDLE_STRATEGY_PROP_NAME = "aeron.archive.idle.strategy"; /** * The {@link IdleStrategy} to be used for the archive recorder {@link Agent} when not busy. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String ARCHIVE_RECORDER_IDLE_STRATEGY_PROP_NAME = "aeron.archive.recorder.idle.strategy"; /** * The {@link IdleStrategy} to be used for the archive replayer {@link Agent} when not busy. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String ARCHIVE_REPLAYER_IDLE_STRATEGY_PROP_NAME = "aeron.archive.replayer.idle.strategy"; /** @@ -328,6 +348,7 @@ public static final class Configuration * * @see #ARCHIVE_IDLE_STRATEGY_PROP_NAME */ + @Config(id = "ARCHIVE_IDLE_STRATEGY") public static final String DEFAULT_IDLE_STRATEGY = "org.agrona.concurrent.BackoffIdleStrategy"; /** @@ -336,6 +357,7 @@ public static final class Configuration * multiple images, and thus multiple recordings, then the limit may come later. It is best to * use session based subscriptions. */ + @Config public static final String MAX_CONCURRENT_RECORDINGS_PROP_NAME = "aeron.archive.max.concurrent.recordings"; /** @@ -344,18 +366,21 @@ public static final class Configuration * * @see #MAX_CONCURRENT_RECORDINGS_PROP_NAME */ + @Config public static final int MAX_CONCURRENT_RECORDINGS_DEFAULT = 20; /** * Maximum number of concurrent replays. Beyond this maximum an exception will be raised and further replays * will be rejected. */ + @Config public static final String MAX_CONCURRENT_REPLAYS_PROP_NAME = "aeron.archive.max.concurrent.replays"; /** * Default maximum number of concurrent replays. Unless on a fast SSD and having sufficient memory * for the page cache then this number should be kept low. */ + @Config public static final int MAX_CONCURRENT_REPLAYS_DEFAULT = 20; /** @@ -366,6 +391,7 @@ public static final class Configuration * @deprecated Use {@link #CATALOG_CAPACITY_PROP_NAME} instead. */ @Deprecated + @Config public static final String MAX_CATALOG_ENTRIES_PROP_NAME = "aeron.archive.max.catalog.entries"; /** @@ -374,12 +400,14 @@ public static final class Configuration * @see #MAX_CATALOG_ENTRIES_PROP_NAME */ @Deprecated + @Config public static final long MAX_CATALOG_ENTRIES_DEFAULT = 8 * 1024; /** * Default capacity in bytes of the archive {@link Catalog}. {@link Catalog} will resize itself when this * limit is reached. */ + @Config public static final String CATALOG_CAPACITY_PROP_NAME = "aeron.archive.catalog.capacity"; /** @@ -387,11 +415,13 @@ public static final class Configuration * * @see #CATALOG_CAPACITY_PROP_NAME */ + @Config public static final long CATALOG_CAPACITY_DEFAULT = Catalog.DEFAULT_CAPACITY; /** * Timeout for making a connection back to a client for a control session or replay. */ + @Config public static final String CONNECT_TIMEOUT_PROP_NAME = "aeron.archive.connect.timeout"; /** @@ -400,11 +430,13 @@ public static final class Configuration * * @see #CONNECT_TIMEOUT_PROP_NAME */ + @Config(defaultType = DefaultType.LONG, defaultLong = 5L * 1000 * 1000 * 1000) public static final long CONNECT_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** * How long a replay publication should linger after all data is sent. Longer linger can help avoid tail loss. */ + @Config public static final String REPLAY_LINGER_TIMEOUT_PROP_NAME = "aeron.archive.replay.linger.timeout"; /** @@ -413,63 +445,75 @@ public static final class Configuration * * @see #REPLAY_LINGER_TIMEOUT_PROP_NAME */ + @Config(defaultType = DefaultType.LONG, defaultLong = 5L * 1000 * 1000 * 1000) public static final long REPLAY_LINGER_TIMEOUT_DEFAULT_NS = io.aeron.driver.Configuration.publicationLingerTimeoutNs(); /** * Property name for threshold value for the conductor work cycle threshold to track for being exceeded. */ + @Config public static final String CONDUCTOR_CYCLE_THRESHOLD_PROP_NAME = "aeron.archive.conductor.cycle.threshold"; /** * Default threshold value for the conductor work cycle threshold to track for being exceeded. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000) public static final long CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000); /** * Property name for threshold value for the recorder work cycle threshold to track for being exceeded. */ + @Config public static final String RECORDER_CYCLE_THRESHOLD_PROP_NAME = "aeron.archive.recorder.cycle.threshold"; /** * Default threshold value for the recorder work cycle threshold to track for being exceeded. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000) public static final long RECORDER_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000); /** * Property name for threshold value for the replayer work cycle threshold to track for being exceeded. */ + @Config public static final String REPLAYER_CYCLE_THRESHOLD_PROP_NAME = "aeron.archive.replayer.cycle.threshold"; /** * Default threshold value for the replayer work cycle threshold to track for being exceeded. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000) public static final long REPLAYER_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000); /** * Should the archive delete existing files on start. Default is false and should only be true for testing. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false) public static final String ARCHIVE_DIR_DELETE_ON_START_PROP_NAME = "aeron.archive.dir.delete.on.start"; /** * Channel for receiving replication streams replayed from another archive. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String REPLICATION_CHANNEL_PROP_NAME = "aeron.archive.replication.channel"; /** * Name of the system property for specifying a supplier of {@link Authenticator} for the archive. */ + @Config public static final String AUTHENTICATOR_SUPPLIER_PROP_NAME = "aeron.archive.authenticator.supplier"; /** * Name of the class to use as a supplier of {@link Authenticator} for the archive. Default is * a non-authenticating option. */ + @Config public static final String AUTHENTICATOR_SUPPLIER_DEFAULT = "io.aeron.security.DefaultAuthenticatorSupplier"; /** * Name of the system property for specifying a supplier of {@link AuthorisationService} for the archive. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME = "aeron.archive.authorisation.service.supplier"; @@ -493,29 +537,34 @@ public static final class Configuration /** * Size in bytes of the error buffer for the archive when not externally provided. */ + @Config public static final String ERROR_BUFFER_LENGTH_PROP_NAME = "aeron.archive.error.buffer.length"; /** * Size in bytes of the error buffer for the archive when not eternally provided. */ + @Config public static final int ERROR_BUFFER_LENGTH_DEFAULT = 1024 * 1024; /** * Property that specifies fully qualified class name of the {@link io.aeron.archive.checksum.Checksum} * to be used for checksum computation during recording. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String RECORD_CHECKSUM_PROP_NAME = "aeron.archive.record.checksum"; /** * Property that specifies fully qualified class name of the {@link io.aeron.archive.checksum.Checksum} * to be used for checksum validation during replay. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String REPLAY_CHECKSUM_PROP_NAME = "aeron.archive.replay.checksum"; /** * Property name for the identity of the Archive instance. If not specified defaults to the * {@link Aeron#clientId()} of the assigned {@link Aeron} instance. */ + @Config(defaultType = DefaultType.LONG, defaultLong = -1) public static final String ARCHIVE_ID_PROP_NAME = "aeron.archive.id"; /** @@ -524,6 +573,7 @@ public static final class Configuration * If set to anything other than {@code true} then control channel is disabled, i.e. the Archive will run in * IPC-only mode. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true) public static final String CONTROL_CHANNEL_ENABLED_PROP_NAME = "aeron.archive.control.channel.enabled"; /** @@ -1496,6 +1546,7 @@ public Context deleteArchiveOnStart(final boolean deleteArchiveOnStart) * * @return {@code true} if an existing archive should be deleted on start up. */ + @Config(id = "ARCHIVE_DIR_DELETE_ON_START") public boolean deleteArchiveOnStart() { return deleteArchiveOnStart; @@ -1520,6 +1571,7 @@ public Context archiveDirectoryName(final String archiveDirectoryName) * * @return the directory name to be used for the archive to store recordings and the {@link Catalog}. */ + @Config(id = "ARCHIVE_DIR") public String archiveDirectoryName() { return archiveDirectoryName; @@ -1556,6 +1608,7 @@ public Context archiveDir(final File archiveDir) * @see Configuration#MARK_FILE_DIR_PROP_NAME * @see #archiveDir() */ + @Config public File markFileDir() { return markFileDir; @@ -1643,6 +1696,7 @@ public AeronArchive.Context archiveClientContext() * @return {@code true} if the UDP control channel should be enabled. * @see Configuration#CONTROL_CHANNEL_ENABLED_PROP_NAME */ + @Config public boolean controlChannelEnabled() { return controlChannelEnabled; @@ -1887,6 +1941,7 @@ public Context recordingEventsStreamId(final int recordingEventsStreamId) * @return {@code true} if the recording events channel should be enabled. * @see io.aeron.archive.client.AeronArchive.Configuration#RECORDING_EVENTS_ENABLED_PROP_NAME */ + @Config public boolean recordingEventsEnabled() { return recordingEventsEnabled; @@ -1911,6 +1966,7 @@ public Context recordingEventsEnabled(final boolean recordingEventsEnabled) * @return the channel URI for replicating stream from another archive as replays. * @see Archive.Configuration#REPLICATION_CHANNEL_PROP_NAME */ + @Config public String replicationChannel() { return replicationChannel; @@ -1948,6 +2004,7 @@ public Context connectTimeoutNs(final long connectTimeoutNs) * @return the message timeout in nanoseconds to wait for a connection to be established. * @see Configuration#CONNECT_TIMEOUT_PROP_NAME */ + @Config public long connectTimeoutNs() { return connectTimeoutNs; @@ -1974,6 +2031,7 @@ public Context replayLingerTimeoutNs(final long replayLingerTimeoutNs) * @see Configuration#REPLAY_LINGER_TIMEOUT_PROP_NAME * @see io.aeron.driver.Configuration#PUBLICATION_LINGER_PROP_NAME */ + @Config public long replayLingerTimeoutNs() { return replayLingerTimeoutNs; @@ -2000,6 +2058,7 @@ public Context conductorCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the conductor work cycle time. */ + @Config public long conductorCycleThresholdNs() { return conductorCycleThresholdNs; @@ -2026,6 +2085,7 @@ public Context recorderCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the recorder work cycle time. */ + @Config public long recorderCycleThresholdNs() { return recorderCycleThresholdNs; @@ -2052,6 +2112,7 @@ public Context replayerCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the replayer work cycle time. */ + @Config public long replayerCycleThresholdNs() { return replayerCycleThresholdNs; @@ -2147,6 +2208,7 @@ public Context recordChecksum(final Checksum recordChecksum) * {@code null} if no {@link Checksum} was configured. * @see Configuration#RECORD_CHECKSUM_PROP_NAME */ + @Config public Checksum recordChecksum() { return recordChecksum; @@ -2172,6 +2234,7 @@ public Context replayChecksum(final Checksum replayChecksum) * {@code null} if no replay {@link Checksum} was configured. * @see Configuration#REPLAY_CHECKSUM_PROP_NAME */ + @Config public Checksum replayChecksum() { return replayChecksum; @@ -2196,6 +2259,7 @@ public Context idleStrategySupplier(final Supplier idleStrategySup * * @return a new {@link IdleStrategy} for idling the conductor or composite {@link Agent}. */ + @Config(id = "ARCHIVE_IDLE_STRATEGY") public IdleStrategy idleStrategy() { return idleStrategySupplier.get(); @@ -2218,6 +2282,7 @@ public Context recorderIdleStrategySupplier(final Supplier idleStr * * @return a new {@link IdleStrategy} for idling the recorder {@link Agent}. */ + @Config(id = "ARCHIVE_RECORDER_IDLE_STRATEGY") public IdleStrategy recorderIdleStrategy() { return recorderIdleStrategySupplier.get(); @@ -2240,6 +2305,7 @@ public Context replayerIdleStrategySupplier(final Supplier idleStr * * @return a new {@link IdleStrategy} for idling the replayer {@link Agent}. */ + @Config(id = "ARCHIVE_REPLAYER_IDLE_STRATEGY") public IdleStrategy replayerIdleStrategy() { return replayerIdleStrategySupplier.get(); @@ -2295,6 +2361,7 @@ public NanoClock nanoClock() * @return the file length used for recording data segment files * @see Configuration#SEGMENT_FILE_LENGTH_PROP_NAME */ + @Config int segmentFileLength() { return segmentFileLength; @@ -2326,6 +2393,7 @@ public Context segmentFileLength(final int segmentFileLength) * @see #catalogFileSyncLevel() * @see Configuration#FILE_SYNC_LEVEL_PROP_NAME */ + @Config int fileSyncLevel() { return fileSyncLevel; @@ -2362,6 +2430,7 @@ public Context fileSyncLevel(final int syncLevel) * @see #fileSyncLevel() * @see Configuration#CATALOG_FILE_SYNC_LEVEL_PROP_NAME */ + @Config int catalogFileSyncLevel() { return catalogFileSyncLevel; @@ -2458,6 +2527,7 @@ public CountedErrorHandler countedErrorHandler() * @return the archive threading mode. * @see Configuration#THREADING_MODE_PROP_NAME */ + @Config public ArchiveThreadingMode threadingMode() { return threadingMode; @@ -2567,6 +2637,7 @@ public Context errorBufferLength(final int errorBufferLength) * @return error buffer length in bytes. * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME */ + @Config public int errorBufferLength() { return errorBufferLength; @@ -2591,6 +2662,7 @@ public Context archiveId(final long archiveId) * @return the id of this Archive instance. * @see io.aeron.archive.Archive.Configuration#ARCHIVE_ID_PROP_NAME */ + @Config public long archiveId() { return archiveId; @@ -2822,6 +2894,7 @@ public Context maxReadTimeCounter(final Counter counter) * @return the max number of concurrent recordings. * @see Configuration#MAX_CONCURRENT_RECORDINGS_PROP_NAME */ + @Config public int maxConcurrentRecordings() { return maxConcurrentRecordings; @@ -2846,6 +2919,7 @@ public Context maxConcurrentRecordings(final int maxConcurrentRecordings) * @return the max number of concurrent replays. * @see Configuration#MAX_CONCURRENT_REPLAYS_PROP_NAME */ + @Config public int maxConcurrentReplays() { return maxConcurrentReplays; @@ -2870,6 +2944,7 @@ public Context maxConcurrentReplays(final int maxConcurrentReplays) * @return the max length of a file IO operation. * @see Configuration#FILE_IO_MAX_LENGTH_PROP_NAME */ + @Config public int fileIoMaxLength() { return fileIoMaxLength; @@ -2907,6 +2982,7 @@ public Context lowStorageSpaceThreshold(final long lowStorageSpaceThreshold) * @return threshold below which the archive will reject new recording requests in bytes. * @see Configuration#LOW_STORAGE_SPACE_THRESHOLD_PROP_NAME */ + @Config public long lowStorageSpaceThreshold() { return lowStorageSpaceThreshold; @@ -3079,6 +3155,7 @@ public Context maxCatalogEntries(final long maxCatalogEntries) * the {@link Catalog} in bytes rather than in number of entries. */ @Deprecated + @Config public long maxCatalogEntries() { return -1; @@ -3103,6 +3180,7 @@ public Context catalogCapacity(final long catalogCapacity) * @return capacity in bytes of the {@link Catalog}. * @see Configuration#CATALOG_CAPACITY_PROP_NAME */ + @Config public long catalogCapacity() { return catalogCapacity; @@ -3113,6 +3191,7 @@ public long catalogCapacity() * * @return the {@link AuthenticatorSupplier} to be used for the Archive. */ + @Config public AuthenticatorSupplier authenticatorSupplier() { return authenticatorSupplier; @@ -3135,6 +3214,7 @@ public Context authenticatorSupplier(final AuthenticatorSupplier authenticatorSu * * @return the {@link AuthorisationServiceSupplier} to be used for the Archive. */ + @Config public AuthorisationServiceSupplier authorisationServiceSupplier() { return authorisationServiceSupplier; diff --git a/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java b/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java index 896d958468..529eaa56da 100644 --- a/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java +++ b/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java @@ -20,6 +20,8 @@ import io.aeron.archive.codecs.RecordingSignal; import io.aeron.archive.codecs.RecordingSignalEventDecoder; import io.aeron.archive.codecs.SourceLocation; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; import io.aeron.exceptions.AeronException; import io.aeron.exceptions.ConcurrentConcludeException; import io.aeron.exceptions.ConfigurationException; @@ -2620,6 +2622,7 @@ private void ensureNotReentrant() /** * Common configuration properties for communicating with an Aeron archive. */ + @Config(existsInC = false) public static final class Configuration { /** @@ -2651,46 +2654,55 @@ public static final class Configuration /** * Timeout in nanoseconds when waiting on a message to be sent or received. */ + @Config public static final String MESSAGE_TIMEOUT_PROP_NAME = "aeron.archive.message.timeout"; /** * Timeout when waiting on a message to be sent or received. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000) public static final long MESSAGE_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** * Channel for sending control messages to an archive. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String CONTROL_CHANNEL_PROP_NAME = "aeron.archive.control.channel"; /** * Stream id within a channel for sending control messages to an archive. */ + @Config public static final String CONTROL_STREAM_ID_PROP_NAME = "aeron.archive.control.stream.id"; /** * Stream id within a channel for sending control messages to an archive. */ + @Config public static final int CONTROL_STREAM_ID_DEFAULT = 10; /** * Channel for sending control messages to a driver local archive. */ + @Config(hasContext = false) public static final String LOCAL_CONTROL_CHANNEL_PROP_NAME = "aeron.archive.local.control.channel"; /** * Channel for sending control messages to a driver local archive. Default to IPC. */ + @Config public static final String LOCAL_CONTROL_CHANNEL_DEFAULT = CommonContext.IPC_CHANNEL; /** * Stream id within a channel for sending control messages to a driver local archive. */ + @Config(hasContext = false) public static final String LOCAL_CONTROL_STREAM_ID_PROP_NAME = "aeron.archive.local.control.stream.id"; /** * Stream id within a channel for sending control messages to a driver local archive. */ + @Config public static final int LOCAL_CONTROL_STREAM_ID_DEFAULT = CONTROL_STREAM_ID_DEFAULT; /** @@ -2708,21 +2720,25 @@ public static final class Configuration * ephemeral port range. * */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String CONTROL_RESPONSE_CHANNEL_PROP_NAME = "aeron.archive.control.response.channel"; /** * Stream id within a channel for receiving control messages from an archive. */ + @Config public static final String CONTROL_RESPONSE_STREAM_ID_PROP_NAME = "aeron.archive.control.response.stream.id"; /** * Stream id within a channel for receiving control messages from an archive. */ + @Config public static final int CONTROL_RESPONSE_STREAM_ID_DEFAULT = 20; /** * Channel for receiving progress events of recordings from an archive. */ + @Config public static final String RECORDING_EVENTS_CHANNEL_PROP_NAME = "aeron.archive.recording.events.channel"; /** @@ -2730,58 +2746,69 @@ public static final class Configuration * For production, it is recommended that multicast or dynamic multi-destination-cast (MDC) is used to allow * for dynamic subscribers, an endpoint can be added to the subscription side for controlling port usage. */ + @Config public static final String RECORDING_EVENTS_CHANNEL_DEFAULT = "aeron:udp?control-mode=dynamic|control=localhost:8030"; /** * Stream id within a channel for receiving progress of recordings from an archive. */ + @Config public static final String RECORDING_EVENTS_STREAM_ID_PROP_NAME = "aeron.archive.recording.events.stream.id"; /** * Stream id within a channel for receiving progress of recordings from an archive. */ + @Config public static final int RECORDING_EVENTS_STREAM_ID_DEFAULT = 30; /** * Is channel enabled for recording progress events of recordings from an archive. */ + @Config public static final String RECORDING_EVENTS_ENABLED_PROP_NAME = "aeron.archive.recording.events.enabled"; /** * Channel enabled for recording progress events of recordings from an archive which defaults to false. */ + @Config public static final boolean RECORDING_EVENTS_ENABLED_DEFAULT = false; /** * Sparse term buffer indicator for control streams. */ + @Config public static final String CONTROL_TERM_BUFFER_SPARSE_PROP_NAME = "aeron.archive.control.term.buffer.sparse"; /** * Overrides {@link io.aeron.driver.Configuration#TERM_BUFFER_SPARSE_FILE_PROP_NAME} for if term buffer files * are sparse on the control channel. */ + @Config public static final boolean CONTROL_TERM_BUFFER_SPARSE_DEFAULT = true; /** * Term length for control streams. */ + @Config public static final String CONTROL_TERM_BUFFER_LENGTH_PROP_NAME = "aeron.archive.control.term.buffer.length"; /** * Low term length for control channel reflects expected low bandwidth usage. */ + @Config public static final int CONTROL_TERM_BUFFER_LENGTH_DEFAULT = 64 * 1024; /** * MTU length for control streams. */ + @Config public static final String CONTROL_MTU_LENGTH_PROP_NAME = "aeron.archive.control.mtu.length"; /** * MTU to reflect default for the control streams. */ + @Config(defaultType = DefaultType.INT, defaultValueString = "io.aeron.driver.Configuration.mtuLength()") public static final int CONTROL_MTU_LENGTH_DEFAULT = io.aeron.driver.Configuration.mtuLength(); /** @@ -3060,6 +3087,7 @@ public Context messageTimeoutNs(final long messageTimeoutNs) * @return the message timeout in nanoseconds to wait for sending or receiving a message. * @see Configuration#MESSAGE_TIMEOUT_PROP_NAME */ + @Config public long messageTimeoutNs() { return messageTimeoutNs; @@ -3070,6 +3098,7 @@ public long messageTimeoutNs() * * @return the channel URI on which the recording events publication will publish. */ + @Config public String recordingEventsChannel() { return recordingEventsChannel; @@ -3096,6 +3125,7 @@ public Context recordingEventsChannel(final String recordingEventsChannel) * * @return the stream id on which the recording events publication will publish. */ + @Config public int recordingEventsStreamId() { return recordingEventsStreamId; @@ -3132,6 +3162,7 @@ public Context controlRequestChannel(final String channel) * @return the channel parameter for the control request channel. * @see Configuration#CONTROL_CHANNEL_PROP_NAME */ + @Config(id = "CONTROL_CHANNEL") public String controlRequestChannel() { return controlRequestChannel; @@ -3156,6 +3187,7 @@ public Context controlRequestStreamId(final int streamId) * @return the stream id for the control request channel. * @see Configuration#CONTROL_STREAM_ID_PROP_NAME */ + @Config(id = "CONTROL_STREAM_ID") public int controlRequestStreamId() { return controlRequestStreamId; @@ -3180,6 +3212,7 @@ public Context controlResponseChannel(final String channel) * @return the channel parameter for the control response channel. * @see Configuration#CONTROL_RESPONSE_CHANNEL_PROP_NAME */ + @Config public String controlResponseChannel() { return controlResponseChannel; @@ -3204,6 +3237,7 @@ public Context controlResponseStreamId(final int streamId) * @return the stream id for the control response channel. * @see Configuration#CONTROL_RESPONSE_STREAM_ID_PROP_NAME */ + @Config public int controlResponseStreamId() { return controlResponseStreamId; @@ -3228,6 +3262,7 @@ public Context controlTermBufferSparse(final boolean controlTermBufferSparse) * @return {@code true} if the control stream should use sparse file term buffers. * @see Configuration#CONTROL_TERM_BUFFER_SPARSE_PROP_NAME */ + @Config public boolean controlTermBufferSparse() { return controlTermBufferSparse; @@ -3252,6 +3287,7 @@ public Context controlTermBufferLength(final int controlTermBufferLength) * @return the term buffer length for the control streams. * @see Configuration#CONTROL_TERM_BUFFER_LENGTH_PROP_NAME */ + @Config public int controlTermBufferLength() { return controlTermBufferLength; @@ -3276,6 +3312,7 @@ public Context controlMtuLength(final int controlMtuLength) * @return the MTU length for the control streams. * @see Configuration#CONTROL_MTU_LENGTH_PROP_NAME */ + @Config public int controlMtuLength() { return controlMtuLength; diff --git a/aeron-client/src/main/c/aeron_counters.h b/aeron-client/src/main/c/aeron_counters.h index 908836a6b1..059256fe55 100644 --- a/aeron-client/src/main/c/aeron_counters.h +++ b/aeron-client/src/main/c/aeron_counters.h @@ -94,6 +94,8 @@ #define AERON_COUNTER_ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID (110); +#define AERON_COUNTER_ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID (112); + // Cluster counters #define AERON_COUNTER_CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID (200) @@ -158,4 +160,10 @@ #define AERON_COUNTER_CLUSTER_STANDBY_SOURCE_MEMBER_ID_TYPE_ID (231) +#define AERON_COUNTER_CLUSTER_TOTAL_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID (235) + +#define AERON_COUNTER_CLUSTERED_SERVICE_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID (237) + +#define AERON_COUNTER_CLUSTER_ELECTION_COUNT_TYPE_ID (238) + #endif //AERON_C_COUNTERS_H diff --git a/aeron-client/src/main/java/io/aeron/Aeron.java b/aeron-client/src/main/java/io/aeron/Aeron.java index 912e7f6f9d..9d5c2db294 100644 --- a/aeron-client/src/main/java/io/aeron/Aeron.java +++ b/aeron-client/src/main/java/io/aeron/Aeron.java @@ -15,6 +15,8 @@ */ package io.aeron; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; import io.aeron.exceptions.AeronException; import io.aeron.exceptions.ConcurrentConcludeException; import io.aeron.exceptions.ConfigurationException; @@ -676,11 +678,13 @@ public static class Configuration /** * Duration to sleep when idle */ + @Config(expectedCDefaultFieldName = "AERON_CONTEXT_IDLE_SLEEP_DURATION_NS_DEFAULT") public static final String IDLE_SLEEP_DURATION_PROP_NAME = "aeron.client.idle.sleep.duration"; /** * Duration in nanoseconds for which the client conductor will sleep between work cycles when idle. */ + @Config(id = "IDLE_SLEEP_DURATION", defaultType = DefaultType.LONG, defaultLong = 16_000_000) static final long IDLE_SLEEP_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(IDLE_SLEEP_DEFAULT_MS); /** @@ -692,11 +696,13 @@ public static class Configuration * Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources * such as memory mapped files. */ + @Config(expectedCDefaultFieldName = "AERON_CONTEXT_RESOURCE_LINGER_DURATION_NS_DEFAULT") public static final String RESOURCE_LINGER_DURATION_PROP_NAME = "aeron.client.resource.linger.duration"; /** * Default duration a resource should linger before deletion. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 3_000_000_000L) public static final long RESOURCE_LINGER_DURATION_DEFAULT_NS = TimeUnit.SECONDS.toNanos(3); /** @@ -704,11 +710,13 @@ public static class Configuration * This value can be set to a few seconds if the application is likely to experience CPU starvation or * long GC pauses. */ + @Config(existsInC = false) public static final String CLOSE_LINGER_DURATION_PROP_NAME = "aeron.client.close.linger.duration"; /** * Default duration to linger on close so that publishers subscribers have time to notice closed resources. */ + @Config public static final long CLOSE_LINGER_DURATION_DEFAULT_NS = 0; /** @@ -717,11 +725,16 @@ public static class Configuration * Pre-touching files can result in it taking longer for resources to become available in * return for avoiding later pauses due to page faults. */ + @Config( + expectedCEnvVarFieldName = "AERON_CLIENT_PRE_TOUCH_MAPPED_MEMORY_ENV_VAR", + expectedCEnvVar = "AERON_CLIENT_PRE_TOUCH_MAPPED_MEMORY", + expectedCDefaultFieldName = "AERON_CONTEXT_PRE_TOUCH_MAPPED_MEMORY_DEFAULT") public static final String PRE_TOUCH_MAPPED_MEMORY_PROP_NAME = "aeron.pre.touch.mapped.memory"; /** * Default for if a memory-mapped filed should be pre-touched to fault it into a process. */ + @Config public static final boolean PRE_TOUCH_MAPPED_MEMORY_DEFAULT = false; /** @@ -729,6 +742,7 @@ public static class Configuration * * @since 1.44.0 */ + @Config(defaultType = DefaultType.STRING, defaultString = "", skipCDefaultValidation = true) public static final String CLIENT_NAME_PROP_NAME = "aeron.client.name"; /** @@ -766,6 +780,7 @@ public static class Configuration * @return duration in nanoseconds to wait when idle in client conductor. * @see #IDLE_SLEEP_DURATION_PROP_NAME */ + @Config public static long idleSleepDurationNs() { return getDurationInNanos(IDLE_SLEEP_DURATION_PROP_NAME, IDLE_SLEEP_DEFAULT_NS); @@ -778,6 +793,7 @@ public static long idleSleepDurationNs() * @return duration in nanoseconds to wait before deleting an expired resource. * @see #RESOURCE_LINGER_DURATION_PROP_NAME */ + @Config public static long resourceLingerDurationNs() { return getDurationInNanos(RESOURCE_LINGER_DURATION_PROP_NAME, RESOURCE_LINGER_DURATION_DEFAULT_NS); @@ -790,6 +806,7 @@ public static long resourceLingerDurationNs() * @return duration in nanoseconds to wait before deleting an expired resource. * @see #RESOURCE_LINGER_DURATION_PROP_NAME */ + @Config public static long closeLingerDurationNs() { return getDurationInNanos(CLOSE_LINGER_DURATION_PROP_NAME, CLOSE_LINGER_DURATION_DEFAULT_NS); @@ -801,6 +818,7 @@ public static long closeLingerDurationNs() * @return true if memory mappings should be pre-touched, otherwise false. * @see #PRE_TOUCH_MAPPED_MEMORY_PROP_NAME */ + @Config public static boolean preTouchMappedMemory() { final String value = System.getProperty(PRE_TOUCH_MAPPED_MEMORY_PROP_NAME); @@ -818,6 +836,7 @@ public static boolean preTouchMappedMemory() * @return specified client name or empty string if not set. * @see #CLIENT_NAME_PROP_NAME */ + @Config public static String clientName() { return getProperty(CLIENT_NAME_PROP_NAME, ""); diff --git a/aeron-client/src/main/java/io/aeron/AeronCounters.java b/aeron-client/src/main/java/io/aeron/AeronCounters.java index 424c36e1d1..39ae895dea 100644 --- a/aeron-client/src/main/java/io/aeron/AeronCounters.java +++ b/aeron-client/src/main/java/io/aeron/AeronCounters.java @@ -15,6 +15,7 @@ */ package io.aeron; +import io.aeron.counter.AeronCounter; import io.aeron.exceptions.ConfigurationException; import io.aeron.status.ChannelEndpointStatus; import org.agrona.MutableDirectBuffer; @@ -41,28 +42,33 @@ public final class AeronCounters /** * System-wide counters for monitoring. These are separate from counters used for position tracking on streams. */ + @AeronCounter public static final int DRIVER_SYSTEM_COUNTER_TYPE_ID = 0; /** * The limit as a position in bytes applied to publishers on a session-channel-stream tuple. Publishers will * experience back pressure when this position is passed as a means of flow control. */ + @AeronCounter public static final int DRIVER_PUBLISHER_LIMIT_TYPE_ID = 1; /** * The position the Sender has reached for sending data to the media on a session-channel-stream tuple. */ + @AeronCounter public static final int DRIVER_SENDER_POSITION_TYPE_ID = 2; /** * The highest position the Receiver has observed on a session-channel-stream tuple while rebuilding the stream. * It is possible the stream is not complete to this point if the stream has experienced loss. */ + @AeronCounter public static final int DRIVER_RECEIVER_HWM_TYPE_ID = 3; /** * The position an individual Subscriber has reached on a session-channel-stream tuple. It is possible to have * multiple */ + @AeronCounter(expectedCName = "SUBSCRIPTION_POSITION") public static final int DRIVER_SUBSCRIBER_POSITION_TYPE_ID = 4; /** @@ -70,31 +76,37 @@ public final class AeronCounters * stream. * The stream is complete up to this point. */ + @AeronCounter(expectedCName = "RECEIVER_POSITION") public static final int DRIVER_RECEIVER_POS_TYPE_ID = 5; /** * The status of a send-channel-endpoint represented as a counter value. */ + @AeronCounter public static final int DRIVER_SEND_CHANNEL_STATUS_TYPE_ID = 6; /** * The status of a receive-channel-endpoint represented as a counter value. */ + @AeronCounter public static final int DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID = 7; /** * The position the Sender can immediately send up-to on a session-channel-stream tuple. */ + @AeronCounter public static final int DRIVER_SENDER_LIMIT_TYPE_ID = 9; /** * A counter per Image indicating presence of the congestion control. */ + @AeronCounter public static final int DRIVER_PER_IMAGE_TYPE_ID = 10; /** * A counter for tracking the last heartbeat of an entity with a given registration id. */ + @AeronCounter(expectedCName = "CLIENT_HEARTBEAT_TIMESTAMP") public static final int DRIVER_HEARTBEAT_TYPE_ID = 11; /** @@ -103,21 +115,25 @@ public final class AeronCounters * Note: This is a not a real-time value like the other and is updated one per second for monitoring * purposes. */ + @AeronCounter(expectedCName = "PUBLISHER_POSITION") public static final int DRIVER_PUBLISHER_POS_TYPE_ID = 12; /** * Count of back-pressure events (BPE)s a sender has experienced on a stream. */ + @AeronCounter public static final int DRIVER_SENDER_BPE_TYPE_ID = 13; /** * Count of media driver neighbors for name resolution. */ + @AeronCounter(existsInC = false) public static final int NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID = 15; /** * Count of entries in the name resolver cache. */ + @AeronCounter(existsInC = false) public static final int NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID = 16; /** @@ -126,89 +142,105 @@ public final class AeronCounters * When the value is {@link ChannelEndpointStatus#ACTIVE} then the key value and label will be updated with the * socket address and port which is bound. */ + @AeronCounter(expectedCName = "LOCAL_SOCKADDR") public static final int DRIVER_LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID = 14; /** * Count of number of active receivers for flow control strategy. */ + @AeronCounter(expectedCName = "FC_NUM_RECEIVERS") public static final int FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID = 17; /** * Count of number of destinations for multi-destination cast channels. */ + @AeronCounter(expectedCName = "CHANNEL_NUM_DESTINATIONS") public static final int MDC_DESTINATIONS_COUNTER_TYPE_ID = 18; // Archive counters /** * The position a recording has reached when being archived. */ + @AeronCounter public static final int ARCHIVE_RECORDING_POSITION_TYPE_ID = 100; /** * The type id of the {@link Counter} used for keeping track of the number of errors that have occurred. */ + @AeronCounter public static final int ARCHIVE_ERROR_COUNT_TYPE_ID = 101; /** * The type id of the {@link Counter} used for keeping track of the count of concurrent control sessions. */ + @AeronCounter public static final int ARCHIVE_CONTROL_SESSIONS_TYPE_ID = 102; /** * The type id of the {@link Counter} used for keeping track of the max duty cycle time of an archive agent. */ + @AeronCounter public static final int ARCHIVE_MAX_CYCLE_TIME_TYPE_ID = 103; /** * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of * an archive agent. */ + @AeronCounter public static final int ARCHIVE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 104; /** * The type id of the {@link Counter} used for keeping track of the max time it took recoder to write a block of * data to the storage. */ + @AeronCounter public static final int ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID = 105; /** * The type id of the {@link Counter} used for keeping track of the total number of bytes written by the recorder * to the storage. */ + @AeronCounter public static final int ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID = 106; /** * The type id of the {@link Counter} used for keeping track of the total time the recorder spent writing data to * the storage. */ + @AeronCounter public static final int ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID = 107; /** * The type id of the {@link Counter} used for keeping track of the max time it took replayer to read a block from * the storage. */ + @AeronCounter public static final int ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID = 108; /** * The type id of the {@link Counter} used for keeping track of the total number of bytes read by the replayer from * the storage. */ + @AeronCounter public static final int ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID = 109; /** * The type id of the {@link Counter} used for keeping track of the total time the replayer spent reading data from * the storage. */ + @AeronCounter public static final int ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID = 110; /** * The type id of the {@link Counter} used for tracking the count of active recording sessions. */ + @AeronCounter(existsInC = false) public static final int ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID = 111; /** * The type id of the {@link Counter} used for tracking the count of active replay sessions. */ + @AeronCounter public static final int ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID = 112; // Cluster counters @@ -216,204 +248,243 @@ public final class AeronCounters /** * Counter type id for the consensus module state. */ + @AeronCounter public static final int CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID = 200; /** * Counter type id for the cluster node role. */ + @AeronCounter public static final int CLUSTER_NODE_ROLE_TYPE_ID = 201; /** * Counter type id for the control toggle. */ + @AeronCounter public static final int CLUSTER_CONTROL_TOGGLE_TYPE_ID = 202; /** * Counter type id of the commit position. */ + @AeronCounter public static final int CLUSTER_COMMIT_POSITION_TYPE_ID = 203; /** * Counter representing the Recovery State for the cluster. */ + @AeronCounter public static final int CLUSTER_RECOVERY_STATE_TYPE_ID = 204; /** * Counter type id for count of snapshots taken. */ + @AeronCounter public static final int CLUSTER_SNAPSHOT_COUNTER_TYPE_ID = 205; /** * Counter type for count of standby snapshots received. */ + @AeronCounter(existsInC = false) public static final int CLUSTER_STANDBY_SNAPSHOT_COUNTER_TYPE_ID = 232; /** * Type id for election state counter. */ + @AeronCounter public static final int CLUSTER_ELECTION_STATE_TYPE_ID = 207; /** * The type id of the {@link Counter} used for the backup state. */ + @AeronCounter public static final int CLUSTER_BACKUP_STATE_TYPE_ID = 208; /** * The type id of the {@link Counter} used for the live log position counter. */ + @AeronCounter public static final int CLUSTER_BACKUP_LIVE_LOG_POSITION_TYPE_ID = 209; /** * The type id of the {@link Counter} used for the next query deadline counter. */ + @AeronCounter public static final int CLUSTER_BACKUP_QUERY_DEADLINE_TYPE_ID = 210; /** * The type id of the {@link Counter} used for keeping track of the number of errors that have occurred. */ + @AeronCounter public static final int CLUSTER_BACKUP_ERROR_COUNT_TYPE_ID = 211; /** * Counter type id for the consensus module error count. */ + @AeronCounter public static final int CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID = 212; /** * Counter type id for the number of cluster clients which have been timed out. */ + @AeronCounter public static final int CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID = 213; /** * Counter type id for the number of invalid requests which the cluster has received. */ + @AeronCounter public static final int CLUSTER_INVALID_REQUEST_COUNT_TYPE_ID = 214; /** * Counter type id for the clustered service error count. */ + @AeronCounter public static final int CLUSTER_CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID = 215; /** * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the consensus module. */ + @AeronCounter public static final int CLUSTER_MAX_CYCLE_TIME_TYPE_ID = 216; /** * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of * the consensus module. */ + @AeronCounter public static final int CLUSTER_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 217; /** * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the service container. */ + @AeronCounter public static final int CLUSTER_CLUSTERED_SERVICE_MAX_CYCLE_TIME_TYPE_ID = 218; /** * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of * the service container. */ + @AeronCounter public static final int CLUSTER_CLUSTERED_SERVICE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 219; /** * The type id of the {@link Counter} used for the cluster standby state. */ + @AeronCounter public static final int CLUSTER_STANDBY_STATE_TYPE_ID = 220; /** * Counter type id for the clustered service error count. */ + @AeronCounter public static final int CLUSTER_STANDBY_ERROR_COUNT_TYPE_ID = 221; /** * Counter type for responses to heartbeat request from the cluster. */ + @AeronCounter public static final int CLUSTER_STANDBY_HEARTBEAT_RESPONSE_COUNT_TYPE_ID = 222; /** * Standby control toggle type id */ + @AeronCounter public static final int CLUSTER_STANDBY_CONTROL_TOGGLE_TYPE_ID = 223; /** * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the cluster standby. */ + @AeronCounter public static final int CLUSTER_STANDBY_MAX_CYCLE_TIME_TYPE_ID = 227; /** * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of * the cluster standby. */ + @AeronCounter public static final int CLUSTER_STANDBY_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 228; /** * The type id of the {@link Counter} to make visible the memberId that the cluster standby is currently using to * as a source for the cluster log. */ + @AeronCounter public static final int CLUSTER_STANDBY_SOURCE_MEMBER_ID_TYPE_ID = 231; /** * Counter type id for the transition module error count. */ + @AeronCounter(expectedCName = "CLUSTER_TRANSITION_MODULE_ERROR_COUNT") public static final int TRANSITION_MODULE_ERROR_COUNT_TYPE_ID = 226; /** * The type if of the {@link Counter} used for transition module state */ + @AeronCounter(expectedCName = "CLUSTER_TRANSITION_MODULE_STATE") public static final int TRANSITION_MODULE_STATE_TYPE_ID = 224; /** * Transition module control toggle type id */ + @AeronCounter(expectedCName = "CLUSTER_TRANSITION_MODULE_CONTROL_TOGGLE") public static final int TRANSITION_MODULE_CONTROL_TOGGLE_TYPE_ID = 225; /** * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the transition module. */ + @AeronCounter(expectedCName = "CLUSTER_TRANSITION_MODULE_MAX_CYCLE_TIME") public static final int TRANSITION_MODULE_MAX_CYCLE_TIME_TYPE_ID = 229; /** * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of * the transition module. */ + @AeronCounter(expectedCName = "CLUSTER_TRANSITION_MODULE_CYCLE_TIME_THRESHOLD_EXCEEDED") public static final int TRANSITION_MODULE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 230; /** * The type of the {@link Counter} used for handling node specific operations. */ + @AeronCounter(existsInC = false) public static final int NODE_CONTROL_TOGGLE_TYPE_ID = 233; /** * The type id of the {@link Counter} used for keeping track of the maximum total snapshot duration. */ + @AeronCounter(existsInC = false) public static final int CLUSTER_TOTAL_MAX_SNAPSHOT_DURATION_TYPE_ID = 234; /** * The type id of the {@link Counter} used for keeping track of the count total snapshot duration * has exceeded the threshold. */ + @AeronCounter public static final int CLUSTER_TOTAL_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID = 235; /** * The type id of the {@link Counter} used for keeping track of the maximum snapshot duration * for a given clustered service. */ + @AeronCounter(existsInC = false) public static final int CLUSTERED_SERVICE_MAX_SNAPSHOT_DURATION_TYPE_ID = 236; /** * The type id of the {@link Counter} used for keeping track of the count snapshot duration * has exceeded the threshold for a given clustered service. */ + @AeronCounter public static final int CLUSTERED_SERVICE_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID = 237; /** * The type id of the {@link Counter} used for keeping track of the number of elections that have occurred. */ + @AeronCounter public static final int CLUSTER_ELECTION_COUNT_TYPE_ID = 238; /** * The type id of the {@link Counter} used for keeping track of the Cluster leadership term id. */ + @AeronCounter(existsInC = false) public static final int CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID = 239; private AeronCounters() diff --git a/aeron-client/src/main/java/io/aeron/CommonContext.java b/aeron-client/src/main/java/io/aeron/CommonContext.java index 78dc5d6502..73f4b8824a 100644 --- a/aeron-client/src/main/java/io/aeron/CommonContext.java +++ b/aeron-client/src/main/java/io/aeron/CommonContext.java @@ -15,6 +15,8 @@ */ package io.aeron; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; import io.aeron.exceptions.AeronException; import io.aeron.exceptions.ConcurrentConcludeException; import io.aeron.exceptions.DriverTimeoutException; @@ -98,17 +100,23 @@ public static InferableBoolean parse(final String value) /** * Property name for driver timeout after which the driver is considered inactive. */ + @Config public static final String DRIVER_TIMEOUT_PROP_NAME = "aeron.driver.timeout"; /** * Property name for the timeout to use in debug mode. By default, this is not set and the configured * timeouts will be used. Setting this value adjusts timeouts to make debugging easier. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 0, existsInC = false) public static final String DEBUG_TIMEOUT_PROP_NAME = "aeron.debug.timeout"; /** * Default timeout in which the driver is expected to respond or heartbeat. */ + @Config( + id = "DRIVER_TIMEOUT", + timeUnit = TimeUnit.MILLISECONDS, + expectedCDefaultFieldName = "AERON_CONTEXT_DRIVER_TIMEOUT_MS_DEFAULT") public static final long DEFAULT_DRIVER_TIMEOUT_MS = 10_000; /** @@ -124,11 +132,13 @@ public static InferableBoolean parse(final String value) /** * The top level Aeron directory used for communication between a Media Driver and client. */ + @Config(skipCDefaultValidation = true) public static final String AERON_DIR_PROP_NAME = "aeron.dir"; /** * The value of the top level Aeron directory unless overridden by {@link #aeronDirectoryName(String)} */ + @Config(id = "AERON_DIR", defaultType = DefaultType.STRING, defaultString = "OS specific") public static final String AERON_DIR_PROP_DEFAULT; /** @@ -136,12 +146,14 @@ public static InferableBoolean parse(final String value) * * @since 1.44.0 */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false, existsInC = false) public static final String ENABLE_EXPERIMENTAL_FEATURES_PROP_NAME = "aeron.enable.experimental.features"; /** * Property name for a fallback {@link PrintStream} based logger when it is not possible to use the error logging * callback. Supported values are stdout, stderr, no_op (stderr is the default). */ + @Config(defaultType = DefaultType.STRING, defaultString = "stderr", existsInC = false) public static final String FALLBACK_LOGGER_PROP_NAME = "aeron.fallback.logger"; /** @@ -412,6 +424,7 @@ public static InferableBoolean parse(final String value) * * @return the configured PrintStream. */ + @Config public static PrintStream fallbackLogger() { final String fallbackLoggerName = getProperty(FALLBACK_LOGGER_PROP_NAME, "stderr"); @@ -499,6 +512,7 @@ public CommonContext clone() * * @return the default directory name to be used if {@link #aeronDirectoryName(String)} is not set. */ + @Config(id = "AERON_DIR") public static String getAeronDirectoryName() { return getProperty(AERON_DIR_PROP_NAME, AERON_DIR_PROP_DEFAULT); @@ -695,6 +709,7 @@ public CommonContext driverTimeoutMs(final long driverTimeoutMs) * * @return driver timeout in milliseconds. */ + @Config(id = "DRIVER_TIMEOUT") public long driverTimeoutMs() { return checkDebugTimeout(driverTimeoutMs, TimeUnit.MILLISECONDS); @@ -707,6 +722,7 @@ public long driverTimeoutMs() * @see #enableExperimentalFeatures(boolean) * @since 1.44.0 */ + @Config public boolean enableExperimentalFeatures() { return enableExperimentalFeatures; diff --git a/aeron-cluster/src/main/java/io/aeron/cluster/ClusterBackup.java b/aeron-cluster/src/main/java/io/aeron/cluster/ClusterBackup.java index 81b99bce1d..ea435e9c07 100644 --- a/aeron-cluster/src/main/java/io/aeron/cluster/ClusterBackup.java +++ b/aeron-cluster/src/main/java/io/aeron/cluster/ClusterBackup.java @@ -24,6 +24,8 @@ import io.aeron.cluster.service.ClusterCounters; import io.aeron.cluster.service.ClusterMarkFile; import io.aeron.cluster.service.ClusteredServiceContainer; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; import io.aeron.exceptions.ConcurrentConcludeException; import io.aeron.exceptions.ConfigurationException; import io.aeron.security.CredentialsSupplier; @@ -302,73 +304,87 @@ public void close() /** * Configuration options for {@link ClusterBackup} with defaults and constants for system properties lookup. */ + @Config(existsInC = false) public static class Configuration { /** * Channel template used for catchup and replication of log and snapshots. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String CLUSTER_BACKUP_CATCHUP_ENDPOINT_PROP_NAME = "aeron.cluster.backup.catchup.endpoint"; /** * Channel template used for catchup and replication of log and snapshots. */ + @Config public static final String CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME = "aeron.cluster.backup.catchup.channel"; /** * Default channel template used for catchup and replication of log and snapshots. */ + @Config public static final String CLUSTER_BACKUP_CATCHUP_CHANNEL_DEFAULT = "aeron:udp?alias=backup|cc=cubic|so-sndbuf=512k|so-rcvbuf=512k|rcv-wnd=512k"; /** * Interval at which a cluster backup will send backup queries. */ + @Config public static final String CLUSTER_BACKUP_INTERVAL_PROP_NAME = "aeron.cluster.backup.interval"; /** * Default interval at which a cluster backup will send backup queries. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 60L * 60 * 1000 * 1000 * 1000) public static final long CLUSTER_BACKUP_INTERVAL_DEFAULT_NS = TimeUnit.HOURS.toNanos(1); /** * Timeout within which a cluster backup will expect a response from a backup query. */ + @Config public static final String CLUSTER_BACKUP_RESPONSE_TIMEOUT_PROP_NAME = "aeron.cluster.backup.response.timeout"; /** * Default timeout within which a cluster backup will expect a response from a backup query. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 5L * 1000 * 1000 * 1000) public static final long CLUSTER_BACKUP_RESPONSE_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** * Timeout within which a cluster backup will expect progress. */ + @Config public static final String CLUSTER_BACKUP_PROGRESS_TIMEOUT_PROP_NAME = "aeron.cluster.backup.progress.timeout"; /** * Interval at which the cluster backup is re-initialised after an exception has been thrown. */ + @Config public static final String CLUSTER_BACKUP_COOL_DOWN_INTERVAL_PROP_NAME = "aeron.cluster.backup.cool.down.interval"; /** * Default interval at which the cluster back is re-initialised after an exception has been thrown. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 30L * 1000 * 1000 * 1000) public static final long CLUSTER_BACKUP_COOL_DOWN_INTERVAL_DEFAULT_NS = TimeUnit.SECONDS.toNanos(30); /** * Default timeout within which a cluster backup will expect progress. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000) public static final long CLUSTER_BACKUP_PROGRESS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** * The source type used for the cluster backup. Should match on of the {@link SourceType} enum values. */ + @Config public static final String CLUSTER_BACKUP_SOURCE_TYPE_PROP_NAME = "aeron.cluster.backup.source.type"; /** * Default source type to receive log traffic from. */ + @Config(defaultType = DefaultType.STRING, defaultString = "ANY") public static final String CLUSTER_BACKUP_SOURCE_TYPE_DEFAULT = SourceType.ANY.name(); /** @@ -515,11 +531,13 @@ public enum ReplayStart /** * Default value for the initial cluster replay start. */ + @Config(defaultType = DefaultType.STRING, defaultString = "BEGINNING") public static final ReplayStart CLUSTER_INITIAL_REPLAY_START_DEFAULT = ReplayStart.BEGINNING; /** * Property name for setting the cluster replay start. */ + @Config public static final String CLUSTER_INITIAL_REPLAY_START_PROP_NAME = "cluster.backup.initial.replay.start"; /** @@ -1326,6 +1344,7 @@ public Context logStreamId(final int streamId) * @return the stream id for the cluster log channel. * @see ConsensusModule.Configuration#LOG_STREAM_ID_PROP_NAME */ + @Config public int logStreamId() { return logStreamId; @@ -1350,6 +1369,7 @@ public Context catchupEndpoint(final String catchupEndpoint) * @return catchup endpoint to use for the log retrieval. * @see Configuration#catchupEndpoint() */ + @Config(id = "CLUSTER_BACKUP_CATCHUP_ENDPOINT") public String catchupEndpoint() { return catchupEndpoint; @@ -1374,6 +1394,7 @@ public Context catchupChannel(final String catchupChannel) * @return catchup endpoint to use for the log and snapshot retrieval. * @see Configuration#CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME */ + @Config(id = "CLUSTER_BACKUP_CATCHUP_CHANNEL") public String catchupChannel() { return catchupChannel; @@ -1400,6 +1421,7 @@ public Context clusterBackupIntervalNs(final long clusterBackupIntervalNs) * @see Configuration#CLUSTER_BACKUP_INTERVAL_PROP_NAME * @see Configuration#CLUSTER_BACKUP_INTERVAL_DEFAULT_NS */ + @Config public long clusterBackupIntervalNs() { return clusterBackupIntervalNs; @@ -1426,6 +1448,7 @@ public Context clusterBackupResponseTimeoutNs(final long clusterBackupResponseTi * @see Configuration#CLUSTER_BACKUP_RESPONSE_TIMEOUT_PROP_NAME * @see Configuration#CLUSTER_BACKUP_RESPONSE_TIMEOUT_DEFAULT_NS */ + @Config public long clusterBackupResponseTimeoutNs() { return clusterBackupResponseTimeoutNs; @@ -1452,6 +1475,7 @@ public Context clusterBackupProgressTimeoutNs(final long clusterBackupProgressTi * @see Configuration#CLUSTER_BACKUP_PROGRESS_TIMEOUT_PROP_NAME * @see Configuration#CLUSTER_BACKUP_PROGRESS_TIMEOUT_DEFAULT_NS */ + @Config public long clusterBackupProgressTimeoutNs() { return clusterBackupProgressTimeoutNs; @@ -1478,6 +1502,7 @@ public Context clusterBackupCoolDownIntervalNs(final long clusterBackupCoolDownI * @see Configuration#CLUSTER_BACKUP_COOL_DOWN_INTERVAL_PROP_NAME * @see Configuration#CLUSTER_BACKUP_COOL_DOWN_INTERVAL_DEFAULT_NS */ + @Config public long clusterBackupCoolDownIntervalNs() { return clusterBackupCoolDownIntervalNs; @@ -1776,6 +1801,7 @@ public Context sourceType(final SourceType sourceType) * @return source type for this backup instance. * @throws IllegalArgumentException if the configured source type is not one of {@link SourceType} */ + @Config(id = "CLUSTER_BACKUP_SOURCE_TYPE") public SourceType sourceType() { return SourceType.valueOf(sourceType); @@ -1800,6 +1826,7 @@ public Context replicationProgressTimeoutNs(final long timeoutNs) * @return timeout in nanoseconds. * @see ConsensusModule.Configuration#replicationProgressTimeoutNs() */ + @Config(id = "CLUSTER_REPLICATION_PROGRESS_TIMEOUT") public long replicationProgressTimeoutNs() { return replicationProgressTimeoutNs; @@ -1825,6 +1852,7 @@ public Context replicationProgressIntervalNs(final long intervalNs) * @return timeout in nanoseconds. * @see ConsensusModule.Configuration#replicationProgressIntervalNs() */ + @Config(id = "CLUSTER_REPLICATION_PROGRESS_INTERVAL") public long replicationProgressIntervalNs() { return replicationProgressIntervalNs; @@ -1853,6 +1881,7 @@ public Context initialReplayStart(final Configuration.ReplayStart replayStart) * @see Configuration.ReplayStart * @see Configuration#CLUSTER_INITIAL_REPLAY_START_DEFAULT */ + @Config(id = "CLUSTER_INITIAL_REPLAY_START") public Configuration.ReplayStart initialReplayStart() { return this.initialReplayStart; diff --git a/aeron-cluster/src/main/java/io/aeron/cluster/ClusterTool.java b/aeron-cluster/src/main/java/io/aeron/cluster/ClusterTool.java index 99adcbeecf..d40f2b127f 100644 --- a/aeron-cluster/src/main/java/io/aeron/cluster/ClusterTool.java +++ b/aeron-cluster/src/main/java/io/aeron/cluster/ClusterTool.java @@ -24,6 +24,8 @@ import io.aeron.cluster.service.ClusterMarkFile; import io.aeron.cluster.service.ClusterNodeControlProperties; import io.aeron.cluster.service.ConsensusModuleProxy; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; import org.agrona.BufferUtil; import org.agrona.DirectBuffer; import org.agrona.IoUtil; @@ -89,26 +91,31 @@ * is-leader: returns zero if the cluster node is leader, non-zero if not * */ +@Config(existsInC = false) public class ClusterTool { /** * Timeout in nanoseconds for the tool to wait while trying to perform an operation. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 0, hasContext = false) public static final String AERON_CLUSTER_TOOL_TIMEOUT_PROP_NAME = "aeron.cluster.tool.timeout"; /** * Delay in nanoseconds to be applied to an operation such as when the new cluster backup query will occur. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 0, hasContext = false) public static final String AERON_CLUSTER_TOOL_DELAY_PROP_NAME = "aeron.cluster.tool.delay"; /** * Property name for setting the channel used for archive replays. */ + @Config(hasContext = false) public static final String AERON_CLUSTER_TOOL_REPLAY_CHANNEL_PROP_NAME = "aeron.cluster.tool.replay.channel"; /** * Default channel used for archive replays. */ + @Config public static final String AERON_CLUSTER_TOOL_REPLAY_CHANNEL_DEFAULT = "aeron:ipc"; /** @@ -120,11 +127,13 @@ public class ClusterTool /** * Property name for setting the stream id used for archive replays. */ + @Config(hasContext = false) public static final String AERON_CLUSTER_TOOL_REPLAY_STREAM_ID_PROP_NAME = "aeron.cluster.tool.replay.stream.id"; /** * Default stream id used for archive replays. */ + @Config public static final int AERON_CLUSTER_TOOL_REPLAY_STREAM_ID_DEFAULT = 103; /** diff --git a/aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java b/aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java index 5866d1a834..0d949c52b8 100644 --- a/aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java +++ b/aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java @@ -28,6 +28,8 @@ import io.aeron.cluster.codecs.StandbySnapshotDecoder; import io.aeron.cluster.codecs.mark.ClusterComponentType; import io.aeron.cluster.service.*; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; import io.aeron.driver.DutyCycleTracker; import io.aeron.driver.NameResolver; import io.aeron.driver.status.DutyCycleStallTracker; @@ -326,6 +328,7 @@ public void close() /** * Configuration options for cluster. */ + @Config(existsInC = false) public static final class Configuration { /** @@ -362,21 +365,25 @@ public static final class Configuration /** * Property name for the limit for fragments to be consumed on each poll of ingress. */ + @Config public static final String CLUSTER_INGRESS_FRAGMENT_LIMIT_PROP_NAME = "aeron.cluster.ingress.fragment.limit"; /** * Default for the limit for fragments to be consumed on each poll of ingress. */ + @Config public static final int CLUSTER_INGRESS_FRAGMENT_LIMIT_DEFAULT = 50; /** * Property name for whether IPC ingress is allowed or not. */ + @Config public static final String CLUSTER_INGRESS_IPC_ALLOWED_PROP_NAME = "aeron.cluster.ingress.ipc.allowed"; /** * Default for whether IPC ingress is allowed or not. */ + @Config public static final String CLUSTER_INGRESS_IPC_ALLOWED_DEFAULT = "false"; /** @@ -387,23 +394,27 @@ public static final class Configuration /** * Property name for the identity of the cluster member. */ + @Config public static final String CLUSTER_MEMBER_ID_PROP_NAME = "aeron.cluster.member.id"; /** * Default property for the cluster member identity. */ + @Config public static final int CLUSTER_MEMBER_ID_DEFAULT = 0; /** * Property name for the identity of the appointed leader. This is when automated leader elections are * not employed. */ + @Config public static final String APPOINTED_LEADER_ID_PROP_NAME = "aeron.cluster.appointed.leader.id"; /** * Default property for the appointed cluster leader id. A value of {@link Aeron#NULL_VALUE} means no leader * has been appointed and thus an automated leader election should occur. */ + @Config public static final int APPOINTED_LEADER_ID_DEFAULT = Aeron.NULL_VALUE; /** @@ -418,38 +429,45 @@ public static final class Configuration * {@link io.aeron.cluster.client.AeronCluster.Configuration#INGRESS_CHANNEL_PROP_NAME} if the endpoint * is not provided when unicast. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String CLUSTER_MEMBERS_PROP_NAME = "aeron.cluster.members"; /** * Property name for the comma separated list of cluster consensus endpoints used for dynamic join, cluster * backup and cluster standby nodes. */ + @Config public static final String CLUSTER_CONSENSUS_ENDPOINTS_PROP_NAME = "aeron.cluster.consensus.endpoints"; /** * Default property for the list of cluster consensus endpoints. */ + @Config public static final String CLUSTER_CONSENSUS_ENDPOINTS_DEFAULT = ""; /** * Property name for whether cluster member information in snapshots should be ignored on load or not. */ + @Config public static final String CLUSTER_MEMBERS_IGNORE_SNAPSHOT_PROP_NAME = "aeron.cluster.members.ignore.snapshot"; /** * Default property for whether cluster member information in snapshots should be ignored or not. */ + @Config public static final String CLUSTER_MEMBERS_IGNORE_SNAPSHOT_DEFAULT = "false"; /** * Channel for the clustered log. */ + @Config public static final String LOG_CHANNEL_PROP_NAME = "aeron.cluster.log.channel"; /** * Channel for the clustered log. This channel can exist for a potentially long time given cluster operation * so attention should be given to configuration such as term-length and mtu. */ + @Config public static final String LOG_CHANNEL_DEFAULT = "aeron:udp?term-length=64m"; /** @@ -461,21 +479,25 @@ public static final class Configuration * * @see #CLUSTER_MEMBERS_PROP_NAME */ + @Config public static final String MEMBER_ENDPOINTS_PROP_NAME = "aeron.cluster.member.endpoints"; /** * Default property for member endpoints. */ + @Config public static final String MEMBER_ENDPOINTS_DEFAULT = ""; /** * Stream id within a channel for the clustered log. */ + @Config public static final String LOG_STREAM_ID_PROP_NAME = "aeron.cluster.log.stream.id"; /** * Stream id within a channel for the clustered log. */ + @Config public static final int LOG_STREAM_ID_DEFAULT = 100; /** @@ -507,49 +529,58 @@ public static final class Configuration * Channel to be used communicating cluster consensus to each other. This can be used for default * configuration with the endpoints replaced with those provided by {@link #CLUSTER_MEMBERS_PROP_NAME}. */ + @Config public static final String CONSENSUS_CHANNEL_PROP_NAME = "aeron.cluster.consensus.channel"; /** * Channel to be used for communicating cluster consensus to each other. This can be used for default * configuration with the endpoints replaced with those provided by {@link #CLUSTER_MEMBERS_PROP_NAME}. */ + @Config public static final String CONSENSUS_CHANNEL_DEFAULT = "aeron:udp?term-length=64k"; /** * Stream id within a channel for communicating consensus messages. */ + @Config public static final String CONSENSUS_STREAM_ID_PROP_NAME = "aeron.cluster.consensus.stream.id"; /** * Stream id for the archived snapshots within a channel. */ + @Config public static final int CONSENSUS_STREAM_ID_DEFAULT = 108; /** * Channel to be used for replicating logs and snapshots from other archives to the local one. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String REPLICATION_CHANNEL_PROP_NAME = "aeron.cluster.replication.channel"; /** * Channel template used for replaying logs to a follower using the {@link ClusterMember#catchupEndpoint()}. */ + @Config public static final String FOLLOWER_CATCHUP_CHANNEL_PROP_NAME = "aeron.cluster.follower.catchup.channel"; /** * Default channel template used for replaying logs to a follower using the * {@link ClusterMember#catchupEndpoint()}. */ + @Config public static final String FOLLOWER_CATCHUP_CHANNEL_DEFAULT = UDP_CHANNEL; /** * Channel used to build the control request channel for the leader Archive. */ + @Config public static final String LEADER_ARCHIVE_CONTROL_CHANNEL_PROP_NAME = "aeron.cluster.leader.archive.control.channel"; /** * Default channel used to build the control request channel for the leader Archive. */ + @Config public static final String LEADER_ARCHIVE_CONTROL_CHANNEL_DEFAULT = "aeron:udp?term-length=64k"; /** @@ -612,105 +643,125 @@ public static final class Configuration * * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME */ + @Config public static final String SERVICE_COUNT_PROP_NAME = "aeron.cluster.service.count"; /** * The number of services in this cluster instance. */ + @Config public static final int SERVICE_COUNT_DEFAULT = 1; /** * Maximum number of cluster sessions that can be active concurrently. */ + @Config public static final String MAX_CONCURRENT_SESSIONS_PROP_NAME = "aeron.cluster.max.sessions"; /** * Maximum number of cluster sessions that can be active concurrently. */ + @Config public static final int MAX_CONCURRENT_SESSIONS_DEFAULT = 10; /** * Timeout for a session if no activity is observed. */ + @Config public static final String SESSION_TIMEOUT_PROP_NAME = "aeron.cluster.session.timeout"; /** * Timeout for a session if no activity is observed. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000) public static final long SESSION_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** * Timeout for a leader if no heartbeat is received by another member. */ + @Config public static final String LEADER_HEARTBEAT_TIMEOUT_PROP_NAME = "aeron.cluster.leader.heartbeat.timeout"; /** * Timeout for a leader if no heartbeat is received by another member. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000) public static final long LEADER_HEARTBEAT_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** * Interval at which a leader will send heartbeats if the log is not progressing. */ + @Config public static final String LEADER_HEARTBEAT_INTERVAL_PROP_NAME = "aeron.cluster.leader.heartbeat.interval"; /** * Interval at which a leader will send heartbeats if the log is not progressing. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 200L * 1000 * 1000) public static final long LEADER_HEARTBEAT_INTERVAL_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(200); /** * Timeout after which an election vote will be attempted after startup while waiting to canvass the status * of members if a majority has been heard from. */ + @Config public static final String STARTUP_CANVASS_TIMEOUT_PROP_NAME = "aeron.cluster.startup.canvass.timeout"; /** * Default timeout after which an election vote will be attempted on startup when waiting to canvass the * status of all members before going for a majority if possible. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 60L * 1000 * 1000 * 1000) public static final long STARTUP_CANVASS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(60); /** * Timeout after which an election fails if the candidate does not get a majority of votes. */ + @Config public static final String ELECTION_TIMEOUT_PROP_NAME = "aeron.cluster.election.timeout"; /** * Default timeout after which an election fails if the candidate does not get a majority of votes. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000) public static final long ELECTION_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(1); /** * Interval at which a member will send out status updates during election phases. */ + @Config public static final String ELECTION_STATUS_INTERVAL_PROP_NAME = "aeron.cluster.election.status.interval"; /** * Default interval at which a member will send out status updates during election phases. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 100L * 1000 * 1000) public static final long ELECTION_STATUS_INTERVAL_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100); /** * Interval at which a dynamic joining member will send add cluster member and snapshot recording * queries. */ + @Config(hasContext = false) public static final String DYNAMIC_JOIN_INTERVAL_PROP_NAME = "aeron.cluster.dynamic.join.interval"; /** * Default interval at which a dynamic joining member will send add cluster member and snapshot recording * queries. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000) public static final long DYNAMIC_JOIN_INTERVAL_DEFAULT_NS = TimeUnit.SECONDS.toNanos(1); /** * Name of the system property for specifying a supplier of {@link Authenticator} for the cluster. */ + @Config(defaultType = DefaultType.STRING, defaultString = "io.aeron.security.DefaultAuthenticatorSupplier") public static final String AUTHENTICATOR_SUPPLIER_PROP_NAME = "aeron.cluster.authenticator.supplier"; /** * Name of the system property for specifying a supplier of {@link AuthorisationService} for the cluster. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME = "aeron.cluster.authorisation.service.supplier"; @@ -727,66 +778,78 @@ public static final class Configuration /** * Size in bytes of the error buffer for the cluster. */ + @Config public static final String ERROR_BUFFER_LENGTH_PROP_NAME = "aeron.cluster.error.buffer.length"; /** * Size in bytes of the error buffer for the cluster. */ + @Config public static final int ERROR_BUFFER_LENGTH_DEFAULT = ClusterMarkFile.ERROR_BUFFER_MIN_LENGTH; /** * Timeout a leader will wait on getting termination ACKs from followers. */ + @Config public static final String TERMINATION_TIMEOUT_PROP_NAME = "aeron.cluster.termination.timeout"; /** * Property name for threshold value for the consensus module agent work cycle threshold to track * for being exceeded. */ + @Config public static final String CYCLE_THRESHOLD_PROP_NAME = "aeron.cluster.cycle.threshold"; /** * Default threshold value for the consensus module agent work cycle threshold to track for being exceeded. */ + @Config(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 total snapshot duration breaches. */ + @Config public static final String TOTAL_SNAPSHOT_DURATION_THRESHOLD_PROP_NAME = "aeron.cluster.total.snapshot.threshold"; /** * Default threshold value, which is used for tracking total snapshot duration breaches. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000) public static final long TOTAL_SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000); /** * Default timeout a leader will wait on getting termination ACKs from followers. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000) public static final long TERMINATION_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** * Resolution in nanoseconds for each tick of the timer wheel for scheduling deadlines. */ + @Config public static final String WHEEL_TICK_RESOLUTION_PROP_NAME = "aeron.cluster.wheel.tick.resolution"; /** * Resolution in nanoseconds for each tick of the timer wheel for scheduling deadlines. Defaults to 8ms. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 4L * 1000 * 1000) public static final long WHEEL_TICK_RESOLUTION_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(8); /** * Number of ticks, or spokes, on the timer wheel. Higher number of ticks reduces potential conflicts * traded off against memory usage. */ + @Config public static final String TICKS_PER_WHEEL_PROP_NAME = "aeron.cluster.ticks.per.wheel"; /** * Number of ticks, or spokes, on the timer wheel. Higher number of ticks reduces potential conflicts * traded off against memory usage. Defaults to 128 per wheel. */ + @Config public static final int TICKS_PER_WHEEL_DEFAULT = 128; /** @@ -797,16 +860,19 @@ public static final class Configuration *
  • 2 - sync file data + metadata.
  • * */ + @Config public static final String FILE_SYNC_LEVEL_PROP_NAME = "aeron.cluster.file.sync.level"; /** * Default file sync level of normal writes. */ + @Config public static final int FILE_SYNC_LEVEL_DEFAULT = 0; /** * {@link TimerServiceSupplier} to be used for creating the {@link TimerService} used by consensus module. */ + @Config public static final String TIMER_SERVICE_SUPPLIER_PROP_NAME = "aeron.cluster.timer.service.supplier"; /** @@ -825,11 +891,13 @@ public static final class Configuration /** * Default {@link TimerServiceSupplier}. */ + @Config public static final String TIMER_SERVICE_SUPPLIER_DEFAULT = TIMER_SERVICE_SUPPLIER_WHEEL; /** * Property name for the name returned from {@link Agent#roleName()} for the consensus module. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String CLUSTER_CONSENSUS_MODULE_AGENT_ROLE_NAME_PROP_NAME = "aeron.cluster.consensus.module.agent.role.name"; @@ -838,6 +906,7 @@ public static final class Configuration * * @since 1.41.0 */ + @Config public static final String CLUSTER_REPLICATION_PROGRESS_TIMEOUT_PROP_NAME = "aeron.cluster.replication.progress.timeout"; @@ -846,6 +915,7 @@ public static final class Configuration * * @since 1.41.0 */ + @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000) public static final long CLUSTER_REPLICATION_PROGRESS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** @@ -853,12 +923,14 @@ public static final class Configuration * * @since 1.41.0 */ + @Config(defaultType = DefaultType.LONG, defaultLong = 0) public static final String CLUSTER_REPLICATION_PROGRESS_INTERVAL_PROP_NAME = "aeron.cluster.replication.progress.interval"; /** * Property name of enabling the acceptance of standby snapshots */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false) public static final String CLUSTER_ACCEPT_STANDBY_SNAPSHOTS_PROP_NAME = "aeron.cluster.accept.standby.snapshots"; @@ -868,6 +940,7 @@ public static final class Configuration * * @since 1.44.0 */ + @Config(defaultType = DefaultType.STRING, defaultString = "io.aeron.cluster.MillisecondClusterClock") public static final String CLUSTER_CLOCK_PROP_NAME = "aeron.cluster.clock"; /** @@ -1971,6 +2044,7 @@ public Context clusterServicesDirectoryName(final String clusterServicesDirector * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_SERVICES_DIR_PROP_NAME * @see #clusterServicesDirectoryName(String) */ + @Config(id = "CLUSTER_SERVICES_DIR") public String clusterServicesDirectoryName() { return clusterServicesDirectoryName; @@ -2113,6 +2187,7 @@ public AppVersionValidator appVersionValidator() * @return the level to be applied for file write. * @see Configuration#FILE_SYNC_LEVEL_PROP_NAME */ + @Config int fileSyncLevel() { return fileSyncLevel; @@ -2179,6 +2254,7 @@ public Context clusterMemberId(final int clusterMemberId) * @return this cluster member identity. * @see Configuration#CLUSTER_MEMBER_ID_PROP_NAME */ + @Config public int clusterMemberId() { return clusterMemberId; @@ -2207,6 +2283,7 @@ public Context appointedLeaderId(final int appointedLeaderId) * @return cluster member id of the appointed cluster leader. * @see Configuration#APPOINTED_LEADER_ID_PROP_NAME */ + @Config public int appointedLeaderId() { return appointedLeaderId; @@ -2242,6 +2319,7 @@ public Context clusterMembers(final String clusterMembers) * @return members of the cluster which are all candidates to be leader. * @see Configuration#CLUSTER_MEMBERS_PROP_NAME */ + @Config public String clusterMembers() { return clusterMembers; @@ -2275,6 +2353,7 @@ public Context clusterConsensusEndpoints(final String endpoints) * deprecated and will be removed in a later release. */ @Deprecated + @Config public String clusterConsensusEndpoints() { return clusterConsensusEndpoints; @@ -2299,6 +2378,7 @@ public Context clusterMembersIgnoreSnapshot(final boolean ignore) * @return ignore or not the cluster members in the snapshot. * @see Configuration#CLUSTER_MEMBERS_IGNORE_SNAPSHOT_PROP_NAME */ + @Config public boolean clusterMembersIgnoreSnapshot() { return clusterMembersIgnoreSnapshot; @@ -2371,6 +2451,7 @@ public Context ingressFragmentLimit(final int ingressFragmentLimit) * @return the limit for fragments to be consumed on each poll of ingress. * @see Configuration#CLUSTER_INGRESS_FRAGMENT_LIMIT_PROP_NAME */ + @Config(id = "CLUSTER_INGRESS_FRAGMENT_LIMIT") public int ingressFragmentLimit() { return ingressFragmentLimit; @@ -2431,6 +2512,7 @@ public Context isIpcIngressAllowed(final boolean isIpcIngressAllowed) * @return whether IPC ingress is allowed or not. * @see Configuration#CLUSTER_INGRESS_IPC_ALLOWED_PROP_NAME */ + @Config(id = "CLUSTER_INGRESS_IPC_ALLOWED") public boolean isIpcIngressAllowed() { return isIpcIngressAllowed; @@ -2455,6 +2537,7 @@ public Context logChannel(final String channel) * @return the channel parameter for the cluster channel. * @see Configuration#LOG_CHANNEL_PROP_NAME */ + @Config public String logChannel() { return logChannel; @@ -2503,6 +2586,7 @@ public Context memberEndpoints(final String endpoints) * @return the endpoints for the cluster node. * @see Configuration#MEMBER_ENDPOINTS_PROP_NAME */ + @Config public String memberEndpoints() { return memberEndpoints; @@ -2695,6 +2779,7 @@ public Context consensusChannel(final String channel) * @return the channel parameter for the consensus communication channel. * @see Configuration#CONSENSUS_CHANNEL_PROP_NAME */ + @Config public String consensusChannel() { return consensusChannel; @@ -2719,6 +2804,7 @@ public Context consensusStreamId(final int streamId) * @return the stream id for the consensus channel. * @see Configuration#CONSENSUS_STREAM_ID_PROP_NAME */ + @Config public int consensusStreamId() { return consensusStreamId; @@ -2747,6 +2833,7 @@ public Context replicationChannel(final String channel) * replication to catch up. * @see Configuration#REPLICATION_CHANNEL_PROP_NAME */ + @Config public String replicationChannel() { return replicationChannel; @@ -2773,6 +2860,7 @@ public Context followerCatchupChannel(final String channel) * @return channel used for replaying older data during a catchup phase. * @see Configuration#FOLLOWER_CATCHUP_CHANNEL_PROP_NAME */ + @Config public String followerCatchupChannel() { return followerCatchupChannel; @@ -2799,6 +2887,7 @@ public Context leaderArchiveControlChannel(final String channel) * @return channel used for replaying older data during a catchup phase. * @see Configuration#LEADER_ARCHIVE_CONTROL_CHANNEL_PROP_NAME */ + @Config public String leaderArchiveControlChannel() { return leaderArchiveControlChannel; @@ -2847,6 +2936,7 @@ public Context wheelTickResolutionNs(final long wheelTickResolutionNs) * @return the resolution in nanoseconds for each tick on the timer wheel. * @see Configuration#WHEEL_TICK_RESOLUTION_PROP_NAME */ + @Config public long wheelTickResolutionNs() { return wheelTickResolutionNs; @@ -2873,6 +2963,7 @@ public Context ticksPerWheel(final int ticksPerWheel) * @return the number of ticks on the timer wheel. * @see Configuration#TICKS_PER_WHEEL_PROP_NAME */ + @Config public int ticksPerWheel() { return ticksPerWheel; @@ -2899,6 +2990,7 @@ public Context serviceCount(final int serviceCount) * @see Configuration#SERVICE_COUNT_PROP_NAME * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME */ + @Config public int serviceCount() { return serviceCount; @@ -2923,6 +3015,7 @@ public Context maxConcurrentSessions(final int maxSessions) * @return the limit for the maximum number of concurrent cluster sessions. * @see Configuration#MAX_CONCURRENT_SESSIONS_PROP_NAME */ + @Config public int maxConcurrentSessions() { return maxConcurrentSessions; @@ -2947,6 +3040,7 @@ public Context sessionTimeoutNs(final long sessionTimeoutNs) * @return the timeout for a session if no activity is observed. * @see Configuration#SESSION_TIMEOUT_PROP_NAME */ + @Config public long sessionTimeoutNs() { return CommonContext.checkDebugTimeout(sessionTimeoutNs, TimeUnit.NANOSECONDS); @@ -2971,6 +3065,7 @@ public Context leaderHeartbeatTimeoutNs(final long heartbeatTimeoutNs) * @return the timeout for a leader if no heartbeat is received by another member. * @see Configuration#LEADER_HEARTBEAT_TIMEOUT_PROP_NAME */ + @Config public long leaderHeartbeatTimeoutNs() { return leaderHeartbeatTimeoutNs; @@ -2983,6 +3078,7 @@ public long leaderHeartbeatTimeoutNs() * @return this for a fluent API. * @see Configuration#LEADER_HEARTBEAT_INTERVAL_PROP_NAME */ + @Config public Context leaderHeartbeatIntervalNs(final long heartbeatIntervalNs) { this.leaderHeartbeatIntervalNs = heartbeatIntervalNs; @@ -3021,6 +3117,7 @@ public Context startupCanvassTimeoutNs(final long timeoutNs) * @return the timeout to wait on startup after recovery before commencing an election. * @see Configuration#STARTUP_CANVASS_TIMEOUT_PROP_NAME */ + @Config public long startupCanvassTimeoutNs() { return startupCanvassTimeoutNs; @@ -3045,6 +3142,7 @@ public Context electionTimeoutNs(final long timeoutNs) * @return the timeout to wait for votes in an elections. * @see Configuration#ELECTION_TIMEOUT_PROP_NAME */ + @Config public long electionTimeoutNs() { return electionTimeoutNs; @@ -3071,6 +3169,7 @@ public Context electionStatusIntervalNs(final long electionStatusIntervalNs) * @see Configuration#ELECTION_STATUS_INTERVAL_PROP_NAME * @see Configuration#ELECTION_STATUS_INTERVAL_DEFAULT_NS */ + @Config public long electionStatusIntervalNs() { return electionStatusIntervalNs; @@ -3097,6 +3196,7 @@ public Context terminationTimeoutNs(final long terminationTimeoutNs) * @see Configuration#TERMINATION_TIMEOUT_PROP_NAME * @see Configuration#TERMINATION_TIMEOUT_DEFAULT_NS */ + @Config public long terminationTimeoutNs() { return terminationTimeoutNs; @@ -3123,6 +3223,7 @@ public Context cycleThresholdNs(final long thresholdNs) * * @return threshold to track for the consensus module agent work cycle time. */ + @Config public long cycleThresholdNs() { return cycleThresholdNs; @@ -3169,6 +3270,7 @@ public Context totalSnapshotDurationThresholdNs(final long thresholdNs) * * @return threshold value in nanoseconds. */ + @Config public long totalSnapshotDurationThresholdNs() { return totalSnapshotDurationThresholdNs; @@ -3202,6 +3304,7 @@ public SnapshotDurationTracker totalSnapshotDurationTracker() * * @return the {@link Agent#roleName()} to be used for the consensus module agent. */ + @Config(id = "CLUSTER_CONSENSUS_MODULE_AGENT_ROLE_NAME") public String agentRoleName() { return agentRoleName; @@ -3280,6 +3383,7 @@ public Context clusterClock(final ClusterClock clock) * * @return the {@link ClusterClock} to used for timestamping messages and timers. */ + @Config public ClusterClock clusterClock() { return clusterClock; @@ -3700,6 +3804,7 @@ public AeronArchive.Context archiveContext() * * @return the {@link AuthenticatorSupplier} to be used for the consensus module. */ + @Config public AuthenticatorSupplier authenticatorSupplier() { return authenticatorSupplier; @@ -3722,6 +3827,7 @@ public Context authenticatorSupplier(final AuthenticatorSupplier authenticatorSu * * @return the {@link AuthorisationServiceSupplier} to be used for the consensus module. */ + @Config public AuthorisationServiceSupplier authorisationServiceSupplier() { return authorisationServiceSupplier; @@ -3900,6 +4006,7 @@ public Context errorBufferLength(final int errorBufferLength) * @return error buffer length in bytes. * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME */ + @Config public int errorBufferLength() { return errorBufferLength; @@ -3966,6 +4073,7 @@ public Context timerServiceSupplier(final TimerServiceSupplier timerServiceSuppl * * @return supplier of dynamically created {@link TimerService} instances. */ + @Config public TimerServiceSupplier timerServiceSupplier() { return timerServiceSupplier; @@ -4002,6 +4110,7 @@ public Context nameResolver(final NameResolver ignore) * @see Configuration#CLUSTER_ACCEPT_STANDBY_SNAPSHOTS_PROP_NAME * @see Configuration#acceptStandbySnapshots() */ + @Config(id = "CLUSTER_ACCEPT_STANDBY_SNAPSHOTS") public boolean acceptStandbySnapshots() { return acceptStandbySnapshots; diff --git a/aeron-cluster/src/main/java/io/aeron/cluster/client/AeronCluster.java b/aeron-cluster/src/main/java/io/aeron/cluster/client/AeronCluster.java index 1b30d9ba5f..3743b177fc 100644 --- a/aeron-cluster/src/main/java/io/aeron/cluster/client/AeronCluster.java +++ b/aeron-cluster/src/main/java/io/aeron/cluster/client/AeronCluster.java @@ -17,6 +17,8 @@ import io.aeron.*; import io.aeron.cluster.codecs.*; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; import io.aeron.exceptions.*; import io.aeron.logbuffer.BufferClaim; import io.aeron.logbuffer.ControlledFragmentHandler; @@ -941,6 +943,7 @@ private void invokeInvokers() /** * Configuration options for cluster client. */ + @Config(existsInC = false) public static final class Configuration { /** @@ -972,11 +975,13 @@ public static final class Configuration /** * Timeout when waiting on a message to be sent or received. */ + @Config public static final String MESSAGE_TIMEOUT_PROP_NAME = "aeron.cluster.message.timeout"; /** * Default timeout when waiting on a message to be sent or received. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 5L * 1000 * 1000 * 1000) public static final long MESSAGE_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** @@ -988,11 +993,13 @@ public static final class Configuration *

    * Each member of the list will be substituted for the endpoint in the {@link #INGRESS_CHANNEL_PROP_NAME} value. */ + @Config public static final String INGRESS_ENDPOINTS_PROP_NAME = "aeron.cluster.ingress.endpoints"; /** * Default comma separated list of cluster ingress endpoints. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String INGRESS_ENDPOINTS_DEFAULT = null; /** @@ -1000,21 +1007,25 @@ public static final class Configuration * be required and the {@link #INGRESS_ENDPOINTS_PROP_NAME} is used to substitute the endpoints from the * {@link #INGRESS_ENDPOINTS_PROP_NAME} list. */ + @Config public static final String INGRESS_CHANNEL_PROP_NAME = "aeron.cluster.ingress.channel"; /** * Channel for sending messages to a cluster. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String INGRESS_CHANNEL_DEFAULT = null; /** * Stream id within a channel for sending messages to a cluster. */ + @Config public static final String INGRESS_STREAM_ID_PROP_NAME = "aeron.cluster.ingress.stream.id"; /** * Default stream id within a channel for sending messages to a cluster. */ + @Config public static final int INGRESS_STREAM_ID_DEFAULT = 101; /** @@ -1031,21 +1042,25 @@ public static final class Configuration * ephemeral port range. * */ + @Config public static final String EGRESS_CHANNEL_PROP_NAME = "aeron.cluster.egress.channel"; /** * Channel for receiving response messages from a cluster. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String EGRESS_CHANNEL_DEFAULT = null; /** * Stream id within a channel for receiving messages from a cluster. */ + @Config public static final String EGRESS_STREAM_ID_PROP_NAME = "aeron.cluster.egress.stream.id"; /** * Default stream id within a channel for receiving messages from a cluster. */ + @Config public static final int EGRESS_STREAM_ID_DEFAULT = 102; /** @@ -1246,6 +1261,7 @@ public Context messageTimeoutNs(final long messageTimeoutNs) * @return the message timeout in nanoseconds to wait for sending or receiving a message. * @see Configuration#MESSAGE_TIMEOUT_PROP_NAME */ + @Config public long messageTimeoutNs() { return CommonContext.checkDebugTimeout(messageTimeoutNs, TimeUnit.NANOSECONDS); @@ -1274,6 +1290,7 @@ public Context ingressEndpoints(final String clusterMembers) * @return member endpoints of the cluster which are all candidates to be leader. * @see Configuration#INGRESS_ENDPOINTS_PROP_NAME */ + @Config public String ingressEndpoints() { return ingressEndpoints; @@ -1305,6 +1322,7 @@ public Context ingressChannel(final String channel) * @return the channel parameter for the ingress channel. * @see Configuration#INGRESS_CHANNEL_PROP_NAME */ + @Config public String ingressChannel() { return ingressChannel; @@ -1329,6 +1347,7 @@ public Context ingressStreamId(final int streamId) * @return the stream id for the ingress channel. * @see Configuration#INGRESS_STREAM_ID_PROP_NAME */ + @Config public int ingressStreamId() { return ingressStreamId; @@ -1353,6 +1372,7 @@ public Context egressChannel(final String channel) * @return the channel parameter for the egress channel. * @see Configuration#EGRESS_CHANNEL_PROP_NAME */ + @Config public String egressChannel() { return egressChannel; @@ -1377,6 +1397,7 @@ public Context egressStreamId(final int streamId) * @return the stream id for the egress channel. * @see Configuration#EGRESS_STREAM_ID_PROP_NAME */ + @Config public int egressStreamId() { return egressStreamId; diff --git a/aeron-cluster/src/main/java/io/aeron/cluster/service/ClusteredServiceContainer.java b/aeron-cluster/src/main/java/io/aeron/cluster/service/ClusteredServiceContainer.java index b3301364f8..aab7d4990a 100644 --- a/aeron-cluster/src/main/java/io/aeron/cluster/service/ClusteredServiceContainer.java +++ b/aeron-cluster/src/main/java/io/aeron/cluster/service/ClusteredServiceContainer.java @@ -21,6 +21,8 @@ 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; @@ -143,6 +145,7 @@ public void close() /** * Configuration options for the consensus module and service container within a cluster. */ + @Config(existsInC = false) public static final class Configuration { /** @@ -163,164 +166,196 @@ public static final class Configuration /** * 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 #CLUSTER_DIR_PROP_NAME} if not * specified. */ + @Config(defaultType = DefaultType.STRING, defaultString = CLUSTER_DIR_DEFAULT) 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"; - /** - * Default directory to use for the aeron cluster. - */ - public static final String CLUSTER_DIR_DEFAULT = "aeron-cluster"; - /** * 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"; @@ -328,11 +363,16 @@ public static final class Configuration * 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); /** @@ -340,6 +380,7 @@ public static final class Configuration * * @since 1.44.0 */ + @Config public static final String SNAPSHOT_DURATION_THRESHOLD_PROP_NAME = "aeron.cluster.service.snapshot.threshold"; /** @@ -347,6 +388,7 @@ public static final class Configuration * * @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); /** @@ -477,17 +519,20 @@ public static int snapshotStreamId() /** * 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"; /** @@ -522,7 +567,7 @@ public static String clusterDirName() */ public static String clusterServicesDirName() { - return System.getProperty(CLUSTER_DIR_PROP_NAME); + return System.getProperty(CLUSTER_SERVICES_DIR_PROP_NAME, clusterDirName()); } /** @@ -1025,6 +1070,7 @@ public Context clusterId(final int clusterId) * @return the id for this cluster instance. * @see Configuration#CLUSTER_ID_PROP_NAME */ + @Config public int clusterId() { return clusterId; @@ -1049,6 +1095,7 @@ public Context serviceId(final int serviceId) * @return the id for this clustered service. * @see Configuration#SERVICE_ID_PROP_NAME */ + @Config public int serviceId() { return serviceId; @@ -1073,6 +1120,7 @@ public Context serviceName(final String serviceName) * @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; @@ -1097,6 +1145,7 @@ public Context replayChannel(final String channel) * @return the channel parameter for the cluster replay channel. * @see Configuration#REPLAY_CHANNEL_PROP_NAME */ + @Config public String replayChannel() { return replayChannel; @@ -1121,6 +1170,7 @@ public Context replayStreamId(final int streamId) * @return the stream id for the cluster log replay channel. * @see Configuration#REPLAY_STREAM_ID_PROP_NAME */ + @Config public int replayStreamId() { return replayStreamId; @@ -1145,6 +1195,7 @@ public Context controlChannel(final String channel) * @return the channel parameter for sending messages to the Consensus Module. * @see Configuration#CONTROL_CHANNEL_PROP_NAME */ + @Config public String controlChannel() { return controlChannel; @@ -1169,6 +1220,7 @@ public Context serviceStreamId(final int streamId) * @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; @@ -1193,6 +1245,7 @@ public Context consensusModuleStreamId(final int streamId) * @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; @@ -1217,6 +1270,7 @@ public Context snapshotChannel(final String channel) * @return the channel parameter for snapshot recordings. * @see Configuration#SNAPSHOT_CHANNEL_PROP_NAME */ + @Config public String snapshotChannel() { return snapshotChannel; @@ -1241,6 +1295,7 @@ public Context snapshotStreamId(final int streamId) * @return the stream id for snapshot recordings. * @see Configuration#SNAPSHOT_STREAM_ID_PROP_NAME */ + @Config public int snapshotStreamId() { return snapshotStreamId; @@ -1278,6 +1333,7 @@ public Context logFragmentLimit(final int logFragmentLimit) * @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; @@ -1289,6 +1345,7 @@ public int logFragmentLimit() * @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; @@ -1333,6 +1390,7 @@ public Context idleStrategySupplier(final Supplier idleStrategySup * * @return a new {@link IdleStrategy} based on configured supplier. */ + @Config(id = "CLUSTER_IDLE_STRATEGY") public IdleStrategy idleStrategy() { return idleStrategySupplier.get(); @@ -1389,6 +1447,7 @@ public Context errorHandler(final ErrorHandler errorHandler) * @return the {@link DelegatingErrorHandler} to be used by the {@link ClusteredServiceContainer}. * @see Configuration#DELEGATING_ERROR_HANDLER_PROP_NAME */ + @Config public DelegatingErrorHandler delegatingErrorHandler() { return delegatingErrorHandler; @@ -1525,6 +1584,7 @@ public boolean ownsAeronClient() * * @return service this container holds. */ + @Config(id = "SERVICE_CLASS_NAME") public ClusteredService clusteredService() { return clusteredService; @@ -1583,6 +1643,7 @@ public Context clusterDirectoryName(final String clusterDirectoryName) * @return directory name for the cluster directory. * @see Configuration#CLUSTER_DIR_PROP_NAME */ + @Config(id = "CLUSTER_DIR") public String clusterDirectoryName() { return clusterDirectoryName; @@ -1622,6 +1683,7 @@ public File clusterDir() * @see ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME * @see #clusterDir() */ + @Config public File markFileDir() { return markFileDir; @@ -1726,6 +1788,7 @@ public Context errorBufferLength(final int errorBufferLength) * * @return error buffer length in bytes. */ + @Config(id = "SERVICE_ERROR_BUFFER_LENGTH") public int errorBufferLength() { return errorBufferLength; @@ -1796,6 +1859,7 @@ public Context cycleThresholdNs(final long thresholdNs) * * @return threshold to track for the container work cycle time. */ + @Config(id = "SERVICE_CYCLE_THRESHOLD") public long cycleThresholdNs() { return cycleThresholdNs; @@ -1844,6 +1908,7 @@ public Context snapshotDurationThresholdNs(final long thresholdNs) * @return threshold value in nanoseconds. * @since 1.44.0 */ + @Config public long snapshotDurationThresholdNs() { return snapshotDurationThresholdNs; @@ -1891,6 +1956,7 @@ public void deleteDirectory() * @see ClusteredServiceContainer.Configuration#STANDBY_SNAPSHOT_ENABLED_PROP_NAME * @see ClusteredServiceContainer.Configuration#standbySnapshotEnabled() */ + @Config public boolean standbySnapshotEnabled() { return standbySnapshotEnabled; diff --git a/aeron-driver/src/main/java/io/aeron/driver/Configuration.java b/aeron-driver/src/main/java/io/aeron/driver/Configuration.java index 2f542442dc..2fa85254ea 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Configuration.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Configuration.java @@ -19,6 +19,8 @@ import io.aeron.CommonContext; import io.aeron.Image; import io.aeron.Publication; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; import io.aeron.driver.media.ReceiveChannelEndpoint; import io.aeron.driver.media.SendChannelEndpoint; import io.aeron.exceptions.ConfigurationException; @@ -59,42 +61,53 @@ public final class Configuration /** * Should the driver print its configuration on start to {@link System#out}. */ + @Config( + expectedCEnvVarFieldName = "AERON_PRINT_CONFIGURATION_ON_START_ENV_VAR", + defaultType = DefaultType.BOOLEAN, + defaultBoolean = false) public static final String PRINT_CONFIGURATION_ON_START_PROP_NAME = "aeron.print.configuration"; /** * Warn if the Aeron directory exists. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false) public static final String DIR_WARN_IF_EXISTS_PROP_NAME = "aeron.dir.warn.if.exists"; /** * Should the Media Driver attempt to immediately delete the directory {@link CommonContext#AERON_DIR_PROP_NAME} * on start if it exists before performing any additional checks. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false) public static final String DIR_DELETE_ON_START_PROP_NAME = "aeron.dir.delete.on.start"; /** * Should driver attempt to delete {@link CommonContext#AERON_DIR_PROP_NAME} on shutdown. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false) public static final String DIR_DELETE_ON_SHUTDOWN_PROP_NAME = "aeron.dir.delete.on.shutdown"; /** * Should high resolution timer be used on Windows. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false, existsInC = false) public static final String USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME = "aeron.use.windows.high.res.timer"; /** * Property name for default boolean value for if subscriptions should have a tether for local flow control. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true) public static final String TETHER_SUBSCRIPTIONS_PROP_NAME = "aeron.tether.subscriptions"; /** * Property name for default boolean value for if a stream is reliable. True to NAK, false to gap fill. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true) public static final String RELIABLE_STREAM_PROP_NAME = "aeron.reliable.stream"; /** * Property name for boolean value of term buffers should be created sparse. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false) public static final String TERM_BUFFER_SPARSE_FILE_PROP_NAME = "aeron.term.buffer.sparse.file"; /** @@ -105,66 +118,87 @@ public final class Configuration /** * Property name for page size to align all files to. */ + @Config public static final String FILE_PAGE_SIZE_PROP_NAME = "aeron.file.page.size"; /** * Default page size for alignment of all files. */ + @Config public static final int FILE_PAGE_SIZE_DEFAULT = 4 * 1024; /** * Property name for boolean value for if storage checks should be performed when allocating files. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true) public static final String PERFORM_STORAGE_CHECKS_PROP_NAME = "aeron.perform.storage.checks"; /** * Length (in bytes) of the log buffers for UDP publication terms. */ + @Config(uriParam = "term-length") public static final String TERM_BUFFER_LENGTH_PROP_NAME = "aeron.term.buffer.length"; /** * Default term buffer length. */ + @Config public static final int TERM_BUFFER_LENGTH_DEFAULT = 16 * 1024 * 1024; /** * Length (in bytes) of the log buffers for IPC publication terms. */ + @Config(uriParam = "term-length") public static final String IPC_TERM_BUFFER_LENGTH_PROP_NAME = "aeron.ipc.term.buffer.length"; /** * Default IPC term buffer length. */ + @Config(id = "IPC_TERM_BUFFER_LENGTH") public static final int TERM_BUFFER_IPC_LENGTH_DEFAULT = 64 * 1024 * 1024; /** * Property name low file storage warning threshold in bytes. */ + @Config public static final String LOW_FILE_STORE_WARNING_THRESHOLD_PROP_NAME = "aeron.low.file.store.warning.threshold"; /** * Default value in bytes for low file storage warning threshold. */ + @Config public static final long LOW_FILE_STORE_WARNING_THRESHOLD_DEFAULT = TERM_BUFFER_LENGTH_DEFAULT * 10L; /** * Length (in bytes) of the conductor buffer for control commands from the clients to the media driver conductor. */ + @Config(expectedCEnvVarFieldName = "AERON_TO_CONDUCTOR_BUFFER_LENGTH_ENV_VAR") public static final String CONDUCTOR_BUFFER_LENGTH_PROP_NAME = "aeron.conductor.buffer.length"; /** * Default buffer length for conductor buffers between the client and the media driver conductor. */ + @Config( + expectedCDefaultFieldName = "AERON_TO_CONDUCTOR_BUFFER_LENGTH_DEFAULT", + skipCDefaultValidation = true, + defaultType = DefaultType.INT, + defaultInt = (1024 * 1024) + 768) public static final int CONDUCTOR_BUFFER_LENGTH_DEFAULT = (1024 * 1024) + RingBufferDescriptor.TRAILER_LENGTH; /** * Length (in bytes) of the broadcast buffers from the media driver to the clients. */ + @Config(expectedCEnvVarFieldName = "AERON_TO_CLIENTS_BUFFER_LENGTH_ENV_VAR") public static final String TO_CLIENTS_BUFFER_LENGTH_PROP_NAME = "aeron.clients.buffer.length"; /** * Default buffer length for broadcast buffers from the media driver and the clients. */ + @Config( + expectedCDefaultFieldName = "AERON_TO_CLIENTS_BUFFER_LENGTH_DEFAULT", + skipCDefaultValidation = true, + defaultType = DefaultType.INT, + defaultInt = (1024 * 1024) + 128) public static final int TO_CLIENTS_BUFFER_LENGTH_DEFAULT = (1024 * 1024) + BroadcastBufferDescriptor.TRAILER_LENGTH; /** @@ -172,11 +206,13 @@ public final class Configuration *

    * Each counter uses {@link org.agrona.concurrent.status.CountersReader#COUNTER_LENGTH} bytes. */ + @Config(expectedCEnvVarFieldName = "AERON_COUNTERS_VALUES_BUFFER_LENGTH_ENV_VAR") public static final String COUNTERS_VALUES_BUFFER_LENGTH_PROP_NAME = "aeron.counters.buffer.length"; /** * Default length of the buffer for the counters file. */ + @Config(expectedCDefaultFieldName = "AERON_COUNTERS_VALUES_BUFFER_LENGTH_DEFAULT") public static final int COUNTERS_VALUES_BUFFER_LENGTH_DEFAULT = 1024 * 1024; /** @@ -187,26 +223,31 @@ public final class Configuration /** * Property name for length of the memory mapped buffer for the distinct error log. */ + @Config public static final String ERROR_BUFFER_LENGTH_PROP_NAME = "aeron.error.buffer.length"; /** * Default buffer length for the error buffer for the media driver. */ + @Config public static final int ERROR_BUFFER_LENGTH_DEFAULT = 1024 * 1024; /** * Property name for length of the memory mapped buffer for the {@link io.aeron.driver.reports.LossReport}. */ + @Config public static final String LOSS_REPORT_BUFFER_LENGTH_PROP_NAME = "aeron.loss.report.buffer.length"; /** * Default buffer length for the {@link io.aeron.driver.reports.LossReport}. */ + @Config public static final int LOSS_REPORT_BUFFER_LENGTH_DEFAULT = 1024 * 1024; /** * Property name for length of the initial window which must be sufficient for Bandwidth Delay Product (BDP). */ + @Config public static final String INITIAL_WINDOW_LENGTH_PROP_NAME = "aeron.rcv.initial.window.length"; /** @@ -221,107 +262,140 @@ public final class Configuration * Buffer = (10 * 1000 * 1000 * 1000 / 8) * 0.0001 = 125000 * Round to 128 KB */ + @Config public static final int INITIAL_WINDOW_LENGTH_DEFAULT = 128 * 1024; /** * Status message timeout in nanoseconds after which one will be sent when data flow has not triggered one. */ + @Config public static final String STATUS_MESSAGE_TIMEOUT_PROP_NAME = "aeron.rcv.status.message.timeout"; /** * Max timeout between Status messages (SM)s. */ + @Config( + defaultType = DefaultType.LONG, + defaultLong = 200 * 1000 * 1000, + expectedCDefaultFieldName = "AERON_RCV_STATUS_MESSAGE_TIMEOUT_NS_DEFAULT") public static final long STATUS_MESSAGE_TIMEOUT_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(200); /** * Property name for ratio of sending data to polling status messages in the {@link Sender}. */ + @Config public static final String SEND_TO_STATUS_POLL_RATIO_PROP_NAME = "aeron.send.to.status.poll.ratio"; /** * The ratio for sending data to polling status messages in the Sender. This may be reduced for smaller windows. */ + @Config public static final int SEND_TO_STATUS_POLL_RATIO_DEFAULT = 6; /** * Property name for the limit of the number of driver managed resources that can be freed in a single duty cycle. */ + @Config public static final String RESOURCE_FREE_LIMIT_PROP_NAME = "aeron.driver.resource.free.limit"; /** * Default value for the limit of the number of driver managed resources that can be freed in a single duty cycle. */ + @Config public static final int RESOURCE_FREE_LIMIT_DEFAULT = 10; /** * Property name for SO_RCVBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP). */ + @Config public static final String SOCKET_RCVBUF_LENGTH_PROP_NAME = "aeron.socket.so_rcvbuf"; /** * Default SO_RCVBUF length. */ + @Config public static final int SOCKET_RCVBUF_LENGTH_DEFAULT = 128 * 1024; /** * Property name for SO_SNDBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP). */ + @Config public static final String SOCKET_SNDBUF_LENGTH_PROP_NAME = "aeron.socket.so_sndbuf"; /** * Default SO_SNDBUF length. */ + @Config public static final int SOCKET_SNDBUF_LENGTH_DEFAULT = 0; /** * Property name for IP_MULTICAST_TTL setting on UDP sockets. */ + @Config public static final String SOCKET_MULTICAST_TTL_PROP_NAME = "aeron.socket.multicast.ttl"; /** * Multicast TTL value, 0 means use OS default. */ + @Config public static final int SOCKET_MULTICAST_TTL_DEFAULT = 0; /** * Property name for linger timeout after draining on {@link Publication}s so they can respond to NAKs. */ + @Config public static final String PUBLICATION_LINGER_PROP_NAME = "aeron.publication.linger.timeout"; /** * Default time for {@link Publication}s to linger after draining and before cleanup in nanoseconds. */ + @Config( + expectedCDefaultFieldName = "AERON_PUBLICATION_LINGER_TIMEOUT_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 5L * 1000 * 1000 * 1000) public static final long PUBLICATION_LINGER_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** * Property name for {@link Aeron} client liveness timeout after which it is considered not alive. */ + @Config public static final String CLIENT_LIVENESS_TIMEOUT_PROP_NAME = "aeron.client.liveness.timeout"; /** * Default timeout for client liveness timeout after which it is considered not alive. */ + @Config( + expectedCDefaultFieldName = "AERON_CLIENT_LIVENESS_TIMEOUT_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 10L * 1000 * 1000 * 1000) public static final long CLIENT_LIVENESS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** * {@link Image} liveness timeout for how long it stays active without heartbeats or lingers around after being * drained. */ + @Config public static final String IMAGE_LIVENESS_TIMEOUT_PROP_NAME = "aeron.image.liveness.timeout"; /** * Default timeout for {@link Image} liveness timeout. */ + @Config( + expectedCDefaultFieldName = "AERON_IMAGE_LIVENESS_TIMEOUT_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 10L * 1000 * 1000 * 1000) public static final long IMAGE_LIVENESS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** * Property name for window limit on {@link Publication} side by which the publisher can get ahead of consumers. */ + @Config(defaultType = DefaultType.INT, defaultInt = 0) public static final String PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME = "aeron.publication.term.window.length"; /** * Property name for window limit for IPC publications. */ + @Config(defaultType = DefaultType.INT, defaultInt = 0) public static final String IPC_PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME = "aeron.ipc.publication.term.window.length"; @@ -332,21 +406,31 @@ public final class Configuration * {@link io.aeron.Publication#tryClaim(int, BufferClaim)} is used without following up by calling * {@link BufferClaim#commit()} or {@link BufferClaim#abort()}. */ + @Config public static final String PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME = "aeron.publication.unblock.timeout"; /** * Timeout for {@link Publication} unblock in nanoseconds. */ + @Config( + expectedCDefaultFieldName = "AERON_PUBLICATION_UNBLOCK_TIMEOUT_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 15L * 1000 * 1000 * 1000) public static final long PUBLICATION_UNBLOCK_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(15); /** * Property name for {@link Publication} timeout due to lack of status messages which indicate a connection. */ + @Config public static final String PUBLICATION_CONNECTION_TIMEOUT_PROP_NAME = "aeron.publication.connection.timeout"; /** * Timeout for {@link Publication} connection timeout in nanoseconds */ + @Config( + expectedCDefaultFieldName = "AERON_PUBLICATION_CONNECTION_TIMEOUT_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 5L * 1000 * 1000 * 1000) public static final long PUBLICATION_CONNECTION_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** @@ -354,6 +438,7 @@ public final class Configuration *

    * If true then this will override the min group size of the min and tagged flow control strategies. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false) public static final String SPIES_SIMULATE_CONNECTION_PROP_NAME = "aeron.spies.simulate.connection"; /** @@ -391,61 +476,116 @@ public final class Configuration /** * Property name for {@link IdleStrategy} to be employed by {@link Sender} for {@link ThreadingMode#DEDICATED}. */ + @Config(skipCDefaultValidation = true) public static final String SENDER_IDLE_STRATEGY_PROP_NAME = "aeron.sender.idle.strategy"; + /** + * Default idle strategy for the sender thread. + */ + @Config + public static final String SENDER_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY; + /** * Property name for {@link IdleStrategy} to be employed by {@link Receiver} for {@link ThreadingMode#DEDICATED}. */ + @Config(skipCDefaultValidation = true) public static final String RECEIVER_IDLE_STRATEGY_PROP_NAME = "aeron.receiver.idle.strategy"; + /** + * Default idle strategy for the receiver thread. + */ + @Config + public static final String RECEIVER_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY; + /** * Property name for {@link IdleStrategy} to be employed by {@link DriverConductor} for * {@link ThreadingMode#DEDICATED} and {@link ThreadingMode#SHARED_NETWORK}. */ + @Config(skipCDefaultValidation = true) public static final String CONDUCTOR_IDLE_STRATEGY_PROP_NAME = "aeron.conductor.idle.strategy"; + /** + * Default idle strategy for the conductor thread. + */ + @Config + public static final String CONDUCTOR_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY; + /** * Property name for {@link IdleStrategy} to be employed by {@link Sender} and {@link Receiver} for * {@link ThreadingMode#SHARED_NETWORK}. */ + @Config(skipCDefaultValidation = true) public static final String SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME = "aeron.sharednetwork.idle.strategy"; + /** + * Default idle strategy for the shared network thread. + */ + @Config + public static final String SHARED_NETWORK_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY; + /** * Property name for {@link IdleStrategy} to be employed by {@link Sender}, {@link Receiver}, * and {@link DriverConductor} for {@link ThreadingMode#SHARED}. */ + @Config(skipCDefaultValidation = true) public static final String SHARED_IDLE_STRATEGY_PROP_NAME = "aeron.shared.idle.strategy"; + /** + * Default idle strategy for the shared thread. + */ + @Config + public static final String SHARED_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY; + /** * Property name for {@link FlowControl} to be employed for unicast channels. */ + @Config(existsInC = false, hasContext = false) public static final String UNICAST_FLOW_CONTROL_STRATEGY_PROP_NAME = "aeron.unicast.flow.control.strategy"; + /** + */ + @Config + public static final String UNICAST_FLOW_CONTROL_STRATEGY_DEFAULT = "io.aeron.driver.UnicastFlowControl"; + /** * {@link FlowControl} to be employed for unicast channels. */ public static final String UNICAST_FLOW_CONTROL_STRATEGY = getProperty( - UNICAST_FLOW_CONTROL_STRATEGY_PROP_NAME, "io.aeron.driver.UnicastFlowControl"); + UNICAST_FLOW_CONTROL_STRATEGY_PROP_NAME, UNICAST_FLOW_CONTROL_STRATEGY_DEFAULT); /** * Property name for {@link FlowControl} to be employed for multicast channels. */ + @Config(existsInC = false, hasContext = false) public static final String MULTICAST_FLOW_CONTROL_STRATEGY_PROP_NAME = "aeron.multicast.flow.control.strategy"; + /** + */ + @Config + public static final String MULTICAST_FLOW_CONTROL_STRATEGY_DEFAULT = "io.aeron.driver.MaxMulticastFlowControl"; + /** * {@link FlowControl} to be employed for multicast channels. */ public static final String MULTICAST_FLOW_CONTROL_STRATEGY = getProperty( - MULTICAST_FLOW_CONTROL_STRATEGY_PROP_NAME, "io.aeron.driver.MaxMulticastFlowControl"); + MULTICAST_FLOW_CONTROL_STRATEGY_PROP_NAME, MULTICAST_FLOW_CONTROL_STRATEGY_DEFAULT); /** * Property name for {@link FlowControlSupplier} to be employed for unicast channels. */ + @Config( + expectedCDefault = "aeron_unicast_flow_control_strategy_supplier", + defaultType = DefaultType.STRING, + defaultString = "io.aeron.driver.DefaultUnicastFlowControlSupplier") public static final String UNICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME = "aeron.unicast.FlowControl.supplier"; /** * Property name for {@link FlowControlSupplier} to be employed for unicast channels. */ + @Config( + expectedCDefault = "aeron_max_multicast_flow_control_strategy_supplier", + defaultType = DefaultType.STRING, + defaultString = "io.aeron.driver.DefaultMulticastFlowControlSupplier") public static final String MULTICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME = "aeron.multicast.FlowControl.supplier"; @@ -461,6 +601,7 @@ public final class Configuration * Length of the maximum transmission unit of the media driver's protocol. If this is greater * than the network MTU for UDP then the packet will be fragmented and can amplify the impact of loss. */ + @Config public static final String MTU_LENGTH_PROP_NAME = "aeron.mtu.length"; /** @@ -469,47 +610,78 @@ public final class Configuration *

    * On networks that suffer little congestion then a larger value can be used to reduce syscall costs. */ + @Config public static final int MTU_LENGTH_DEFAULT = 1408; /** * Length of the maximum transmission unit of the media driver's protocol for IPC. This can be larger than the * UDP version but if recorded replay needs to be considered. */ + @Config public static final String IPC_MTU_LENGTH_PROP_NAME = "aeron.ipc.mtu.length"; + /** + */ + @Config + public static final int IPC_MTU_LENGTH_DEFAULT = MTU_LENGTH_DEFAULT; + /** * {@link ThreadingMode} to be used by the Aeron {@link MediaDriver}. */ + @Config( + expectedCDefault = "AERON_THREADING_MODE_DEDICATED", + defaultType = DefaultType.STRING, + defaultString = "DEDICATED") public static final String THREADING_MODE_PROP_NAME = "aeron.threading.mode"; /** * Interval between checks for timers and timeouts. */ + @Config public static final String TIMER_INTERVAL_PROP_NAME = "aeron.timer.interval"; /** * Default interval between checks for timers and timeouts. */ + @Config( + id = "TIMER_INTERVAL", + expectedCDefaultFieldName = "AERON_TIMER_INTERVAL_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 1000 * 1000 * 1000) public static final long DEFAULT_TIMER_INTERVAL_NS = TimeUnit.SECONDS.toNanos(1); /** * Timeout between a counter being freed and being available to be reused. */ + @Config public static final String COUNTER_FREE_TO_REUSE_TIMEOUT_PROP_NAME = "aeron.counters.free.to.reuse.timeout"; /** * Default timeout between a counter being freed and being available to be reused. */ + @Config( + id = "COUNTER_FREE_TO_REUSE_TIMEOUT", + expectedCDefaultFieldName = "AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 1000 * 1000 * 1000) public static final long DEFAULT_COUNTER_FREE_TO_REUSE_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(1); /** * Property name for {@link SendChannelEndpointSupplier}. */ + @Config( + existsInC = false, + defaultType = DefaultType.STRING, + defaultString = "io.aeron.driver.DefaultSendChannelEndpointSupplier") public static final String SEND_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME = "aeron.SendChannelEndpoint.supplier"; /** * Property name for {@link ReceiveChannelEndpointSupplier}. */ + @Config( + existsInC = false, + defaultType = DefaultType.STRING, + defaultString = "io.aeron.driver.DefaultReceiveChannelEndpointSupplier") public static final String RECEIVE_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME = "aeron.ReceiveChannelEndpoint.supplier"; /** @@ -518,34 +690,43 @@ public final class Configuration * Replaced by {@link #RECEIVER_GROUP_TAG_PROP_NAME}. */ @Deprecated + @Config(defaultType = DefaultType.STRING, defaultString = "", existsInC = false) public static final String SM_APPLICATION_SPECIFIC_FEEDBACK_PROP_NAME = "aeron.flow.control.sm.applicationSpecificFeedback"; /** * Property name for {@link CongestionControlSupplier} to be employed for receivers. */ + @Config( + expectedCDefault = "aeron_congestion_control_default_strategy_supplier", + defaultType = DefaultType.STRING, + defaultString = "io.aeron.driver.DefaultCongestionControlSupplier") public static final String CONGESTION_CONTROL_STRATEGY_SUPPLIER_PROP_NAME = "aeron.CongestionControl.supplier"; /** * Property name for low end of the publication reserved session-id range which will not be automatically assigned. */ + @Config public static final String PUBLICATION_RESERVED_SESSION_ID_LOW_PROP_NAME = "aeron.publication.reserved.session.id.low"; /** * Low-end of the publication reserved session-id range which will not be automatically assigned. */ + @Config public static final int PUBLICATION_RESERVED_SESSION_ID_LOW_DEFAULT = -1; /** * High-end of the publication reserved session-id range which will not be automatically assigned. */ + @Config public static final String PUBLICATION_RESERVED_SESSION_ID_HIGH_PROP_NAME = "aeron.publication.reserved.session.id.high"; /** * High-end of the publication reserved session-id range which will not be automatically assigned. */ + @Config public static final int PUBLICATION_RESERVED_SESSION_ID_HIGH_DEFAULT = 1000; /** @@ -576,85 +757,120 @@ public final class Configuration /** * Expected size of multicast receiver groups property name. */ + @Config public static final String NAK_MULTICAST_GROUP_SIZE_PROP_NAME = "aeron.nak.multicast.group.size"; /** * Default multicast receiver group size estimate for NAK delay randomisation. */ + @Config public static final int NAK_MULTICAST_GROUP_SIZE_DEFAULT = 10; /** * Max backoff time for multicast NAK delay randomisation in nanoseconds. */ + @Config public static final String NAK_MULTICAST_MAX_BACKOFF_PROP_NAME = "aeron.nak.multicast.max.backoff"; /** * Default max backoff for NAK delay randomisation in nanoseconds. */ + @Config( + id = "NAK_MULTICAST_MAX_BACKOFF", + expectedCDefaultFieldName = "AERON_NAK_MULTICAST_MAX_BACKOFF_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 10L * 1000 * 1000) public static final long NAK_MAX_BACKOFF_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(10); /** * Unicast NAK delay in nanoseconds property name. */ + @Config public static final String NAK_UNICAST_DELAY_PROP_NAME = "aeron.nak.unicast.delay"; /** * Default Unicast NAK delay in nanoseconds. */ + @Config( + expectedCDefaultFieldName = "AERON_NAK_UNICAST_DELAY_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 100 * 1000) public static final long NAK_UNICAST_DELAY_DEFAULT_NS = TimeUnit.MICROSECONDS.toNanos(100); /** * Unicast NAK retry delay ratio property name. */ + @Config public static final String NAK_UNICAST_RETRY_DELAY_RATIO_PROP_NAME = "aeron.nak.unicast.retry.delay.ratio"; /** * Default Unicast NAK retry delay ratio. */ + @Config public static final long NAK_UNICAST_RETRY_DELAY_RATIO_DEFAULT = 100; /** * Property for setting how long to delay before sending a retransmit after receiving a NAK. */ + @Config public static final String RETRANSMIT_UNICAST_DELAY_PROP_NAME = "aeron.retransmit.unicast.delay"; /** * Default delay before retransmission of data for unicast in nanoseconds. */ + @Config( + expectedCDefaultFieldName = "AERON_RETRANSMIT_UNICAST_DELAY_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 0) public static final long RETRANSMIT_UNICAST_DELAY_DEFAULT_NS = TimeUnit.NANOSECONDS.toNanos(0); /** * Property for setting how long to linger after delay on a NAK before responding to another NAK. */ + @Config public static final String RETRANSMIT_UNICAST_LINGER_PROP_NAME = "aeron.retransmit.unicast.linger"; /** * Default delay for linger for unicast in nanoseconds. */ + @Config( + expectedCDefaultFieldName = "AERON_RETRANSMIT_UNICAST_LINGER_NS_DEFAULT", + defaultType = DefaultType.LONG, + defaultLong = 10L * 1000 * 1000) public static final long RETRANSMIT_UNICAST_LINGER_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(10); /** * Property name of the timeout for when an untethered subscription that is outside the window limit will * participate in local flow control. */ + @Config public static final String UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME = "aeron.untethered.window.limit.timeout"; /** * Default timeout for when an untethered subscription that is outside the window limit will participate in * local flow control. */ + @Config( + defaultType = DefaultType.LONG, + defaultLong = 5_000_000_000L, + expectedCDefaultFieldName = "AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_DEFAULT") public static final long UNTETHERED_WINDOW_LIMIT_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** * Property name of the timeout for when an untethered subscription is resting after not being able to keep up * before it is allowed to rejoin a stream. */ + @Config public static final String UNTETHERED_RESTING_TIMEOUT_PROP_NAME = "aeron.untethered.resting.timeout"; /** * Default timeout for when an untethered subscription is resting after not being able to keep up * before it is allowed to rejoin a stream. */ + @Config( + defaultType = DefaultType.LONG, + defaultLong = 10_000_000_000L, + expectedCDefaultFieldName = "AERON_UNTETHERED_RESTING_TIMEOUT_NS_DEFAULT") public static final long UNTETHERED_RESTING_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** @@ -665,45 +881,68 @@ public final class Configuration /** * Property name for the class used to validate if a driver should terminate based on token. */ + @Config( + defaultType = DefaultType.STRING, + defaultString = "io.aeron.driver.DefaultDenyTerminationValidator", + expectedCDefault = "aeron_driver_termination_validator_default_deny", + skipCDefaultValidation = true) public static final String TERMINATION_VALIDATOR_PROP_NAME = "aeron.driver.termination.validator"; /** * Property name for default boolean value for if a stream can be rejoined. True to allow stream rejoin. */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true) public static final String REJOIN_STREAM_PROP_NAME = "aeron.rejoin.stream"; /** * Property name for default group tag (gtag) to send in all Status Messages. */ + @Config( + defaultType = DefaultType.LONG, + defaultLong = 0, + expectedCDefaultFieldName = "AERON_RECEIVER_GROUP_TAG_VALUE_DEFAULT", + expectedCDefault = "-1") public static final String RECEIVER_GROUP_TAG_PROP_NAME = "aeron.receiver.group.tag"; /** * Property name for default group tag (gtag) used by the tagged flow control strategy to group receivers. */ + @Config(defaultType = DefaultType.LONG, defaultLong = -1) public static final String FLOW_CONTROL_GROUP_TAG_PROP_NAME = "aeron.flow.control.group.tag"; /** * Property name for default minimum group size used by flow control strategies to determine connectivity. */ + @Config(defaultType = DefaultType.INT, defaultInt = 0) public static final String FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME = "aeron.flow.control.group.min.size"; /** * Default value for the receiver timeout used to determine if the receiver should still be monitored for * flow control purposes. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 5_000_000_000L) public static final long FLOW_CONTROL_RECEIVER_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** * Property name for flow control timeout after which with no status messages the receiver is considered gone. */ + @Config( + expectedCEnvVarFieldName = "AERON_MIN_MULTICAST_FLOW_CONTROL_RECEIVER_TIMEOUT_ENV_VAR", + expectedCEnvVar = "AERON_MIN_MULTICAST_FLOW_CONTROL_RECEIVER_TIMEOUT", + expectedCDefaultFieldName = "AERON_FLOW_CONTROL_RECEIVER_TIMEOUT_NS_DEFAULT") public static final String FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME = "aeron.flow.control.receiver.timeout"; + /** + */ + // TODO is this supposed to be deprecated? + @Config(defaultType = DefaultType.LONG, defaultLong = 5_000_000_000L, hasContext = false, existsInC = false) private static final String MIN_FLOW_CONTROL_TIMEOUT_OLD_PROP_NAME = "aeron.MinMulticastFlowControl.receiverTimeout"; /** * Property name for resolver name of the Media Driver used in name resolution. */ + @Config(defaultType = DefaultType.STRING, defaultString = "", skipCDefaultValidation = true) public static final String RESOLVER_NAME_PROP_NAME = "aeron.driver.resolver.name"; /** @@ -711,6 +950,7 @@ public final class Configuration * * @see #RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME */ + @Config(defaultType = DefaultType.STRING, defaultString = "", skipCDefaultValidation = true) public static final String RESOLVER_INTERFACE_PROP_NAME = "aeron.driver.resolver.interface"; /** @@ -719,66 +959,102 @@ public final class Configuration * * @see #RESOLVER_INTERFACE_PROP_NAME */ + @Config(defaultType = DefaultType.STRING, defaultString = "", skipCDefaultValidation = true) public static final String RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME = "aeron.driver.resolver.bootstrap.neighbor"; /** * Property name for re-resolution check interval for resolving names to IP address. */ + @Config public static final String RE_RESOLUTION_CHECK_INTERVAL_PROP_NAME = "aeron.driver.reresolution.check.interval"; /** * Default value for the re-resolution check interval. */ + @Config( + defaultType = DefaultType.LONG, + defaultLong = 1_000_000_000L, + expectedCDefaultFieldName = "AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL_NS_DEFAULT") public static final long RE_RESOLUTION_CHECK_INTERVAL_DEFAULT_NS = TimeUnit.SECONDS.toNanos(1); /** * Property name for threshold value for the conductor work cycle threshold to track for being exceeded. */ + @Config public static final String CONDUCTOR_CYCLE_THRESHOLD_PROP_NAME = "aeron.driver.conductor.cycle.threshold"; /** * Default threshold value for the conductor work cycle threshold to track for being exceeded. */ + @Config( + defaultType = DefaultType.LONG, + defaultLong = 1_000_000_000L, + expectedCDefaultFieldName = "AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD_NS_DEFAULT") public static final long CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000); /** * Property name for threshold value for the sender work cycle threshold to track for being exceeded. */ + @Config public static final String SENDER_CYCLE_THRESHOLD_PROP_NAME = "aeron.driver.sender.cycle.threshold"; /** * Default threshold value for the sender work cycle threshold to track for being exceeded. */ + @Config( + defaultType = DefaultType.LONG, + defaultLong = 1_000_000_000L, + expectedCDefaultFieldName = "AERON_DRIVER_SENDER_CYCLE_THRESHOLD_NS_DEFAULT") public static final long SENDER_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000); /** * Property name for threshold value for the receiver work cycle threshold to track for being exceeded. */ + @Config public static final String RECEIVER_CYCLE_THRESHOLD_PROP_NAME = "aeron.driver.receiver.cycle.threshold"; /** * Default threshold value for the receiver work cycle threshold to track for being exceeded. */ + @Config( + defaultType = DefaultType.LONG, + defaultLong = 1_000_000_000, + expectedCDefaultFieldName = "AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD_NS_DEFAULT") public static final long RECEIVER_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000); /** * Property name for threshold value for the name resolution threshold to track for being exceeded. */ + @Config( + expectedCEnvVarFieldName = "AERON_DRIVER_NAME_RESOLVER_THRESHOLD_ENV_VAR", + expectedCEnvVar = "AERON_DRIVER_NAME_RESOLVER_THRESHOLD", + expectedCDefaultFieldName = "AERON_DRIVER_NAME_RESOLVER_THRESHOLD_NS_DEFAULT") public static final String NAME_RESOLVER_THRESHOLD_PROP_NAME = "aeron.name.resolver.threshold"; /** * Default threshold value for the name resolution threshold to track for being exceeded. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 5_000_000_000L) public static final long NAME_RESOLVER_THRESHOLD_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** * Property name for wildcard port range for the Sender. */ + @Config( + defaultType = DefaultType.STRING, + defaultString = "", + expectedCEnvVarFieldName = "AERON_DRIVER_SENDER_WILDCARD_PORT_RANGE_ENV_VAR", + skipCDefaultValidation = true) public static final String SENDER_WILDCARD_PORT_RANGE_PROP_NAME = "aeron.sender.wildcard.port.range"; /** * Property name for wildcard port range for the Receiver. */ + @Config( + defaultType = DefaultType.STRING, + defaultString = "", + expectedCEnvVarFieldName = "AERON_DRIVER_RECEIVER_WILDCARD_PORT_RANGE_ENV_VAR", + skipCDefaultValidation = true) public static final String RECEIVER_WILDCARD_PORT_RANGE_PROP_NAME = "aeron.receiver.wildcard.port.range"; /** @@ -787,6 +1063,7 @@ public final class Configuration * * @since 1.44.0 */ + @Config(defaultType = DefaultType.INT, defaultInt = 1) public static final String ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME = "aeron.driver.async.executor.threads"; public static final String STREAM_SESSION_LIMIT_PROP_NAME = "aeron.driver.stream.session.limit"; @@ -1301,7 +1578,7 @@ public static int mtuLength() */ public static int ipcMtuLength() { - return getSizeAsInt(IPC_MTU_LENGTH_PROP_NAME, MTU_LENGTH_DEFAULT); + return getSizeAsInt(IPC_MTU_LENGTH_PROP_NAME, IPC_MTU_LENGTH_DEFAULT); } /** @@ -1654,7 +1931,7 @@ public static IdleStrategy agentIdleStrategy(final String strategyName, final St public static IdleStrategy senderIdleStrategy(final StatusIndicator controllableStatus) { return agentIdleStrategy( - getProperty(SENDER_IDLE_STRATEGY_PROP_NAME, DEFAULT_IDLE_STRATEGY), controllableStatus); + getProperty(SENDER_IDLE_STRATEGY_PROP_NAME, SENDER_IDLE_STRATEGY_DEFAULT), controllableStatus); } /** @@ -1667,7 +1944,7 @@ public static IdleStrategy senderIdleStrategy(final StatusIndicator controllable public static IdleStrategy receiverIdleStrategy(final StatusIndicator controllableStatus) { return agentIdleStrategy( - getProperty(RECEIVER_IDLE_STRATEGY_PROP_NAME, DEFAULT_IDLE_STRATEGY), controllableStatus); + getProperty(RECEIVER_IDLE_STRATEGY_PROP_NAME, RECEIVER_IDLE_STRATEGY_DEFAULT), controllableStatus); } /** @@ -1682,7 +1959,7 @@ public static IdleStrategy receiverIdleStrategy(final StatusIndicator controllab public static IdleStrategy conductorIdleStrategy(final StatusIndicator controllableStatus) { return agentIdleStrategy( - getProperty(CONDUCTOR_IDLE_STRATEGY_PROP_NAME, DEFAULT_IDLE_STRATEGY), controllableStatus); + getProperty(CONDUCTOR_IDLE_STRATEGY_PROP_NAME, CONDUCTOR_IDLE_STRATEGY_DEFAULT), controllableStatus); } /** @@ -1696,8 +1973,8 @@ public static IdleStrategy conductorIdleStrategy(final StatusIndicator controlla */ public static IdleStrategy sharedNetworkIdleStrategy(final StatusIndicator controllableStatus) { - return agentIdleStrategy( - getProperty(SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME, DEFAULT_IDLE_STRATEGY), controllableStatus); + return agentIdleStrategy(getProperty(SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME, + SHARED_NETWORK_IDLE_STRATEGY_DEFAULT), controllableStatus); } /** @@ -1712,7 +1989,7 @@ public static IdleStrategy sharedNetworkIdleStrategy(final StatusIndicator contr public static IdleStrategy sharedIdleStrategy(final StatusIndicator controllableStatus) { return agentIdleStrategy( - getProperty(SHARED_IDLE_STRATEGY_PROP_NAME, DEFAULT_IDLE_STRATEGY), controllableStatus); + getProperty(SHARED_IDLE_STRATEGY_PROP_NAME, SHARED_IDLE_STRATEGY_DEFAULT), controllableStatus); } /** diff --git a/aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java b/aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java index 732927de23..d2e65b3305 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java +++ b/aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java @@ -18,6 +18,7 @@ import io.aeron.Aeron; import io.aeron.CncFileDescriptor; import io.aeron.CommonContext; +import io.aeron.config.Config; import io.aeron.driver.buffer.FileStoreLogFactory; import io.aeron.driver.buffer.LogFactory; import io.aeron.driver.exceptions.ActiveDriverException; @@ -753,6 +754,7 @@ public Context countersValuesBuffer(final UnsafeBuffer countersValuesBuffer) * @return true if the configuration should be printed on start. * @see Configuration#PRINT_CONFIGURATION_ON_START_PROP_NAME */ + @Config public boolean printConfigurationOnStart() { return printConfigurationOnStart; @@ -790,6 +792,7 @@ public Context useWindowsHighResTimer(final boolean useWindowsHighResTimers) * @return true if an attempt be made to use the high resolution timers for waiting on Windows. * @see Configuration#USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME */ + @Config public boolean useWindowsHighResTimer() { return useWindowsHighResTimer; @@ -801,6 +804,7 @@ public boolean useWindowsHighResTimer() * @return should a warning be issued if the {@link #aeronDirectoryName()} exists? * @see Configuration#DIR_WARN_IF_EXISTS_PROP_NAME */ + @Config(id = "DIR_WARN_IF_EXISTS") public boolean warnIfDirectoryExists() { return warnIfDirectoryExists; @@ -827,6 +831,7 @@ public Context warnIfDirectoryExists(final boolean warnIfDirectoryExists) * @return true when directory will be recreated without checks, otherwise false. * @see Configuration#DIR_DELETE_ON_START_PROP_NAME */ + @Config public boolean dirDeleteOnStart() { return dirDeleteOnStart; @@ -853,6 +858,7 @@ public Context dirDeleteOnStart(final boolean dirDeleteOnStart) * @return true when directory will be deleted, otherwise false. * @see Configuration#DIR_DELETE_ON_SHUTDOWN_PROP_NAME */ + @Config public boolean dirDeleteOnShutdown() { return dirDeleteOnShutdown; @@ -877,6 +883,7 @@ public Context dirDeleteOnShutdown(final boolean dirDeleteOnShutdown) * @return should the term buffers be created with sparse files? * @see Configuration#TERM_BUFFER_SPARSE_FILE_PROP_NAME */ + @Config public boolean termBufferSparseFile() { return termBufferSparseFile; @@ -901,6 +908,7 @@ public Context termBufferSparseFile(final boolean termBufferSparseFile) * @return length of the {@link RingBuffer} for sending commands to the driver conductor from clients. * @see Configuration#CONDUCTOR_BUFFER_LENGTH_PROP_NAME */ + @Config public int conductorBufferLength() { return conductorBufferLength; @@ -925,6 +933,7 @@ public Context conductorBufferLength(final int length) * @return length of the {@link BroadcastTransmitter} buffer for sending events to the clients. * @see Configuration#TO_CLIENTS_BUFFER_LENGTH_PROP_NAME */ + @Config public int toClientsBufferLength() { return toClientsBufferLength; @@ -949,6 +958,7 @@ public Context toClientsBufferLength(final int length) * @return length of the buffer for storing values by the {@link CountersManager}. * @see Configuration#COUNTERS_VALUES_BUFFER_LENGTH_PROP_NAME */ + @Config(id = "COUNTERS_VALUES_BUFFER_LENGTH") public int counterValuesBufferLength() { return counterValuesBufferLength; @@ -973,6 +983,7 @@ public Context counterValuesBufferLength(final int length) * @return length of the {@link DistinctErrorLog} buffer for recording exceptions. * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME */ + @Config public int errorBufferLength() { return errorBufferLength; @@ -997,6 +1008,7 @@ public Context errorBufferLength(final int length) * @return true if the driver should perform storage checks when allocating files. * @see Configuration#PERFORM_STORAGE_CHECKS_PROP_NAME */ + @Config public boolean performStorageChecks() { return performStorageChecks; @@ -1021,6 +1033,7 @@ public Context performStorageChecks(final boolean performStorageChecks) * @return the threshold below which storage warnings are issued. * @see Configuration#LOW_FILE_STORE_WARNING_THRESHOLD_PROP_NAME */ + @Config(id = "LOW_FILE_STORE_WARNING_THRESHOLD") public long lowStorageWarningThreshold() { return lowStorageWarningThreshold; @@ -1045,6 +1058,7 @@ public Context lowStorageWarningThreshold(final long lowStorageWarningThreshold) * @return the length in bytes of the loss report buffer. * @see Configuration#LOSS_REPORT_BUFFER_LENGTH_PROP_NAME */ + @Config public int lossReportBufferLength() { return lossReportBufferLength; @@ -1069,6 +1083,7 @@ public Context lossReportBufferLength(final int length) * @return page size for alignment of all files. * @see Configuration#FILE_PAGE_SIZE_PROP_NAME */ + @Config public int filePageSize() { return filePageSize; @@ -1093,6 +1108,7 @@ public Context filePageSize(final int filePageSize) * @return nanoseconds between checks for timers and timeouts. * @see Configuration#TIMER_INTERVAL_PROP_NAME */ + @Config public long timerIntervalNs() { return timerIntervalNs; @@ -1117,6 +1133,7 @@ public Context timerIntervalNs(final long timerIntervalNs) * @return nanoseconds that an Image will be kept alive for its subscribers to consume it. * @see Configuration#IMAGE_LIVENESS_TIMEOUT_PROP_NAME */ + @Config public long imageLivenessTimeoutNs() { return imageLivenessTimeoutNs; @@ -1141,6 +1158,7 @@ public Context imageLivenessTimeoutNs(final long timeout) * @return nanoseconds that a publication will linger once it is drained. * @see Configuration#PUBLICATION_LINGER_PROP_NAME */ + @Config(id = "PUBLICATION_LINGER") public long publicationLingerTimeoutNs() { return publicationLingerTimeoutNs; @@ -1166,6 +1184,7 @@ public Context publicationLingerTimeoutNs(final long timeoutNs) * @return timeout that an untethered subscription outside the window limit will participate in flow control. * @see Configuration#UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME */ + @Config public long untetheredWindowLimitTimeoutNs() { return untetheredWindowLimitTimeoutNs; @@ -1192,6 +1211,7 @@ public Context untetheredWindowLimitTimeoutNs(final long timeoutNs) * @return timeout that an untethered subscription is resting before being allowed to rejoin a stream. * @see Configuration#UNTETHERED_RESTING_TIMEOUT_PROP_NAME */ + @Config public long untetheredRestingTimeoutNs() { return untetheredRestingTimeoutNs; @@ -1241,6 +1261,7 @@ public Context retransmitUnicastDelayNs(final long retransmitUnicastDelayNs) * @return how long to linger after delay on a NAK before responding to another NAK. * @see Configuration#RETRANSMIT_UNICAST_LINGER_PROP_NAME */ + @Config public long retransmitUnicastLingerNs() { return retransmitUnicastLingerNs; @@ -1265,6 +1286,7 @@ public Context retransmitUnicastLingerNs(final long retransmitUnicastLingerNs) * @return delay before retransmitting after a NAK. * @see Configuration#NAK_UNICAST_DELAY_PROP_NAME */ + @Config public long nakUnicastDelayNs() { return nakUnicastDelayNs; @@ -1290,6 +1312,7 @@ public Context nakUnicastDelayNs(final long nakUnicastDelayNs) * @return ratio to apply to the retry delay for unicast * @see #nakUnicastRetryDelayRatio(long) */ + @Config public long nakUnicastRetryDelayRatio() { return nakUnicastRetryDelayRatio; @@ -1317,6 +1340,7 @@ public Context nakUnicastRetryDelayRatio(final long nakUnicastRetryDelayRatio) * @return maximum time to backoff before sending a NAK on multicast. * @see Configuration#NAK_MULTICAST_MAX_BACKOFF_PROP_NAME */ + @Config public long nakMulticastMaxBackoffNs() { return nakMulticastMaxBackoffNs; @@ -1341,6 +1365,7 @@ public Context nakMulticastMaxBackoffNs(final long nakMulticastMaxBackoffNs) * @return estimate of the multicast receiver group size on a stream. * @see Configuration#NAK_MULTICAST_GROUP_SIZE_PROP_NAME */ + @Config public int nakMulticastGroupSize() { return nakMulticastGroupSize; @@ -1365,6 +1390,7 @@ public Context nakMulticastGroupSize(final int nakMulticastGroupSize) * @return time in nanoseconds after which a client is considered dead if a keep alive is not received. * @see Configuration#CLIENT_LIVENESS_TIMEOUT_PROP_NAME */ + @Config public long clientLivenessTimeoutNs() { return CommonContext.checkDebugTimeout(clientLivenessTimeoutNs, TimeUnit.NANOSECONDS); @@ -1389,6 +1415,7 @@ public Context clientLivenessTimeoutNs(final long timeoutNs) * @return time in nanoseconds after which a status message will be sent if data is flowing slowly. * @see Configuration#STATUS_MESSAGE_TIMEOUT_PROP_NAME */ + @Config public long statusMessageTimeoutNs() { return statusMessageTimeoutNs; @@ -1413,6 +1440,7 @@ public Context statusMessageTimeoutNs(final long statusMessageTimeoutNs) * @return time in nanoseconds after which a freed counter may be reused. * @see Configuration#COUNTER_FREE_TO_REUSE_TIMEOUT_PROP_NAME */ + @Config public long counterFreeToReuseTimeoutNs() { return counterFreeToReuseTimeoutNs; @@ -1442,6 +1470,7 @@ public Context counterFreeToReuseTimeoutNs(final long counterFreeToReuseTimeoutN * @return timeout in nanoseconds after which a publication will be unblocked. * @see Configuration#PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME */ + @Config public long publicationUnblockTimeoutNs() { return CommonContext.checkDebugTimeout(publicationUnblockTimeoutNs, TimeUnit.NANOSECONDS, 1.5); @@ -1472,6 +1501,7 @@ public Context publicationUnblockTimeoutNs(final long timeoutNs) * @return timeout in nanoseconds after which a publication is considered not connected. * @see Configuration#PUBLICATION_CONNECTION_TIMEOUT_PROP_NAME */ + @Config public long publicationConnectionTimeoutNs() { return publicationConnectionTimeoutNs; @@ -1499,6 +1529,7 @@ public Context publicationConnectionTimeoutNs(final long timeoutNs) * @return true if a spy subscription should simulate a connection to a network publication. * @see Configuration#SPIES_SIMULATE_CONNECTION_PROP_NAME */ + @Config public boolean spiesSimulateConnection() { return spiesSimulateConnection; @@ -1528,6 +1559,7 @@ public Context spiesSimulateConnection(final boolean spiesSimulateConnection) * @see Configuration#RELIABLE_STREAM_PROP_NAME * @see CommonContext#RELIABLE_STREAM_PARAM_NAME */ + @Config public boolean reliableStream() { return reliableStream; @@ -1558,6 +1590,7 @@ public Context reliableStream(final boolean reliableStream) * @see Configuration#TETHER_SUBSCRIPTIONS_PROP_NAME * @see CommonContext#TETHER_PARAM_NAME */ + @Config public boolean tetherSubscriptions() { return tetherSubscriptions; @@ -1624,6 +1657,7 @@ public Context receiverGroupConsideration(final InferableBoolean receiverGroupCo * @see Configuration#REJOIN_STREAM_PROP_NAME * @see CommonContext#REJOIN_PARAM_NAME */ + @Config public boolean rejoinStream() { return rejoinStream; @@ -1651,6 +1685,7 @@ public Context rejoinStream(final boolean rejoinStream) * @return default length for a term buffer on a network publication. * @see Configuration#TERM_BUFFER_LENGTH_PROP_NAME */ + @Config(id = "TERM_BUFFER_LENGTH") public int publicationTermBufferLength() { return publicationTermBufferLength; @@ -1677,6 +1712,7 @@ public Context publicationTermBufferLength(final int termBufferLength) * @return default length for a term buffer on an IPC publication. * @see Configuration#IPC_TERM_BUFFER_LENGTH_PROP_NAME */ + @Config public int ipcTermBufferLength() { return ipcTermBufferLength; @@ -1703,6 +1739,7 @@ public Context ipcTermBufferLength(final int termBufferLength) * @return default length for a term buffer window on a network publication. * @see Configuration#PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME */ + @Config public int publicationTermWindowLength() { return publicationTermWindowLength; @@ -1727,6 +1764,7 @@ public Context publicationTermWindowLength(final int termWindowLength) * @return default length for a term buffer window on an IPC publication. * @see Configuration#IPC_PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME */ + @Config public int ipcPublicationTermWindowLength() { return ipcPublicationTermWindowLength; @@ -1753,6 +1791,7 @@ public Context ipcPublicationTermWindowLength(final int termWindowLength) * @return The initial window for in flight data on a connection * @see Configuration#INITIAL_WINDOW_LENGTH_PROP_NAME */ + @Config public int initialWindowLength() { return initialWindowLength; @@ -1779,6 +1818,7 @@ public Context initialWindowLength(final int initialWindowLength) * @return the socket send buffer length. * @see Configuration#SOCKET_SNDBUF_LENGTH_PROP_NAME */ + @Config public int socketSndbufLength() { return socketSndbufLength; @@ -1803,6 +1843,7 @@ public Context socketSndbufLength(final int socketSndbufLength) * @return the socket send buffer length. * @see Configuration#SOCKET_RCVBUF_LENGTH_PROP_NAME */ + @Config public int socketRcvbufLength() { return socketRcvbufLength; @@ -1827,6 +1868,7 @@ public Context socketRcvbufLength(final int socketRcvbufLength) * @return TTL value to be used for multicast sockets. * @see Configuration#SOCKET_MULTICAST_TTL_PROP_NAME */ + @Config public int socketMulticastTtl() { return socketMulticastTtl; @@ -1855,6 +1897,7 @@ public Context socketMulticastTtl(final int ttl) * @return MTU in bytes for datagrams sent to the network. * @see Configuration#MTU_LENGTH_PROP_NAME */ + @Config public int mtuLength() { return mtuLength; @@ -1886,6 +1929,7 @@ public Context mtuLength(final int mtuLength) * @return MTU in bytes for message fragments. * @see Configuration#IPC_MTU_LENGTH_PROP_NAME */ + @Config public int ipcMtuLength() { return ipcMtuLength; @@ -2051,6 +2095,7 @@ public Context receiverCachedNanoClock(final CachedNanoClock clock) * @return {@link ThreadingMode} that should be used for the driver. * @see Configuration#THREADING_MODE_PROP_NAME */ + @Config public ThreadingMode threadingMode() { return threadingMode; @@ -2206,6 +2251,7 @@ public Context sharedNetworkThreadFactory(final ThreadFactory factory) * @see Configuration#ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME * @since 1.44.0 */ + @Config public int asyncTaskExecutorThreads() { return asyncTaskExecutorThreads; @@ -2255,6 +2301,7 @@ public Context asyncTaskExecutor(final Executor asyncTaskExecutor) * @return {@link IdleStrategy} to be used by the {@link Sender} when in {@link ThreadingMode#DEDICATED}. * @see Configuration#SENDER_IDLE_STRATEGY_PROP_NAME */ + @Config public IdleStrategy senderIdleStrategy() { return senderIdleStrategy; @@ -2279,6 +2326,7 @@ public Context senderIdleStrategy(final IdleStrategy strategy) * @return {@link IdleStrategy} used by the {@link Receiver} when in {@link ThreadingMode#DEDICATED}. * @see Configuration#RECEIVER_IDLE_STRATEGY_PROP_NAME */ + @Config public IdleStrategy receiverIdleStrategy() { return receiverIdleStrategy; @@ -2304,6 +2352,7 @@ public Context receiverIdleStrategy(final IdleStrategy strategy) * @return {@link IdleStrategy} used by the {@link DriverConductor} * @see Configuration#CONDUCTOR_IDLE_STRATEGY_PROP_NAME */ + @Config public IdleStrategy conductorIdleStrategy() { return conductorIdleStrategy; @@ -2330,6 +2379,7 @@ public Context conductorIdleStrategy(final IdleStrategy strategy) * @return {@link IdleStrategy} used by the {@link Sender} and {@link Receiver}. * @see Configuration#SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME */ + @Config public IdleStrategy sharedNetworkIdleStrategy() { return sharedNetworkIdleStrategy; @@ -2356,6 +2406,7 @@ public Context sharedNetworkIdleStrategy(final IdleStrategy strategy) * @return {@link IdleStrategy} used by the {@link Sender}, {@link Receiver} and {@link DriverConductor}. * @see Configuration#SHARED_IDLE_STRATEGY_PROP_NAME */ + @Config public IdleStrategy sharedIdleStrategy() { return sharedIdleStrategy; @@ -2382,6 +2433,7 @@ public Context sharedIdleStrategy(final IdleStrategy strategy) * @return the supplier of dynamically created {@link SendChannelEndpoint} subclasses. * @see Configuration#SEND_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME */ + @Config public SendChannelEndpointSupplier sendChannelEndpointSupplier() { return sendChannelEndpointSupplier; @@ -2408,6 +2460,7 @@ public Context sendChannelEndpointSupplier(final SendChannelEndpointSupplier sup * @return the supplier of dynamically created {@link ReceiveChannelEndpoint} subclasses. * @see Configuration#RECEIVE_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME */ + @Config public ReceiveChannelEndpointSupplier receiveChannelEndpointSupplier() { return receiveChannelEndpointSupplier; @@ -2477,6 +2530,7 @@ public Context tempBuffer(final MutableDirectBuffer tempBuffer) * @return supplier of dynamically created {@link FlowControl} strategies for unicast connections. * @see Configuration#UNICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME */ + @Config(id = "UNICAST_FLOW_CONTROL_STRATEGY_SUPPLIER") public FlowControlSupplier unicastFlowControlSupplier() { return unicastFlowControlSupplier; @@ -2501,6 +2555,7 @@ public Context unicastFlowControlSupplier(final FlowControlSupplier flowControlS * @return supplier of dynamically created {@link FlowControl} strategies for multicast connections. * @see Configuration#MULTICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME */ + @Config(id = "MULTICAST_FLOW_CONTROL_STRATEGY_SUPPLIER") public FlowControlSupplier multicastFlowControlSupplier() { return multicastFlowControlSupplier; @@ -2525,6 +2580,7 @@ public Context multicastFlowControlSupplier(final FlowControlSupplier flowContro * @return timeout in ns. * @see Configuration#FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME */ + @Config public long flowControlReceiverTimeoutNs() { return flowControlReceiverTimeoutNs; @@ -2553,6 +2609,7 @@ public Context flowControlReceiverTimeoutNs(final long timeoutNs) * @see Configuration#SM_APPLICATION_SPECIFIC_FEEDBACK_PROP_NAME */ @Deprecated + @Config(id = "SM_APPLICATION_SPECIFIC_FEEDBACK") public byte[] applicationSpecificFeedback() { return applicationSpecificFeedback; @@ -2581,6 +2638,7 @@ public Context applicationSpecificFeedback(final byte[] asfBytes) * @return supplier of dynamically created {@link CongestionControl} strategies for individual connections. * @see Configuration#CONGESTION_CONTROL_STRATEGY_SUPPLIER_PROP_NAME */ + @Config(id = "CONGESTION_CONTROL_STRATEGY_SUPPLIER") public CongestionControlSupplier congestionControlSupplier() { return congestionControlSupplier; @@ -2749,6 +2807,7 @@ Context lossReport(final LossReport lossReport) * @see #publicationReservedSessionIdHigh() * @see Configuration#PUBLICATION_RESERVED_SESSION_ID_LOW_PROP_NAME */ + @Config public int publicationReservedSessionIdLow() { return publicationReservedSessionIdLow; @@ -2776,6 +2835,7 @@ public Context publicationReservedSessionIdLow(final int sessionId) * @see #publicationReservedSessionIdLow() * @see Configuration#PUBLICATION_RESERVED_SESSION_ID_HIGH_PROP_NAME */ + @Config public int publicationReservedSessionIdHigh() { return publicationReservedSessionIdHigh; @@ -2802,6 +2862,7 @@ public Context publicationReservedSessionIdHigh(final int sessionId) * @return {@link FeedbackDelayGenerator} for controlling the delay before sending a retransmit frame. * @see Configuration#RETRANSMIT_UNICAST_DELAY_PROP_NAME */ + @Config(id = "RETRANSMIT_UNICAST_DELAY") public FeedbackDelayGenerator retransmitUnicastDelayGenerator() { return retransmitUnicastDelayGenerator; @@ -2935,6 +2996,7 @@ public Context terminationValidator(final TerminationValidator validator) * * @return {@link TerminationValidator} to validate termination requests. */ + @Config public TerminationValidator terminationValidator() { return terminationValidator; @@ -2945,6 +3007,7 @@ public TerminationValidator terminationValidator() * * @return ratio for sending data to polling status messages in the Sender. */ + @Config(id = "SEND_TO_STATUS_POLL_RATIO") public int sendToStatusMessagePollRatio() { return sendToStatusMessagePollRatio; @@ -2968,6 +3031,7 @@ public Context sendToStatusMessagePollRatio(final int ratio) * @return group tag value or null if not set. * @see Configuration#RECEIVER_GROUP_TAG_PROP_NAME */ + @Config public Long receiverGroupTag() { return receiverGroupTag; @@ -2992,6 +3056,7 @@ public Context receiverGroupTag(final Long groupTag) * @return group tag value or null if not set. * @see Configuration#FLOW_CONTROL_GROUP_TAG_PROP_NAME */ + @Config public long flowControlGroupTag() { return flowControlGroupTag; @@ -3016,6 +3081,7 @@ public Context flowControlGroupTag(final long groupTag) * @return required group size. * @see Configuration#FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME */ + @Config public int flowControlGroupMinSize() { return flowControlGroupMinSize; @@ -3062,6 +3128,7 @@ public Context nameResolver(final NameResolver nameResolver) * @return name of the {@link MediaDriver}. * @see Configuration#RESOLVER_NAME_PROP_NAME */ + @Config public String resolverName() { return resolverName; @@ -3090,6 +3157,7 @@ public Context resolverName(final String resolverName) * @see Configuration#RESOLVER_INTERFACE_PROP_NAME * @see CommonContext#INTERFACE_PARAM_NAME */ + @Config public String resolverInterface() { return resolverInterface; @@ -3122,6 +3190,7 @@ public Context resolverInterface(final String resolverInterface) * @see Configuration#RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME * @see CommonContext#ENDPOINT_PARAM_NAME */ + @Config public String resolverBootstrapNeighbor() { return resolverBootstrapNeighbor; @@ -3152,6 +3221,7 @@ public Context resolverBootstrapNeighbor(final String resolverBootstrapNeighbor) * @see Configuration#RE_RESOLUTION_CHECK_INTERVAL_PROP_NAME * @see Configuration#RE_RESOLUTION_CHECK_INTERVAL_DEFAULT_NS */ + @Config public long reResolutionCheckIntervalNs() { return reResolutionCheckIntervalNs; @@ -3194,6 +3264,7 @@ public Context conductorCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the conductor work cycle time. */ + @Config public long conductorCycleThresholdNs() { return conductorCycleThresholdNs; @@ -3232,6 +3303,7 @@ public Context senderCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the sender work cycle time. */ + @Config public long senderCycleThresholdNs() { return senderCycleThresholdNs; @@ -3258,6 +3330,7 @@ public Context receiverCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the receiver work cycle time. */ + @Config public long receiverCycleThresholdNs() { return receiverCycleThresholdNs; @@ -3284,6 +3357,7 @@ public Context nameResolverThresholdNs(final long thresholdNs) * * @return threshold to track for the name resolution. */ + @Config public long nameResolverThresholdNs() { return nameResolverThresholdNs; @@ -3311,6 +3385,7 @@ public Context resourceFreeLimit(final int resourceFreeLimit) * @return limit on the number of resources that can be freed. * @since 1.41.0 */ + @Config public int resourceFreeLimit() { return resourceFreeLimit; @@ -3431,6 +3506,7 @@ public Context nameResolverTimeTracker(final DutyCycleTracker dutyCycleTracker) * * @return port range as a string in the format "low high". */ + @Config public String senderWildcardPortRange() { return senderWildcardPortRange; @@ -3458,6 +3534,7 @@ public Context senderWildcardPortRange(final String portRange) * * @return port range as a string in the format "low high". */ + @Config public String receiverWildcardPortRange() { return receiverWildcardPortRange; diff --git a/aeron-driver/src/main/java/io/aeron/driver/ext/DebugChannelEndpointConfiguration.java b/aeron-driver/src/main/java/io/aeron/driver/ext/DebugChannelEndpointConfiguration.java index a7deccaf1b..d9fbaef883 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/ext/DebugChannelEndpointConfiguration.java +++ b/aeron-driver/src/main/java/io/aeron/driver/ext/DebugChannelEndpointConfiguration.java @@ -15,6 +15,9 @@ */ package io.aeron.driver.ext; +import io.aeron.config.Config; +import io.aeron.config.DefaultType; + import static java.lang.Long.getLong; import static java.lang.System.getProperty; @@ -27,41 +30,49 @@ public class DebugChannelEndpointConfiguration /** * Property name for receiver inbound data loss rate. */ + @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false, existsInC = false) public static final String RECEIVE_DATA_LOSS_RATE_PROP_NAME = "aeron.debug.receive.data.loss.rate"; /** * Property name for receiver inbound data loss seed. */ + @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = false, existsInC = false) public static final String RECEIVE_DATA_LOSS_SEED_PROP_NAME = "aeron.debug.receive.data.loss.seed"; /** * Property name for receiver outbound control loss rate. */ + @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false, existsInC = false) public static final String RECEIVE_CONTROL_LOSS_RATE_PROP_NAME = "aeron.debug.receive.control.loss.rate"; /** * Property name for receiver outbound control loss seed. */ + @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = false, existsInC = false) public static final String RECEIVE_CONTROL_LOSS_SEED_PROP_NAME = "aeron.debug.receive.control.loss.seed"; /** * Property name for sender outbound data loss rate. */ + @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false, existsInC = false) public static final String SEND_DATA_LOSS_RATE_PROP_NAME = "aeron.debug.send.data.loss.rate"; /** * Property name for sender outbound data loss seed. */ + @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = false, existsInC = false) public static final String SEND_DATA_LOSS_SEED_PROP_NAME = "aeron.debug.send.data.loss.seed"; /** * Property name for sender inbound control loss rate. */ + @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false, existsInC = false) public static final String SEND_CONTROL_LOSS_RATE_PROP_NAME = "aeron.debug.send.control.loss.rate"; /** * Property name for sender inbound control loss seed. */ + @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = false, existsInC = false) public static final String SEND_CONTROL_LOSS_SEED_PROP_NAME = "aeron.debug.send.control.loss.seed"; private static final long RECEIVE_DATA_LOSS_SEED = getLong(RECEIVE_DATA_LOSS_SEED_PROP_NAME, -1); diff --git a/build.gradle b/build.gradle index eb3ec78c5c..2349dc42e8 100644 --- a/build.gradle +++ b/build.gradle @@ -468,6 +468,40 @@ project(':aeron-client') { signing { sign publishing.publications.aeronClient } + + tasks.register('validateConfigExpectations', JavaExec) { + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' + def sourceDir = 'src/main/c' + + inputs.files(configInfoFile) + + mainClass.set('io.aeron.config.validation.ValidateConfigExpectationsTask') + classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath + args = [configInfoFile, sourceDir] + } + + tasks.register('generateConfigDoc', JavaExec) { + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' + def generatedDocFile = "${buildDir}/generated-doc/out.md" + + inputs.files(configInfoFile) + outputs.files(generatedDocFile) + + mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask') + classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath + args = [configInfoFile, generatedDocFile] + } + + tasks.register('validateCounterExpectations', JavaExec) { + def counterInfoFile = 'build/generated/sources/headers/java/main/counter-info.dat' + def sourceDir = 'src/main/c' + + inputs.files(counterInfoFile) + + mainClass.set('io.aeron.counter.validation.ValidateCounterExpectationsTask') + classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath + args = [counterInfoFile, sourceDir] + } } project(':aeron-driver') { @@ -541,6 +575,29 @@ project(':aeron-driver') { signing { sign publishing.publications.aeronDriver } + + tasks.register('validateConfigExpectations', JavaExec) { + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' + def sourceDir = 'src/main/c' + + inputs.files(configInfoFile) + + mainClass.set('io.aeron.config.validation.ValidateConfigExpectationsTask') + classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath + args = [configInfoFile, sourceDir] + } + + tasks.register('generateConfigDoc', JavaExec) { + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' + def generatedDocFile = "${buildDir}/generated-doc/out.md" + + inputs.files(configInfoFile) + outputs.files(generatedDocFile) + + mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask') + classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath + args = [configInfoFile, generatedDocFile] + } } project(':aeron-archive') { @@ -696,6 +753,18 @@ project(':aeron-archive') { signing { sign publishing.publications.aeronArchive } + + tasks.register('generateConfigDoc', JavaExec) { + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' + def generatedDocFile = "${buildDir}/generated-doc/out.md" + + inputs.files(configInfoFile) + outputs.files(generatedDocFile) + + mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask') + classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath + args = [configInfoFile, generatedDocFile] + } } project(':aeron-cluster') { @@ -813,6 +882,18 @@ project(':aeron-cluster') { signing { sign publishing.publications.aeronCluster } + + tasks.register('generateConfigDoc', JavaExec) { + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' + def generatedDocFile = "${buildDir}/generated-doc/out.md" + + inputs.files(configInfoFile) + outputs.files(generatedDocFile) + + mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask') + classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath + args = [configInfoFile, generatedDocFile] + } } project(':aeron-agent') {