Skip to content
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

Migrating from Spring 6.1.x to 6.2.x leads to exceptions in a Pekko setup #34303

Open
gmarfia88 opened this issue Jan 22, 2025 · 14 comments
Open
Assignees
Labels
status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged or decided on

Comments

@gmarfia88
Copy link

I am working on a Jakarta EE application that uses Spring Framework (not Spring Boot). After upgrading the spring-framework-bom version from 6.1.16 to 6.2.x, the following issues have emerged:

  1. Issue with injecting generic beans. Some beans defined with generics are no longer injected correctly. For example:

    @Bean public MyInterface myBean() { return new MyClass<>(); } // raw type definition

    bean injected as follows:

    @Qualifier("myBean") @Autowired private MyClass<MyObject> myBean; // MyClass implements MyInterface

    This generates an exception stating that no bean of type MyClass exists.

  2. Random errors during deployment. In some cases, the application deployment fails with the following exception: Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'exampleBean': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?

    The issue is non-deterministic: in subsequent deployments, the exception is reported for different beans. I have verified that these issues have been reported and tracked in the official Spring Framework tickets: Migrating spring 6.1.x to 6.2.x leads to BeanCurrentlyInCreationException #34271 and BeanCurrentlyInCreationException is thrown when multiple threads simultaneously try to create bean #34186

  3. Java Config injection sometimes fails. In certain cases, the injection of a Java Config class fails. For example:

    @Autowired private MyJavaConfig myJavaConfig;

    @Bean public MyBuilder myBuilder() { return new MyBuilder(this.myJavaConfig.serviceFactory()); }

    This generates the following exception: Caused by: java.lang.NullPointerException: Cannot invoke "MyJavaConfig.serviceFactory()" because "this.myJavaConfig" is null

These issues occur randomly, and I have not been able to reproduce them in a greenfield project. However, reverting to version 6.1.16 of Spring Framework resolves the deployment issues.

Has anyone encountered similar problems with Spring Framework 6.2.x, or does anyone have suggestions on how to address these cases?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 22, 2025
@snicoll
Copy link
Member

snicoll commented Jan 22, 2025

What version are you using. You mentioned 6.2.x but not the actual version. 1 and 2 looks duplicate of already raised concerns that are fixed in 6.2.2.

Also please do not create "meta-issue" like this. For each problem, search first in the issue tracker if an issue has already been raised.

@snicoll snicoll added the status: waiting-for-feedback We need additional information before we can continue label Jan 22, 2025
@gmarfia88
Copy link
Author

The latest tests were done with version 6.2.2 but I confirm the problems reported in points 2 and 3. (For point 1 I will probably have to check again on 6.2.2).

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 22, 2025
@bclozel
Copy link
Member

bclozel commented Jan 22, 2025

  1. already got an extensive comment from Juergen explaining the situation. Please apply the advice in your application.

As for 3), your configuration has been relying on an unspecified order and worked by accident. Declaring dependencies is the expected way:

@Bean 
public MyBuilder myBuilder(MayJavaConfig myJavaConfig) { return new MyBuilder(myJavaConfig.serviceFactory()); }

I'm closing this issue for now. We can reopen it if you provide a sample application that uses Spring Framework 6.2.2 and reproduces the problem.

@bclozel bclozel closed this as not planned Won't fix, can't repro, duplicate, stale Jan 22, 2025
@bclozel bclozel added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Jan 22, 2025
@gmarfia88
Copy link
Author

Hi,
I am commenting on the ticket because the issue with circular dependencies occurs in a single-threaded context. I do not have any custom configurations for multi-threading, and, as far as I know, Spring by default does not use multiple threads for context initialization. Could you please confirm if this statement is correct?

Regarding point 3, I would like to emphasize that my application has been working correctly for years with the current configuration. The suggestion you proposed would force me to modify thousands of configurations due to a minor update (from 6.1.16 to 6.2.x) of Spring Framework.

Moreover, by injecting a Java Config via @Autowired, I can define a bean without needing to pass a parameter as input, as shown in the following example:

public interface ConfigInterface { MyBuilder myBuilder(); }

@Configuration public class ConfigA implements ConfigInterface {

@Autowired private MyJavaConfig myJavaConfig;

@Bean @Override public MyBuilder myBuilder() { return new MyBuilder(this.myJavaConfig.serviceFactory()); }
}

@Configuration
public class ConfigB {

@Autowired private ConfigInterface configInterface;

@Bean public MyBean myBean() { return new MyBean(this.configInterface.myBuilder()); }

}

