Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public class PropertySourcePropertyResolver implements PropertyResolver, AutoClo
private final Map<ConversionCacheKey, Object> resolvedValueCache = new ConcurrentHashMap<>(20);
private final EnvironmentProperties environmentProperties = EnvironmentProperties.fork(CURRENT_ENV);

private volatile boolean refreshing = false;

/**
* Creates a new, initially empty, {@link PropertySourcePropertyResolver} for the given {@link ConversionService}.
*
Expand Down Expand Up @@ -143,8 +145,15 @@ public PropertySourcePropertyResolver(PropertySource... propertySources) {

void reset() {
synchronized (catalog) {
Arrays.fill(catalog, null);
resetCaches();
refreshing = true;
try {
Arrays.fill(nonGenerated, null);
Arrays.fill(rawCatalog, null);
Arrays.fill(catalog, null);
resetCaches();
} finally {
refreshing = false;
}
}
}

Expand Down Expand Up @@ -283,7 +292,9 @@ public boolean containsProperty(@Nullable String name) {
if (result == null) {
result = false;
}
containsCache.put(name, result);
if (!refreshing) {
containsCache.put(name, result);
}
}
return result;
}
Expand Down Expand Up @@ -488,12 +499,15 @@ public <T> Optional<T> getProperty(@NonNull String name, @NonNull ArgumentConver
}
}

boolean inRefresh = refreshing;
if (value != null) {
Optional<T> converted;
if (entries != null) {
// iff entries is null, the value is from placeholderResolutionCache and doesn't need this step
value = resolvePlaceHoldersIfNecessary(value);
placeholderResolutionCache.put(name, value);
if (!inRefresh) {
placeholderResolutionCache.put(name, value);
}
}
if (requiredType.isInstance(value) && !CollectionUtils.isIterableOrMap(requiredType)) {
converted = (Optional<T>) Optional.of(value);
Expand All @@ -509,12 +523,14 @@ public <T> Optional<T> getProperty(@NonNull String name, @NonNull ArgumentConver
}
}

if (cacheableType) {
if (cacheableType && !inRefresh) {
resolvedValueCache.put(cacheKey, converted.orElse((T) NO_VALUE));
}
return converted;
} else if (cacheableType) {
resolvedValueCache.put(cacheKey, NO_VALUE);
if (!inRefresh) {
resolvedValueCache.put(cacheKey, NO_VALUE);
}
return Optional.empty();
} else if (Properties.class.isAssignableFrom(requiredType)) {
Properties properties = resolveSubProperties(name, entries, conversionContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ import io.micronaut.context.exceptions.ConfigurationException
import io.micronaut.core.naming.NameUtils
import io.micronaut.core.order.OrderUtil
import io.micronaut.core.util.StringUtils
import io.micronaut.core.value.PropertyCatalog
import io.micronaut.core.value.PropertyNotFoundException
import spock.lang.Issue
import spock.lang.Specification
import spock.util.environment.RestoreSystemProperties

import java.util.concurrent.Executors
import java.util.function.Function

/**
Expand Down Expand Up @@ -95,6 +98,66 @@ class DefaultEnvironmentSpec extends Specification {
diff.isEmpty()
}

void "test cache related issue when refresh method is executed"() {
given:
def propertyMap = ['testPropKey': 'testPropValueOld']
def propertySource = new MapPropertySource('CustomPS', propertyMap)
def env = new DefaultEnvironment({['test']})
env.addPropertySource(propertySource)
env.start()

expect:
env.getRequiredProperty("testPropKey", String.class) == 'testPropValueOld'

and:
def testFinished = false
def executor = Executors.newSingleThreadExecutor()
executor.submit(new Runnable() {
@Override
void run() {
while (!testFinished) {
env.getRequiredProperty("testPropKey", String.class)
}
}
})

when:
propertyMap.put('testPropKey', 'testPropValueNew')
def diff = env.refreshAndDiff()
testFinished = true

then:
env.getRequiredProperty("testPropKey", String.class) == 'testPropValueNew'
diff.get('test-prop-key') == 'testPropValueOld'
}

void "test all catalogs are cleared when properties are dropped"() {
given:
def propertyMap = ['testPropKey': 'testPropValueOld']
def propertySource = new MapPropertySource('CustomPS', propertyMap)
def env = new DefaultEnvironment({['test']})
env.addPropertySource(propertySource)
env.start()

expect:
env.getProperty("testPropKey", String.class).get() == 'testPropValueOld'
env.getProperty("test-prop-key", String.class).get() == 'testPropValueOld'

when:
env.stop() // Using this to invoke dropProperties() -> reset() method since they are private

then:
env.getProperty("testPropKey", String.class).isEmpty() // Retrieves from rawCatalog
env.getProperty("test-prop-key", String.class).isEmpty() // Retrieves from catalog

when:
env.start()

then:
env.getProperty("testPropKey", String.class).get() == 'testPropValueOld'
env.getProperty("test-prop-key", String.class).get() == 'testPropValueOld'
}

void "test environment system property refresh"() {
when:
System.setProperty("test.foo.bar", "10")
Expand Down
Loading