-
Notifications
You must be signed in to change notification settings - Fork 38.3k
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
Synchronization during singleton creation may result in deadlock #23501
Comments
I am seeing this exact same deadlock. Why are we |
We've just seen this problem again in spring-projects/spring-boot#33070. Here's a slightly simpler recreation that uses an package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct;
public class SingletonCreationDeadlockTests {
@Test
public void create() {
new AnnotationConfigApplicationContext(Config.class).close();;
}
private static final class Registry {
private final ObjectProvider<ConfigProperties> properties;
Registry(ObjectProvider<ConfigProperties> properties) {
this.properties = properties;
}
void register() {
this.properties.getIfAvailable().getSetting();
}
}
static class ConfigProperties {
private int setting = 5;
public int getSetting() {
return this.setting;
}
public void setSetting(int setting) {
this.setting = setting;
}
}
@Configuration
static class Config {
@Bean
public Registry registry(ObjectProvider<ConfigProperties> properties) {
return new Registry(properties);
}
@Bean
public Registrar registrar(Registry registry) {
return new Registrar(registry);
}
@Bean
public ConfigProperties properties() {
return new ConfigProperties();
}
}
static class Registrar {
private final Registry registry;
Registrar(Registry registry) {
this.registry = registry;
}
@PostConstruct
void register() {
Thread thread = new Thread(() -> {
registry.register();
});
thread.start();
try {
thread.join();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
} |
The problem here is the starting of a new thread in Doing extensive work that might trigger new threads - and even wait for them to return - is rather meant to happen in a Revisiting the singleton creation lock is a tough challenge due to singleton beans typically being part of a larger bean dependency graph. With a relaxed per-bean lock, circular references might run into a deadlock when triggered from different threads. There is no simple works-for-everything solution here, I'm afraid. |
Thanks, Juergen. The new thread started in The two threads that are involved are the main thread and the JVM's "Notification Thread". Due to Micrometer listening for GC notifications, the notification thread is making a call to a |
Thanks for the clarification, Andy - that clarifies a lot. Could Micrometer possibly only start listening to GC notifications once it is fully initialized, including the beans that it depends on? It seems brittle to let GC notifications trigger any kind of bean initialization to begin with... |
It's tricky and quite complex. In some situations, listening to the GC notification won't cause any bean creation. It will cause bean creation if you're using Prometheus, have Exemplars enabled, and the lazily created
I take the general point though that in all likelihood we need to find a way to resolve this in Boot and/or Micrometer. /cc @jonatan-ivanov |
Affects: 5.1.9.RELEASE
During singleton creation,
DefaultSingletonBeanRegistry
synchronises onthis.singletonObjects
:spring-framework/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
Line 204 in b1171d8
While synchronized, it then uses the
singletonFactory
to create the singleton:spring-framework/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
Line 222 in b1171d8
This call into user code while holding a lock can result in deadlock. We've seen one example reported in this Spring Boot issue where Micrometer is also involved. I've also reproduced a very similar problem without Micrometer and with no synchronization in user code:
Here's a zip of a complete project containing the above test: singleton-creation-deadlock.zip
The deadlock occurs because the main thread has locked
singletonObjects
and then waits for the thread created byRegistrar
to complete. The thread created byRegistrar
ends up waiting to locksingletonObjects
due toConfigProperties
being@Validated
and the resolution of the@Lazy
Validator
requiring a call toDefaultListableBeanFactory.doResolveDependency
which results in a call toDefaultSingletonBeanRegistry.getSingleton
where the attempt to locksingletonObjects
is made.The text was updated successfully, but these errors were encountered: