From b6f2e4ce7ff23ef1069ce9c9778252cec746f3ac Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Tue, 23 Apr 2024 18:28:43 -0500 Subject: [PATCH 01/20] [Java] initial work --- .../src/main/java/io/aeron/config/Config.java | 59 +++ .../main/java/io/aeron/config/ConfigInfo.java | 62 +++ .../java/io/aeron/config/ConfigProcessor.java | 430 ++++++++++++++++++ .../java/io/aeron/config/DefaultType.java | 71 +++ .../java/io/aeron/config/ExpectedCConfig.java | 35 ++ .../java/io/aeron/config/ExpectedConfig.java | 30 ++ .../config/docgen/GenerateConfigDocTask.java | 175 +++++++ .../io/aeron/config/validation/Entry.java | 40 ++ .../java/io/aeron/config/validation/Grep.java | 106 +++++ .../ValidateConfigExpectationsTask.java | 47 ++ .../aeron/config/validation/Validation.java | 60 +++ .../config/validation/ValidationReport.java | 64 +++ .../io/aeron/config/validation/Validator.java | 174 +++++++ .../javax.annotation.processing.Processor | 3 +- .../src/main/java/io/aeron/CommonContext.java | 3 + .../java/io/aeron/driver/Configuration.java | 222 ++++++++- .../java/io/aeron/driver/MediaDriver.java | 3 + build.gradle | 27 ++ 18 files changed, 1601 insertions(+), 10 deletions(-) create mode 100644 aeron-annotations/src/main/java/io/aeron/config/Config.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/DefaultType.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/validation/Entry.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/validation/Grep.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java create mode 100644 aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java 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..f187e74cc9 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/Config.java @@ -0,0 +1,59 @@ +/* + * 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.*; + +/** + * Annotation to indicate this is a config option + */ +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.SOURCE) +@SuppressWarnings("checkstyle:MissingJavadocMethod") +public @interface Config +{ + @SuppressWarnings("checkstyle:MissingJavadocType") + enum Type + { + UNDEFINED, PROPERTY_NAME, DEFAULT + } + + Type configType() default Type.UNDEFINED; + + String id() default ""; + + String uriParam() default ""; + + boolean existsInC() default true; + + String expectedCEnvVarFieldName() default ""; + + String expectedCEnvVar() default ""; + + String expectedCDefaultFieldName() default ""; + + String expectedCDefault() default ""; + + DefaultType defaultType() default DefaultType.UNDEFINED; + + boolean defaultBoolean() default false; + + int defaultInt() default 0; + + long defaultLong() default 0; + + String defaultString() default ""; +} 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..84a3237d4f --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -0,0 +1,62 @@ +/* + * 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 com.fasterxml.jackson.annotation.JsonProperty; + +@SuppressWarnings("checkstyle:MissingJavadocType") +public class ConfigInfo +{ + @JsonProperty + public final String id; + @JsonProperty + public final ExpectedConfig expectations; + + public ConfigInfo(@JsonProperty("id") final String id) + { + this.id = id; + expectations = new ExpectedConfig(); + } + + public boolean foundPropertyName = false; + public boolean foundDefault = false; + @JsonProperty + public String propertyNameDescription; + @JsonProperty + public String propertyNameFieldName; + @JsonProperty + public String propertyNameClassName; + @JsonProperty + public String propertyName; + @JsonProperty + public String defaultDescription; + @JsonProperty + public String defaultFieldName; + @JsonProperty + public String defaultClassName; + @JsonProperty + public Object defaultValue; + @JsonProperty + public DefaultType defaultValueType = DefaultType.UNDEFINED; + @JsonProperty + public Object overrideDefaultValue; + @JsonProperty + public DefaultType overrideDefaultValueType = DefaultType.UNDEFINED; + @JsonProperty + public String uriParam; + @JsonProperty + public String context; +} 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..7ae62bd320 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -0,0 +1,430 @@ +/* + * 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 com.fasterxml.jackson.databind.ObjectMapper; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.*; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.io.*; +import java.util.*; + +/** + * ConfigOption processor + */ +@SupportedAnnotationTypes("io.aeron.config.Config") +public class ConfigProcessor extends AbstractProcessor +{ + private static final String[] PROPERTY_NAME_SUFFIXES = new String[] {"_PROP_NAME"}; + + private static final String[] DEFAULT_SUFFIXES = new String[] {"_DEFAULT", "_DEFAULT_NS"}; + + private final Diagnostic.Kind kind = Diagnostic.Kind.NOTE; + + /** + * {@inheritDoc} + */ + @Override + public SourceVersion getSupportedSourceVersion() + { + return SourceVersion.latest(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean process(final Set annotations, final RoundEnvironment roundEnv) + { + final Map configInfoMap = new HashMap<>(); + + for (final TypeElement annotation : annotations) + { + for (final Element element : roundEnv.getElementsAnnotatedWith(annotation)) + { + try + { + if (element instanceof VariableElement) + { + processElement(configInfoMap, (VariableElement)element); + } + else if (element instanceof ExecutableElement) + { + processExecutableElement(configInfoMap, (ExecutableElement)element); + } + else + { + } + } + 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::deriveCExpectations); + configInfoMap.forEach(this::sanityCheck); + } + catch (final Exception e) + { + e.printStackTrace(System.err); + } + + try + { + final FileObject resourceFile = processingEnv.getFiler() + .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "config-info.json"); + try (OutputStream out = resourceFile.openOutputStream()) + { + new ObjectMapper().writeValue(out, configInfoMap.values()); + } + } + catch (final IOException e) + { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "an error occurred while writing output: " + e.getMessage()); + } + } + + return false; + } + + @SuppressWarnings({ "checkstyle:MethodLength", "checkstyle:LineLength" }) + private void 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; + } + + 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.foundPropertyName = true; + + configInfo.propertyNameFieldName = element.toString(); + configInfo.propertyNameClassName = element.getEnclosingElement().toString(); + configInfo.propertyNameDescription = processingEnv.getElementUtils().getDocComment(element).trim(); + + 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.foundDefault = true; + + configInfo.defaultFieldName = element.toString(); + configInfo.defaultClassName = element.getEnclosingElement().toString(); + configInfo.defaultDescription = processingEnv.getElementUtils().getDocComment(element).trim(); + + if (constantValue != null) + { + configInfo.defaultValue = constantValue; + configInfo.defaultValueType = DefaultType.fromCanonicalName(constantValue.getClass().getCanonicalName()); + } + break; + default: + error("unable to determine config type", element); + return; + } + + if (!config.uriParam().isEmpty()) + { + configInfo.uriParam = config.uriParam(); + } + + if (!DefaultType.isUndefined(config.defaultType())) + { + if (DefaultType.isUndefined(configInfo.defaultValueType)) + { + configInfo.overrideDefaultValueType = config.defaultType(); + switch (config.defaultType()) + { + case INT: + configInfo.overrideDefaultValue = config.defaultInt(); + break; + case LONG: + configInfo.overrideDefaultValue = config.defaultLong(); + break; + case BOOLEAN: + configInfo.overrideDefaultValue = config.defaultBoolean(); + break; + case STRING: + configInfo.overrideDefaultValue = config.defaultString(); + break; + default: + error("unhandled default type", element); + break; + } + } + else + { + // TODO bad + } + } + + final ExpectedCConfig c = configInfo.expectations.c; + + if (!config.existsInC()) + { + c.exists = false; + } + + if (c.exists) + { + // TODO fix isEmpty - check for NPE + if (c.envVarFieldName == null && !config.expectedCEnvVarFieldName().isEmpty()) + { + c.envVarFieldName = config.expectedCEnvVarFieldName(); + } + + if (c.envVar == null && !config.expectedCEnvVar().isEmpty()) + { + c.envVar = config.expectedCEnvVar(); + } + + if (c.defaultFieldName == null && !config.expectedCDefaultFieldName().isEmpty()) + { + c.defaultFieldName = config.expectedCDefaultFieldName(); + } + + if (c.defaultValue == null && !config.expectedCDefault().isEmpty()) + { + c.defaultValue = config.expectedCDefault(); + } + } + } + + private void 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; + } + + 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; + } + + 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) + { + if (null != id && !id.isEmpty()) + { + return 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)); + } + } + + return builder.toString(); + } + + private String getConfigId(final VariableElement element, final String[] suffixes, final String id) + { + if (null != id && !id.isEmpty()) + { + 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 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) + { + insane(id, "no default value found"); + } + } + + private void insane(final String id, final String errMsg) + { + error("Configuration (" + id + "): " + errMsg); + } + + private void error(final String errMsg) + { + processingEnv.getMessager().printMessage(kind, errMsg); + } + + private void error(final String errMsg, final Element element) + { + processingEnv.getMessager().printMessage(kind, errMsg, element); + } +} 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..8c1649a587 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java @@ -0,0 +1,71 @@ +/* + * 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; + +@SuppressWarnings({ "checkstyle:MissingJavadocMethod", "checkstyle:MissingJavadocType" }) +public enum DefaultType +{ + UNDEFINED("", "", false), + BOOLEAN("java.lang.Boolean", "Boolean", false), + INT("java.lang.Integer", "Integer", true), + LONG("java.lang.Long", "Long", 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); + } + } + + public static DefaultType fromCanonicalName(final String canonicalName) + { + return BY_CANONICAL_NAME.getOrDefault(canonicalName, UNDEFINED); + } + + 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; + } + + public boolean isNumeric() + { + return this.numeric; + } + + 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..89fca4732b --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java @@ -0,0 +1,35 @@ +/* + * 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 com.fasterxml.jackson.annotation.JsonProperty; + +@SuppressWarnings("checkstyle:MissingJavadocType") +public class ExpectedCConfig +{ + @JsonProperty + public boolean exists = true; + @JsonProperty + public String envVarFieldName; + @JsonProperty + public String envVar; + @JsonProperty + public String defaultFieldName; + @JsonProperty + public Object defaultValue; + @JsonProperty + public DefaultType defaultValueType; +} 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..fbc3df64a3 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java @@ -0,0 +1,30 @@ +/* + * 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 com.fasterxml.jackson.annotation.JsonProperty; + +@SuppressWarnings("checkstyle:MissingJavadocType") +public class ExpectedConfig +{ + @JsonProperty + public final ExpectedCConfig c; + + ExpectedConfig() + { + c = new ExpectedCConfig(); + } +} 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..84221595ea --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java @@ -0,0 +1,175 @@ +package io.aeron.config.docgen; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.aeron.config.ConfigInfo; +import io.aeron.config.DefaultType; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Paths; +import java.text.DecimalFormat; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class GenerateConfigDocTask +{ + private static FileWriter writer; + + public static void main(final String[] args) throws Exception + { + try (FileWriter writer = new FileWriter(args[1])) + { + GenerateConfigDocTask.writer = writer; + + final List config = fetchConfig(args[0]) + .stream() + .sorted(Comparator.comparing(a -> a.id)) + .collect(Collectors.toList()); + + for (final ConfigInfo configInfo: config) + { + writeHeader(toHeaderString(configInfo.id)); + 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); + } + else + { + System.err.println("missing context for " + configInfo.id); + } + if (configInfo.uriParam != null && !configInfo.uriParam.isEmpty()) + { + writeCode("URI Param", configInfo.uriParam); + } + write("Default", getDefaultString( + configInfo.overrideDefaultValue == null ? + configInfo.defaultValue.toString() : + configInfo.overrideDefaultValue.toString())); + if (configInfo.expectations.c.exists) + { + writeCode("C Env Var", configInfo.expectations.c.envVar); + write("C Default", getDefaultString( + configInfo.expectations.c.defaultValue.toString())); + } + writeLine(); + } + } + catch (final IOException e) + { + e.printStackTrace(System.err); + } + finally + { + GenerateConfigDocTask.writer = null; + } + } + + private static List fetchConfig(final String configInfoFilename) throws Exception + { + return new ObjectMapper().readValue( + Paths.get(configInfoFilename).toFile(), + new TypeReference>() + { + }); + } + + + private static void writeHeader(final String t) throws IOException + { + writeRow("", t); + writeLine(); + writeRow("---", "---"); + writeLine(); + } + + private static void writeCode(final String a, final String b) throws IOException + { + write(a, "`" + b + "`"); + } + + private static void write(final String a, final String b) throws IOException + { + writeRow("**" + a + "**", b.replaceAll("\n", " ")); + writeLine(); + } + + private static void writeLine() throws IOException + { + writer.write("\n"); + } + + private static void writeRow(final String a, final String b) throws IOException + { + writer.write("| " + a + " | " + b + " |"); + } + + private static 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 static String getDefaultString(final String d) + { + if (d != null && d.length() > 3 && d.chars().allMatch(Character::isDigit)) + { + final long defaultLong = Long.parseLong(d); + + final StringBuilder builder = new StringBuilder(); + builder.append(d); + 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(")"); + } + + return builder.toString(); + + /* TODO if there were some way to know that this was a timeout of some sort, we could + indicate the number of (milli|micro|nano)seconds + */ + } + return d; + } +} 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..563a323d79 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Entry.java @@ -0,0 +1,40 @@ +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/Grep.java b/aeron-annotations/src/main/java/io/aeron/config/validation/Grep.java new file mode 100644 index 0000000000..a834d99135 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Grep.java @@ -0,0 +1,106 @@ +package io.aeron.config.validation; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +final class Grep +{ + static Grep execute(final String pattern, final String sourceDir) + { + final String[] command = {"grep", "-r", "-n", "-E", "^" + pattern, sourceDir}; + final String commandString = "grep -r -n -E '^" + pattern + "' " + sourceDir; + + final Process process; + try + { + process = new ProcessBuilder() + .redirectErrorStream(true) + .command(command) + .start(); + } + catch (final IOException e) + { + return new Grep(commandString, e); + } + + final int exitCode; + try + { + exitCode = process.waitFor(); + } + catch (final InterruptedException e) + { + return new Grep(commandString, e); + } + + final List lines = new BufferedReader(new InputStreamReader(process.getInputStream())) + .lines() + .collect(Collectors.toList()); + + return new Grep(commandString, exitCode, lines); + } + + 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; + } + + boolean success() + { + if (this.e != null) + { + return false; + } + + if (this.exitCode != 0) + { + return false; + } + + if (this.lines.size() != 1) + { + return false; + } + + return true; + } + + String getCommandString() + { + return this.commandString; + } + + String getFilenameAndLine() + { + final String[] pieces = this.lines.get(0).split(":"); + return pieces[0] + ":" + pieces[1]; + } + + String getOutput() + { + return this.lines.get(0).split(":")[2]; + } +} 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..200d234deb --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java @@ -0,0 +1,47 @@ +/* + * 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 com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.aeron.config.ConfigInfo; + +import java.nio.file.Paths; +import java.util.List; + +/** + */ +public class ValidateConfigExpectationsTask +{ + + /** + * asdf + * @param args + */ + public static void main(final String[] args) throws Exception + { + Validator.validate(fetchConfig(args[0]), args[1]).printFailuresOn(System.out); + } + + private static List fetchConfig(final String configInfoFilename) throws Exception + { + return new ObjectMapper().readValue( + Paths.get(configInfoFilename).toFile(), + new TypeReference>() + { + }); + } +} 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..517319d6ca --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java @@ -0,0 +1,60 @@ +package io.aeron.config.validation; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +class Validation +{ + private boolean valid = false; + + private String message; + + private ByteArrayOutputStream ba_out; + + private PrintStream ps_out; + + boolean isValid() + { + return valid; + } + + void close() + { + if (this.ps_out != null) + { + this.ps_out.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.ps_out == null) + { + this.ba_out = new ByteArrayOutputStream(); + this.ps_out = new PrintStream(ba_out); + } + + return ps_out; + } + + void printOn(final PrintStream out) + { + out.println(" " + (this.valid ? "+" : "-") + " " + this.message); + if (this.ps_out != null) + { + out.println(this.ba_out); + } + } +} 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..d0b4d4ebad --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java @@ -0,0 +1,64 @@ +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); + 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..e9bd8fb9fd --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -0,0 +1,174 @@ +package io.aeron.config.validation; + +import io.aeron.config.ConfigInfo; +import io.aeron.config.DefaultType; +import io.aeron.config.ExpectedCConfig; + +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) + { + final Validator validator = new Validator(sourceDir); + validator.validate(configInfoCollection); + return validator.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 void validate(final Collection configInfoCollection) + { + configInfoCollection.forEach(this::validateCExpectations); + } + + 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 Matcher matcher = Pattern.compile(pattern + "(.*)$").matcher(grep.getOutput()); + if (!matcher.find()) + { + throw new RuntimeException("asdf"); + } + + final String originalFoundDefaultString = matcher.group(1).trim(); + + if (c.defaultValueType == DefaultType.STRING) + { + final String foundDefaultString = originalFoundDefaultString + .replaceFirst("^\\(", "") + .replaceFirst("\\)$", "") + .replaceFirst("^\"", "") + .replaceFirst("\"$", ""); + + if (foundDefaultString.equals(c.defaultValue)) + { + validation.valid("Expected Default (\"" + foundDefaultString + "\") found in " + + grep.getFilenameAndLine()); + } + else + { + validation.invalid("Expected Default string doesn't match. Expected '" + c.defaultValue + + "' but found '" + foundDefaultString + "' in " + grep.getFilenameAndLine()); + } + } + else if (c.defaultValueType == DefaultType.BOOLEAN) + { + final String foundDefaultString = originalFoundDefaultString + .replaceFirst("^\\(", "") + .replaceFirst("\\)$", ""); + + if (foundDefaultString.equals(c.defaultValue.toString())) + { + validation.valid("Expected Default '" + foundDefaultString + "' found in " + + grep.getFilenameAndLine()); + } + else + { + validation.invalid("boolean doesn't match"); + } + } + else if (c.defaultValueType.isNumeric()) + { + final String foundDefaultString = originalFoundDefaultString + .replaceAll("INT64_C", "") + .replaceAll("UINT32_C", ""); + + 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.toString())) + { + validation.valid("Expected Default '" + foundDefaultString + "'" + + (foundDefaultString.equals(evaluatedFoundDefaultString) ? + "" : " (" + evaluatedFoundDefaultString + ")") + + " found in " + grep.getFilenameAndLine()); + } + else + { + validation.invalid("found " + foundDefaultString + + " (" + evaluatedFoundDefaultString + ") but expected " + c.defaultValue); + } + } + catch (final ScriptException e) + { + validation.invalid("Expected Default - unable to evaluate expression '" + + originalFoundDefaultString + "' in " + grep.getFilenameAndLine()); + e.printStackTrace(validation.out()); + } + } + else + { + validation.invalid("bad default type"); + } + } +} 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..e3b6bda1c2 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,2 @@ -io.aeron.version.VersionProcessor \ No newline at end of file +io.aeron.version.VersionProcessor +io.aeron.config.ConfigProcessor diff --git a/aeron-client/src/main/java/io/aeron/CommonContext.java b/aeron-client/src/main/java/io/aeron/CommonContext.java index f951d7e85e..66109fe4a8 100644 --- a/aeron-client/src/main/java/io/aeron/CommonContext.java +++ b/aeron-client/src/main/java/io/aeron/CommonContext.java @@ -15,6 +15,7 @@ */ package io.aeron; +import io.aeron.config.Config; import io.aeron.exceptions.AeronException; import io.aeron.exceptions.ConcurrentConcludeException; import io.aeron.exceptions.DriverTimeoutException; @@ -98,6 +99,7 @@ public static InferableBoolean parse(final String value) /** * Property name for driver timeout after which the driver is considered inactive. */ + @Config(id = "Driver Timeout") public static final String DRIVER_TIMEOUT_PROP_NAME = "aeron.driver.timeout"; /** @@ -109,6 +111,7 @@ public static InferableBoolean parse(final String value) /** * Default timeout in which the driver is expected to respond or heartbeat. */ + @Config(id = "Driver Timeout") public static final long DEFAULT_DRIVER_TIMEOUT_MS = 10_000; /** 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 f6f32a850a..94a79ff869 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; @@ -57,42 +59,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"; /** @@ -103,66 +116,85 @@ 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", + 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", + defaultType = DefaultType.INT, + defaultInt = (1024 * 1024) + 128) public static final int TO_CLIENTS_BUFFER_LENGTH_DEFAULT = (1024 * 1024) + BroadcastBufferDescriptor.TRAILER_LENGTH; /** @@ -170,11 +202,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; /** @@ -185,26 +219,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"; /** @@ -219,107 +258,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"; @@ -330,21 +402,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); /** @@ -352,6 +434,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"; /** @@ -389,61 +472,116 @@ public final class Configuration /** * Property name for {@link IdleStrategy} to be employed by {@link Sender} for {@link ThreadingMode#DEDICATED}. */ + @Config 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 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 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 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 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) 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) 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"; @@ -459,6 +597,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"; /** @@ -467,47 +606,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"; /** @@ -516,34 +686,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; /** @@ -574,61 +753,86 @@ 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); /** @@ -1295,7 +1499,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); } /** @@ -1648,7 +1852,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); } /** @@ -1661,7 +1865,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); } /** @@ -1676,7 +1880,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); } /** @@ -1690,8 +1894,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); } /** @@ -1706,7 +1910,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 cb45e7c684..318f8aa2e9 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; @@ -1500,6 +1501,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; @@ -1529,6 +1531,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; diff --git a/build.gradle b/build.gradle index 14e89e8a46..940f373dc2 100644 --- a/build.gradle +++ b/build.gradle @@ -350,6 +350,10 @@ project(':aeron-annotations') { apply plugin: 'signing' apply plugin: 'biz.aQute.bnd.builder' + dependencies { + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' + } + jar { bundle { bnd """ @@ -539,6 +543,29 @@ project(':aeron-driver') { signing { sign publishing.publications.aeronDriver } + + tasks.register('validateConfigExpectations', JavaExec) { + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.json' + 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.json' + 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') { From 9023ddf35f6b185c1a9c338c12e5cdcf9db0daef Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Mon, 29 Apr 2024 16:36:31 -0500 Subject: [PATCH 02/20] [Java] add basic aeron counter annotation/validation --- .../ValidateConfigExpectationsTask.java | 2 +- .../io/aeron/config/validation/Validator.java | 1 + .../java/io/aeron/counter/AeronCounter.java | 31 ++++ .../java/io/aeron/counter/CounterInfo.java | 37 +++++ .../io/aeron/counter/CounterProcessor.java | 153 ++++++++++++++++++ .../ValidateCounterExpectationsTask.java | 47 ++++++ .../aeron/counter/validation/Validation.java | 67 ++++++++ .../counter/validation/ValidationReport.java | 74 +++++++++ .../aeron/counter/validation/Validator.java | 105 ++++++++++++ .../aeron/{config => }/validation/Grep.java | 50 ++++-- .../javax.annotation.processing.Processor | 1 + aeron-client/src/main/c/aeron_counters.h | 8 + .../src/main/java/io/aeron/AeronCounters.java | 71 ++++++++ build.gradle | 11 ++ 14 files changed, 642 insertions(+), 16 deletions(-) create mode 100644 aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java create mode 100644 aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java create mode 100644 aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java create mode 100644 aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java create mode 100644 aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java create mode 100644 aeron-annotations/src/main/java/io/aeron/counter/validation/ValidationReport.java create mode 100644 aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java rename aeron-annotations/src/main/java/io/aeron/{config => }/validation/Grep.java (66%) 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 index 200d234deb..a081aca7af 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java @@ -33,7 +33,7 @@ public class ValidateConfigExpectationsTask */ public static void main(final String[] args) throws Exception { - Validator.validate(fetchConfig(args[0]), args[1]).printFailuresOn(System.out); + Validator.validate(fetchConfig(args[0]), args[1]).printFailuresOn(System.err); } private static List fetchConfig(final String configInfoFilename) throws Exception 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 index e9bd8fb9fd..71d74a84f2 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -3,6 +3,7 @@ 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; 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..b7ace2f06a --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java @@ -0,0 +1,31 @@ +/* + * 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 +{ + 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..1f290e7543 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.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; + +import com.fasterxml.jackson.annotation.JsonProperty; + +@SuppressWarnings("checkstyle:MissingJavadocType") +public class CounterInfo +{ + @JsonProperty + public final String name; + + public CounterInfo(@JsonProperty("name") final String name) + { + this.name = name; + } + + @JsonProperty + public int id; + @JsonProperty + public String counterDescription; + @JsonProperty + 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..fb289be2a9 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java @@ -0,0 +1,153 @@ +/* + * 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 com.fasterxml.jackson.databind.ObjectMapper; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.*; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ConfigOption processor + */ +@SupportedAnnotationTypes("io.aeron.counter.AeronCounter") +public class CounterProcessor extends AbstractProcessor +{ + + private final Diagnostic.Kind kind = Diagnostic.Kind.NOTE; + /** + * {@inheritDoc} + */ + @Override + public SourceVersion getSupportedSourceVersion() + { + return SourceVersion.latest(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean process(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 + { + } + } + 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.json"); + try (OutputStream out = resourceFile.openOutputStream()) + { + new ObjectMapper().writeValue(out, counterInfoMap.values()); + } + } + catch (final IOException e) + { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "an error occurred while writing output: " + e.getMessage()); + } + } + + return false; + } + + 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 = processingEnv.getElementUtils().getDocComment(element).trim(); + + final Object constantValue = element.getConstantValue(); + if (constantValue instanceof Integer) + { + counterInfo.id = (Integer)constantValue; + } + else + { + error("Counter value must be an Integer", element); + } + + counterInfo.expectedCName = "AERON_COUNTER_" + + (counter.expectedCName().isEmpty() ? + counterInfo.name.replaceAll("^DRIVER_", "") : + counter.expectedCName()) + + "_TYPE_ID"; + } + + private void error(final String errMsg, final Element element) + { + processingEnv.getMessager().printMessage(kind, errMsg, element); + } +} 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..a2a097d0d3 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java @@ -0,0 +1,47 @@ +/* + * 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 com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.aeron.counter.CounterInfo; + +import java.nio.file.Paths; +import java.util.List; + +/** + */ +public class ValidateCounterExpectationsTask +{ + + /** + * asdf + * @param args + */ + public static void main(final String[] args) throws Exception + { + Validator.validate(fetchCounter(args[0]), args[1]).printFailuresOn(System.err); + } + + private static List fetchCounter(final String counterInfoFilename) throws Exception + { + return new ObjectMapper().readValue( + Paths.get(counterInfoFilename).toFile(), + new TypeReference>() + { + }); + } +} 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..7ff003aa2d --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java @@ -0,0 +1,67 @@ +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 ba_out; + + private PrintStream ps_out; + + Validation(final String name) + { + this.name = name; + } + + boolean isValid() + { + return valid; + } + + void close() + { + if (this.ps_out != null) + { + this.ps_out.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.ps_out == null) + { + this.ba_out = new ByteArrayOutputStream(); + this.ps_out = new PrintStream(ba_out); + } + + return ps_out; + } + + void printOn(final PrintStream out) + { + out.println(name); + out.println(" " + (this.valid ? "+" : "-") + " " + this.message); + if (this.ps_out != null) + { + out.println(this.ba_out); + } + } +} 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..778c032c1e --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidationReport.java @@ -0,0 +1,74 @@ +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..00ca674213 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java @@ -0,0 +1,105 @@ +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) + { + final Validator validator = new Validator(sourceDir); + validator.validate(counterInfoCollection); + return validator.report; + } + + private final String sourceDir; + private final ValidationReport report; + + private Validator(final String sourceDir) + { + this.sourceDir = sourceDir; + this.report = new ValidationReport(); + } + + private void validate(final Collection counterInfoCollection) + { + counterInfoCollection.forEach(this::validateCExpectations); + + identifyExtraCCounters(counterInfoCollection); + } + + private void identifyExtraCCounters(final Collection counterInfoCollection) + { + final Pattern compiledPattern = Pattern.compile("#define[ \t]+([A-Z_]+)[ \t]+\\([0-9]+\\)"); + final List expectedCNames = counterInfoCollection.stream().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) + { + 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); + + 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); + } + } + 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/config/validation/Grep.java b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java similarity index 66% rename from aeron-annotations/src/main/java/io/aeron/config/validation/Grep.java rename to aeron-annotations/src/main/java/io/aeron/validation/Grep.java index a834d99135..8724cde588 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Grep.java +++ b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java @@ -1,15 +1,17 @@ -package io.aeron.config.validation; +package io.aeron.validation; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Collections; import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.stream.Collectors; -final class Grep +public final class Grep { - static Grep execute(final String pattern, final String sourceDir) + public static Grep execute(final String pattern, final String sourceDir) { final String[] command = {"grep", "-r", "-n", "-E", "^" + pattern, sourceDir}; final String commandString = "grep -r -n -E '^" + pattern + "' " + sourceDir; @@ -68,7 +70,12 @@ private Grep(final String commandString, final int exitCode, final List this.e = null; } - boolean success() + public boolean success() + { + return success(true); + } + + public boolean success(final boolean expectOneLine) { if (this.e != null) { @@ -80,27 +87,40 @@ boolean success() return false; } - if (this.lines.size() != 1) - { - return false; - } - - return true; + return !expectOneLine || this.lines.size() == 1; } - String getCommandString() + public String getCommandString() { return this.commandString; } - String getFilenameAndLine() + public String getFilenameAndLine() + { + return getFilenameAndLine(0); + } + + public String getFilenameAndLine(final int lineNumber) { - final String[] pieces = this.lines.get(0).split(":"); + final String[] pieces = this.lines.get(lineNumber).split(":"); return pieces[0] + ":" + pieces[1]; } - String getOutput() + public String getOutput() { - return this.lines.get(0).split(":")[2]; + return getOutput(0); + } + + public String getOutput(final int lineNumber) + { + return this.lines.get(lineNumber).split(":")[2]; + } + + 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 e3b6bda1c2..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,2 +1,3 @@ io.aeron.version.VersionProcessor io.aeron.config.ConfigProcessor +io.aeron.counter.CounterProcessor 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/AeronCounters.java b/aeron-client/src/main/java/io/aeron/AeronCounters.java index 424c36e1d1..60db1b3468 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 public static final int NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID = 15; /** * Count of entries in the name resolver cache. */ + @AeronCounter 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 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 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 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 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 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 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 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 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 public static final int CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID = 239; private AeronCounters() diff --git a/build.gradle b/build.gradle index 940f373dc2..ccca9691e3 100644 --- a/build.gradle +++ b/build.gradle @@ -470,6 +470,17 @@ project(':aeron-client') { signing { sign publishing.publications.aeronClient } + + tasks.register('validateCounterExpectations', JavaExec) { + def counterInfoFile = 'build/generated/sources/headers/java/main/counter-info.json' + 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') { From 149c60891b360c3b5aa119ef9b79d35ef21b2429 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Tue, 30 Apr 2024 11:29:00 -0500 Subject: [PATCH 03/20] [Java] appease checkstyle --- .../src/main/java/io/aeron/config/Config.java | 44 ++++++++++- .../main/java/io/aeron/config/ConfigInfo.java | 7 +- .../java/io/aeron/config/DefaultType.java | 18 ++++- .../java/io/aeron/config/ExpectedCConfig.java | 4 +- .../java/io/aeron/config/ExpectedConfig.java | 3 +- .../config/docgen/GenerateConfigDocTask.java | 79 +++++++++++++++++-- .../io/aeron/config/validation/Entry.java | 15 ++++ .../ValidateConfigExpectationsTask.java | 8 +- .../aeron/config/validation/Validation.java | 15 ++++ .../config/validation/ValidationReport.java | 15 ++++ .../io/aeron/config/validation/Validator.java | 16 ++++ .../java/io/aeron/counter/AeronCounter.java | 3 + .../java/io/aeron/counter/CounterInfo.java | 7 +- .../ValidateCounterExpectationsTask.java | 7 +- .../aeron/counter/validation/Validation.java | 15 ++++ .../counter/validation/ValidationReport.java | 15 ++++ .../aeron/counter/validation/Validator.java | 24 +++++- .../main/java/io/aeron/validation/Grep.java | 52 +++++++++++- 18 files changed, 326 insertions(+), 21 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/Config.java b/aeron-annotations/src/main/java/io/aeron/config/Config.java index f187e74cc9..4993888ac1 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/Config.java +++ b/aeron-annotations/src/main/java/io/aeron/config/Config.java @@ -22,38 +22,78 @@ */ @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) -@SuppressWarnings("checkstyle:MissingJavadocMethod") public @interface Config { - @SuppressWarnings("checkstyle:MissingJavadocType") + /** + * 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 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 string, if defaultType is STRING + */ String defaultString() default ""; } diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java index 84a3237d4f..ecee6291f3 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -17,7 +17,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; -@SuppressWarnings("checkstyle:MissingJavadocType") +/** + * A handy class for storing data that gets serialized into json + */ public class ConfigInfo { @JsonProperty @@ -25,6 +27,9 @@ public class ConfigInfo @JsonProperty public final ExpectedConfig expectations; + /** + * @param id the unique identifier for this block o' config information + */ public ConfigInfo(@JsonProperty("id") final String id) { this.id = id; diff --git a/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java b/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java index 8c1649a587..9e7baa6e6b 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java +++ b/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java @@ -19,7 +19,9 @@ import java.util.Map; import java.util.Objects; -@SuppressWarnings({ "checkstyle:MissingJavadocMethod", "checkstyle:MissingJavadocType" }) +/** + * Indicates the 'type' of the default field/value + */ public enum DefaultType { UNDEFINED("", "", false), @@ -38,11 +40,19 @@ public enum DefaultType } } + /** + * @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; @@ -59,11 +69,17 @@ public static boolean isUndefined(final DefaultType defaultType) 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 index 89fca4732b..abe1778888 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java @@ -17,7 +17,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; -@SuppressWarnings("checkstyle:MissingJavadocType") +/** + * A handy class for storing expected C config info that can be serialized into json + */ public class ExpectedCConfig { @JsonProperty diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java index fbc3df64a3..a67a17fae4 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java @@ -17,7 +17,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; -@SuppressWarnings("checkstyle:MissingJavadocType") +/** + */ public class ExpectedConfig { @JsonProperty 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 index 84221595ea..0955e964c3 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java @@ -1,3 +1,18 @@ +/* + * 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 com.fasterxml.jackson.core.type.TypeReference; @@ -13,11 +28,23 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; +/** + * A gradle task for generating config documentation + */ public class GenerateConfigDocTask { private static FileWriter writer; + /** + * @param args + * Arg 0 should be the location of a config-info.json 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 { try (FileWriter writer = new FileWriter(args[1])) @@ -31,6 +58,8 @@ public static void main(final String[] args) throws Exception for (final ConfigInfo configInfo: config) { + final boolean isTime = isTime(configInfo.propertyName); + writeHeader(toHeaderString(configInfo.id)); write("Description", configInfo.propertyNameDescription); write("Type", @@ -53,12 +82,12 @@ public static void main(final String[] args) throws Exception write("Default", getDefaultString( configInfo.overrideDefaultValue == null ? configInfo.defaultValue.toString() : - configInfo.overrideDefaultValue.toString())); + configInfo.overrideDefaultValue.toString(), isTime)); if (configInfo.expectations.c.exists) { writeCode("C Env Var", configInfo.expectations.c.envVar); write("C Default", getDefaultString( - configInfo.expectations.c.defaultValue.toString())); + configInfo.expectations.c.defaultValue.toString(), isTime)); } writeLine(); } @@ -136,7 +165,13 @@ else if (previous == '_') return builder.toString(); } - private static String getDefaultString(final String d) + private static boolean isTime(final String propertyName) + { + return Stream.of("timeout", "backoff", "delay", "linger", "interval") + .anyMatch(k -> propertyName.toLowerCase().contains(k)); + } + + private static String getDefaultString(final String d, final boolean asTime) { if (d != null && d.length() > 3 && d.chars().allMatch(Character::isDigit)) { @@ -164,11 +199,41 @@ private static String getDefaultString(final String d) builder.append(")"); } - return builder.toString(); + if (asTime) + { + int tCount = 0; + String remaining = d; + while (remaining.endsWith("000") && tCount < 3) + { + tCount++; + remaining = remaining.replaceAll("000$", ""); + } + 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.equals("1")) + { + builder.append("s"); + } + builder.append(")"); + } - /* TODO if there were some way to know that this was a timeout of some sort, we could - indicate the number of (milli|micro|nano)seconds - */ + return builder.toString(); } return d; } 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 index 563a323d79..1d74d8bddf 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Entry.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Entry.java @@ -1,3 +1,18 @@ +/* + * 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; 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 index a081aca7af..bd23a91f42 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java @@ -23,13 +23,17 @@ import java.util.List; /** + * A gradle task for validating the C code looks as expected, based on the contents of the @Config java annotations */ public class ValidateConfigExpectationsTask { - /** - * asdf * @param args + * Arg 0 should be the location of a config-info.json 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 { 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 index 517319d6ca..95a5506b30 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java @@ -1,3 +1,18 @@ +/* + * 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; 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 index d0b4d4ebad..5fe8249528 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java @@ -1,3 +1,18 @@ +/* + * 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; 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 index 71d74a84f2..33bc4e4add 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -1,3 +1,18 @@ +/* + * 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; @@ -67,6 +82,7 @@ private void validateCEnvVar(final Validation validation, final ExpectedCConfig } } + @SuppressWarnings("checkstyle:MethodLength") private void validateCDefault(final Validation validation, final ExpectedCConfig c) { if (Objects.isNull(c.defaultFieldName)) diff --git a/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java b/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java index b7ace2f06a..007ed14bf2 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java @@ -27,5 +27,8 @@ @Retention(RetentionPolicy.SOURCE) public @interface AeronCounter { + /** + * @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 index 1f290e7543..e7c0ce05f8 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java @@ -17,12 +17,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; -@SuppressWarnings("checkstyle:MissingJavadocType") +/** + * A handy class for storing data that gets serialized into json + */ public class CounterInfo { @JsonProperty public final String name; + /** + * @param name the name of the counter + */ public CounterInfo(@JsonProperty("name") final String name) { this.name = name; 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 index a2a097d0d3..c2120e1ffa 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java @@ -23,13 +23,18 @@ import java.util.List; /** + * A gradle task for validating C counters conform to expectations set by the AeronCounter annotation in java */ public class ValidateCounterExpectationsTask { /** - * asdf * @param args + * Arg 0 should be the location of a counter-info.json 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 { 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 index 7ff003aa2d..cf4b75338b 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java @@ -1,3 +1,18 @@ +/* + * 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; 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 index 778c032c1e..838ae63a34 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidationReport.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidationReport.java @@ -1,3 +1,18 @@ +/* + * 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; 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 index 00ca674213..7811ac287b 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java @@ -1,3 +1,18 @@ +/* + * 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; @@ -39,7 +54,8 @@ private void validate(final Collection counterInfoCollection) private void identifyExtraCCounters(final Collection counterInfoCollection) { final Pattern compiledPattern = Pattern.compile("#define[ \t]+([A-Z_]+)[ \t]+\\([0-9]+\\)"); - final List expectedCNames = counterInfoCollection.stream().map(counterInfo -> counterInfo.expectedCName).collect(Collectors.toList()); + final List expectedCNames = + counterInfoCollection.stream().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); @@ -53,7 +69,8 @@ private void identifyExtraCCounters(final Collection counterInfoCol if (expectedCNames.stream().noneMatch(cName -> cName.equals(name))) { - report.addValidation(false, name, "Found C counter with no matching Java counter - " + fileAndLineNo); + report.addValidation(false, name, + "Found C counter with no matching Java counter - " + fileAndLineNo); } } else @@ -78,7 +95,8 @@ private void validate(final Validation validation, final CounterInfo counterInfo if (grep.success()) { - final Matcher matcher = Pattern.compile("#define[ \t]+[A-Z_]+[ \t]+\\(([0-9]+)\\)").matcher(grep.getOutput()); + final Matcher matcher = + Pattern.compile("#define[ \t]+[A-Z_]+[ \t]+\\(([0-9]+)\\)").matcher(grep.getOutput()); if (matcher.find()) { final String id = matcher.group(1); diff --git a/aeron-annotations/src/main/java/io/aeron/validation/Grep.java b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java index 8724cde588..15b6272222 100644 --- a/aeron-annotations/src/main/java/io/aeron/validation/Grep.java +++ b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java @@ -1,3 +1,18 @@ +/* + * 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; @@ -6,11 +21,18 @@ import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; -import java.util.function.Consumer; 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[] command = {"grep", "-r", "-n", "-E", "^" + pattern, sourceDir}; @@ -70,11 +92,19 @@ private Grep(final String commandString, final int exitCode, final List 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) @@ -90,32 +120,52 @@ public boolean success(final boolean expectOneLine) 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++) From ecd220e96be16adc821de7a59f33746b3f2ba337 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Tue, 7 May 2024 12:07:41 -0500 Subject: [PATCH 04/20] [Java] add aeron-client tasks, annotate more elements --- .../src/main/java/io/aeron/config/Config.java | 24 +++++ .../main/java/io/aeron/config/ConfigInfo.java | 8 ++ .../java/io/aeron/config/ConfigProcessor.java | 73 +++++++++++-- .../java/io/aeron/config/DefaultType.java | 1 + .../config/docgen/GenerateConfigDocTask.java | 101 +++++++++++------- .../io/aeron/config/validation/Validator.java | 3 +- .../src/main/java/io/aeron/Aeron.java | 16 +++ .../src/main/java/io/aeron/CommonContext.java | 17 ++- .../java/io/aeron/driver/Configuration.java | 31 ++++++ .../java/io/aeron/driver/MediaDriver.java | 22 ++++ .../DebugChannelEndpointConfiguration.java | 16 +++ build.gradle | 23 ++++ 12 files changed, 284 insertions(+), 51 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/Config.java b/aeron-annotations/src/main/java/io/aeron/config/Config.java index 4993888ac1..c57c96fc9d 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/Config.java +++ b/aeron-annotations/src/main/java/io/aeron/config/Config.java @@ -16,6 +16,7 @@ package io.aeron.config; import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; /** * Annotation to indicate this is a config option @@ -92,8 +93,31 @@ enum Type */ 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 ""; + + /** + * 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; } diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java index ecee6291f3..cc395648b2 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -17,6 +17,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.concurrent.TimeUnit; + /** * A handy class for storing data that gets serialized into json */ @@ -64,4 +66,10 @@ public ConfigInfo(@JsonProperty("id") final String id) public String uriParam; @JsonProperty public String context; + @JsonProperty + public String contextDescription; + @JsonProperty + public Boolean isTimeValue; + @JsonProperty + public TimeUnit timeUnit; } diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index 7ae62bd320..c551577511 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -27,6 +27,7 @@ import javax.tools.StandardLocation; import java.io.*; import java.util.*; +import java.util.stream.Stream; /** * ConfigOption processor @@ -143,7 +144,7 @@ private void processElement(final Map configInfoMap, final V configInfo.propertyNameFieldName = element.toString(); configInfo.propertyNameClassName = element.getEnclosingElement().toString(); - configInfo.propertyNameDescription = processingEnv.getElementUtils().getDocComment(element).trim(); + configInfo.propertyNameDescription = getDocComment(element); if (constantValue instanceof String) { @@ -167,7 +168,7 @@ private void processElement(final Map configInfoMap, final V configInfo.defaultFieldName = element.toString(); configInfo.defaultClassName = element.getEnclosingElement().toString(); - configInfo.defaultDescription = processingEnv.getElementUtils().getDocComment(element).trim(); + configInfo.defaultDescription = getDocComment(element); if (constantValue != null) { @@ -185,6 +186,30 @@ private void processElement(final Map configInfoMap, final V configInfo.uriParam = config.uriParam(); } + switch (config.isTimeValue()) + { + case TRUE: + configInfo.isTimeValue = true; + break; + case FALSE: + configInfo.isTimeValue = false; + break; + case UNDEFINED: + if (configInfo.isTimeValue == null) + { + configInfo.isTimeValue = + Stream.of("timeout", "backoff", "delay", "linger", "interval", "duration") + .anyMatch(k -> id.toLowerCase().contains(k)); + } + break; + } + + if (configInfo.isTimeValue) + { + // TODO make sure this is either seconds, milliseconds, microseconds, or nanoseconds + configInfo.timeUnit = config.timeUnit(); + } + if (!DefaultType.isUndefined(config.defaultType())) { if (DefaultType.isUndefined(configInfo.defaultValueType)) @@ -198,6 +223,9 @@ private void processElement(final Map configInfoMap, final V case LONG: configInfo.overrideDefaultValue = config.defaultLong(); break; + case DOUBLE: + configInfo.overrideDefaultValue = config.defaultDouble(); + break; case BOOLEAN: configInfo.overrideDefaultValue = config.defaultBoolean(); break; @@ -247,6 +275,18 @@ private void processElement(final Map configInfoMap, final V } } + private 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(); + } + private void processExecutableElement(final Map configInfoMap, final ExecutableElement element) { final Config config = element.getAnnotation(Config.class); @@ -261,6 +301,7 @@ private void processExecutableElement(final Map configInfoMa final ConfigInfo configInfo = configInfoMap.computeIfAbsent(id, ConfigInfo::new); final String methodName = element.toString(); + System.out.println("-- " + methodName); final String enclosingElementName = element.getEnclosingElement().toString(); @@ -273,6 +314,7 @@ private void processExecutableElement(final Map configInfoMa final String packageName = e.toString(); configInfo.context = enclosingElementName.substring(packageName.length() + 1) + "." + methodName; + configInfo.contextDescription = getDocComment(element); } private Config.Type getConfigType(final VariableElement element, final Config config) @@ -298,11 +340,6 @@ private Config.Type getConfigType(final VariableElement element, final Config co private String getConfigId(final ExecutableElement element, final String id) { - if (null != id && !id.isEmpty()) - { - return id; - } - final StringBuilder builder = new StringBuilder(); for (final char next: element.toString().toCharArray()) @@ -315,9 +352,24 @@ private String getConfigId(final ExecutableElement element, final String id) } 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); + } + return id; } - return builder.toString(); + return calculatedId; } private String getConfigId(final VariableElement element, final String[] suffixes, final String id) @@ -411,6 +463,11 @@ private void sanityCheck(final String id, final ConfigInfo configInfo) { insane(id, "no default value found"); } + + if (configInfo.context == null || configInfo.context.isEmpty()) + { + insane(id, "missing context"); + } } private void insane(final String id, final String 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 index 9e7baa6e6b..0b15d6db4c 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java +++ b/aeron-annotations/src/main/java/io/aeron/config/DefaultType.java @@ -28,6 +28,7 @@ public enum DefaultType 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<>(); 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 index 0955e964c3..214b1e1d65 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java @@ -26,15 +26,20 @@ 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; -import java.util.stream.Stream; /** * A gradle task for generating config documentation */ public class GenerateConfigDocTask { + private static String toString(final Object a) + { + return a == null ? "" : a.toString(); + } + private static FileWriter writer; /** @@ -58,8 +63,6 @@ public static void main(final String[] args) throws Exception for (final ConfigInfo configInfo: config) { - final boolean isTime = isTime(configInfo.propertyName); - writeHeader(toHeaderString(configInfo.id)); write("Description", configInfo.propertyNameDescription); write("Type", @@ -71,23 +74,38 @@ public static void main(final String[] args) throws Exception { writeCode("Context", configInfo.context); } - else + if (configInfo.contextDescription != null && !configInfo.contextDescription.isEmpty()) { - System.err.println("missing context for " + configInfo.id); + 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 ? + toString(configInfo.defaultValue) : + configInfo.overrideDefaultValue.toString(); write("Default", getDefaultString( - configInfo.overrideDefaultValue == null ? - configInfo.defaultValue.toString() : - configInfo.overrideDefaultValue.toString(), isTime)); + defaultValue, + configInfo.isTimeValue == Boolean.TRUE, + 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.toString(), isTime)); + toString(configInfo.expectations.c.defaultValue), + configInfo.isTimeValue == Boolean.TRUE, + configInfo.timeUnit)); } writeLine(); } @@ -165,51 +183,54 @@ else if (previous == '_') return builder.toString(); } - private static boolean isTime(final String propertyName) - { - return Stream.of("timeout", "backoff", "delay", "linger", "interval") - .anyMatch(k -> propertyName.toLowerCase().contains(k)); - } - - private static String getDefaultString(final String d, final boolean asTime) + private static String getDefaultString( + final String defaultValue, + final boolean isTimeValue, + final TimeUnit timeUnit) { - if (d != null && d.length() > 3 && d.chars().allMatch(Character::isDigit)) + if (defaultValue != null && !defaultValue.isEmpty() && defaultValue.chars().allMatch(Character::isDigit)) { - final long defaultLong = Long.parseLong(d); - + final long defaultLong = Long.parseLong(defaultValue); final StringBuilder builder = new StringBuilder(); - builder.append(d); - 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(defaultValue); + + if (defaultValue.length() > 3) { builder.append(" ("); - builder.append(remainingValue); - IntStream.range(0, kCount).forEach(i -> builder.append(" * 1024")); + 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 (asTime) + if (isTimeValue) { int tCount = 0; - String remaining = d; - while (remaining.endsWith("000") && tCount < 3) + + long remaining = timeUnit.toNanos(defaultLong); + while (remaining % 1000 == 0 && tCount < 3) { tCount++; - remaining = remaining.replaceAll("000$", ""); + remaining = remaining / 1000; } builder.append(" ("); builder.append(remaining); + switch (tCount) { case 0: @@ -226,7 +247,7 @@ private static String getDefaultString(final String d, final boolean asTime) break; } builder.append("second"); - if (!remaining.equals("1")) + if (remaining != 1) { builder.append("s"); } @@ -235,6 +256,6 @@ private static String getDefaultString(final String d, final boolean asTime) return builder.toString(); } - return d; + return defaultValue; } } 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 index 33bc4e4add..4dd4152505 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -154,7 +154,8 @@ else if (c.defaultValueType.isNumeric()) { final String foundDefaultString = originalFoundDefaultString .replaceAll("INT64_C", "") - .replaceAll("UINT32_C", ""); + .replaceAll("UINT32_C", "") + .replaceAll("([0-9]+)L", "$1"); try { diff --git a/aeron-client/src/main/java/io/aeron/Aeron.java b/aeron-client/src/main/java/io/aeron/Aeron.java index 912e7f6f9d..783391bb9d 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 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 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 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,13 @@ 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 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 +739,7 @@ public static class Configuration * * @since 1.44.0 */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String CLIENT_NAME_PROP_NAME = "aeron.client.name"; /** @@ -766,6 +777,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 +790,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 +803,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 +815,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 +833,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/CommonContext.java b/aeron-client/src/main/java/io/aeron/CommonContext.java index 98366008f3..0c485e608c 100644 --- a/aeron-client/src/main/java/io/aeron/CommonContext.java +++ b/aeron-client/src/main/java/io/aeron/CommonContext.java @@ -16,6 +16,7 @@ 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; @@ -99,19 +100,23 @@ public static InferableBoolean parse(final String value) /** * Property name for driver timeout after which the driver is considered inactive. */ - @Config(id = "Driver Timeout") + @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) 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") + @Config( + id = "DRIVER_TIMEOUT", + timeUnit = TimeUnit.MILLISECONDS, + expectedCDefaultFieldName = "AERON_CONTEXT_DRIVER_TIMEOUT_MS_DEFAULT") public static final long DEFAULT_DRIVER_TIMEOUT_MS = 10_000; /** @@ -127,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 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; /** @@ -139,12 +146,14 @@ public static InferableBoolean parse(final String value) * * @since 1.44.0 */ + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = 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") public static final String FALLBACK_LOGGER_PROP_NAME = "aeron.fallback.logger"; /** @@ -415,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"); @@ -502,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); @@ -698,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); @@ -710,6 +722,7 @@ public long driverTimeoutMs() * @see #enableExperimentalFeatures(boolean) * @since 1.44.0 */ + @Config public boolean enableExperimentalFeatures() { return enableExperimentalFeatures; 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 94a79ff869..12282b724b 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Configuration.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Configuration.java @@ -839,6 +839,7 @@ public final class Configuration * 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"; /** @@ -851,6 +852,7 @@ public final class Configuration * 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"; /** @@ -867,45 +869,54 @@ public final class Configuration /** * Property name for the class used to validate if a driver should terminate based on token. */ + @Config 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 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 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.STRING, defaultString = "") 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 public static final String FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME = "aeron.flow.control.receiver.timeout"; + @Config 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 = "") public static final String RESOLVER_NAME_PROP_NAME = "aeron.driver.resolver.name"; /** @@ -913,6 +924,7 @@ public final class Configuration * * @see #RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME */ + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String RESOLVER_INTERFACE_PROP_NAME = "aeron.driver.resolver.interface"; /** @@ -921,21 +933,25 @@ public final class Configuration * * @see #RESOLVER_INTERFACE_PROP_NAME */ + @Config 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) 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"; /** @@ -946,26 +962,31 @@ public final class Configuration /** * 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) 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) 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 public static final String NAME_RESOLVER_THRESHOLD_PROP_NAME = "aeron.name.resolver.threshold"; /** @@ -976,11 +997,13 @@ public final class Configuration /** * Property name for wildcard port range for the Sender. */ + @Config(defaultType = DefaultType.STRING, defaultString = "") 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 public static final String RECEIVER_WILDCARD_PORT_RANGE_PROP_NAME = "aeron.receiver.wildcard.port.range"; /** @@ -989,6 +1012,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"; /** @@ -1013,6 +1037,7 @@ public static boolean printConfigurationOnStart() * @return true if the high-resolution timer be used when running on Windows. * @see #USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME */ + @Config public static boolean useWindowsHighResTimer() { return "true".equals(getProperty(USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME)); @@ -1302,6 +1327,7 @@ public static boolean rejoinStream() * @return Default group tag (gtag) to send in all Status Messages. * @see #RECEIVER_GROUP_TAG_PROP_NAME */ + @Config(id = "RECEIVER_GROUP_TAG") public static Long groupTag() { return getLong(RECEIVER_GROUP_TAG_PROP_NAME, null); @@ -1313,6 +1339,7 @@ public static Long groupTag() * @return group tag (gtag) used by the tagged flow control strategy to group receivers. * @see #FLOW_CONTROL_GROUP_TAG_PROP_NAME */ + @Config @SuppressWarnings("deprecation") public static long flowControlGroupTag() { @@ -1329,6 +1356,7 @@ public static long flowControlGroupTag() * @return default minimum group size used by flow control strategies to determine connectivity. * @see #FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME */ + @Config public static int flowControlGroupMinSize() { return getInteger(FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME, 0); @@ -1340,6 +1368,7 @@ public static int flowControlGroupMinSize() * @return flow control timeout after which with no status messages the receiver is considered gone. * @see #FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME */ + @Config public static long flowControlReceiverTimeoutNs() { return getDurationInNanos( @@ -1610,6 +1639,7 @@ public static long clientLivenessTimeoutNs() * being drained. * @see #IMAGE_LIVENESS_TIMEOUT_PROP_NAME */ + @Config public static long imageLivenessTimeoutNs() { return getDurationInNanos(IMAGE_LIVENESS_TIMEOUT_PROP_NAME, IMAGE_LIVENESS_TIMEOUT_DEFAULT_NS); @@ -1625,6 +1655,7 @@ public static long imageLivenessTimeoutNs() * @return {@link Publication} unblock timeout due to client crash or untimely commit. * @see #PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME */ + @Config public static long publicationUnblockTimeoutNs() { return getDurationInNanos(PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME, PUBLICATION_UNBLOCK_TIMEOUT_DEFAULT_NS); 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 0d2dbe0d12..ead615ba5a 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java +++ b/aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java @@ -900,6 +900,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; @@ -924,6 +925,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; @@ -972,6 +974,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; @@ -1020,6 +1023,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; @@ -1068,6 +1072,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; @@ -1140,6 +1145,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; @@ -1264,6 +1270,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; @@ -1340,6 +1347,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; @@ -1364,6 +1372,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); @@ -1388,6 +1397,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; @@ -1652,6 +1662,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; @@ -1678,6 +1689,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; @@ -1728,6 +1740,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; @@ -2052,6 +2065,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; @@ -2280,6 +2294,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; @@ -2305,6 +2320,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; @@ -2331,6 +2347,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; @@ -3091,6 +3108,7 @@ public Context resolverName(final String resolverName) * @see Configuration#RESOLVER_INTERFACE_PROP_NAME * @see CommonContext#INTERFACE_PARAM_NAME */ + @Config public String resolverInterface() { return resolverInterface; @@ -3153,6 +3171,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; @@ -3233,6 +3252,7 @@ public Context senderCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the sender work cycle time. */ + @Config public long senderCycleThresholdNs() { return senderCycleThresholdNs; @@ -3259,6 +3279,7 @@ public Context receiverCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the receiver work cycle time. */ + @Config public long receiverCycleThresholdNs() { return receiverCycleThresholdNs; @@ -3432,6 +3453,7 @@ public Context nameResolverTimeTracker(final DutyCycleTracker dutyCycleTracker) * * @return port range as a string in the format "low high". */ + @Config public String senderWildcardPortRange() { return senderWildcardPortRange; 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..5f041d96db 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,45 +30,58 @@ public class DebugChannelEndpointConfiguration /** * Property name for receiver inbound data loss rate. */ + @Config 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 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) 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 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) 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 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) 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 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); + @Config( + id = "RECEIVE_DATA_LOSS_RATE", + configType = Config.Type.DEFAULT, + defaultType = DefaultType.DOUBLE, + defaultDouble = 0.0) private static final double RECEIVE_DATA_LOSS_RATE = Double.parseDouble(getProperty(RECEIVE_DATA_LOSS_RATE_PROP_NAME, "0.0")); diff --git a/build.gradle b/build.gradle index e50f5bdefc..6e59ffc8c7 100644 --- a/build.gradle +++ b/build.gradle @@ -471,6 +471,29 @@ project(':aeron-client') { sign publishing.publications.aeronClient } + tasks.register('validateConfigExpectations', JavaExec) { + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.json' + 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.json' + 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.json' def sourceDir = 'src/main/c' From ea4a3cbcdd33440444d9854ba5971d5055103e32 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Tue, 7 May 2024 21:28:33 -0500 Subject: [PATCH 05/20] [Java] more driver annotations --- .../src/main/java/io/aeron/config/Config.java | 5 ++ .../main/java/io/aeron/config/ConfigInfo.java | 4 ++ .../java/io/aeron/config/ConfigProcessor.java | 44 +++++++++++----- .../config/docgen/GenerateConfigDocTask.java | 2 +- .../java/io/aeron/driver/Configuration.java | 25 ++++----- .../java/io/aeron/driver/MediaDriver.java | 52 +++++++++++++++++++ .../DebugChannelEndpointConfiguration.java | 21 +++----- 7 files changed, 113 insertions(+), 40 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/Config.java b/aeron-annotations/src/main/java/io/aeron/config/Config.java index c57c96fc9d..276fe23839 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/Config.java +++ b/aeron-annotations/src/main/java/io/aeron/config/Config.java @@ -120,4 +120,9 @@ enum IsTimeValue * @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 index cc395648b2..0b4bc8114a 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -65,6 +65,8 @@ public ConfigInfo(@JsonProperty("id") final String id) @JsonProperty public String uriParam; @JsonProperty + public boolean hasContext = true; + @JsonProperty public String context; @JsonProperty public String contextDescription; @@ -72,4 +74,6 @@ public ConfigInfo(@JsonProperty("id") final String id) public Boolean isTimeValue; @JsonProperty public TimeUnit timeUnit; + @JsonProperty + 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 index c551577511..2182b0136c 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -64,17 +64,29 @@ public boolean process(final Set annotations, final Round { try { + final ConfigInfo configInfo; + if (element instanceof VariableElement) { - processElement(configInfoMap, (VariableElement)element); + configInfo = processElement(configInfoMap, (VariableElement)element); } else if (element instanceof ExecutableElement) { - processExecutableElement(configInfoMap, (ExecutableElement)element); + configInfo = processExecutableElement(configInfoMap, (ExecutableElement)element); } else { + configInfo = null; + } + + if (configInfo != null) + { + if (element.getAnnotation(Deprecated.class) != null) + { + configInfo.deprecated = true; + } } + } catch (final Exception e) { @@ -116,14 +128,14 @@ else if (element instanceof ExecutableElement) } @SuppressWarnings({ "checkstyle:MethodLength", "checkstyle:LineLength" }) - private void processElement(final Map configInfoMap, final VariableElement element) + 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; + return null; } final String id; @@ -138,7 +150,7 @@ private void processElement(final Map configInfoMap, final V { error("duplicate config option info for id: " + id + ". Previous definition found at " + configInfo.propertyNameClassName + ":" + configInfo.propertyNameFieldName, element); - return; + return configInfo; } configInfo.foundPropertyName = true; @@ -162,7 +174,7 @@ private void processElement(final Map configInfoMap, final V { error("duplicate config default info for id: " + id + ". Previous definition found at " + configInfo.defaultClassName + ":" + configInfo.defaultFieldName, element); - return; + return configInfo; } configInfo.foundDefault = true; @@ -178,7 +190,7 @@ private void processElement(final Map configInfoMap, final V break; default: error("unable to determine config type", element); - return; + return null; } if (!config.uriParam().isEmpty()) @@ -186,6 +198,11 @@ private void processElement(final Map configInfoMap, final V configInfo.uriParam = config.uriParam(); } + if (!config.hasContext()) + { + configInfo.hasContext = false; + } + switch (config.isTimeValue()) { case TRUE: @@ -273,6 +290,8 @@ private void processElement(final Map configInfoMap, final V c.defaultValue = config.expectedCDefault(); } } + + return configInfo; } private String getDocComment(final Element element) @@ -287,22 +306,21 @@ private String getDocComment(final Element element) return description.trim(); } - private void processExecutableElement(final Map configInfoMap, final ExecutableElement element) + 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; + return null; } final String id = getConfigId(element, config.id()); final ConfigInfo configInfo = configInfoMap.computeIfAbsent(id, ConfigInfo::new); final String methodName = element.toString(); - System.out.println("-- " + methodName); - final String enclosingElementName = element.getEnclosingElement().toString(); Element e = element.getEnclosingElement(); @@ -315,6 +333,8 @@ private void processExecutableElement(final Map configInfoMa configInfo.context = enclosingElementName.substring(packageName.length() + 1) + "." + methodName; configInfo.contextDescription = getDocComment(element); + + return configInfo; } private Config.Type getConfigType(final VariableElement element, final Config config) @@ -464,7 +484,7 @@ private void sanityCheck(final String id, final ConfigInfo configInfo) insane(id, "no default value found"); } - if (configInfo.context == null || configInfo.context.isEmpty()) + if (configInfo.hasContext && (configInfo.context == null || configInfo.context.isEmpty())) { insane(id, "missing context"); } 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 index 214b1e1d65..c55f1ff6ee 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java @@ -63,7 +63,7 @@ public static void main(final String[] args) throws Exception for (final ConfigInfo configInfo: config) { - writeHeader(toHeaderString(configInfo.id)); + writeHeader(toHeaderString(configInfo.id) + (configInfo.deprecated ? " (***deprecated***)" : "")); write("Description", configInfo.propertyNameDescription); write("Type", (DefaultType.isUndefined(configInfo.overrideDefaultValueType) ? 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 12282b724b..71b1a1321b 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Configuration.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Configuration.java @@ -535,7 +535,7 @@ public final class Configuration /** * Property name for {@link FlowControl} to be employed for unicast channels. */ - @Config(existsInC = false) + @Config(existsInC = false, hasContext = false) public static final String UNICAST_FLOW_CONTROL_STRATEGY_PROP_NAME = "aeron.unicast.flow.control.strategy"; /** @@ -552,7 +552,7 @@ public final class Configuration /** * Property name for {@link FlowControl} to be employed for multicast channels. */ - @Config(existsInC = false) + @Config(existsInC = false, hasContext = false) public static final String MULTICAST_FLOW_CONTROL_STRATEGY_PROP_NAME = "aeron.multicast.flow.control.strategy"; /** @@ -846,6 +846,7 @@ public final class Configuration * 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) public static final long UNTETHERED_WINDOW_LIMIT_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5); /** @@ -859,6 +860,7 @@ public final class Configuration * 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) public static final long UNTETHERED_RESTING_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10); /** @@ -869,13 +871,13 @@ public final class Configuration /** * Property name for the class used to validate if a driver should terminate based on token. */ - @Config + @Config(defaultType = DefaultType.STRING, defaultString = "io.aeron.driver.DefaultDenyTerminationValidator") 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 + @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true) public static final String REJOIN_STREAM_PROP_NAME = "aeron.rejoin.stream"; /** @@ -909,7 +911,7 @@ public final class Configuration @Config public static final String FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME = "aeron.flow.control.receiver.timeout"; - @Config + @Config(defaultType = DefaultType.LONG, defaultLong = 5_000_000_000L, hasContext = false) private static final String MIN_FLOW_CONTROL_TIMEOUT_OLD_PROP_NAME = "aeron.MinMulticastFlowControl.receiverTimeout"; @@ -933,7 +935,7 @@ public final class Configuration * * @see #RESOLVER_INTERFACE_PROP_NAME */ - @Config + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME = "aeron.driver.resolver.bootstrap.neighbor"; /** @@ -957,6 +959,7 @@ public final class Configuration /** * Default threshold value for the conductor work cycle threshold to track for being exceeded. */ + @Config(defaultType = DefaultType.LONG, defaultLong = 1_000_000_000L) public static final long CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000); /** @@ -992,6 +995,7 @@ public final class Configuration /** * 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); /** @@ -1003,7 +1007,7 @@ public final class Configuration /** * Property name for wildcard port range for the Receiver. */ - @Config + @Config(defaultType = DefaultType.STRING, defaultString = "") public static final String RECEIVER_WILDCARD_PORT_RANGE_PROP_NAME = "aeron.receiver.wildcard.port.range"; /** @@ -1037,7 +1041,6 @@ public static boolean printConfigurationOnStart() * @return true if the high-resolution timer be used when running on Windows. * @see #USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME */ - @Config public static boolean useWindowsHighResTimer() { return "true".equals(getProperty(USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME)); @@ -1327,7 +1330,6 @@ public static boolean rejoinStream() * @return Default group tag (gtag) to send in all Status Messages. * @see #RECEIVER_GROUP_TAG_PROP_NAME */ - @Config(id = "RECEIVER_GROUP_TAG") public static Long groupTag() { return getLong(RECEIVER_GROUP_TAG_PROP_NAME, null); @@ -1339,7 +1341,6 @@ public static Long groupTag() * @return group tag (gtag) used by the tagged flow control strategy to group receivers. * @see #FLOW_CONTROL_GROUP_TAG_PROP_NAME */ - @Config @SuppressWarnings("deprecation") public static long flowControlGroupTag() { @@ -1356,7 +1357,6 @@ public static long flowControlGroupTag() * @return default minimum group size used by flow control strategies to determine connectivity. * @see #FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME */ - @Config public static int flowControlGroupMinSize() { return getInteger(FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME, 0); @@ -1368,7 +1368,6 @@ public static int flowControlGroupMinSize() * @return flow control timeout after which with no status messages the receiver is considered gone. * @see #FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME */ - @Config public static long flowControlReceiverTimeoutNs() { return getDurationInNanos( @@ -1639,7 +1638,6 @@ public static long clientLivenessTimeoutNs() * being drained. * @see #IMAGE_LIVENESS_TIMEOUT_PROP_NAME */ - @Config public static long imageLivenessTimeoutNs() { return getDurationInNanos(IMAGE_LIVENESS_TIMEOUT_PROP_NAME, IMAGE_LIVENESS_TIMEOUT_DEFAULT_NS); @@ -1655,7 +1653,6 @@ public static long imageLivenessTimeoutNs() * @return {@link Publication} unblock timeout due to client crash or untimely commit. * @see #PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME */ - @Config public static long publicationUnblockTimeoutNs() { return getDurationInNanos(PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME, PUBLICATION_UNBLOCK_TIMEOUT_DEFAULT_NS); 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 f463424c94..51adad2f7e 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java +++ b/aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java @@ -753,6 +753,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 +791,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 +803,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 +830,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 +857,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 +882,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; @@ -951,6 +957,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; @@ -1000,6 +1007,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; @@ -1049,6 +1057,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; @@ -1098,6 +1107,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; @@ -1122,6 +1132,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; @@ -1172,6 +1183,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; @@ -1198,6 +1210,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; @@ -1247,6 +1260,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; @@ -1297,6 +1311,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; @@ -1324,6 +1339,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; @@ -1423,6 +1439,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; @@ -1452,6 +1469,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); @@ -1482,6 +1500,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; @@ -1570,6 +1589,7 @@ public Context reliableStream(final boolean reliableStream) * @see Configuration#TETHER_SUBSCRIPTIONS_PROP_NAME * @see CommonContext#TETHER_PARAM_NAME */ + @Config public boolean tetherSubscriptions() { return tetherSubscriptions; @@ -1636,6 +1656,7 @@ public Context receiverGroupConsideration(final InferableBoolean receiverGroupCo * @see Configuration#REJOIN_STREAM_PROP_NAME * @see CommonContext#REJOIN_PARAM_NAME */ + @Config public boolean rejoinStream() { return rejoinStream; @@ -1717,6 +1738,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; @@ -1768,6 +1790,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; @@ -1794,6 +1817,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; @@ -1818,6 +1842,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; @@ -1842,6 +1867,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; @@ -1870,6 +1896,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; @@ -1901,6 +1928,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; @@ -2222,6 +2250,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; @@ -2271,6 +2300,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; @@ -2375,6 +2405,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; @@ -2401,6 +2432,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; @@ -2427,6 +2459,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; @@ -2496,6 +2529,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; @@ -2520,6 +2554,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; @@ -2544,6 +2579,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; @@ -2572,6 +2608,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; @@ -2600,6 +2637,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; @@ -2768,6 +2806,7 @@ Context lossReport(final LossReport lossReport) * @see #publicationReservedSessionIdHigh() * @see Configuration#PUBLICATION_RESERVED_SESSION_ID_LOW_PROP_NAME */ + @Config public int publicationReservedSessionIdLow() { return publicationReservedSessionIdLow; @@ -2795,6 +2834,7 @@ public Context publicationReservedSessionIdLow(final int sessionId) * @see #publicationReservedSessionIdLow() * @see Configuration#PUBLICATION_RESERVED_SESSION_ID_HIGH_PROP_NAME */ + @Config public int publicationReservedSessionIdHigh() { return publicationReservedSessionIdHigh; @@ -2821,6 +2861,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; @@ -2954,6 +2995,7 @@ public Context terminationValidator(final TerminationValidator validator) * * @return {@link TerminationValidator} to validate termination requests. */ + @Config public TerminationValidator terminationValidator() { return terminationValidator; @@ -2964,6 +3006,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; @@ -2987,6 +3030,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; @@ -3011,6 +3055,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; @@ -3035,6 +3080,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; @@ -3081,6 +3127,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; @@ -3142,6 +3189,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; @@ -3215,6 +3263,7 @@ public Context conductorCycleThresholdNs(final long thresholdNs) * * @return threshold to track for the conductor work cycle time. */ + @Config public long conductorCycleThresholdNs() { return conductorCycleThresholdNs; @@ -3307,6 +3356,7 @@ public Context nameResolverThresholdNs(final long thresholdNs) * * @return threshold to track for the name resolution. */ + @Config public long nameResolverThresholdNs() { return nameResolverThresholdNs; @@ -3334,6 +3384,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; @@ -3482,6 +3533,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 5f041d96db..4ae5e0e910 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 @@ -30,58 +30,53 @@ public class DebugChannelEndpointConfiguration /** * Property name for receiver inbound data loss rate. */ - @Config + @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = 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 + @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = 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) + @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = 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 + @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = 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) + @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = 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 + @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = 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) + @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = 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 + @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = 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); - @Config( - id = "RECEIVE_DATA_LOSS_RATE", - configType = Config.Type.DEFAULT, - defaultType = DefaultType.DOUBLE, - defaultDouble = 0.0) private static final double RECEIVE_DATA_LOSS_RATE = Double.parseDouble(getProperty(RECEIVE_DATA_LOSS_RATE_PROP_NAME, "0.0")); From 301407881ad206c3422ed4bd7690575f813c742f Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Wed, 8 May 2024 08:44:02 -0500 Subject: [PATCH 06/20] [JAVA] switch flow control group tag to long instead of string --- aeron-driver/src/main/java/io/aeron/driver/Configuration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 71b1a1321b..b809ab7631 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Configuration.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Configuration.java @@ -889,7 +889,7 @@ public final class Configuration /** * Property name for default group tag (gtag) used by the tagged flow control strategy to group receivers. */ - @Config(defaultType = DefaultType.STRING, defaultString = "") + @Config(defaultType = DefaultType.LONG, defaultLong = -1) public static final String FLOW_CONTROL_GROUP_TAG_PROP_NAME = "aeron.flow.control.group.tag"; /** From 17f9a991e2b9ee3ba90b8de91ee9fd55d2ff31ce Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Wed, 8 May 2024 15:31:52 -0500 Subject: [PATCH 07/20] [Java] refactor to use xml instead of json as intermediate data format --- .../main/java/io/aeron/config/ConfigInfo.java | 36 +++----- .../java/io/aeron/config/ConfigProcessor.java | 14 ++-- .../java/io/aeron/config/ExpectedCConfig.java | 8 -- .../java/io/aeron/config/ExpectedConfig.java | 3 - .../config/docgen/GenerateConfigDocTask.java | 25 ++---- .../ValidateConfigExpectationsTask.java | 20 +---- .../io/aeron/config/validation/Validator.java | 8 +- .../java/io/aeron/counter/CounterInfo.java | 15 ++-- .../io/aeron/counter/CounterProcessor.java | 20 ++--- .../ValidateCounterExpectationsTask.java | 20 +---- .../aeron/counter/validation/Validator.java | 8 +- .../main/java/io/aeron/utility/ElementIO.java | 83 +++++++++++++++++++ build.gradle | 14 ++-- 13 files changed, 144 insertions(+), 130 deletions(-) create mode 100644 aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java index 0b4bc8114a..6eb7df6253 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -15,8 +15,7 @@ */ package io.aeron.config; -import com.fasterxml.jackson.annotation.JsonProperty; - +import javax.xml.bind.annotation.XmlTransient; import java.util.concurrent.TimeUnit; /** @@ -24,56 +23,47 @@ */ public class ConfigInfo { - @JsonProperty public final String id; - @JsonProperty public final ExpectedConfig expectations; + /** + */ + public ConfigInfo() + { + this.id = null; + expectations = new ExpectedConfig(); + } + /** * @param id the unique identifier for this block o' config information */ - public ConfigInfo(@JsonProperty("id") final String id) + public ConfigInfo(final String id) { this.id = id; expectations = new ExpectedConfig(); } + @XmlTransient public boolean foundPropertyName = false; + @XmlTransient public boolean foundDefault = false; - @JsonProperty + public String propertyNameDescription; - @JsonProperty public String propertyNameFieldName; - @JsonProperty public String propertyNameClassName; - @JsonProperty public String propertyName; - @JsonProperty public String defaultDescription; - @JsonProperty public String defaultFieldName; - @JsonProperty public String defaultClassName; - @JsonProperty public Object defaultValue; - @JsonProperty public DefaultType defaultValueType = DefaultType.UNDEFINED; - @JsonProperty public Object overrideDefaultValue; - @JsonProperty public DefaultType overrideDefaultValueType = DefaultType.UNDEFINED; - @JsonProperty public String uriParam; - @JsonProperty public boolean hasContext = true; - @JsonProperty public String context; - @JsonProperty public String contextDescription; - @JsonProperty public Boolean isTimeValue; - @JsonProperty public TimeUnit timeUnit; - @JsonProperty 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 index 2182b0136c..40872c8013 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -15,7 +15,7 @@ */ package io.aeron.config; -import com.fasterxml.jackson.databind.ObjectMapper; +import io.aeron.utility.ElementIO; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -25,7 +25,6 @@ import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; -import java.io.*; import java.util.*; import java.util.stream.Stream; @@ -111,14 +110,13 @@ else if (element instanceof ExecutableElement) try { final FileObject resourceFile = processingEnv.getFiler() - .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "config-info.json"); - try (OutputStream out = resourceFile.openOutputStream()) - { - new ObjectMapper().writeValue(out, configInfoMap.values()); - } + .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "config-info.xml"); + + ElementIO.write(resourceFile, configInfoMap); } - catch (final IOException e) + catch (final Exception e) { + e.printStackTrace(System.err); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "an error occurred while writing output: " + e.getMessage()); } diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java index abe1778888..172303f060 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java @@ -15,23 +15,15 @@ */ package io.aeron.config; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * A handy class for storing expected C config info that can be serialized into json */ public class ExpectedCConfig { - @JsonProperty public boolean exists = true; - @JsonProperty public String envVarFieldName; - @JsonProperty public String envVar; - @JsonProperty public String defaultFieldName; - @JsonProperty public Object defaultValue; - @JsonProperty public DefaultType defaultValueType; } diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java index a67a17fae4..8263766df0 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java @@ -15,13 +15,10 @@ */ package io.aeron.config; -import com.fasterxml.jackson.annotation.JsonProperty; - /** */ public class ExpectedConfig { - @JsonProperty public final ExpectedCConfig c; ExpectedConfig() 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 index c55f1ff6ee..bcde38ce62 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java @@ -15,14 +15,12 @@ */ package io.aeron.config.docgen; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import io.aeron.utility.ElementIO; import io.aeron.config.ConfigInfo; import io.aeron.config.DefaultType; import java.io.FileWriter; import java.io.IOException; -import java.nio.file.Paths; import java.text.DecimalFormat; import java.util.Comparator; import java.util.List; @@ -44,7 +42,7 @@ private static String toString(final Object a) /** * @param args - * Arg 0 should be the location of a config-info.json file with a list of ConfigInfo objects + * 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 @@ -56,12 +54,7 @@ public static void main(final String[] args) throws Exception { GenerateConfigDocTask.writer = writer; - final List config = fetchConfig(args[0]) - .stream() - .sorted(Comparator.comparing(a -> a.id)) - .collect(Collectors.toList()); - - for (final ConfigInfo configInfo: config) + for (final ConfigInfo configInfo: sort(ElementIO.fetch(args[0]))) { writeHeader(toHeaderString(configInfo.id) + (configInfo.deprecated ? " (***deprecated***)" : "")); write("Description", configInfo.propertyNameDescription); @@ -120,16 +113,14 @@ public static void main(final String[] args) throws Exception } } - private static List fetchConfig(final String configInfoFilename) throws Exception + private static List sort(final List config) { - return new ObjectMapper().readValue( - Paths.get(configInfoFilename).toFile(), - new TypeReference>() - { - }); + return config + .stream() + .sorted(Comparator.comparing(a -> a.id)) + .collect(Collectors.toList()); } - private static void writeHeader(final String t) throws IOException { writeRow("", t); 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 index bd23a91f42..a33f4fbbba 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java @@ -15,12 +15,7 @@ */ package io.aeron.config.validation; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.aeron.config.ConfigInfo; - -import java.nio.file.Paths; -import java.util.List; +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 @@ -29,7 +24,7 @@ public class ValidateConfigExpectationsTask { /** * @param args - * Arg 0 should be the location of a config-info.json file with a list of ConfigInfo objects + * 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 @@ -37,15 +32,6 @@ public class ValidateConfigExpectationsTask */ public static void main(final String[] args) throws Exception { - Validator.validate(fetchConfig(args[0]), args[1]).printFailuresOn(System.err); - } - - private static List fetchConfig(final String configInfoFilename) throws Exception - { - return new ObjectMapper().readValue( - Paths.get(configInfoFilename).toFile(), - new TypeReference>() - { - }); + Validator.validate(ElementIO.fetch(args[0]), args[1]).printFailuresOn(System.err); } } 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 index 4dd4152505..bd32a08f7d 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -34,9 +34,7 @@ static ValidationReport validate( final Collection configInfoCollection, final String sourceDir) { - final Validator validator = new Validator(sourceDir); - validator.validate(configInfoCollection); - return validator.report; + return new Validator(sourceDir).validate(configInfoCollection).report; } private final String sourceDir; @@ -50,9 +48,11 @@ private Validator(final String sourceDir) this.report = new ValidationReport(); } - private void validate(final Collection configInfoCollection) + private Validator validate(final Collection configInfoCollection) { configInfoCollection.forEach(this::validateCExpectations); + + return this; } private void validateCExpectations(final ConfigInfo configInfo) diff --git a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java index e7c0ce05f8..963b64af99 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java @@ -15,28 +15,29 @@ */ package io.aeron.counter; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * A handy class for storing data that gets serialized into json */ public class CounterInfo { - @JsonProperty public final String name; + /** + */ + public CounterInfo() + { + this.name = null; + } + /** * @param name the name of the counter */ - public CounterInfo(@JsonProperty("name") final String name) + public CounterInfo(final String name) { this.name = name; } - @JsonProperty public int id; - @JsonProperty public String counterDescription; - @JsonProperty 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 index fb289be2a9..0ee6ced75d 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java @@ -15,7 +15,7 @@ */ package io.aeron.counter; -import com.fasterxml.jackson.databind.ObjectMapper; +import io.aeron.utility.ElementIO; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -25,12 +25,7 @@ import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; -import java.io.IOException; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -86,14 +81,13 @@ public boolean process(final Set annotations, final Round try { final FileObject resourceFile = processingEnv.getFiler() - .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "counter-info.json"); - try (OutputStream out = resourceFile.openOutputStream()) - { - new ObjectMapper().writeValue(out, counterInfoMap.values()); - } + .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "counter-info.xml"); + + ElementIO.write(resourceFile, counterInfoMap); } - catch (final IOException e) + catch (final Exception e) { + e.printStackTrace(System.err); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "an error occurred while writing output: " + e.getMessage()); } 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 index c2120e1ffa..f08ec99554 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java @@ -15,12 +15,7 @@ */ package io.aeron.counter.validation; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.aeron.counter.CounterInfo; - -import java.nio.file.Paths; -import java.util.List; +import io.aeron.utility.ElementIO; /** * A gradle task for validating C counters conform to expectations set by the AeronCounter annotation in java @@ -30,7 +25,7 @@ public class ValidateCounterExpectationsTask /** * @param args - * Arg 0 should be the location of a counter-info.json file with a list of CounterInfo objects + * 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 @@ -38,15 +33,6 @@ public class ValidateCounterExpectationsTask */ public static void main(final String[] args) throws Exception { - Validator.validate(fetchCounter(args[0]), args[1]).printFailuresOn(System.err); - } - - private static List fetchCounter(final String counterInfoFilename) throws Exception - { - return new ObjectMapper().readValue( - Paths.get(counterInfoFilename).toFile(), - new TypeReference>() - { - }); + Validator.validate(ElementIO.fetch(args[0]), args[1]).printFailuresOn(System.err); } } 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 index 7811ac287b..1aa7556364 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java @@ -30,9 +30,7 @@ static ValidationReport validate( final Collection counterInfoCollection, final String sourceDir) { - final Validator validator = new Validator(sourceDir); - validator.validate(counterInfoCollection); - return validator.report; + return new Validator(sourceDir).validate(counterInfoCollection).report; } private final String sourceDir; @@ -44,11 +42,13 @@ private Validator(final String sourceDir) this.report = new ValidationReport(); } - private void validate(final Collection counterInfoCollection) + private Validator validate(final Collection counterInfoCollection) { counterInfoCollection.forEach(this::validateCExpectations); identifyExtraCCounters(counterInfoCollection); + + return this; } private void identifyExtraCCounters(final Collection counterInfoCollection) 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..8300f51227 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java @@ -0,0 +1,83 @@ +/* + * 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 io.aeron.config.ConfigInfo; +import io.aeron.counter.CounterInfo; + +import javax.tools.FileObject; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + */ +@SuppressWarnings("checkstyle:MissingJavadocMethod") +public class ElementIO +{ + @SuppressWarnings("unchecked") + public static List fetch(final String elementsFilename) throws Exception + { + return ((ElementList)acquireContext() + .createUnmarshaller() + .unmarshal(Paths.get(elementsFilename).toFile())).element; + } + + public static void write(final FileObject resourceFile, final Map elements) throws Exception + { + final Marshaller marshaller = acquireContext().createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + + try (OutputStream out = resourceFile.openOutputStream()) + { + marshaller.marshal(new ElementList<>(elements), out); + } + } + + private static JAXBContext acquireContext() throws JAXBException + { + return JAXBContext.newInstance(ElementList.class, ConfigInfo.class, CounterInfo.class); + } + + /** + * @param + */ + @XmlRootElement + public static class ElementList + { + public List element; + + /** + */ + public ElementList() + { + } + + /** + * @param elementMap + */ + public ElementList(final Map elementMap) + { + this.element = new ArrayList<>(elementMap.values()); + } + } +} diff --git a/build.gradle b/build.gradle index cd029197c4..4812a447c4 100644 --- a/build.gradle +++ b/build.gradle @@ -350,10 +350,6 @@ project(':aeron-annotations') { apply plugin: 'signing' apply plugin: 'biz.aQute.bnd.builder' - dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' - } - jar { bundle { bnd """ @@ -472,7 +468,7 @@ project(':aeron-client') { } tasks.register('validateConfigExpectations', JavaExec) { - def configInfoFile = 'build/generated/sources/headers/java/main/config-info.json' + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.xml' def sourceDir = 'src/main/c' inputs.files(configInfoFile) @@ -483,7 +479,7 @@ project(':aeron-client') { } tasks.register('generateConfigDoc', JavaExec) { - def configInfoFile = 'build/generated/sources/headers/java/main/config-info.json' + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.xml' def generatedDocFile = "${buildDir}/generated-doc/out.md" inputs.files(configInfoFile) @@ -495,7 +491,7 @@ project(':aeron-client') { } tasks.register('validateCounterExpectations', JavaExec) { - def counterInfoFile = 'build/generated/sources/headers/java/main/counter-info.json' + def counterInfoFile = 'build/generated/sources/headers/java/main/counter-info.xml' def sourceDir = 'src/main/c' inputs.files(counterInfoFile) @@ -579,7 +575,7 @@ project(':aeron-driver') { } tasks.register('validateConfigExpectations', JavaExec) { - def configInfoFile = 'build/generated/sources/headers/java/main/config-info.json' + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.xml' def sourceDir = 'src/main/c' inputs.files(configInfoFile) @@ -590,7 +586,7 @@ project(':aeron-driver') { } tasks.register('generateConfigDoc', JavaExec) { - def configInfoFile = 'build/generated/sources/headers/java/main/config-info.json' + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.xml' def generatedDocFile = "${buildDir}/generated-doc/out.md" inputs.files(configInfoFile) From 214fefbafb8b7c444873dbe833e9f34546344700 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Thu, 9 May 2024 11:41:18 -0500 Subject: [PATCH 08/20] [Java] use native java object serialization instead of xml --- .../main/java/io/aeron/config/ConfigInfo.java | 16 +- .../java/io/aeron/config/ConfigProcessor.java | 4 +- .../java/io/aeron/config/ExpectedCConfig.java | 6 +- .../java/io/aeron/config/ExpectedConfig.java | 6 +- .../config/docgen/ConfigDocGenerator.java | 253 ++++++++++++++++++ .../config/docgen/GenerateConfigDocTask.java | 217 +-------------- .../ValidateConfigExpectationsTask.java | 2 +- .../io/aeron/config/validation/Validator.java | 143 ++++++---- .../java/io/aeron/counter/CounterInfo.java | 6 +- .../io/aeron/counter/CounterProcessor.java | 4 +- .../ValidateCounterExpectationsTask.java | 3 +- .../main/java/io/aeron/utility/ElementIO.java | 55 +--- build.gradle | 10 +- 13 files changed, 378 insertions(+), 347 deletions(-) create mode 100644 aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java index 6eb7df6253..8abf39a7e2 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -15,25 +15,19 @@ */ package io.aeron.config; -import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; import java.util.concurrent.TimeUnit; /** * A handy class for storing data that gets serialized into json */ -public class ConfigInfo +public class ConfigInfo implements Serializable { + private static final long serialVersionUID = 6600224566064248728L; + public final String id; public final ExpectedConfig expectations; - /** - */ - public ConfigInfo() - { - this.id = null; - expectations = new ExpectedConfig(); - } - /** * @param id the unique identifier for this block o' config information */ @@ -43,9 +37,7 @@ public ConfigInfo(final String id) expectations = new ExpectedConfig(); } - @XmlTransient public boolean foundPropertyName = false; - @XmlTransient public boolean foundDefault = false; public String propertyNameDescription; diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index 40872c8013..979027e641 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -110,9 +110,9 @@ else if (element instanceof ExecutableElement) try { final FileObject resourceFile = processingEnv.getFiler() - .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "config-info.xml"); + .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "config-info.dat"); - ElementIO.write(resourceFile, configInfoMap); + ElementIO.write(resourceFile, configInfoMap.values()); } catch (final Exception e) { diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java index 172303f060..6a119e36c0 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java @@ -15,11 +15,15 @@ */ 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 +public class ExpectedCConfig implements Serializable { + private static final long serialVersionUID = -4549394851227986144L; + public boolean exists = true; public String envVarFieldName; public String envVar; diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java index 8263766df0..eecd20e9c7 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java @@ -15,10 +15,14 @@ */ package io.aeron.config; +import java.io.Serializable; + /** */ -public class ExpectedConfig +public class ExpectedConfig implements Serializable { + private static final long serialVersionUID = -2025994445988286324L; + public final ExpectedCConfig c; ExpectedConfig() 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..288a79b156 --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java @@ -0,0 +1,253 @@ +/* + * 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.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 ? + toString(configInfo.defaultValue) : + configInfo.overrideDefaultValue.toString(); + write("Default", getDefaultString( + defaultValue, + 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( + toString(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) + { + if (defaultValue != null && !defaultValue.isEmpty() && defaultValue.chars().allMatch(Character::isDigit)) + { + final long defaultLong = Long.parseLong(defaultValue); + 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; + } + + private String toString(final Object a) + { + return a == null ? "" : a.toString(); + } +} 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 index bcde38ce62..325de5255b 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java @@ -16,30 +16,12 @@ package io.aeron.config.docgen; import io.aeron.utility.ElementIO; -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; /** * A gradle task for generating config documentation */ public class GenerateConfigDocTask { - private static String toString(final Object a) - { - return a == null ? "" : a.toString(); - } - - private static FileWriter writer; - /** * @param args * Arg 0 should be the location of a config-info.xml file with a list of ConfigInfo objects @@ -50,203 +32,6 @@ private static String toString(final Object a) */ public static void main(final String[] args) throws Exception { - try (FileWriter writer = new FileWriter(args[1])) - { - GenerateConfigDocTask.writer = writer; - - for (final ConfigInfo configInfo: sort(ElementIO.fetch(args[0]))) - { - writeHeader(toHeaderString(configInfo.id) + (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 ? - toString(configInfo.defaultValue) : - configInfo.overrideDefaultValue.toString(); - write("Default", getDefaultString( - defaultValue, - configInfo.isTimeValue == Boolean.TRUE, - 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( - toString(configInfo.expectations.c.defaultValue), - configInfo.isTimeValue == Boolean.TRUE, - configInfo.timeUnit)); - } - writeLine(); - } - } - catch (final IOException e) - { - e.printStackTrace(System.err); - } - finally - { - GenerateConfigDocTask.writer = null; - } - } - - private static List sort(final List config) - { - return config - .stream() - .sorted(Comparator.comparing(a -> a.id)) - .collect(Collectors.toList()); - } - - private static void writeHeader(final String t) throws IOException - { - writeRow("", t); - writeLine(); - writeRow("---", "---"); - writeLine(); - } - - private static void writeCode(final String a, final String b) throws IOException - { - write(a, "`" + b + "`"); - } - - private static void write(final String a, final String b) throws IOException - { - writeRow("**" + a + "**", b.replaceAll("\n", " ")); - writeLine(); - } - - private static void writeLine() throws IOException - { - writer.write("\n"); - } - - private static void writeRow(final String a, final String b) throws IOException - { - writer.write("| " + a + " | " + b + " |"); - } - - private static 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 static String getDefaultString( - final String defaultValue, - final boolean isTimeValue, - final TimeUnit timeUnit) - { - if (defaultValue != null && !defaultValue.isEmpty() && defaultValue.chars().allMatch(Character::isDigit)) - { - final long defaultLong = Long.parseLong(defaultValue); - 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; + ConfigDocGenerator.generate(ElementIO.read(args[0]), args[1]); } } 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 index a33f4fbbba..cd4c7e5c27 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java @@ -32,6 +32,6 @@ public class ValidateConfigExpectationsTask */ public static void main(final String[] args) throws Exception { - Validator.validate(ElementIO.fetch(args[0]), args[1]).printFailuresOn(System.err); + Validator.validate(ElementIO.read(args[0]), args[1]).printFailuresOn(System.err); } } 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 index bd32a08f7d..275ad2a5a0 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -82,7 +82,6 @@ private void validateCEnvVar(final Validation validation, final ExpectedCConfig } } - @SuppressWarnings("checkstyle:MethodLength") private void validateCDefault(final Validation validation, final ExpectedCConfig c) { if (Objects.isNull(c.defaultFieldName)) @@ -110,83 +109,109 @@ private void validateCDefault(final Validation validation, final ExpectedCConfig final Matcher matcher = Pattern.compile(pattern + "(.*)$").matcher(grep.getOutput()); if (!matcher.find()) { - throw new RuntimeException("asdf"); + throw new RuntimeException("asdf"); // TODO } final String originalFoundDefaultString = matcher.group(1).trim(); + final String location = grep.getFilenameAndLine(); if (c.defaultValueType == DefaultType.STRING) { - final String foundDefaultString = originalFoundDefaultString - .replaceFirst("^\\(", "") - .replaceFirst("\\)$", "") - .replaceFirst("^\"", "") - .replaceFirst("\"$", ""); - - if (foundDefaultString.equals(c.defaultValue)) - { - validation.valid("Expected Default (\"" + foundDefaultString + "\") found in " + - grep.getFilenameAndLine()); - } - else - { - validation.invalid("Expected Default string doesn't match. Expected '" + c.defaultValue + - "' but found '" + foundDefaultString + "' in " + grep.getFilenameAndLine()); - } + validateCDefaultString(validation, c, originalFoundDefaultString, location); } else if (c.defaultValueType == DefaultType.BOOLEAN) { - final String foundDefaultString = originalFoundDefaultString - .replaceFirst("^\\(", "") - .replaceFirst("\\)$", ""); - - if (foundDefaultString.equals(c.defaultValue.toString())) - { - validation.valid("Expected Default '" + foundDefaultString + "' found in " + - grep.getFilenameAndLine()); - } - else - { - validation.invalid("boolean doesn't match"); - } + validateCDefaultBoolean(validation, c, originalFoundDefaultString, location); } else if (c.defaultValueType.isNumeric()) { - final String foundDefaultString = originalFoundDefaultString - .replaceAll("INT64_C", "") - .replaceAll("UINT32_C", "") - .replaceAll("([0-9]+)L", "$1"); + 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("\"$", ""); - try + 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.toString())) + { + 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]+)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.toString())) { - 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.toString())) - { - validation.valid("Expected Default '" + foundDefaultString + "'" + - (foundDefaultString.equals(evaluatedFoundDefaultString) ? - "" : " (" + evaluatedFoundDefaultString + ")") + - " found in " + grep.getFilenameAndLine()); - } - else - { - validation.invalid("found " + foundDefaultString + - " (" + evaluatedFoundDefaultString + ") but expected " + c.defaultValue); - } + validation.valid("Expected Default '" + foundDefaultString + "'" + + (foundDefaultString.equals(evaluatedFoundDefaultString) ? + "" : " (" + evaluatedFoundDefaultString + ")") + + " found in " + location); } - catch (final ScriptException e) + else { - validation.invalid("Expected Default - unable to evaluate expression '" + - originalFoundDefaultString + "' in " + grep.getFilenameAndLine()); - e.printStackTrace(validation.out()); + validation.invalid("found " + foundDefaultString + + " (" + evaluatedFoundDefaultString + ") but expected " + c.defaultValue); } } - else + catch (final ScriptException e) { - validation.invalid("bad default type"); + 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/CounterInfo.java b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java index 963b64af99..351ac61eed 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java @@ -15,11 +15,15 @@ */ package io.aeron.counter; +import java.io.Serializable; + /** * A handy class for storing data that gets serialized into json */ -public class CounterInfo +public class CounterInfo implements Serializable { + private static final long serialVersionUID = -5863246029522577056L; + public final String name; /** diff --git a/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java index 0ee6ced75d..b0ce7fc349 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java @@ -81,9 +81,9 @@ public boolean process(final Set annotations, final Round try { final FileObject resourceFile = processingEnv.getFiler() - .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "counter-info.xml"); + .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "", "counter-info.dat"); - ElementIO.write(resourceFile, counterInfoMap); + ElementIO.write(resourceFile, counterInfoMap.values()); } catch (final Exception e) { 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 index f08ec99554..3c76e36b14 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java @@ -22,7 +22,6 @@ */ public class ValidateCounterExpectationsTask { - /** * @param args * Arg 0 should be the location of a counter-info.xml file with a list of CounterInfo objects @@ -33,6 +32,6 @@ public class ValidateCounterExpectationsTask */ public static void main(final String[] args) throws Exception { - Validator.validate(ElementIO.fetch(args[0]), args[1]).printFailuresOn(System.err); + Validator.validate(ElementIO.read(args[0]), args[1]).printFailuresOn(System.err); } } diff --git a/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java b/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java index 8300f51227..d9af9b8b69 100644 --- a/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java +++ b/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java @@ -15,19 +15,14 @@ */ package io.aeron.utility; -import io.aeron.config.ConfigInfo; -import io.aeron.counter.CounterInfo; - import javax.tools.FileObject; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.annotation.XmlRootElement; -import java.io.OutputStream; +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; -import java.util.Map; /** */ @@ -35,49 +30,19 @@ public class ElementIO { @SuppressWarnings("unchecked") - public static List fetch(final String elementsFilename) throws Exception - { - return ((ElementList)acquireContext() - .createUnmarshaller() - .unmarshal(Paths.get(elementsFilename).toFile())).element; - } - - public static void write(final FileObject resourceFile, final Map elements) throws Exception + public static List read(final String elementsFilename) throws Exception { - final Marshaller marshaller = acquireContext().createMarshaller(); - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - - try (OutputStream out = resourceFile.openOutputStream()) + try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(elementsFilename)))) { - marshaller.marshal(new ElementList<>(elements), out); + return ((List)in.readObject()); } } - private static JAXBContext acquireContext() throws JAXBException - { - return JAXBContext.newInstance(ElementList.class, ConfigInfo.class, CounterInfo.class); - } - - /** - * @param - */ - @XmlRootElement - public static class ElementList + public static void write(final FileObject resourceFile, final Collection elements) throws Exception { - public List element; - - /** - */ - public ElementList() - { - } - - /** - * @param elementMap - */ - public ElementList(final Map elementMap) + try (ObjectOutputStream out = new ObjectOutputStream(resourceFile.openOutputStream())) { - this.element = new ArrayList<>(elementMap.values()); + out.writeObject(new ArrayList<>(elements)); } } } diff --git a/build.gradle b/build.gradle index 4812a447c4..0f37f64920 100644 --- a/build.gradle +++ b/build.gradle @@ -468,7 +468,7 @@ project(':aeron-client') { } tasks.register('validateConfigExpectations', JavaExec) { - def configInfoFile = 'build/generated/sources/headers/java/main/config-info.xml' + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' def sourceDir = 'src/main/c' inputs.files(configInfoFile) @@ -479,7 +479,7 @@ project(':aeron-client') { } tasks.register('generateConfigDoc', JavaExec) { - def configInfoFile = 'build/generated/sources/headers/java/main/config-info.xml' + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' def generatedDocFile = "${buildDir}/generated-doc/out.md" inputs.files(configInfoFile) @@ -491,7 +491,7 @@ project(':aeron-client') { } tasks.register('validateCounterExpectations', JavaExec) { - def counterInfoFile = 'build/generated/sources/headers/java/main/counter-info.xml' + def counterInfoFile = 'build/generated/sources/headers/java/main/counter-info.dat' def sourceDir = 'src/main/c' inputs.files(counterInfoFile) @@ -575,7 +575,7 @@ project(':aeron-driver') { } tasks.register('validateConfigExpectations', JavaExec) { - def configInfoFile = 'build/generated/sources/headers/java/main/config-info.xml' + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' def sourceDir = 'src/main/c' inputs.files(configInfoFile) @@ -586,7 +586,7 @@ project(':aeron-driver') { } tasks.register('generateConfigDoc', JavaExec) { - def configInfoFile = 'build/generated/sources/headers/java/main/config-info.xml' + def configInfoFile = 'build/generated/sources/headers/java/main/config-info.dat' def generatedDocFile = "${buildDir}/generated-doc/out.md" inputs.files(configInfoFile) From 50b110d504333a36126d51e35646efccfc1c7543 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Thu, 9 May 2024 12:08:03 -0500 Subject: [PATCH 09/20] [Java] store Objects as Serializable --- .../src/main/java/io/aeron/config/ConfigInfo.java | 4 ++-- .../src/main/java/io/aeron/config/ConfigProcessor.java | 3 ++- .../src/main/java/io/aeron/config/ExpectedCConfig.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java index 8abf39a7e2..c2aff706c9 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -47,9 +47,9 @@ public ConfigInfo(final String id) public String defaultDescription; public String defaultFieldName; public String defaultClassName; - public Object defaultValue; + public Serializable defaultValue; public DefaultType defaultValueType = DefaultType.UNDEFINED; - public Object overrideDefaultValue; + public Serializable overrideDefaultValue; public DefaultType overrideDefaultValueType = DefaultType.UNDEFINED; public String uriParam; public boolean hasContext = true; diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index 979027e641..56b3c3052f 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -25,6 +25,7 @@ import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; +import java.io.Serializable; import java.util.*; import java.util.stream.Stream; @@ -182,7 +183,7 @@ private ConfigInfo processElement(final Map configInfoMap, f if (constantValue != null) { - configInfo.defaultValue = constantValue; + configInfo.defaultValue = (Serializable)constantValue; configInfo.defaultValueType = DefaultType.fromCanonicalName(constantValue.getClass().getCanonicalName()); } break; diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java index 6a119e36c0..f73906298e 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java @@ -28,6 +28,6 @@ public class ExpectedCConfig implements Serializable public String envVarFieldName; public String envVar; public String defaultFieldName; - public Object defaultValue; + public Serializable defaultValue; public DefaultType defaultValueType; } From 454f9a8e86959248b25762e70e09767bb1527508 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Thu, 9 May 2024 12:28:45 -0500 Subject: [PATCH 10/20] [Java] store defaults as Strings --- .../main/java/io/aeron/config/ConfigInfo.java | 21 ++++++++++++++-- .../java/io/aeron/config/ConfigProcessor.java | 10 ++++---- .../java/io/aeron/config/ExpectedCConfig.java | 7 +++++- .../config/docgen/ConfigDocGenerator.java | 25 +++++++++++++------ .../java/io/aeron/counter/CounterInfo.java | 2 ++ 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java index c2aff706c9..48ff51ca19 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -41,21 +41,38 @@ public ConfigInfo(final String id) 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 Serializable defaultValue; + + public String defaultValue; + public DefaultType defaultValueType = DefaultType.UNDEFINED; - public Serializable overrideDefaultValue; + + 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 index 56b3c3052f..a6636047bd 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -183,7 +183,7 @@ private ConfigInfo processElement(final Map configInfoMap, f if (constantValue != null) { - configInfo.defaultValue = (Serializable)constantValue; + configInfo.defaultValue = constantValue.toString(); configInfo.defaultValueType = DefaultType.fromCanonicalName(constantValue.getClass().getCanonicalName()); } break; @@ -234,16 +234,16 @@ private ConfigInfo processElement(final Map configInfoMap, f switch (config.defaultType()) { case INT: - configInfo.overrideDefaultValue = config.defaultInt(); + configInfo.overrideDefaultValue = "" + config.defaultInt(); break; case LONG: - configInfo.overrideDefaultValue = config.defaultLong(); + configInfo.overrideDefaultValue = "" + config.defaultLong(); break; case DOUBLE: - configInfo.overrideDefaultValue = config.defaultDouble(); + configInfo.overrideDefaultValue = "" + config.defaultDouble(); break; case BOOLEAN: - configInfo.overrideDefaultValue = config.defaultBoolean(); + configInfo.overrideDefaultValue = "" + config.defaultBoolean(); break; case STRING: configInfo.overrideDefaultValue = config.defaultString(); diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java index f73906298e..e5c15fee9d 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java @@ -25,9 +25,14 @@ 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 Serializable defaultValue; + + public String defaultValue; + public DefaultType defaultValueType; } 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 index 288a79b156..a12f9a0a48 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java @@ -86,8 +86,8 @@ private void generateDoc(final List configInfoCollection) throws Exc write("Default Description", configInfo.defaultDescription); } final String defaultValue = configInfo.overrideDefaultValue == null ? - toString(configInfo.defaultValue) : - configInfo.overrideDefaultValue.toString(); + configInfo.defaultValue : + configInfo.overrideDefaultValue; write("Default", getDefaultString( defaultValue, configInfo.isTimeValue, @@ -101,7 +101,7 @@ private void generateDoc(final List configInfoCollection) throws Exc { writeCode("C Env Var", configInfo.expectations.c.envVar); write("C Default", getDefaultString( - toString(configInfo.expectations.c.defaultValue), + configInfo.expectations.c.defaultValue, configInfo.isTimeValue, configInfo.timeUnit)); } @@ -173,11 +173,22 @@ else if (previous == '_') private String getDefaultString( final String defaultValue, final boolean isTimeValue, - final TimeUnit timeUnit) + final TimeUnit timeUnit) throws Exception { if (defaultValue != null && !defaultValue.isEmpty() && defaultValue.chars().allMatch(Character::isDigit)) { - final long defaultLong = Long.parseLong(defaultValue); + 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); @@ -246,8 +257,8 @@ private String getDefaultString( return defaultValue; } - private String toString(final Object a) + private String toString(final String a) { - return a == null ? "" : a.toString(); + return a == null ? "" : a; } } diff --git a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java index 351ac61eed..8055b9e551 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java @@ -42,6 +42,8 @@ public CounterInfo(final String name) } public int id; + public String counterDescription; + public String expectedCName; } From f2239cd23350e761970e28c52eb6b11d99558f17 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Thu, 9 May 2024 12:31:19 -0500 Subject: [PATCH 11/20] [Java] remove unused import --- .../src/main/java/io/aeron/config/ConfigProcessor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index a6636047bd..c895974af6 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -25,7 +25,6 @@ import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; -import java.io.Serializable; import java.util.*; import java.util.stream.Stream; From 90d4a0143aed722ba856ddcbc85d74498f391d99 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Thu, 9 May 2024 15:51:58 -0500 Subject: [PATCH 12/20] [Java] allow c default validation to be skipped --- .../src/main/java/io/aeron/config/Config.java | 5 +++++ .../java/io/aeron/config/ConfigProcessor.java | 6 +++++- .../java/io/aeron/config/ExpectedCConfig.java | 2 ++ .../config/docgen/ConfigDocGenerator.java | 18 ++++++++++++------ .../config/validation/ValidationReport.java | 10 +++++++++- .../io/aeron/config/validation/Validator.java | 8 +++++--- .../main/java/io/aeron/utility/ElementIO.java | 14 ++++++++++++-- aeron-client/src/main/java/io/aeron/Aeron.java | 13 ++++++++----- .../src/main/java/io/aeron/CommonContext.java | 8 ++++---- .../java/io/aeron/driver/Configuration.java | 2 +- 10 files changed, 63 insertions(+), 23 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/Config.java b/aeron-annotations/src/main/java/io/aeron/config/Config.java index 276fe23839..6dae9018c3 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/Config.java +++ b/aeron-annotations/src/main/java/io/aeron/config/Config.java @@ -73,6 +73,11 @@ enum Type */ 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...) */ diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index c895974af6..a8aae63913 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -85,7 +85,6 @@ else if (element instanceof ExecutableElement) configInfo.deprecated = true; } } - } catch (final Exception e) { @@ -287,6 +286,11 @@ private ConfigInfo processElement(final Map configInfoMap, f { c.defaultValue = config.expectedCDefault(); } + + if (config.skipCDefaultValidation()) + { + c.skipDefaultValidation = true; + } } return configInfo; diff --git a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java index e5c15fee9d..99900ea090 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java @@ -35,4 +35,6 @@ public class ExpectedCConfig implements Serializable public String defaultValue; public DefaultType defaultValueType; + + public boolean skipDefaultValidation = false; } 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 index a12f9a0a48..0714be2bdc 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java @@ -61,7 +61,10 @@ private void generateDoc(final List configInfoCollection) throws Exc { for (final ConfigInfo configInfo: sort(configInfoCollection)) { - writeHeader(toHeaderString(configInfo.id) + (configInfo.deprecated ? " (***deprecated***)" : "")); + writeHeader( + toHeaderString(configInfo.id) + + (configInfo.expectations.c.exists ? "" : " (***JAVA ONLY***)") + + (configInfo.deprecated ? " (***DEPRECATED***)" : "")); write("Description", configInfo.propertyNameDescription); write("Type", (DefaultType.isUndefined(configInfo.overrideDefaultValueType) ? @@ -88,6 +91,14 @@ private void generateDoc(final List configInfoCollection) throws Exc final String defaultValue = configInfo.overrideDefaultValue == null ? configInfo.defaultValue : configInfo.overrideDefaultValue; + + + if (defaultValue == null) + { + System.err.println("!!!!!!!! " + configInfo.id); + } + + write("Default", getDefaultString( defaultValue, configInfo.isTimeValue, @@ -256,9 +267,4 @@ private String getDefaultString( } return defaultValue; } - - private String toString(final String a) - { - return a == null ? "" : a; - } } 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 index 5fe8249528..55a08a4cde 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java @@ -42,7 +42,15 @@ void addEntry( if (c.exists) { validate(validateCEnvVar, entry.envVarValidation, c); - validate(validateCDefault, entry.defaultValidation, c); + + if (c.skipDefaultValidation) + { + entry.defaultValidation.valid("skipped"); + } + else + { + validate(validateCDefault, entry.defaultValidation, c); + } } entries.add(entry); } 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 index 275ad2a5a0..03c59dce88 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -105,15 +105,16 @@ private void validateCDefault(final Validation validation, final ExpectedCConfig 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()) { - throw new RuntimeException("asdf"); // TODO + validation.invalid("Found Default but the pattern doesn't match at " + location); + return; } final String originalFoundDefaultString = matcher.group(1).trim(); - final String location = grep.getFilenameAndLine(); if (c.defaultValueType == DefaultType.STRING) { @@ -185,6 +186,7 @@ private void validateCDefaultNumeric( final String foundDefaultString = originalFoundDefaultString .replaceAll("INT64_C", "") .replaceAll("UINT32_C", "") + .replaceAll("([0-9]+)LL", "$1") .replaceAll("([0-9]+)L", "$1"); try @@ -194,7 +196,7 @@ private void validateCDefaultNumeric( "(" + foundDefaultString + ").toFixed(0)" // avoid scientific notation ).toString(); - if (evaluatedFoundDefaultString.equals(c.defaultValue.toString())) + if (evaluatedFoundDefaultString.equals(c.defaultValue)) { validation.valid("Expected Default '" + foundDefaultString + "'" + (foundDefaultString.equals(evaluatedFoundDefaultString) ? diff --git a/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java b/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java index d9af9b8b69..80266fbbba 100644 --- a/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java +++ b/aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java @@ -26,9 +26,14 @@ /** */ -@SuppressWarnings("checkstyle:MissingJavadocMethod") 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 { @@ -38,7 +43,12 @@ public static List read(final String elementsFilename) throws Exception } } - public static void write(final FileObject resourceFile, final Collection elements) throws Exception + /** + * @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())) { diff --git a/aeron-client/src/main/java/io/aeron/Aeron.java b/aeron-client/src/main/java/io/aeron/Aeron.java index 783391bb9d..9d5c2db294 100644 --- a/aeron-client/src/main/java/io/aeron/Aeron.java +++ b/aeron-client/src/main/java/io/aeron/Aeron.java @@ -678,7 +678,7 @@ public static class Configuration /** * Duration to sleep when idle */ - @Config + @Config(expectedCDefaultFieldName = "AERON_CONTEXT_IDLE_SLEEP_DURATION_NS_DEFAULT") public static final String IDLE_SLEEP_DURATION_PROP_NAME = "aeron.client.idle.sleep.duration"; /** @@ -696,7 +696,7 @@ 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 + @Config(expectedCDefaultFieldName = "AERON_CONTEXT_RESOURCE_LINGER_DURATION_NS_DEFAULT") public static final String RESOURCE_LINGER_DURATION_PROP_NAME = "aeron.client.resource.linger.duration"; /** @@ -710,7 +710,7 @@ 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 + @Config(existsInC = false) public static final String CLOSE_LINGER_DURATION_PROP_NAME = "aeron.client.close.linger.duration"; /** @@ -725,7 +725,10 @@ 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 + @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"; /** @@ -739,7 +742,7 @@ public static class Configuration * * @since 1.44.0 */ - @Config(defaultType = DefaultType.STRING, defaultString = "") + @Config(defaultType = DefaultType.STRING, defaultString = "", skipCDefaultValidation = true) public static final String CLIENT_NAME_PROP_NAME = "aeron.client.name"; /** diff --git a/aeron-client/src/main/java/io/aeron/CommonContext.java b/aeron-client/src/main/java/io/aeron/CommonContext.java index 0c485e608c..73f4b8824a 100644 --- a/aeron-client/src/main/java/io/aeron/CommonContext.java +++ b/aeron-client/src/main/java/io/aeron/CommonContext.java @@ -107,7 +107,7 @@ public static InferableBoolean parse(final String value) * 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) + @Config(defaultType = DefaultType.LONG, defaultLong = 0, existsInC = false) public static final String DEBUG_TIMEOUT_PROP_NAME = "aeron.debug.timeout"; /** @@ -132,7 +132,7 @@ public static InferableBoolean parse(final String value) /** * The top level Aeron directory used for communication between a Media Driver and client. */ - @Config + @Config(skipCDefaultValidation = true) public static final String AERON_DIR_PROP_NAME = "aeron.dir"; /** @@ -146,14 +146,14 @@ public static InferableBoolean parse(final String value) * * @since 1.44.0 */ - @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false) + @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") + @Config(defaultType = DefaultType.STRING, defaultString = "stderr", existsInC = false) public static final String FALLBACK_LOGGER_PROP_NAME = "aeron.fallback.logger"; /** 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 b809ab7631..ff1dbe81c0 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Configuration.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Configuration.java @@ -883,7 +883,7 @@ public final class Configuration /** * Property name for default group tag (gtag) to send in all Status Messages. */ - @Config + @Config(defaultType = DefaultType.LONG, defaultLong = 0) public static final String RECEIVER_GROUP_TAG_PROP_NAME = "aeron.receiver.group.tag"; /** From 4d4425cf8fe307d97e6b3baaef87ca2d89c7bd73 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Fri, 10 May 2024 11:47:46 -0500 Subject: [PATCH 13/20] [Java] driver c validation complete --- .../java/io/aeron/config/ConfigProcessor.java | 48 ++++++++-- .../config/docgen/ConfigDocGenerator.java | 7 -- .../io/aeron/config/validation/Validator.java | 6 +- .../java/io/aeron/driver/Configuration.java | 87 ++++++++++++++----- .../DebugChannelEndpointConfiguration.java | 16 ++-- 5 files changed, 121 insertions(+), 43 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index a8aae63913..3f669f051a 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -38,7 +38,11 @@ public class ConfigProcessor extends AbstractProcessor private static final String[] DEFAULT_SUFFIXES = new String[] {"_DEFAULT", "_DEFAULT_NS"}; - private final Diagnostic.Kind kind = Diagnostic.Kind.NOTE; + private final boolean printNotes = false; // TODO make this configurable somehow + + private final boolean failOnError = false; // TODO make this configurable somehow + + private final Diagnostic.Kind errorKind = failOnError ? Diagnostic.Kind.ERROR : Diagnostic.Kind.NOTE; /** * {@inheritDoc} @@ -253,7 +257,7 @@ private ConfigInfo processElement(final Map configInfoMap, f } else { - // TODO bad + error("defaultType specified twice", element); } } @@ -266,29 +270,33 @@ private ConfigInfo processElement(final Map configInfoMap, f if (c.exists) { - // TODO fix isEmpty - check for NPE 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; } } @@ -488,7 +496,7 @@ private void sanityCheck(final String id, final ConfigInfo configInfo) if (configInfo.hasContext && (configInfo.context == null || configInfo.context.isEmpty())) { - insane(id, "missing context"); + note("Configuration (" + id + ") is missing context"); } } @@ -499,11 +507,39 @@ private void insane(final String id, final String errMsg) private void error(final String errMsg) { - processingEnv.getMessager().printMessage(kind, errMsg); + printMessage(errorKind, errMsg, null); } private void error(final String errMsg, final Element element) { - processingEnv.getMessager().printMessage(kind, errMsg, element); + printMessage(errorKind, errMsg, element); + } + + private void note(final String msg) + { + if (printNotes) + { + printMessage(Diagnostic.Kind.NOTE, msg, null); + } + } + + private 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) + { + if (element == null) + { + processingEnv.getMessager().printMessage(kind, msg); + } + else + { + processingEnv.getMessager().printMessage(kind, msg, element); + } } } 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 index 0714be2bdc..df962b54ab 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java @@ -92,13 +92,6 @@ private void generateDoc(final List configInfoCollection) throws Exc configInfo.defaultValue : configInfo.overrideDefaultValue; - - if (defaultValue == null) - { - System.err.println("!!!!!!!! " + configInfo.id); - } - - write("Default", getDefaultString( defaultValue, configInfo.isTimeValue, 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 index 03c59dce88..6bfa5d53c4 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java @@ -52,6 +52,10 @@ 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; } @@ -167,7 +171,7 @@ private void validateCDefaultBoolean( .replaceFirst("^\\(", "") .replaceFirst("\\)$", ""); - if (foundDefaultString.equals(c.defaultValue.toString())) + if (foundDefaultString.equals(c.defaultValue)) { validation.valid("Expected Default '" + foundDefaultString + "' found in " + location); } 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 ff1dbe81c0..4404e5d878 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Configuration.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Configuration.java @@ -178,6 +178,7 @@ public final class Configuration */ @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; @@ -193,6 +194,7 @@ public final class Configuration */ @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; @@ -472,7 +474,7 @@ public final class Configuration /** * Property name for {@link IdleStrategy} to be employed by {@link Sender} for {@link ThreadingMode#DEDICATED}. */ - @Config + @Config(skipCDefaultValidation = true) public static final String SENDER_IDLE_STRATEGY_PROP_NAME = "aeron.sender.idle.strategy"; /** @@ -484,7 +486,7 @@ public final class Configuration /** * Property name for {@link IdleStrategy} to be employed by {@link Receiver} for {@link ThreadingMode#DEDICATED}. */ - @Config + @Config(skipCDefaultValidation = true) public static final String RECEIVER_IDLE_STRATEGY_PROP_NAME = "aeron.receiver.idle.strategy"; /** @@ -497,7 +499,7 @@ public final class Configuration * Property name for {@link IdleStrategy} to be employed by {@link DriverConductor} for * {@link ThreadingMode#DEDICATED} and {@link ThreadingMode#SHARED_NETWORK}. */ - @Config + @Config(skipCDefaultValidation = true) public static final String CONDUCTOR_IDLE_STRATEGY_PROP_NAME = "aeron.conductor.idle.strategy"; /** @@ -510,7 +512,7 @@ public final class Configuration * Property name for {@link IdleStrategy} to be employed by {@link Sender} and {@link Receiver} for * {@link ThreadingMode#SHARED_NETWORK}. */ - @Config + @Config(skipCDefaultValidation = true) public static final String SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME = "aeron.sharednetwork.idle.strategy"; /** @@ -523,7 +525,7 @@ public final class Configuration * Property name for {@link IdleStrategy} to be employed by {@link Sender}, {@link Receiver}, * and {@link DriverConductor} for {@link ThreadingMode#SHARED}. */ - @Config + @Config(skipCDefaultValidation = true) public static final String SHARED_IDLE_STRATEGY_PROP_NAME = "aeron.shared.idle.strategy"; /** @@ -846,7 +848,10 @@ public final class Configuration * 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) + @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); /** @@ -860,7 +865,10 @@ public final class Configuration * 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) + @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); /** @@ -871,7 +879,11 @@ 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") + @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"; /** @@ -883,7 +895,11 @@ public final class Configuration /** * Property name for default group tag (gtag) to send in all Status Messages. */ - @Config(defaultType = DefaultType.LONG, defaultLong = 0) + @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"; /** @@ -908,17 +924,23 @@ public final class Configuration /** * Property name for flow control timeout after which with no status messages the receiver is considered gone. */ - @Config + @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"; - @Config(defaultType = DefaultType.LONG, defaultLong = 5_000_000_000L, hasContext = false) + /** + */ + // 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 = "") + @Config(defaultType = DefaultType.STRING, defaultString = "", skipCDefaultValidation = true) public static final String RESOLVER_NAME_PROP_NAME = "aeron.driver.resolver.name"; /** @@ -926,7 +948,7 @@ public final class Configuration * * @see #RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME */ - @Config(defaultType = DefaultType.STRING, defaultString = "") + @Config(defaultType = DefaultType.STRING, defaultString = "", skipCDefaultValidation = true) public static final String RESOLVER_INTERFACE_PROP_NAME = "aeron.driver.resolver.interface"; /** @@ -935,7 +957,7 @@ public final class Configuration * * @see #RESOLVER_INTERFACE_PROP_NAME */ - @Config(defaultType = DefaultType.STRING, defaultString = "") + @Config(defaultType = DefaultType.STRING, defaultString = "", skipCDefaultValidation = true) public static final String RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME = "aeron.driver.resolver.bootstrap.neighbor"; /** @@ -947,7 +969,10 @@ public final class Configuration /** * Default value for the re-resolution check interval. */ - @Config(defaultType = DefaultType.LONG, defaultLong = 1_000_000_000L) + @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); /** @@ -959,7 +984,10 @@ public final class Configuration /** * Default threshold value for the conductor work cycle threshold to track for being exceeded. */ - @Config(defaultType = DefaultType.LONG, defaultLong = 1_000_000_000L) + @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); /** @@ -971,7 +999,10 @@ public final class Configuration /** * Default threshold value for the sender work cycle threshold to track for being exceeded. */ - @Config(defaultType = DefaultType.LONG, defaultLong = 1_000_000_000L) + @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); /** @@ -983,13 +1014,19 @@ public final class Configuration /** * Default threshold value for the receiver work cycle threshold to track for being exceeded. */ - @Config(defaultType = DefaultType.LONG, defaultLong = 1_000_000_000) + @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 + @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"; /** @@ -1001,13 +1038,21 @@ public final class Configuration /** * Property name for wildcard port range for the Sender. */ - @Config(defaultType = DefaultType.STRING, defaultString = "") + @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 = "") + @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"; /** 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 4ae5e0e910..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 @@ -30,49 +30,49 @@ public class DebugChannelEndpointConfiguration /** * Property name for receiver inbound data loss rate. */ - @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false) + @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) + @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) + @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) + @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) + @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) + @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) + @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) + @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); From fbee03228bd54180d86f707e1b2c6a4cc56f627c Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Fri, 10 May 2024 11:59:05 -0500 Subject: [PATCH 14/20] [Java] refactor Grep object to avoid leaving a stream open --- .../main/java/io/aeron/validation/Grep.java | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/validation/Grep.java b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java index 15b6272222..715ce2aaca 100644 --- a/aeron-annotations/src/main/java/io/aeron/validation/Grep.java +++ b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java @@ -16,7 +16,6 @@ package io.aeron.validation; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; import java.util.Collections; import java.util.List; @@ -35,37 +34,26 @@ public final class Grep */ public static Grep execute(final String pattern, final String sourceDir) { - final String[] command = {"grep", "-r", "-n", "-E", "^" + pattern, sourceDir}; final String commandString = "grep -r -n -E '^" + pattern + "' " + sourceDir; - final Process process; try { - process = new ProcessBuilder() + final Process process = new ProcessBuilder() .redirectErrorStream(true) - .command(command) + .command(new String[] {"grep", "-r", "-n", "-E", "^" + pattern, sourceDir}) .start(); - } - catch (final IOException e) - { - return new Grep(commandString, e); - } - final int exitCode; - try - { - exitCode = process.waitFor(); + final int exitCode = process.waitFor(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) + { + return new Grep(commandString, exitCode, reader.lines().collect(Collectors.toList())); + } } - catch (final InterruptedException e) + catch (final Exception e) { return new Grep(commandString, e); } - - final List lines = new BufferedReader(new InputStreamReader(process.getInputStream())) - .lines() - .collect(Collectors.toList()); - - return new Grep(commandString, exitCode, lines); } private final String commandString; From 7502c0a475b99d2f061ebb8f49253eec3ed3a25f Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Wed, 15 May 2024 14:11:12 -0500 Subject: [PATCH 15/20] [Java] add config to refactored processor, update AeronCounters --- .../java/io/aeron/config/ConfigProcessor.java | 204 ++++++++---------- .../java/io/aeron/counter/AeronCounter.java | 5 + .../java/io/aeron/counter/CounterInfo.java | 2 + .../io/aeron/counter/CounterProcessor.java | 58 +++-- .../aeron/counter/validation/Validator.java | 12 +- .../main/java/io/aeron/utility/Processor.java | 99 +++++++++ .../src/main/java/io/aeron/AeronCounters.java | 22 +- 7 files changed, 249 insertions(+), 153 deletions(-) create mode 100644 aeron-annotations/src/main/java/io/aeron/utility/Processor.java diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index 3f669f051a..9153720b7e 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -16,11 +16,10 @@ package io.aeron.config; import io.aeron.utility.ElementIO; +import io.aeron.utility.Processor; -import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; -import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.tools.Diagnostic; import javax.tools.FileObject; @@ -32,25 +31,22 @@ * ConfigOption processor */ @SupportedAnnotationTypes("io.aeron.config.Config") -public class ConfigProcessor extends AbstractProcessor +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 boolean printNotes = false; // TODO make this configurable somehow - - private final boolean failOnError = false; // TODO make this configurable somehow - - private final Diagnostic.Kind errorKind = failOnError ? Diagnostic.Kind.ERROR : Diagnostic.Kind.NOTE; + @Override + protected String getPrintNotesPropertyName() + { + return "aeron.build.configProcessor.printNotes"; + } - /** - * {@inheritDoc} - */ @Override - public SourceVersion getSupportedSourceVersion() + protected String getFailOnErrorPropertyName() { - return SourceVersion.latest(); + return "aeron.build.configProcessor.failOnError"; } /** @@ -128,7 +124,6 @@ else if (element instanceof ExecutableElement) return false; } - @SuppressWarnings({ "checkstyle:MethodLength", "checkstyle:LineLength" }) private ConfigInfo processElement(final Map configInfoMap, final VariableElement element) { final Config config = element.getAnnotation(Config.class); @@ -186,7 +181,8 @@ private ConfigInfo processElement(final Map configInfoMap, f if (constantValue != null) { configInfo.defaultValue = constantValue.toString(); - configInfo.defaultValueType = DefaultType.fromCanonicalName(constantValue.getClass().getCanonicalName()); + configInfo.defaultValueType = + DefaultType.fromCanonicalName(constantValue.getClass().getCanonicalName()); } break; default: @@ -204,6 +200,17 @@ private ConfigInfo processElement(final Map configInfoMap, f configInfo.hasContext = false; } + 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 TRUE: @@ -227,93 +234,90 @@ private ConfigInfo processElement(final Map configInfoMap, f // TODO make sure this is either seconds, milliseconds, microseconds, or nanoseconds configInfo.timeUnit = config.timeUnit(); } + } - if (!DefaultType.isUndefined(config.defaultType())) + private void handleDefaultTypeOverride( + final VariableElement element, + final Config config, + final ConfigInfo configInfo) + { + if (DefaultType.isUndefined(config.defaultType())) { - if (DefaultType.isUndefined(configInfo.defaultValueType)) - { - 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 + 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()) { - error("defaultType specified twice", element); + 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.exists) + if (c.envVarFieldName == null && !config.expectedCEnvVarFieldName().isEmpty()) { - 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(); - } + note("expectedCEnvVarFieldName is set", element); + c.envVarFieldName = config.expectedCEnvVarFieldName(); + } - if (config.skipCDefaultValidation()) - { - note("skipCDefaultValidation is set", element); - c.skipDefaultValidation = true; - } + if (c.envVar == null && !config.expectedCEnvVar().isEmpty()) + { + note("expectedCEnvVar is set", element); + c.envVar = config.expectedCEnvVar(); } - return configInfo; - } + if (c.defaultFieldName == null && !config.expectedCDefaultFieldName().isEmpty()) + { + note("expectedCDefaultFieldName is set", element); + c.defaultFieldName = config.expectedCDefaultFieldName(); + } - private String getDocComment(final Element element) - { - final String description = processingEnv.getElementUtils().getDocComment(element); - if (description == null) + if (c.defaultValue == null && !config.expectedCDefault().isEmpty()) { - error("no javadoc found", element); - return "NO DESCRIPTION FOUND"; + note("expectedCDefault is set", element); + c.defaultValue = config.expectedCDefault(); } - return description.trim(); + if (config.skipCDefaultValidation()) + { + note("skipCDefaultValidation is set", element); + c.skipDefaultValidation = true; + } } private ConfigInfo processExecutableElement( @@ -504,42 +508,4 @@ private void insane(final String id, final String errMsg) { error("Configuration (" + id + "): " + errMsg); } - - private void error(final String errMsg) - { - printMessage(errorKind, errMsg, null); - } - - private void error(final String errMsg, final Element element) - { - printMessage(errorKind, errMsg, element); - } - - private void note(final String msg) - { - if (printNotes) - { - printMessage(Diagnostic.Kind.NOTE, msg, null); - } - } - - private 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) - { - if (element == null) - { - processingEnv.getMessager().printMessage(kind, msg); - } - else - { - processingEnv.getMessager().printMessage(kind, msg, element); - } - } } diff --git a/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java b/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java index 007ed14bf2..a4eb8bcd46 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java @@ -27,6 +27,11 @@ @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 */ diff --git a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java index 8055b9e551..f55cb059af 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java @@ -45,5 +45,7 @@ public CounterInfo(final String name) 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 index b0ce7fc349..2d22b1f270 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java @@ -16,11 +16,10 @@ package io.aeron.counter; import io.aeron.utility.ElementIO; +import io.aeron.utility.Processor; -import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; -import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.tools.Diagnostic; import javax.tools.FileObject; @@ -30,20 +29,21 @@ import java.util.regex.Pattern; /** - * ConfigOption processor + * AeronCounter processor */ @SupportedAnnotationTypes("io.aeron.counter.AeronCounter") -public class CounterProcessor extends AbstractProcessor +public class CounterProcessor extends Processor { + @Override + protected String getPrintNotesPropertyName() + { + return "aeron.build.counterProcessor.printNotes"; + } - private final Diagnostic.Kind kind = Diagnostic.Kind.NOTE; - /** - * {@inheritDoc} - */ @Override - public SourceVersion getSupportedSourceVersion() + protected String getFailOnErrorPropertyName() { - return SourceVersion.latest(); + return "aeron.build.counterProcessor.failOnError"; } /** @@ -66,6 +66,7 @@ public boolean process(final Set annotations, final Round } else { + // TODO } } catch (final Exception e) @@ -121,7 +122,7 @@ private void processElement(final Map counterInfoMap, final return; } - counterInfo.counterDescription = processingEnv.getElementUtils().getDocComment(element).trim(); + counterInfo.counterDescription = getDocComment(element); final Object constantValue = element.getConstantValue(); if (constantValue instanceof Integer) @@ -133,15 +134,32 @@ private void processElement(final Map counterInfoMap, final error("Counter value must be an Integer", element); } - counterInfo.expectedCName = "AERON_COUNTER_" + - (counter.expectedCName().isEmpty() ? - counterInfo.name.replaceAll("^DRIVER_", "") : - counter.expectedCName()) + - "_TYPE_ID"; - } + if (!counter.existsInC()) + { + note("Counter isn't expected to exist in C", element); + counterInfo.existsInC = false; + } - private void error(final String errMsg, final Element element) - { - processingEnv.getMessager().printMessage(kind, errMsg, element); + 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/Validator.java b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java index 1aa7556364..72f2a88169 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java @@ -54,8 +54,11 @@ private Validator validate(final Collection counterInfoCollection) private void identifyExtraCCounters(final Collection counterInfoCollection) { final Pattern compiledPattern = Pattern.compile("#define[ \t]+([A-Z_]+)[ \t]+\\([0-9]+\\)"); - final List expectedCNames = - counterInfoCollection.stream().map(counterInfo -> counterInfo.expectedCName).collect(Collectors.toList()); + 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); @@ -82,7 +85,10 @@ private void identifyExtraCCounters(final Collection counterInfoCol private void validateCExpectations(final CounterInfo counterInfo) { - report.addValidation(counterInfo, this::validate); + if (counterInfo.existsInC) + { + report.addValidation(counterInfo, this::validate); + } } private void validate(final Validation validation, final CounterInfo counterInfo) 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..12c08bbafc --- /dev/null +++ b/aeron-annotations/src/main/java/io/aeron/utility/Processor.java @@ -0,0 +1,99 @@ +/* + * 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.lang.model.SourceVersion; +import javax.lang.model.element.*; +import javax.tools.Diagnostic; + +/** + * abstract processor + */ +public abstract class Processor extends AbstractProcessor +{ + 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) + { + 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 getPrintNotesPropertyName(); + + protected abstract String getFailOnErrorPropertyName(); + + 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-client/src/main/java/io/aeron/AeronCounters.java b/aeron-client/src/main/java/io/aeron/AeronCounters.java index 60db1b3468..39ae895dea 100644 --- a/aeron-client/src/main/java/io/aeron/AeronCounters.java +++ b/aeron-client/src/main/java/io/aeron/AeronCounters.java @@ -127,13 +127,13 @@ public final class AeronCounters /** * Count of media driver neighbors for name resolution. */ - @AeronCounter + @AeronCounter(existsInC = false) public static final int NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID = 15; /** * Count of entries in the name resolver cache. */ - @AeronCounter + @AeronCounter(existsInC = false) public static final int NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID = 16; /** @@ -154,7 +154,7 @@ public final class AeronCounters /** * Count of number of destinations for multi-destination cast channels. */ - @AeronCounter + @AeronCounter(expectedCName = "CHANNEL_NUM_DESTINATIONS") public static final int MDC_DESTINATIONS_COUNTER_TYPE_ID = 18; // Archive counters @@ -234,7 +234,7 @@ public final class AeronCounters /** * The type id of the {@link Counter} used for tracking the count of active recording sessions. */ - @AeronCounter + @AeronCounter(existsInC = false) public static final int ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID = 111; /** @@ -284,7 +284,7 @@ public final class AeronCounters /** * Counter type for count of standby snapshots received. */ - @AeronCounter + @AeronCounter(existsInC = false) public static final int CLUSTER_STANDBY_SNAPSHOT_COUNTER_TYPE_ID = 232; /** @@ -414,7 +414,7 @@ public final class AeronCounters /** * Counter type id for the transition module error count. */ - @AeronCounter + @AeronCounter(expectedCName = "CLUSTER_TRANSITION_MODULE_ERROR_COUNT") public static final int TRANSITION_MODULE_ERROR_COUNT_TYPE_ID = 226; /** @@ -439,19 +439,19 @@ public final class AeronCounters * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of * the transition module. */ - @AeronCounter + @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 + @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 + @AeronCounter(existsInC = false) public static final int CLUSTER_TOTAL_MAX_SNAPSHOT_DURATION_TYPE_ID = 234; /** @@ -465,7 +465,7 @@ public final class AeronCounters * The type id of the {@link Counter} used for keeping track of the maximum snapshot duration * for a given clustered service. */ - @AeronCounter + @AeronCounter(existsInC = false) public static final int CLUSTERED_SERVICE_MAX_SNAPSHOT_DURATION_TYPE_ID = 236; /** @@ -484,7 +484,7 @@ public final class AeronCounters /** * The type id of the {@link Counter} used for keeping track of the Cluster leadership term id. */ - @AeronCounter + @AeronCounter(existsInC = false) public static final int CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID = 239; private AeronCounters() From f4c40918971cbcef6163f6f1748744b60aa7fbe9 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Thu, 16 May 2024 16:37:55 -0500 Subject: [PATCH 16/20] [Java] add config annotation to cluster --- .../src/main/java/io/aeron/config/Config.java | 2 +- .../java/io/aeron/config/ConfigProcessor.java | 32 +++++ .../config/docgen/ConfigDocGenerator.java | 2 +- .../java/io/aeron/cluster/ClusterBackup.java | 29 +++++ .../java/io/aeron/cluster/ClusterTool.java | 9 ++ .../io/aeron/cluster/ConsensusModule.java | 109 ++++++++++++++++++ .../io/aeron/cluster/client/AeronCluster.java | 21 ++++ .../service/ClusteredServiceContainer.java | 71 +++++++++++- build.gradle | 12 ++ 9 files changed, 279 insertions(+), 8 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/Config.java b/aeron-annotations/src/main/java/io/aeron/config/Config.java index 6dae9018c3..5ab9bf3865 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/Config.java +++ b/aeron-annotations/src/main/java/io/aeron/config/Config.java @@ -21,7 +21,7 @@ /** * Annotation to indicate this is a config option */ -@Target({ElementType.FIELD, ElementType.METHOD}) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.SOURCE) public @interface Config { diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index 9153720b7e..f26f6bae9c 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -37,6 +37,8 @@ public class ConfigProcessor extends Processor private static final String[] DEFAULT_SUFFIXES = new String[] {"_DEFAULT", "_DEFAULT_NS"}; + private final Map typeConfigMap = new HashMap<>(); + @Override protected String getPrintNotesPropertyName() { @@ -73,6 +75,11 @@ else if (element instanceof ExecutableElement) { configInfo = processExecutableElement(configInfoMap, (ExecutableElement)element); } + else if (element instanceof TypeElement) + { + processTypeElement((TypeElement)element); + configInfo = null; + } else { configInfo = null; @@ -98,6 +105,7 @@ else if (element instanceof ExecutableElement) { try { + configInfoMap.forEach(this::applyTypeDefaults); configInfoMap.forEach(this::deriveCExpectations); configInfoMap.forEach(this::sanityCheck); } @@ -351,6 +359,21 @@ private ConfigInfo processExecutableElement( 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; + } + + System.out.println("+++ " + element.getQualifiedName()); + + typeConfigMap.put(element.getQualifiedName().toString(), config); + } + private Config.Type getConfigType(final VariableElement element, final Config config) { // use an explicitly configured type @@ -400,6 +423,7 @@ else if (next == '(') { error("redundant id specified", element); } + note("Config ID is overridden", element); return id; } @@ -410,6 +434,7 @@ private String getConfigId(final VariableElement element, final String[] suffixe { if (null != id && !id.isEmpty()) { + note("Config ID is overridden", element); return id; } @@ -428,6 +453,13 @@ private String getConfigId(final VariableElement element, final String[] suffixe 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) 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 index df962b54ab..e01f4bc149 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java @@ -89,7 +89,7 @@ private void generateDoc(final List configInfoCollection) throws Exc write("Default Description", configInfo.defaultDescription); } final String defaultValue = configInfo.overrideDefaultValue == null ? - configInfo.defaultValue : + (configInfo.defaultValue == null ? "" : configInfo.defaultValue) : configInfo.overrideDefaultValue; write("Default", getDefaultString( 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..a832f0173c 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,133 +166,159 @@ 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 public static final String ERROR_BUFFER_LENGTH_PROP_NAME = "aeron.cluster.service.error.buffer.length"; /** @@ -300,27 +329,32 @@ public static final class Configuration /** * 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,6 +362,7 @@ public static final class Configuration * Property name for threshold value for the container work cycle threshold to track * for being exceeded. */ + @Config public static final String CYCLE_THRESHOLD_PROP_NAME = "aeron.cluster.service.cycle.threshold"; /** @@ -340,6 +375,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 +383,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 +514,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 +562,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 +1065,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 +1090,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 +1115,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 +1140,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 +1165,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 +1190,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 +1215,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 +1240,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 +1265,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 +1290,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 +1328,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 +1340,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 +1385,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 +1442,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 +1579,7 @@ public boolean ownsAeronClient() * * @return service this container holds. */ + @Config(id = "SERVICE_CLASS_NAME") public ClusteredService clusteredService() { return clusteredService; @@ -1583,6 +1638,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 +1678,7 @@ public File clusterDir() * @see ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME * @see #clusterDir() */ + @Config public File markFileDir() { return markFileDir; @@ -1844,6 +1901,7 @@ public Context snapshotDurationThresholdNs(final long thresholdNs) * @return threshold value in nanoseconds. * @since 1.44.0 */ + @Config public long snapshotDurationThresholdNs() { return snapshotDurationThresholdNs; @@ -1891,6 +1949,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/build.gradle b/build.gradle index 0f37f64920..cb10c50160 100644 --- a/build.gradle +++ b/build.gradle @@ -868,6 +868,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') { From e6186086b04bc2fb2854a4a8e1329ff3cf758967 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Fri, 17 May 2024 10:26:03 -0500 Subject: [PATCH 17/20] [Java] add config annotation to archive --- .../src/main/java/io/aeron/config/Config.java | 5 ++ .../main/java/io/aeron/config/ConfigInfo.java | 2 + .../java/io/aeron/config/ConfigProcessor.java | 11 ++- .../config/docgen/ConfigDocGenerator.java | 2 +- .../main/java/io/aeron/archive/Archive.java | 80 +++++++++++++++++++ .../io/aeron/archive/client/AeronArchive.java | 37 +++++++++ build.gradle | 12 +++ 7 files changed, 145 insertions(+), 4 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/Config.java b/aeron-annotations/src/main/java/io/aeron/config/Config.java index 5ab9bf3865..82177d8f1e 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/Config.java +++ b/aeron-annotations/src/main/java/io/aeron/config/Config.java @@ -108,6 +108,11 @@ enum Type */ 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 */ diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java index 48ff51ca19..9e275f4594 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java @@ -56,6 +56,8 @@ public ConfigInfo(final String id) public String defaultValue; + public String defaultValueString; + public DefaultType defaultValueType = DefaultType.UNDEFINED; public String overrideDefaultValue; diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index f26f6bae9c..876eeb810d 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -208,6 +208,11 @@ private ConfigInfo processElement(final Map configInfoMap, f configInfo.hasContext = false; } + if (!config.defaultValueString().isEmpty()) + { + configInfo.defaultValueString = config.defaultValueString(); + } + handleTimeValue(config, configInfo, id); handleDefaultTypeOverride(element, config, configInfo); @@ -369,8 +374,6 @@ private void processTypeElement(final TypeElement element) return; } - System.out.println("+++ " + element.getQualifiedName()); - typeConfigMap.put(element.getQualifiedName().toString(), config); } @@ -525,7 +528,9 @@ private void sanityCheck(final String id, final ConfigInfo configInfo) insane(id, "no property name found"); } - if (configInfo.defaultValue == null && configInfo.overrideDefaultValue == null) + if (configInfo.defaultValue == null && + configInfo.overrideDefaultValue == null && + configInfo.defaultValueString == null) { insane(id, "no default value found"); } 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 index e01f4bc149..d9580e662a 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java +++ b/aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java @@ -93,7 +93,7 @@ private void generateDoc(final List configInfoCollection) throws Exc configInfo.overrideDefaultValue; write("Default", getDefaultString( - defaultValue, + configInfo.defaultValueString == null ? defaultValue : configInfo.defaultValueString, configInfo.isTimeValue, configInfo.timeUnit)); if (configInfo.isTimeValue == Boolean.TRUE) 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..46e4d85962 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(id = "RECORDING_EVENTS_ENABLED") 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/build.gradle b/build.gradle index cb10c50160..156320c6bd 100644 --- a/build.gradle +++ b/build.gradle @@ -751,6 +751,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') { From 4a498ebe16ad0362a5df1739919a85ae623baacc Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Tue, 21 May 2024 08:50:23 -0500 Subject: [PATCH 18/20] [Java] fix duplicate config ids --- .../src/main/java/io/aeron/archive/Archive.java | 2 +- .../cluster/service/ClusteredServiceContainer.java | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) 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 46e4d85962..25426cd47a 100644 --- a/aeron-archive/src/main/java/io/aeron/archive/Archive.java +++ b/aeron-archive/src/main/java/io/aeron/archive/Archive.java @@ -1941,7 +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(id = "RECORDING_EVENTS_ENABLED") + @Config public boolean recordingEventsEnabled() { return recordingEventsEnabled; 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 a832f0173c..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 @@ -318,12 +318,13 @@ public static final class Configuration /** * Length in bytes of the error buffer for the cluster container. */ - @Config + @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; /** @@ -362,12 +363,16 @@ public static final class Configuration * Property name for threshold value for the container work cycle threshold to track * for being exceeded. */ - @Config + @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); /** @@ -1783,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; @@ -1853,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; From dfb30329edd9fd1c4c680874591ac78eed291d32 Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Mon, 3 Jun 2024 12:14:50 -0500 Subject: [PATCH 19/20] [Java] allow processors to be disabled via system property --- .../java/io/aeron/config/ConfigProcessor.java | 11 +++++++--- .../aeron/config/validation/Validation.java | 20 +++++++++---------- .../io/aeron/counter/CounterProcessor.java | 10 +++++++--- .../aeron/counter/validation/Validation.java | 20 +++++++++---------- .../main/java/io/aeron/utility/Processor.java | 20 +++++++++++++++++++ 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index 876eeb810d..c89ca72b49 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -39,6 +39,12 @@ public class ConfigProcessor extends Processor private final Map typeConfigMap = new HashMap<>(); + @Override + protected String getEnabledPropertyName() + { + return "aeron.build.configProcessor.enabled"; + } + @Override protected String getPrintNotesPropertyName() { @@ -55,7 +61,7 @@ protected String getFailOnErrorPropertyName() * {@inheritDoc} */ @Override - public boolean process(final Set annotations, final RoundEnvironment roundEnv) + public void doProcess(final Set annotations, final RoundEnvironment roundEnv) { final Map configInfoMap = new HashMap<>(); @@ -112,6 +118,7 @@ else if (element instanceof TypeElement) catch (final Exception e) { e.printStackTrace(System.err); + return; } try @@ -128,8 +135,6 @@ else if (element instanceof TypeElement) "an error occurred while writing output: " + e.getMessage()); } } - - return false; } private ConfigInfo processElement(final Map configInfoMap, final VariableElement element) 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 index 95a5506b30..c16fa968bf 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java +++ b/aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java @@ -24,9 +24,9 @@ class Validation private String message; - private ByteArrayOutputStream ba_out; + private ByteArrayOutputStream baOut; - private PrintStream ps_out; + private PrintStream psOut; boolean isValid() { @@ -35,9 +35,9 @@ boolean isValid() void close() { - if (this.ps_out != null) + if (this.psOut != null) { - this.ps_out.close(); + this.psOut.close(); } } @@ -55,21 +55,21 @@ void invalid(final String message) PrintStream out() { - if (this.ps_out == null) + if (this.psOut == null) { - this.ba_out = new ByteArrayOutputStream(); - this.ps_out = new PrintStream(ba_out); + this.baOut = new ByteArrayOutputStream(); + this.psOut = new PrintStream(baOut); } - return ps_out; + return psOut; } void printOn(final PrintStream out) { out.println(" " + (this.valid ? "+" : "-") + " " + this.message); - if (this.ps_out != null) + if (this.psOut != null) { - out.println(this.ba_out); + out.println(this.baOut); } } } diff --git a/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java index 2d22b1f270..88cd7906a5 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java @@ -34,6 +34,12 @@ @SupportedAnnotationTypes("io.aeron.counter.AeronCounter") public class CounterProcessor extends Processor { + @Override + protected String getEnabledPropertyName() + { + return "aeron.build.counterProcessor.enabled"; + } + @Override protected String getPrintNotesPropertyName() { @@ -50,7 +56,7 @@ protected String getFailOnErrorPropertyName() * {@inheritDoc} */ @Override - public boolean process(final Set annotations, final RoundEnvironment roundEnv) + public void doProcess(final Set annotations, final RoundEnvironment roundEnv) { final Map counterInfoMap = new HashMap<>(); @@ -93,8 +99,6 @@ public boolean process(final Set annotations, final Round "an error occurred while writing output: " + e.getMessage()); } } - - return false; } private void processElement(final Map counterInfoMap, final VariableElement element) 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 index cf4b75338b..31ee2c1a3e 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java @@ -25,9 +25,9 @@ class Validation private String message; - private ByteArrayOutputStream ba_out; + private ByteArrayOutputStream baOut; - private PrintStream ps_out; + private PrintStream psOut; Validation(final String name) { @@ -41,9 +41,9 @@ boolean isValid() void close() { - if (this.ps_out != null) + if (this.psOut != null) { - this.ps_out.close(); + this.psOut.close(); } } @@ -61,22 +61,22 @@ void invalid(final String message) PrintStream out() { - if (this.ps_out == null) + if (this.psOut == null) { - this.ba_out = new ByteArrayOutputStream(); - this.ps_out = new PrintStream(ba_out); + this.baOut = new ByteArrayOutputStream(); + this.psOut = new PrintStream(baOut); } - return ps_out; + return psOut; } void printOn(final PrintStream out) { out.println(name); out.println(" " + (this.valid ? "+" : "-") + " " + this.message); - if (this.ps_out != null) + if (this.psOut != null) { - out.println(this.ba_out); + out.println(this.baOut); } } } diff --git a/aeron-annotations/src/main/java/io/aeron/utility/Processor.java b/aeron-annotations/src/main/java/io/aeron/utility/Processor.java index 12c08bbafc..a7c67f135b 100644 --- a/aeron-annotations/src/main/java/io/aeron/utility/Processor.java +++ b/aeron-annotations/src/main/java/io/aeron/utility/Processor.java @@ -17,15 +17,19 @@ 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; @@ -45,6 +49,7 @@ public SourceVersion getSupportedSourceVersion() @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") ? @@ -53,10 +58,25 @@ public synchronized void init(final ProcessingEnvironment processingEnv) 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); From 8b5550918035db3db0be2c3d03dfdb2f5579345d Mon Sep 17 00:00:00 2001 From: Nate Bradac Date: Tue, 4 Jun 2024 08:52:59 -0500 Subject: [PATCH 20/20] [Java] address CodeQL issues --- .../java/io/aeron/config/ConfigProcessor.java | 14 ++++++++------ .../io/aeron/counter/validation/Validator.java | 15 +++++++++++---- .../src/main/java/io/aeron/validation/Grep.java | 7 +++++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java index c89ca72b49..f870365037 100644 --- a/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java +++ b/aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java @@ -231,12 +231,6 @@ private static void handleTimeValue(final Config config, final ConfigInfo config { switch (config.isTimeValue()) { - case TRUE: - configInfo.isTimeValue = true; - break; - case FALSE: - configInfo.isTimeValue = false; - break; case UNDEFINED: if (configInfo.isTimeValue == null) { @@ -245,6 +239,14 @@ private static void handleTimeValue(final Config config, final ConfigInfo config .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) 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 index 72f2a88169..e5e1e29975 100644 --- a/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java +++ b/aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java @@ -107,13 +107,20 @@ private void validate(final Validation validation, final CounterInfo counterInfo { final String id = matcher.group(1); - if (counterInfo.id == Integer.parseInt(id)) + try { - validation.valid("Expected ID found in " + grep.getFilenameAndLine()); + 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); + } } - else + catch (final NumberFormatException numberFormatException) { - validation.invalid("Incorrect ID found. Expected: " + counterInfo.id + " but found: " + id); + validation.invalid("Unable to parse ID. Expected a number but found: " + id); } } else diff --git a/aeron-annotations/src/main/java/io/aeron/validation/Grep.java b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java index 715ce2aaca..5f051be354 100644 --- a/aeron-annotations/src/main/java/io/aeron/validation/Grep.java +++ b/aeron-annotations/src/main/java/io/aeron/validation/Grep.java @@ -38,14 +38,17 @@ public static Grep execute(final String pattern, final String sourceDir) try { + // TODO make grep location configurable?? final Process process = new ProcessBuilder() .redirectErrorStream(true) - .command(new String[] {"grep", "-r", "-n", "-E", "^" + pattern, sourceDir}) + .command(new String[] {"/usr/bin/grep", "-r", "-n", "-E", "^" + pattern, sourceDir}) .start(); final int exitCode = process.waitFor(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) + try ( + InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream()); + BufferedReader reader = new BufferedReader(inputStreamReader)) { return new Grep(commandString, exitCode, reader.lines().collect(Collectors.toList())); }