If I were to follow your suggestion and add the configuration as a bean parameter, I would also need to inject MyJavaConfig, which would further complicate the management:

@Configuration public class ConfigA implements ConfigInterface {

@Bean @Override public MyBuilder myBuilder(MyJavaConfig myJavaConfig) {
return new MyBuilder(myJavaConfig.serviceFactory());
}
}

@Configuration public class ConfigB {

@Bean public MyBean myBean(ConfigInterface configInterface, MyJavaConfig myJavaConfig) {
return new MyBean(configInterface.myBuilder(myJavaConfig));
}
}

This approach could result in a less clean design, as I might not want to expose MyJavaConfig to ConfigB.

@bclozel
Copy link
Member

bclozel commented Jan 24, 2025

I am commenting on the ticket because the issue with circular dependencies occurs in a single-threaded context. I do not have any custom configurations for multi-threading, and, as far as I know, Spring by default does not use multiple threads for context initialization. Could you please confirm if this statement is correct?

This is correct, Spring Framework does not use multiple threads by default for context initialization. You can confirm whether this is the case in your application by applying the advice from this comment: #34308 (comment)

Regarding point 3, I would like to emphasize that my application has been working correctly for years with the current configuration. The suggestion you proposed would force me to modify thousands of configurations due to a minor update (from 6.1.16 to 6.2.x) of Spring Framework.

Apologies, I didn't realize that your MyJavaConfig was an actual @Configuration class. I think that autowiring a configuration class into another is a major design issue in the first place. As you've pointed out, this has been working for a long time anyway so we'll be waiting for your analysis on the probable multi-threaded initialization.

@jhoeller
Copy link
Contributor

@gmarfia88 to be clear, if you encounter an unexpected BeanCurrentlyInCreationException during single-threaded startup just going from 6.1.x to 6.2.x, that would qualify as a bug that we would definitely like to have a reproducer for. That said, since it is non-deterministic, it is highly likely that there is another thread involved during startup; we would like to learn about where that is being started so that we can fine-tune the locking algorithm for 6.2.3 if possible.

The config class injection failure may be a consequence of circular reference resolution. So this may possibly be connected to the problem above, with the container trying to resolve a BeanCurrentlyInCreationException through a fallback and failing there. I would not worry about that part too much at this point, there is no need to revisit your config classes. Let's try to track down the BeanCurrentlyInCreationException scenario first and then see whether a remaining problem persists for config classes specifically.

Your type matching problem is tied to a different area of fine-tuning. We've fixed several generic type matching regressions in 6.2.2 already and are about to address one more in 6.2.3. A reproducer against 6.2.2 would be very much appreciated there, ideally attached to a new issue for that specific problem.

@gmarfia88
Copy link
Author

Hi @jhoeller,
I have probably identified a part of my application that uses a thread pool to instantiate some beans. As a result (although I am not yet completely sure), the context loading might be happening in a multi-threaded manner.

I agree that we should not worry for now about the issue regarding configuration injection and generics, but instead focus on understanding and resolving the BeanCurrentlyInCreationException. Unfortunately, I need some time to figure out how to postpone the initialization of those beans or, in general, how to bypass the issue to verify that, once this is fixed, everything works correctly again.

As soon as possible, I will try to detail the bean creation logic in my scenario so that you can evaluate possible improvements for the next version.

Thanks a lot!

@gmarfia88
Copy link
Author

Hi,
I confirm that I am experiencing the same situation as described in issue 34308

In my case, I am using Pekko, which relies on a thread pool. To correctly create the actors, I request beans from Spring's ApplicationContext through these threads.

Despite trying to comment out the problematic code section, I am still encountering issues with injecting beans that use generics. The error I receive is as follows:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myConfig': Unsatisfied dependency expressed through field 'myBean': No qualifying bean of type 'MyInterface<java.lang.Object, MyObject>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier("myBean")}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'MyInterface<java.lang.Object, MyObject>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier("myBean")}

This issue occurs because myBean is defined in another configuration as a raw type, rather than using the interface with generics. Here's an example of the current definition:

@Bean
public MyClass myBean() { return new MyClass<>(); }

Do you think these issues could be resolved in the upcoming framework patches?

Thanks a lot!

@jhoeller
Copy link
Contributor

jhoeller commented Jan 30, 2025

@gmarfia88 for the bean type matching, could you try the latest 6.2.3 build snapshot? This fixes a couple of reported regressions in the generic type matching algorithm in 6.2.

That said, we are actually not aware of a specific regression with generics versus raw classes. If you keep seeing this, you could try to isolate it into a unit test that passes on 6.1.x and fails against 6.2.x.

