From cadaab6eaed91b2a8e4a0b3f6091057c8eab4625 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 8 Jan 2025 12:52:46 +0200 Subject: [PATCH] Add option for empty default values (#477) In Quarkus at static init time we build what is essentially a no-op CP instance, but creating this instance currently incurs some overhead due to the use of Config to look up default values. This change, will allow Quarkus to set empty default values option --- .../context/SmallRyeContextManager.java | 13 ++- .../smallrye/context/impl/DefaultValues.java | 97 ++-------------- .../context/impl/DefaultValuesFromConfig.java | 106 ++++++++++++++++++ .../context/impl/EmptyDefaultValues.java | 48 ++++++++ .../java/io/smallrye/context/ManagerTest.java | 2 +- .../classloading/MultiClassloadingTest.java | 4 +- 6 files changed, 177 insertions(+), 93 deletions(-) create mode 100644 core/src/main/java/io/smallrye/context/impl/DefaultValuesFromConfig.java create mode 100644 core/src/main/java/io/smallrye/context/impl/EmptyDefaultValues.java diff --git a/core/src/main/java/io/smallrye/context/SmallRyeContextManager.java b/core/src/main/java/io/smallrye/context/SmallRyeContextManager.java index 64686775..24891caf 100644 --- a/core/src/main/java/io/smallrye/context/SmallRyeContextManager.java +++ b/core/src/main/java/io/smallrye/context/SmallRyeContextManager.java @@ -20,6 +20,7 @@ import org.eclipse.microprofile.context.spi.ThreadContextProvider; import io.smallrye.context.impl.DefaultValues; +import io.smallrye.context.impl.DefaultValuesFromConfig; import io.smallrye.context.impl.ThreadContextProviderPlan; public class SmallRyeContextManager implements ContextManager { @@ -45,7 +46,7 @@ public class SmallRyeContextManager implements ContextManager { SmallRyeContextManager(List providers, List extensions, ExecutorService defaultExecutorService, boolean registerOnProvider, ClassLoader registrationClassLoader, - boolean enableFastThreadContextProviders) { + boolean enableFastThreadContextProviders, DefaultValues defaultValues) { this.defaultExecutorService = defaultExecutorService; this.enableFastThreadContextProviders = enableFastThreadContextProviders; List providersCopy = new ArrayList<>(providers); @@ -60,7 +61,7 @@ public class SmallRyeContextManager implements ContextManager { } allProviderTypes = providersByType.keySet().toArray(new String[providersCopy.size()]); this.extensions = new ArrayList<>(extensions); - this.defaultValues = new DefaultValues(); + this.defaultValues = defaultValues != null ? defaultValues : new DefaultValuesFromConfig(); // if our intention is to register on the provider, let's do it before we setup the extensions which may need us to be registered if (registerOnProvider) { SmallRyeContextManagerProvider.instance().registerContextManager(this, registrationClassLoader); @@ -271,6 +272,7 @@ public static class Builder implements ContextManager.Builder { private ExecutorService defaultExecutorService; private boolean registerOnProvider; private boolean enableFastThreadContextProviders = true; + private DefaultValues defaultValues = null; @Override public Builder withThreadContextProviders(ThreadContextProvider... providers) { @@ -327,6 +329,11 @@ public Builder forClassLoader(ClassLoader classLoader) { return this; } + public Builder withDefaultValues(DefaultValues defaultValues) { + this.defaultValues = defaultValues; + return this; + } + @Override public SmallRyeContextManager build() { if (addDiscoveredThreadContextProviders) @@ -335,7 +342,7 @@ public SmallRyeContextManager build() { contextManagerExtensions.addAll(discoverContextManagerExtensions()); return new SmallRyeContextManager(contextProviders, contextManagerExtensions, defaultExecutorService, - registerOnProvider, classLoader, enableFastThreadContextProviders); + registerOnProvider, classLoader, enableFastThreadContextProviders, defaultValues); } // diff --git a/core/src/main/java/io/smallrye/context/impl/DefaultValues.java b/core/src/main/java/io/smallrye/context/impl/DefaultValues.java index 952a4aff..59242424 100644 --- a/core/src/main/java/io/smallrye/context/impl/DefaultValues.java +++ b/core/src/main/java/io/smallrye/context/impl/DefaultValues.java @@ -1,99 +1,22 @@ package io.smallrye.context.impl; -import java.util.HashSet; -import java.util.NoSuchElementException; -import java.util.Set; +public interface DefaultValues { -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; + String[] getExecutorPropagated(); -import io.smallrye.context.SmallRyeContextManager; -import io.smallrye.context.api.ManagedExecutorConfig; + String[] getExecutorCleared(); -/** - * Holds default values for {@code ManagedExecutor} and {@code ThreadContext}. It firstly looks into MP Config - * for any user-specified defaults and if not defined, then it uses SmallRye defaults which propagate everything. - * - * @author Matej Novotny - */ -public class DefaultValues { + int getExecutorAsync(); - // constants defined by spec for MP Config - private final String EXEC_ASYNC = "mp.context.ManagedExecutor.maxAsync"; - private final String EXEC_QUEUE = "mp.context.ManagedExecutor.maxQueued"; - private final String EXEC_PROPAGATED = "mp.context.ManagedExecutor.propagated"; - private final String EXEC_CLEARED = "mp.context.ManagedExecutor.cleared"; - private final String THREAD_CLEARED = "mp.context.ThreadContext.cleared"; - private final String THREAD_PROPAGATED = "mp.context.ThreadContext.propagated"; - private final String THREAD_UNCHANGED = "mp.context.ThreadContext.unchanged"; + int getExecutorQueue(); - // actual defaults - private String[] executorPropagated; - private String[] executorCleared; - private int executorAsync; - private int executorQueue; - private String[] threadPropagated; - private String[] threadCleared; - private String[] threadUnchanged; + String[] getThreadPropagated(); - public DefaultValues() { - // NOTE: we do not perform sanity check here, that's done in SmallRyeContextManager - Config config = ConfigProvider.getConfig(); - Set allkeys = new HashSet<>(); - config.getPropertyNames().forEach(item -> allkeys.add(item)); - this.executorAsync = config.getOptionalValue(EXEC_ASYNC, Integer.class) - .orElse(ManagedExecutorConfig.Literal.DEFAULT_INSTANCE.maxAsync()); - this.executorQueue = config.getOptionalValue(EXEC_QUEUE, Integer.class) - .orElse(ManagedExecutorConfig.Literal.DEFAULT_INSTANCE.maxQueued()); - // remaining values have to be done via try-catch block because SmallRye Config - // considers key with empty value as non-existent - // https://github.com/smallrye/smallrye-config/issues/83 - this.executorPropagated = resolveConfiguration(config, EXEC_PROPAGATED, SmallRyeContextManager.ALL_REMAINING_ARRAY, - allkeys); - this.executorCleared = resolveConfiguration(config, EXEC_CLEARED, SmallRyeContextManager.NO_STRING, allkeys); - this.threadCleared = resolveConfiguration(config, THREAD_CLEARED, SmallRyeContextManager.NO_STRING, allkeys); - this.threadPropagated = resolveConfiguration(config, THREAD_PROPAGATED, SmallRyeContextManager.ALL_REMAINING_ARRAY, - allkeys); - this.threadUnchanged = resolveConfiguration(config, THREAD_UNCHANGED, SmallRyeContextManager.NO_STRING, allkeys); - } - - private String[] resolveConfiguration(Config mpConfig, String key, String[] originalValue, Set allKeys) { - try { - return mpConfig.getValue(key, String[].class); - } catch (NoSuchElementException e) { - // check keys, there still might be a key with no value assigned - if (allKeys.contains(key)) { - return new String[] {}; - } - return originalValue; - } - } - - public String[] getExecutorPropagated() { - return executorPropagated; - } - - public String[] getExecutorCleared() { - return executorCleared; - } + String[] getThreadCleared(); - public int getExecutorAsync() { - return executorAsync; - } - - public int getExecutorQueue() { - return executorQueue; - } - - public String[] getThreadPropagated() { - return threadPropagated; - } - - public String[] getThreadCleared() { - return threadCleared; - } + String[] getThreadUnchanged(); - public String[] getThreadUnchanged() { - return threadUnchanged; + static DefaultValues empty() { + return EmptyDefaultValues.INSTANCE; } } diff --git a/core/src/main/java/io/smallrye/context/impl/DefaultValuesFromConfig.java b/core/src/main/java/io/smallrye/context/impl/DefaultValuesFromConfig.java new file mode 100644 index 00000000..772fde6e --- /dev/null +++ b/core/src/main/java/io/smallrye/context/impl/DefaultValuesFromConfig.java @@ -0,0 +1,106 @@ +package io.smallrye.context.impl; + +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; + +import io.smallrye.context.SmallRyeContextManager; +import io.smallrye.context.api.ManagedExecutorConfig; + +/** + * Holds default values for {@code ManagedExecutor} and {@code ThreadContext}. It firstly looks into MP Config + * for any user-specified defaults and if not defined, then it uses SmallRye defaults which propagate everything. + * + * @author Matej Novotny + */ +public class DefaultValuesFromConfig implements DefaultValues { + + // constants defined by spec for MP Config + private final String EXEC_ASYNC = "mp.context.ManagedExecutor.maxAsync"; + private final String EXEC_QUEUE = "mp.context.ManagedExecutor.maxQueued"; + private final String EXEC_PROPAGATED = "mp.context.ManagedExecutor.propagated"; + private final String EXEC_CLEARED = "mp.context.ManagedExecutor.cleared"; + private final String THREAD_CLEARED = "mp.context.ThreadContext.cleared"; + private final String THREAD_PROPAGATED = "mp.context.ThreadContext.propagated"; + private final String THREAD_UNCHANGED = "mp.context.ThreadContext.unchanged"; + + // actual defaults + private String[] executorPropagated; + private String[] executorCleared; + private int executorAsync; + private int executorQueue; + private String[] threadPropagated; + private String[] threadCleared; + private String[] threadUnchanged; + + public DefaultValuesFromConfig() { + // NOTE: we do not perform sanity check here, that's done in SmallRyeContextManager + Config config = ConfigProvider.getConfig(); + Set allkeys = new HashSet<>(); + config.getPropertyNames().forEach(item -> allkeys.add(item)); + this.executorAsync = config.getOptionalValue(EXEC_ASYNC, Integer.class) + .orElse(ManagedExecutorConfig.Literal.DEFAULT_INSTANCE.maxAsync()); + this.executorQueue = config.getOptionalValue(EXEC_QUEUE, Integer.class) + .orElse(ManagedExecutorConfig.Literal.DEFAULT_INSTANCE.maxQueued()); + // remaining values have to be done via try-catch block because SmallRye Config + // considers key with empty value as non-existent + // https://github.com/smallrye/smallrye-config/issues/83 + this.executorPropagated = resolveConfiguration(config, EXEC_PROPAGATED, SmallRyeContextManager.ALL_REMAINING_ARRAY, + allkeys); + this.executorCleared = resolveConfiguration(config, EXEC_CLEARED, SmallRyeContextManager.NO_STRING, allkeys); + this.threadCleared = resolveConfiguration(config, THREAD_CLEARED, SmallRyeContextManager.NO_STRING, allkeys); + this.threadPropagated = resolveConfiguration(config, THREAD_PROPAGATED, SmallRyeContextManager.ALL_REMAINING_ARRAY, + allkeys); + this.threadUnchanged = resolveConfiguration(config, THREAD_UNCHANGED, SmallRyeContextManager.NO_STRING, allkeys); + } + + private String[] resolveConfiguration(Config mpConfig, String key, String[] originalValue, Set allKeys) { + try { + return mpConfig.getValue(key, String[].class); + } catch (NoSuchElementException e) { + // check keys, there still might be a key with no value assigned + if (allKeys.contains(key)) { + return new String[] {}; + } + return originalValue; + } + } + + @Override + public String[] getExecutorPropagated() { + return executorPropagated; + } + + @Override + public String[] getExecutorCleared() { + return executorCleared; + } + + @Override + public int getExecutorAsync() { + return executorAsync; + } + + @Override + public int getExecutorQueue() { + return executorQueue; + } + + @Override + public String[] getThreadPropagated() { + return threadPropagated; + } + + @Override + public String[] getThreadCleared() { + return threadCleared; + } + + @Override + public String[] getThreadUnchanged() { + return threadUnchanged; + } +} diff --git a/core/src/main/java/io/smallrye/context/impl/EmptyDefaultValues.java b/core/src/main/java/io/smallrye/context/impl/EmptyDefaultValues.java new file mode 100644 index 00000000..e9a8f887 --- /dev/null +++ b/core/src/main/java/io/smallrye/context/impl/EmptyDefaultValues.java @@ -0,0 +1,48 @@ +package io.smallrye.context.impl; + +import io.smallrye.context.api.ManagedExecutorConfig; + +final class EmptyDefaultValues implements DefaultValues { + + static final EmptyDefaultValues INSTANCE = new EmptyDefaultValues(); + + private static final String[] EMPTY_ARRAY = new String[0]; + + private EmptyDefaultValues() { + } + + @Override + public String[] getExecutorPropagated() { + return EMPTY_ARRAY; + } + + @Override + public String[] getExecutorCleared() { + return EMPTY_ARRAY; + } + + @Override + public int getExecutorAsync() { + return ManagedExecutorConfig.Literal.DEFAULT_INSTANCE.maxAsync(); + } + + @Override + public int getExecutorQueue() { + return ManagedExecutorConfig.Literal.DEFAULT_INSTANCE.maxQueued(); + } + + @Override + public String[] getThreadPropagated() { + return EMPTY_ARRAY; + } + + @Override + public String[] getThreadCleared() { + return EMPTY_ARRAY; + } + + @Override + public String[] getThreadUnchanged() { + return EMPTY_ARRAY; + } +} diff --git a/testsuite/extra/src/test/java/io/smallrye/context/ManagerTest.java b/testsuite/extra/src/test/java/io/smallrye/context/ManagerTest.java index 5864a9a8..0d5e10e9 100644 --- a/testsuite/extra/src/test/java/io/smallrye/context/ManagerTest.java +++ b/testsuite/extra/src/test/java/io/smallrye/context/ManagerTest.java @@ -26,7 +26,7 @@ class ManagerTest extends AbstractTest { @Test void testContext() { SmallRyeContextManager manager = new SmallRyeContextManager(Arrays.asList(A, B), Collections.emptyList(), null, false, - null, true); + null, true, null); // all providers ThreadContextProviderPlan providers = manager.getProviderPlan(); diff --git a/testsuite/extra/src/test/java/io/smallrye/context/test/classloading/MultiClassloadingTest.java b/testsuite/extra/src/test/java/io/smallrye/context/test/classloading/MultiClassloadingTest.java index ec51c2a9..baee51b8 100644 --- a/testsuite/extra/src/test/java/io/smallrye/context/test/classloading/MultiClassloadingTest.java +++ b/testsuite/extra/src/test/java/io/smallrye/context/test/classloading/MultiClassloadingTest.java @@ -46,7 +46,7 @@ import io.smallrye.config.SmallRyeConfigProviderResolver; import io.smallrye.context.SmallRyeContextManagerProvider; import io.smallrye.context.api.ManagedExecutorConfig; -import io.smallrye.context.impl.DefaultValues; +import io.smallrye.context.impl.DefaultValuesFromConfig; import io.smallrye.context.test.util.AbstractTest; class MultiClassloadingTest extends AbstractTest { @@ -132,7 +132,7 @@ void test() throws Exception { // dont use addPackages for Smallrye-Context because it // would include test packages .addPackage(SmallRyeContextManagerProvider.class.getPackage().getName()) - .addPackage(DefaultValues.class.getPackage().getName()) + .addPackage(DefaultValuesFromConfig.class.getPackage().getName()) .addPackage(ContextManagerExtension.class.getPackage().getName()) .addPackage(ManagedExecutorConfig.class.getPackage().getName()) .addAsServiceProvider(ConfigProviderResolver.class, SmallRyeConfigProviderResolver.class)