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

Removal of beans loads referenced classes #24338

Closed
Postremus opened this issue Mar 16, 2022 · 13 comments · Fixed by #24398
Closed

Removal of beans loads referenced classes #24338

Postremus opened this issue Mar 16, 2022 · 13 comments · Fixed by #24398
Labels
area/arc Issue related to ARC (dependency injection) env/windows Impacts Windows machines kind/bug Something isn't working
Milestone

Comments

@Postremus
Copy link
Member

Describe the bug

I have following project structure:

  • commons (external module)
  • web (quarkus)

The commons module includes hibernate-validator (and custom ConstraintValidators), which I just don't need for this project.
I tried to simply add an exclusion on quarkus-hibernate-validator, to remove this extension.

However, I got the exception from below.
Apperantly, during removal of unused beans, the bean is initialized?

@ApplicationScoped
public class FutureDayValidator implements ConstraintValidator<FutureDay, Date> {

    @Override
    public void initialize(FutureDay constraintAnnotation) {
    }

    @Override
    public boolean isValid(Date value, ConstraintValidatorContext context) {
return false;
    }
}

Expected behavior

App starts, just with the ConstraintValidator removed.

Actual behavior

2022-03-16 08:22:17,682 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus: java.lang.RuntimeException: java.lang.NoClassDefFoundError: javax/validation/ConstraintValidator
        at io.quarkus.dev.appstate.ApplicationStateNotification.waitForApplicationStart(ApplicationStateNotification.java:51)
        at io.quarkus.runner.bootstrap.StartupActionImpl.runMainClass(StartupActionImpl.java:122)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.firstStart(IsolatedDevModeMain.java:144)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:455)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:66)
        at io.quarkus.bootstrap.app.CuratedApplication.runInCl(CuratedApplication.java:140)
        at io.quarkus.bootstrap.app.CuratedApplication.runInAugmentClassLoader(CuratedApplication.java:96)
        at io.quarkus.deployment.dev.DevModeMain.start(DevModeMain.java:132)
        at io.quarkus.deployment.dev.DevModeMain.main(DevModeMain.java:62)
Caused by: java.lang.NoClassDefFoundError: javax/validation/ConstraintValidator
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:454)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:414)
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Class.java:467)
        at io.quarkus.arc.setup.Default_ComponentsProvider.addRemovedBeans2(Unknown Source)
        at io.quarkus.arc.setup.Default_ComponentsProvider.getComponents(Unknown Source)
        at io.quarkus.arc.impl.ArcContainerImpl.<init>(ArcContainerImpl.java:118)
        at io.quarkus.arc.Arc.initialize(Arc.java:24)
        at io.quarkus.arc.runtime.ArcRecorder.getContainer(ArcRecorder.java:40)
        at io.quarkus.deployment.steps.ArcProcessor$generateResources686947423.deploy_0(Unknown Source)
        at io.quarkus.deployment.steps.ArcProcessor$generateResources686947423.deploy(Unknown Source)
        at io.quarkus.runner.ApplicationImpl.<clinit>(Unknown Source)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
        at java.base/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128)
        at java.base/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347)
        at java.base/java.lang.Class.newInstance(Class.java:645)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:66)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:41)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:120)
        at io.quarkus.runner.GeneratedMain.main(Unknown Source)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:103)
        at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.ClassNotFoundException: javax.validation.ConstraintValidator
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:464)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:414)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:464)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:414)
        ... 31 more

How to Reproduce?

Download the reproducer:
removed-bean-classloading.zip

  1. mvn quarkus:dev
  2. Exception happens
  3. Remove exclusion on hibernate-validator in web/pom.xml
  4. mvn quarkus:dev
  5. It starts

Output of uname -a or ver

Microsoft Windows [Version 10.0.19044.1586]

Output of java -version

openjdk 17.0.2 2022-01-18 OpenJDK Runtime Environment Temurin-17.0.2+8 (build 17.0.2+8) OpenJDK 64-Bit Server VM Temurin-17.0.2+8 (build 17.0.2+8, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.7.4.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537) Maven home: C:\eclipse\tools\java\maven Java version: 17.0.2, vendor: Eclipse Adoptium, runtime: C:\eclipse\tools\java\17 Default locale: de_DE, platform encoding: Cp1252 OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

Additional information

No response

@Postremus Postremus added the kind/bug Something isn't working label Mar 16, 2022
@quarkus-bot quarkus-bot bot added env/windows Impacts Windows machines triage/needs-triage labels Mar 16, 2022
@gsmet gsmet added area/arc Issue related to ARC (dependency injection) and removed triage/needs-triage labels Mar 16, 2022
@quarkus-bot
Copy link

quarkus-bot bot commented Mar 16, 2022

/cc @manovotn, @mkouba

@mkouba
Copy link
Contributor

mkouba commented Mar 16, 2022

If the bean is removed then it's not excluded from discovery. I'm not sure whether quarkus honors dependency exclusions in a multi-module app. CC @aloubyansky

@aloubyansky
Copy link
Member