Also, the return type of a @Bean method should usually be declared as specific as possible - ideally the actual implementation type -, in particular if injection points happen to refer to it by the implementation type.

@jhoeller
Copy link
Contributor

jhoeller commented Jan 30, 2025

@gmarfia88 for the Pekko part, where is the Pekko subsystem being started? I assume this is some special Spring bean declaration that exposes the actor system and internally starts the actor thread pool? If this is the case, you should make sure for the actor bean to be initialized as late as possible.

Ideally such actor bootstrapping should happen in a SmartInitializingSingleton.afterSingletonsInstantiated method or in a ContextRefreshedEvent listener. If it needs to happen as a regular Spring @Bean method, you could try to enforce the early initialization of the common beans that those actors need through a corresponding @DependsOn declaration on the actor @Bean, enforcing their early initialization within the startup lock.

Alternatively, you could also mark those common beans (the ones that you are seeing a BeanCurrentlyInCreationException for) as @Lazy so that the main bootstrap thread skips their initialization within its startup lock, leaving their initialization entirely up to actor threads (effectively avoiding the threading conflict as well).

Maybe we can fine-tune 6.2's startup behavior with fallbacks at runtime that can handle such multi-threading conflicts still. Even if so, depends-on declarations as suggested above will help for clarity and for a better controlled bootstrap phase.

@jhoeller
Copy link
Contributor

On a related note, I'm about to bring #34349 into the next 6.2.3 snapshot. This ignores BeanCurrentlyInCreationException on mainline pre-instantiation, silently accepting that some other thread will finish initializing such a bean concurrently. This might help for your actor scenario; please give the upcoming 6.2.3 snapshot a try for that reason as well. That said, I still recommend starting the actor subsystem as late as possible - and to express async initialization dependencies through @DependsOn so that such common beans get initialized in the main bootstrap thread first - in order to avoid such unmanaged concurrent initialization as far as possible.

@gmarfia88
Copy link
Author

Hi @jhoeller,
I tried using version 6.2.3-SNAPSHOT of spring-framework-bom, but the issues persist.

I’m working on some modifications to the Pekko part, but I haven’t found a definitive solution yet - it will take some more time.

The initial changes I made allowed me to proceed with the deployment, but I encountered the same issues mentioned in my first message:

  • Issue with injecting generic beans
  • Java Config injection sometimes fails

I'm not sure if a permanent modification to the Pekko part will resolve these issues, but I can confirm that over the years, up until version 6.1.x, I had never encountered any.

Thanks for your support.

@jhoeller
Copy link
Contributor

jhoeller commented Feb 5, 2025

@gmarfia88 thanks for following up. Your scenario is the last remaining case where we are aware of regressions in 6.2, so let's try to get to the bottom of it.

For the generics part, I'll be happy to debug it if we can narrow it down a reproducer. Ideally a unit test in the style of our ResolvableTypeTests but a minimal application context setup where injection fails accordingly would also help.

As of 6.2.3, the generic matching regressions have been fixed as far we understand them. It's generally recommendable to define a MyClass<> return type rather than relying on raw type matches - but even the raw matching fallback keeps working according to our unit tests. I am unfortunately out of educated guesses for where a remaining problem could emerge here.

As for the concurrent initialization part, I still see some potential for refinements based on the specific thread interactions we are encountering at runtime. If necessary, we could even have an application-wide flag to opt out of lenient locking, restoring pre-6.2 locking behavior for specific applications (always picking the hard-locking code path, never attempting lenient locking).

@jhoeller jhoeller reopened this Feb 5, 2025
@jhoeller jhoeller removed the status: invalid An issue that we don't feel is valid label Feb 5, 2025
@jhoeller jhoeller self-assigned this Feb 5, 2025
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Feb 5, 2025
@jhoeller jhoeller added the status: waiting-for-feedback We need additional information before we can continue label Feb 5, 2025
@jhoeller jhoeller changed the title Migrating from Spring 6.1.x to 6.2.x leads to exceptions Migrating from Spring 6.1.x to 6.2.x leads to exceptions in a Pekko setup Feb 5, 2025
@gmarfia88
Copy link
Author

Hi @jhoeller,
I had already tried to replicate the bean issue in a from scratch project but without success.

The possibility of having an application-wide flag would be really useful, as it could help us understand if the other issues are also related to the bean instantiation done by Pekko threads.

Let me know and thanks!

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Feb 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged or decided on
Projects
None yet
Development

No branches or pull requests

5 participants