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

Classloader set by QuarkusTest breaks subsequent non-quarkus unit tests #22510

Closed
vladykin opened this issue Dec 23, 2021 · 7 comments
Closed
Labels
area/testing kind/bug Something isn't working triage/wontfix This will not be worked on

Comments

@vladykin
Copy link

vladykin commented Dec 23, 2021

Describe the bug

I have a mix of @QuarkusTest-annotated tests and plain old junit tests in my project. The @QuarkusTest seems to set its thread context class loader on the ForkJoinPool.commonPool() and not clear it, so that subsequent non-Quarkus unit tests still see this class loader and are impacted by it.

It causes different kinds of
java.util.concurrent.CompletionException: java.util.ServiceConfigurationError: BLAH-BLAH-BLAH not a subtype

I've prepared a minimal reproducer: https://github.com/vladykin/quarkus-test-classloader-issue

If the QTest runs before PlainOldTest, then PlainOldTest fails with

Thread = ForkJoinPool.commonPool-worker-29, context classloader = QuarkusClassLoader:Quarkus Runtime ClassLoader: TEST restart no:0@65ef48f2

java.util.concurrent.CompletionException: java.util.ServiceConfigurationError: io.smallrye.mutiny.infrastructure.CallbackDecorator: io.smallrye.mutiny.context.DefaultContextPropagationInterceptor not a subtype
	at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
	at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
	at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1803)
	at java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1792)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:295)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1016)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1665)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1598)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
Caused by: java.util.ServiceConfigurationError: io.smallrye.mutiny.infrastructure.CallbackDecorator: io.smallrye.mutiny.context.DefaultContextPropagationInterceptor not a subtype
	at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:589)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1236)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1264)
	at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1299)
	at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1383)
	at java.base/java.lang.Iterable.forEach(Iterable.java:74)
	at io.smallrye.mutiny.infrastructure.Infrastructure.reloadCallbackDecorators(Infrastructure.java:322)
	at io.smallrye.mutiny.infrastructure.Infrastructure.reload(Infrastructure.java:57)
	at io.smallrye.mutiny.infrastructure.Infrastructure.<clinit>(Infrastructure.java:37)
	at io.smallrye.mutiny.groups.UniCreate.item(UniCreate.java:271)
	at io.smallrye.mutiny.groups.UniCreate.<clinit>(UniCreate.java:33)
	at io.smallrye.mutiny.Uni.createFrom(Uni.java:64)
	at com.acme.PlainOldTest.lambda$empty$1(PlainOldTest.java:17)
	at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1800)
	... 6 more

If they execute in reverse order, they both pass.

Expected behavior

@QuarkusTest should not impact other non-Quarkus tests in the project.

Actual behavior

No response

How to Reproduce?

  1. Use this minimal reproducer project: https://github.com/vladykin/quarkus-test-classloader-issue
  2. Import it to IntelliJ and run all unit tests.
  3. Or run mvn clean package and see the same failure
  4. Reverse the order of the test classes by changing the values in the @Ordered annotation to make the failure go away

Output of uname -a or ver

Linux 5.14.21-gentoo #9 SMP Wed Dec 15 22:26:07 PST 2021 x86_64 11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz GenuineIntel GNU/Linux

Output of java -version

