This is an automated email from the ASF dual-hosted git repository. kturner pushed a commit to branch 3.1 in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/3.1 by this push: new 9f8c06979f caches duration computation in accumulo configuration (#4890) 9f8c06979f is described below commit 9f8c06979fe62b882c08b9fc0a7b24a75a77d693 Author: Keith Turner <ktur...@apache.org> AuthorDate: Tue Sep 17 12:11:52 2024 -0400 caches duration computation in accumulo configuration (#4890) --- .../accumulo/core/conf/AccumuloConfiguration.java | 39 +++++++- .../core/conf/AccumuloConfigurationTest.java | 103 +++++++++++++++++++++ 2 files changed, 139 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/apache/accumulo/core/conf/AccumuloConfiguration.java b/core/src/main/java/org/apache/accumulo/core/conf/AccumuloConfiguration.java index 5dc3a29c2c..724dab45b9 100644 --- a/core/src/main/java/org/apache/accumulo/core/conf/AccumuloConfiguration.java +++ b/core/src/main/java/org/apache/accumulo/core/conf/AccumuloConfiguration.java @@ -22,7 +22,9 @@ import static org.apache.accumulo.core.conf.Property.GENERAL_ARBITRARY_PROP_PREF import static org.apache.accumulo.core.conf.Property.INSTANCE_CRYPTO_PREFIX; import static org.apache.accumulo.core.conf.Property.TABLE_CRYPTO_PREFIX; +import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; @@ -71,6 +73,27 @@ public abstract class AccumuloConfiguration implements Iterable<Entry<String,Str private static final Logger log = LoggerFactory.getLogger(AccumuloConfiguration.class); + private static final List<Property> DURATION_PROPS = Arrays.stream(Property.values()) + .filter(property -> property.getType() == PropertyType.TIMEDURATION) + .collect(Collectors.toUnmodifiableList()); + + private final Deriver<EnumMap<Property,Duration>> durationDeriver = newDeriver(conf -> { + EnumMap<Property,Duration> durations = new EnumMap<>(Property.class); + for (Property prop : DURATION_PROPS) { + var value = conf.get(prop); + if (value != null) { + try { + var durationMillis = ConfigurationTypeHelper.getTimeInMillis(value); + durations.put(prop, Duration.ofMillis(durationMillis)); + } catch (RuntimeException e) { + log.error("Failed to parse duration for {}={}", prop, value, e); + } + } + } + log.trace("recomputed durations {}", durations); + return durations; + }); + /** * Gets a property value from this configuration. * @@ -259,13 +282,23 @@ public abstract class AccumuloConfiguration implements Iterable<Entry<String,Str * Gets a property of type {@link PropertyType#TIMEDURATION}, interpreting the value properly. * * @param property property to get - * @return property value * @throws IllegalArgumentException if the property is of the wrong type */ - public long getTimeInMillis(Property property) { + public Duration getDuration(Property property) { checkType(property, PropertyType.TIMEDURATION); + return Objects.requireNonNull(durationDeriver.derive().get(property), () -> "Property " + + property.getKey() + " is not set or its value did not parse correctly."); + } - return ConfigurationTypeHelper.getTimeInMillis(get(property)); + /** + * Gets a property of type {@link PropertyType#TIMEDURATION}, interpreting the value properly. + * + * @param property property to get + * @return property value + * @throws IllegalArgumentException if the property is of the wrong type + */ + public long getTimeInMillis(Property property) { + return getDuration(property).toMillis(); } /** diff --git a/core/src/test/java/org/apache/accumulo/core/conf/AccumuloConfigurationTest.java b/core/src/test/java/org/apache/accumulo/core/conf/AccumuloConfigurationTest.java index 51c4930a95..cfcf240306 100644 --- a/core/src/test/java/org/apache/accumulo/core/conf/AccumuloConfigurationTest.java +++ b/core/src/test/java/org/apache/accumulo/core/conf/AccumuloConfigurationTest.java @@ -27,6 +27,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Field; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -445,4 +447,105 @@ public class AccumuloConfigurationTest { } } + @Test + public void testDefaultDurations() { + var conf = DefaultConfiguration.getInstance(); + for (Property prop : Property.values()) { + if (prop.getType() == PropertyType.TIMEDURATION) { + var expectedDuration = + Duration.ofMillis(ConfigurationTypeHelper.getTimeInMillis(prop.getDefaultValue())); + assertEquals(expectedDuration, conf.getDuration(prop)); + assertEquals(expectedDuration.toMillis(), conf.getTimeInMillis(prop)); + } + } + } + + /* + * AccumuloConfiguration will cache the results of computing durations, this test ensures it + * recomputes them on change. + */ + @Test + public void testDurationUpdate() { + var conf = new ConfigurationCopy(DefaultConfiguration.getInstance()); + + for (Property prop : Property.values()) { + if (prop.getType() == PropertyType.TIMEDURATION) { + var expectedDuration = + Duration.ofMillis(ConfigurationTypeHelper.getTimeInMillis(prop.getDefaultValue())); + assertEquals(expectedDuration, conf.getDuration(prop)); + assertEquals(expectedDuration.toMillis(), conf.getTimeInMillis(prop)); + } + } + + for (int toAdd = 1; toAdd <= 4; toAdd++) { + for (Property prop : Property.values()) { + if (prop.getType() == PropertyType.TIMEDURATION) { + var defaultDuration = + Duration.ofMillis(ConfigurationTypeHelper.getTimeInMillis(prop.getDefaultValue())); + var expectedDuration = defaultDuration.plusMinutes(toAdd); + conf.set(prop.getKey(), expectedDuration.toMillis() + "ms"); + } + } + + for (Property prop : Property.values()) { + if (prop.getType() == PropertyType.TIMEDURATION) { + var defaultDuration = + Duration.ofMillis(ConfigurationTypeHelper.getTimeInMillis(prop.getDefaultValue())); + var expectedDuration = defaultDuration.plus(toAdd, ChronoUnit.MINUTES); + assertEquals(expectedDuration, conf.getDuration(prop)); + assertEquals(expectedDuration.toMillis(), conf.getTimeInMillis(prop)); + } + } + } + } + + @Test + public void testMalformedDuration() { + assertEquals(PropertyType.TIMEDURATION, Property.TSERV_WAL_MAX_AGE.getType()); + assertEquals(PropertyType.TIMEDURATION, Property.GENERAL_RPC_TIMEOUT.getType()); + + var conf = new ConfigurationCopy(DefaultConfiguration.getInstance()); + conf.getDuration(Property.TSERV_WAL_MAX_AGE); + var expectedDuration = Duration.ofMillis( + ConfigurationTypeHelper.getTimeInMillis(Property.TSERV_WAL_MAX_AGE.getDefaultValue())); + var rpcTimeout = Duration.ofMillis( + ConfigurationTypeHelper.getTimeInMillis(Property.GENERAL_RPC_TIMEOUT.getDefaultValue())); + assertEquals(expectedDuration, conf.getDuration(Property.TSERV_WAL_MAX_AGE)); + assertEquals(rpcTimeout, conf.getDuration(Property.GENERAL_RPC_TIMEOUT)); + conf.set(Property.TSERV_WAL_MAX_AGE.getKey(), "abc"); + conf.set(Property.GENERAL_RPC_TIMEOUT, "2h"); + var npe = assertThrows(NullPointerException.class, + () -> conf.getDuration(Property.TSERV_WAL_MAX_AGE)); + assertTrue(npe.getMessage().contains(Property.TSERV_WAL_MAX_AGE.getKey())); + assertEquals(Duration.ofHours(2), conf.getDuration(Property.GENERAL_RPC_TIMEOUT)); + conf.set(Property.TSERV_WAL_MAX_AGE.getKey(), "1h"); + assertEquals(Duration.ofHours(1), conf.getDuration(Property.TSERV_WAL_MAX_AGE)); + assertEquals(Duration.ofHours(2), conf.getDuration(Property.GENERAL_RPC_TIMEOUT)); + } + + @Test + public void testDurationSubset() { + // test only having some of the duration properties actually set in the configuration + assertEquals(PropertyType.TIMEDURATION, Property.TSERV_WAL_MAX_AGE.getType()); + assertEquals(PropertyType.TIMEDURATION, Property.GENERAL_RPC_TIMEOUT.getType()); + + var conf = new ConfigurationCopy(); + var npe = assertThrows(NullPointerException.class, + () -> conf.getDuration(Property.TSERV_WAL_MAX_AGE)); + assertTrue(npe.getMessage().contains(Property.TSERV_WAL_MAX_AGE.getKey())); + npe = assertThrows(NullPointerException.class, + () -> conf.getTimeInMillis(Property.TSERV_WAL_MAX_AGE)); + assertTrue(npe.getMessage().contains(Property.TSERV_WAL_MAX_AGE.getKey())); + + conf.set(Property.TSERV_WAL_MAX_AGE.getKey(), "1h"); + assertEquals(Duration.ofHours(1), conf.getDuration(Property.TSERV_WAL_MAX_AGE)); + assertEquals(Duration.ofHours(1).toMillis(), conf.getTimeInMillis(Property.TSERV_WAL_MAX_AGE)); + + assertThrows(NullPointerException.class, () -> conf.getDuration(Property.GENERAL_RPC_TIMEOUT)); + assertThrows(NullPointerException.class, + () -> conf.getTimeInMillis(Property.GENERAL_RPC_TIMEOUT)); + + assertEquals(Duration.ofHours(1), conf.getDuration(Property.TSERV_WAL_MAX_AGE)); + assertEquals(Duration.ofHours(1).toMillis(), conf.getTimeInMillis(Property.TSERV_WAL_MAX_AGE)); + } }