Affects: spring-boot 2.3.2 and higher
Since this commit https://github.com/spring-projects/spring-framework/commit/550f13e8ed678447a61872b4f9ff96d918003102#diff-00728bf33ef5ae1a833bdd47f5095b4bb377206328c288ad2290e9d89ba0d3d4 we get intermittent ConcurrentModificationException
from GenericConversionService$Converters.getMatchableConverters(GenericConversionService.java:522)
during start:
ERROR 2020-11-30 18:26:00.296 [AcxSpringBoot-56.starting][c1a00a] - Application run failed [SpringApplication]
java.util.ConcurrentModificationException: null
at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1134)
at org.springframework.core.convert.support.GenericConversionService$Converters.getMatchableConverters(GenericConversionService.java:522)
at org.springframework.core.convert.support.GenericConversionService$Converters.add(GenericConversionService.java:515)
at org.springframework.core.convert.support.GenericConversionService.addConverter(GenericConversionService.java:105)
at org.springframework.core.convert.support.GenericConversionService.addConverter(GenericConversionService.java:99)
at org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor.postProcessBeanFactory(RsaKeyConversionServicePostProcessor.java:72)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:291)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:182)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at com.wamas.acx4.mfs.core.server.remote.rest.AcxSpringAppForRest.start(AcxSpringAppForRest.java:97)
at com.wamas.acx4.mfs.core.server.remote.rest.AcxSpringDaemon.onStart(AcxSpringDaemon.java:57)
at com.wamas.acx4.mfs.core.etc.executor.AcxRunnable.lambda$onStartInternal$2(AcxRunnable.java:318)
at com.wamas.acx4.mfs.core.decorator.internal.InternalDecorateWith.execWithNewContextAndTransaction(InternalDecorateWith.java:183)
at com.wamas.acx4.mfs.core.decorator.Decorate.exec(Decorate.java:840)
at com.wamas.acx4.mfs.core.etc.executor.AcxRunnable.onStartInternal(AcxRunnable.java:318)
at com.wamas.acx4.mfs.core.server.platform.daemon.ServiceDaemon.onStartInternal(ServiceDaemon.java:96)
at com.wamas.acx4.mfs.core.etc.executor.AcxRunnable.run(AcxRunnable.java:181)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Since Java 9 java.util.HashMap.computeIfAbsent
shows this behavior.
See https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html#computeIfAbsent(K,java.util.function.Function)
Comment From: jhoeller
I wonder where the actual concurrent modification comes from in your scenario? HashMap
is only supposed to throw such an exception if the mapping function modifies the map... which doesn't seem to be the case here, with a simple ConvertersForPair
instance being constructed.
Comment From: jhoeller
I have a suspicion that there might be concurrent modifications from another thread, even if this isn't designed for multi-threaded configuration calls at all at this point. The check in JDK 15's HashMap
implementation would throw a ConcurrentModificationException
on any intermediate modification, not just on modification from the mapping function.
If this is indeed a multi-threaded scenario, we need to consider making addConverter
configuration calls actually thread-safe.
Comment From: jhoeller
I ended up switching our GenericConversionService
state management to ConcurrentHashMap
(with a supporting ConcurrentLinkedDeque
in ConvertersForPair
), not imposing any restrictions on our computeIfAbsent
call and generally supporting addConverter
calls from multiple threads now. This seems safer and covering more use scenarios than simply reverting to independent get
/´put` calls (as we had them before the aforementioned commit, but not actually supporting concurrent registration back then, being exposed to race conditions and visibility limitations in any case).
Comment From: HelJani
Thanks, in our use case we have two Spring-Apps that are normally running in 2 JVMs. For unit testing we start them in a single JVM in 2 parallel Threads. There these race conditions lead to these sporadic exceptions. We will try with 396fb0c