openjdk version "16.0.1" 2021-04-20
OpenJDK Runtime Environment Zulu16.30+15-CA (build 16.0.1+9)
OpenJDK 64-Bit Server VM Zulu16.30+15-CA (build 16.0.1+9, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.5.4.Final

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

Apache Maven 3.6.3

Additional information

No response

@vladykin vladykin added the kind/bug Something isn't working label Dec 23, 2021
@geoand
Copy link
Contributor

geoand commented Dec 24, 2021

Thanks for reporting.

This isn't actually an issue of @QuarkusTest as the extension does restore the TCCL properly. You can verify this by printing the TCCL before running CompletableFuture.runAsync in each test.

The issue here is that the ForkJoin pool holds on to the TCCL... @cescoffier I think we have seen this before, right?

@vladykin
Copy link
Author

vladykin commented Jan 1, 2022

This reproduces with Quarkus 2.6.1.Final as well. But it didn't with Quarkus 2.1.3.Final, which we are trying to upgrade from.

@geoand
Copy link
Contributor

geoand commented Sep 20, 2024

Is this still an issue with Quarkus 3.14?

@geoand geoand added area/testing triage/needs-feedback We are waiting for feedback. labels Sep 20, 2024
@vladykin
Copy link
Author

Yes, the same error reproduces with Quarkus 3.14.1.

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.acme.QTest
2024-09-20 11:28:36,406 WARNING [org.jun.pla.lau.cor.LauncherConfigurationParameters] (main) Discovered 2 'junit-platform.properties' configuration files in the classpath; only the first will be used.
2024-09-20 11:28:36,420 WARNING [org.jun.pla.lau.cor.LauncherConfigurationParameters] (main) Discovered 2 'junit-platform.properties' configuration files in the classpath; only the first will be used.
2024-09-20 11:28:36,513 WARNING [org.jun.pla.lau.cor.LauncherConfigurationParameters] (main) Discovered 2 'junit-platform.properties' configuration files in the classpath; only the first will be used.
2024-09-20 11:28:36,516 WARNING [org.jun.pla.lau.cor.LauncherConfigurationParameters] (main) Discovered 2 'junit-platform.properties' configuration files in the classpath; only the first will be used.
2024-09-20 11:28:39,107 INFO  [io.quarkus] (main) quarkus-test-classloader 1.0-SNAPSHOT on JVM (powered by Quarkus 3.14.1) started in 1.322s. Listening on: http://localhost:8081
2024-09-20 11:28:39,108 INFO  [io.quarkus] (main) Profile test activated. 
2024-09-20 11:28:39,108 INFO  [io.quarkus] (main) Installed features: [cdi, reactive-routes, smallrye-context-propagation, vertx]
Thread = ForkJoinPool.commonPool-worker-1, context classloader = QuarkusClassLoader:Quarkus Runtime ClassLoader: TEST for QTest (QuarkusTest) restart no:0@179ee36b
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.584 s -- in com.acme.QTest
[INFO] Running com.acme.PlainOldTest
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.010 s <<< FAILURE! -- in com.acme.PlainOldTest
[ERROR] com.acme.PlainOldTest.test -- Time elapsed: 0.007 s <<< ERROR!
java.util.concurrent.CompletionException: java.util.ServiceConfigurationError: io.smallrye.mutiny.infrastructure.CallbackDecorator: io.smallrye.mutiny.context.DefaultContextPropagationInterceptor not a subtype
	at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
	at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
	at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1807)
	at java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1796)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
Caused by: java.util.ServiceConfigurationError: io.smallrye.mutiny.infrastructure.CallbackDecorator: io.smallrye.mutiny.context.DefaultContextPropagationInterceptor not a subtype
	at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:593)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1244)
	at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273)
	at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
	at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
	at io.smallrye.mutiny.infrastructure.Infrastructure.toInterceptorList(Infrastructure.java:349)
	at io.smallrye.mutiny.infrastructure.Infrastructure.reloadCallbackDecorators(Infrastructure.java:342)
	at io.smallrye.mutiny.infrastructure.Infrastructure.reload(Infrastructure.java:76)
	at io.smallrye.mutiny.infrastructure.Infrastructure.<clinit>(Infrastructure.java:51)
	at io.smallrye.mutiny.groups.UniCreate.item(UniCreate.java:342)
	at io.smallrye.mutiny.groups.UniCreate.<clinit>(UniCreate.java:35)
	at io.smallrye.mutiny.Uni.createFrom(Uni.java:65)
	at com.acme.PlainOldTest.lambda$test$1(PlainOldTest.java:17)
	at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804)
	... 6 more

2024-09-20 11:28:39,173 INFO  [io.quarkus] (main) quarkus-test-classloader stopped in 0.017s
Thread = ForkJoinPool.commonPool-worker-1, context classloader = QuarkusClassLoader:Quarkus Runtime ClassLoader: TEST for QTest (QuarkusTest) restart no:0@179ee36b

@geoand geoand removed the triage/needs-feedback We are waiting for feedback. label Sep 20, 2024
@geoand
Copy link
Contributor

geoand commented Sep 20, 2024

Thanks for checking

@cescoffier
Copy link
Member

In general, I discourage the usage of the async methods without a specified executor. Using a specific executor would probably work around the issue.

Unfortunately, the common pool does not allow us to set a TCCL or to reset it. We can try a best-effort approach submitting n tasks (n == parallelism) to be executed in parallel. These tasks would reset the TTLC. It is quite fragile, as you have no guarantee that the TCCL is resetted on al the platform threads composing the common pool.

@geoand
Copy link
Contributor

geoand commented Sep 23, 2024

In general, I discourage the usage of the async methods without a specified executor

You can use the one Quarkus manages by injecting it:

@Inject ExecutorService executor;

@geoand geoand closed this as completed Sep 23, 2024
@geoand geoand added kind/bug Something isn't working triage/wontfix This will not be worked on and removed kind/bug Something isn't working labels Sep 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/testing kind/bug Something isn't working triage/wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

4 participants