It does, hence the CNFE. I am not sure if ArC can handle this by not loading the class that appears to be an unused bean.

@mkouba
Copy link
Contributor

mkouba commented Mar 16, 2022

It does, hence the CNFE.

I'm sorry but I don't understand. If it does then ArC should not see the bean class at all... in other words, it should not be part of the app index.

@aloubyansky
Copy link
Member

Here is the exclusion

                <dependency>
                        <groupId>com.acme</groupId>
                        <artifactId>removed-bean-classloading-service</artifactId>
                        <version>1.0-SNAPSHOT</version>
                        <exclusions>
                                <exclusion>
                                        <groupId>io.quarkus</groupId>
                                        <artifactId>quarkus-hibernate-validator</artifactId>
                                </exclusion>
                        </exclusions>
                </dependency>

The bean is in the removed-bean-classloading-service JAR but the JAR containing javax.validation.ConstraintValidator was excluded.

@manovotn
Copy link
Contributor

Hm, so the issue is that io.quarkus.arc.impl.RemovedBeanImpl holds Set<Type> types; which in case of class leads to loading the class?

As far as I know, we use these removed beans in two places.
Firstly we have a list of these in the dev console. For this purpose we could only store String values.
However, second use case is bean resolution - if user attempts dynamic lookup and there are no matches, we also list through unused beans and perform type matching so that we can inform user that some of their beans might have been mistakenly removed. See https://github.com/quarkusio/quarkus/blob/main/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java#L629-L644
And for this we need to be able to load those classes.

In this scenario, wouldn't it instead make more sense to exclude the bean (or the JAR) from bean discovery somehow?

@mkouba
Copy link
Contributor

mkouba commented Mar 16, 2022

The bean is in the removed-bean-classloading-service JAR but the JAR containing javax.validation.ConstraintValidator was excluded.

I see. Well, in that case it's an invalid use case because there is a bean declared but not all bean types are loadable (AFAIK Weld does ignore similar classes and logs some warning). @Postremus you can try to exclude the bean class from discovery via the quarkus.arc.exclude-types config property.

In this scenario, wouldn't it instead make more sense to exclude the bean (or the JAR) from bean discovery somehow?

Yep, quarkus.arc.exclude-types ;-)

@manovotn
Copy link
Contributor

Yep, quarkus.arc.exclude-types ;-)

Yeah, that's what I meant! I knew we had something but couldn't remember :)

@aloubyansky
Copy link
Member

Not sure if it's worth it and how complex this is, could this be considered a kind of optimization to load the classes of unused beans lazily, given that it's probably rare when they have to be loaded at the end?

@mkouba
Copy link
Contributor

mkouba commented Mar 16, 2022

Not sure if it's worth it and how complex this is, could this be considered a kind of optimization to load the classes of unused beans lazily, given that it's probably rare when they have to be loaded at the end?

We have an open issue for this optimization: #18345, but the priority is low due to the impact and the fact that you can also set quarkus.arc.detect-unused-false-positives=false too.

@Postremus
Copy link
Member Author

The bean is in the removed-bean-classloading-service JAR but the JAR containing javax.validation.ConstraintValidator was excluded.

I see. Well, in that case it's an invalid use case because there is a bean declared but not all bean types are loadable (AFAIK Weld does ignore similar classes and logs some warning). @Postremus you can try to exclude the bean class from discovery via the quarkus.arc.exclude-types config property.

In this scenario, wouldn't it instead make more sense to exclude the bean (or the JAR) from bean discovery somehow?

Yep, quarkus.arc.exclude-types ;-)

That works.

However, could you please add an actionable error message?
The exception only says CNFE ConstraintValidator, but not that this situation is caused by org.acme.FutureDayValidator.

@mkouba
Copy link
Contributor

mkouba commented Mar 17, 2022

However, could you please add an actionable error message?
The exception only says CNFE ConstraintValidator, but not that this situation is caused by org.acme.FutureDayValidator.

Hm, that's not so easy. I mean the CNFE is a generic error that can occur in many places. We could catch the error in the generated ComponentsProvider but it might jump out elsewhere...

@mkouba
Copy link
Contributor

mkouba commented Mar 17, 2022

I've tried to implement something and then I realized that we share the loaded types for removed beans and this optimization (1) means that we can assign a CNFE to the first component that uses the type, (2) makes it really difficult to implement this elegantly. I'll try to come with something but given the impact it has and a trivial workaround exists I'd consider this a low priority issue..

mkouba added a commit to mkouba/quarkus that referenced this issue Mar 18, 2022
- log a warning if unable to load a bean type of a removed bean
- previously the app failed at startup
- resolves quarkusio#24338
mkouba added a commit to mkouba/quarkus that referenced this issue Mar 18, 2022
- log a warning if unable to load a bean type of a removed bean
- previously the app failed at startup
- resolves quarkusio#24338
@quarkus-bot quarkus-bot bot added this to the 2.8 - main milestone Mar 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/arc Issue related to ARC (dependency injection) env/windows Impacts Windows machines kind/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants