Skip to content

Synchronization during singleton creation may result in deadlock #23501

Closed
@wilkinsona

Description

@wilkinsona

Affects: 5.1.9.RELEASE

During singleton creation, DefaultSingletonBeanRegistry synchronises on this.singletonObjects:

While synchronized, it then uses the singletonFactory to create the singleton:

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:

package example;

import javax.annotation.PostConstruct;
import javax.validation.Validator;
import javax.validation.constraints.Max;

import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

public class SingletonCreationDeadlockTests {
	
	@Test
	public void create() {
		new AnnotationConfigApplicationContext(Config.class).close();;
	}
	
	private static final class Registry {
		
		private final ConfigProperties properties;
		
		Registry(ConfigProperties properties) {
			this.properties = properties;
		}
		
		void register() {
			this.properties.getSetting();
		}
		
	}
	
	@Validated
	static class ConfigProperties {

		@Max(10)
		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(ConfigProperties properties) {
			return new Registry(properties);
		}
		
		@Bean
		public ConfigProperties properties() {
			return new ConfigProperties();
		}
		
		@Bean
		public LocalValidatorFactoryBean localValidatorFactoryBean() {
			return new LocalValidatorFactoryBean();
		}
		
		@Bean
		public static MethodValidationPostProcessor methodValidationPostProcessor(@Lazy Validator validator) {
			MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
			postProcessor.setValidator(validator);
			return postProcessor;
		}
		
		@Bean
		public Registrar registrar(Registry registry) {
			return new Registrar(registry);
		}
		
	}
	
	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();
			}
		}
		
	}

}

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 by Registrar to complete. The thread created by Registrar ends up waiting to lock singletonObjects due to ConfigProperties being @Validated and the resolution of the @Lazy Validator requiring a call to DefaultListableBeanFactory.doResolveDependency which results in a call to DefaultSingletonBeanRegistry.getSingleton where the attempt to lock singletonObjects is made.

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions