-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Race condition on concurrent ApplicationContext start [3.10.x] #10091
Comments
WorkaroundCall The root cause
ConvertiblePair pair = new ConvertiblePair(sourceType, targetType, null);
// Some thread gets null for 'micronaut.server.cors.enabled' because converterCache has been reset by other thread
TypeConverter typeConverter = converterCache.get(pair); // line 144
if (typeConverter == null) {
// Also gets null here
typeConverter = findTypeConverter(sourceType, targetType, null);
if (typeConverter == null) {
// Puts UNCONVERTIBLE into the converter cache
// So, another thread reads this value from the cache on the line 144
// This causes the error
converterCache.put(pair, UNCONVERTIBLE);
return Optional.empty();
} else {
converterCache.put(pair, typeConverter);
if (typeConverter == UNCONVERTIBLE) {
return Optional.empty();
} else {
return typeConverter.convert(object, targetType, context);
}
} Here is how protected <T> TypeConverter findTypeConverter(Class<?> sourceType, Class<T> targetType, String formattingAnnotation) {
TypeConverter typeConverter = UNCONVERTIBLE;
List<Class> sourceHierarchy = ClassUtils.resolveHierarchy(sourceType);
List<Class> targetHierarchy = ClassUtils.resolveHierarchy(targetType);
for (Class sourceSuperType : sourceHierarchy) {
for (Class targetSuperType : targetHierarchy) {
ConvertiblePair pair = new ConvertiblePair(sourceSuperType, targetSuperType, formattingAnnotation);
// it gets in from here because there is a small amount of time when typeConverters are empty
typeConverter = typeConverters.get(pair);
if (typeConverter != null) {
converterCache.put(pair, typeConverter);
return typeConverter;
}
}
} Here is what happening on the application context start in if (initializing.compareAndSet(false, true)) {
// Reset possibly modified shared context
((DefaultConversionService) ConversionService.SHARED).reset(); // <- HERE shared caches a reseted on each start Looking at the public void reset() {
typeConverters.clear(); // thread 1 exec this
converterCache.clear(); // thread 1 sleep here
registerDefaultConverters();
}
// thread 2 gets null typeConverters.get('micronaut.server.cors.enabled')
// thread 2 puts 'micronaut.server.cors.enabled' = UNCONVERTIBLE into converterCache
// any other thread can read the UNCONVERTIVLE value from converterCache Proposed fixUse global shared lock for the |
I know that the I would like to contribute a fix for the issue in 3.10.x branch. @dstepanov I saw your changes in the codebase, WDYT? |
It happens bacause of the race condition inside micronaut. micronaut-projects/micronaut-core#10091
It happens because of the race conditions inside the micronaut. micronaut-projects/micronaut-core#10091
It happens because of the race conditions inside the micronaut. micronaut-projects/micronaut-core#10091
@PakhomovAlexander Do you have a solution in mind for this? I'm not confident locking on the ping @dstepanov do you have some thoughts about this? I have created a reproducible example project with the code provided in the description at https://github.com/wetted/core-10091-race-condition. |
@wetted thanks for your response. I found a benchmark |
Expected Behavior
Concurrent calls for
Micronaut#start()
will launch multiple servers without errors.Actual Behaviour
The code in
Steps To Reproduce
part sometimes fails with the output into stderr:Steps To Reproduce
Environment Information
java version "17.0.8" 2023-07-18 LTS
Example Application
No response
Version
3.10.x
The text was updated successfully, but these errors were encountered: