Skip to content

Commit

Permalink
Add option for empty default values (#477)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
geoand authored Jan 8, 2025
1 parent 7beee47 commit cadaab6
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 93 deletions.
13 changes: 10 additions & 3 deletions core/src/main/java/io/smallrye/context/SmallRyeContextManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -45,7 +46,7 @@ public class SmallRyeContextManager implements ContextManager {

SmallRyeContextManager(List<ThreadContextProvider> providers, List<ContextManagerExtension> extensions,
ExecutorService defaultExecutorService, boolean registerOnProvider, ClassLoader registrationClassLoader,
boolean enableFastThreadContextProviders) {
boolean enableFastThreadContextProviders, DefaultValues defaultValues) {
this.defaultExecutorService = defaultExecutorService;
this.enableFastThreadContextProviders = enableFastThreadContextProviders;
List<ThreadContextProvider> providersCopy = new ArrayList<>(providers);
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -335,7 +342,7 @@ public SmallRyeContextManager build() {
contextManagerExtensions.addAll(discoverContextManagerExtensions());

return new SmallRyeContextManager(contextProviders, contextManagerExtensions, defaultExecutorService,
registerOnProvider, classLoader, enableFastThreadContextProviders);
registerOnProvider, classLoader, enableFastThreadContextProviders, defaultValues);
}

//
Expand Down
97 changes: 10 additions & 87 deletions core/src/main/java/io/smallrye/context/impl/DefaultValues.java
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit cadaab6

Please sign in to comment.