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

Java 21, Virtual Threads and ThreadLocal #2518

Open
hgschmie opened this issue Oct 24, 2023 · 14 comments
Open

Java 21, Virtual Threads and ThreadLocal #2518

hgschmie opened this issue Oct 24, 2023 · 14 comments

Comments

@hgschmie
Copy link
Contributor

Jdbi at this point has one relevant ThreadLocal instance, which is a central one:

We maintain a reference to the "current handle supplier" in Jdbi#threadHandleSupplier. This enables the ability to bind extension objects in Jdbi#withExtension and Handles in Jdbi#withHandle and transparently keep using the same handle even in recursive calls as long as these operations are done on the same thread.

We do not use the "threadlocal as cache" scenario as described in JEP 429/446, so it is not clear whether there is real pressure to remove the thread local. Even with thousands of virtual threads, the number of concurrent instances will be limited by e.g. the number of concurrent database connections supported, which is a pretty limited resource. A ThreadLocal with a few hundred instance s that all get removed don't seem to be a big deal. The code cleans out the ThreadLocal instances by calling #remove() so the ThreadLocal itself should not grow unbounded.

JEP 446 offers scoped values as an alternative. However, as of Java 21 LTS, scoped values have not been shipped, so we would need to build a multi-version jar to support it.

There actually is a second ThreadLocal, in the StringTemplate loader/cache mechanism; this should be easily replaced with a cache using the core cache facility.

@stevenschlansker
Copy link
Member

ThreadLocal is supposed to be fully supported by virtual threads, so I agree that since we limit it by number of DB connections, it should be fine. Scoped values sound cool but I think it's too early to consider them.

@vszakd
Copy link

vszakd commented Dec 29, 2023

Hello,
does that mean that Jdbi can be used with Virtual Threads with no problems?
Apart from thread-local storage, does it do any thread pinning?

@stevenschlansker
Copy link
Member

We hope that it just works, but I haven't personally tested it yet. If you do run it and find any problems please let us know and they should get fixed.

@vszakd
Copy link

vszakd commented Jan 3, 2024

I did some testing, if I integrate jdbi with Quarkus and run without VT (I created a demo project with a REST endpoint and a JMeter client calling it with 100 threads), everything works correctly. If I annotate the resource to run on a VT with @RunOnVirtualThread, I get a lot of errors:

2024-01-03 17:04:07,046 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (quarkus-virtual-thread-1045) HTTP Request to /things failed, error id: cbdaa320-0709-4f3d-81ff-dcc2d4a97f0c-1199: org.jdbi.v3.core.ConnectionException: java.sql.SQLException: Sorry, acquisition timeout!
at org.jdbi.v3.core.Jdbi.open(Jdbi.java:341)
at org.jdbi.v3.core.LazyHandleSupplier.createHandle(LazyHandleSupplier.java:50)
at org.jdbi.v3.core.internal.MemoizingSupplier.init(MemoizingSupplier.java:68)
at org.jdbi.v3.core.internal.MemoizingSupplier.get(MemoizingSupplier.java:46)
at org.jdbi.v3.core.LazyHandleSupplier.getHandle(LazyHandleSupplier.java:45)
at org.jdbi.v3.sqlobject.statement.internal.CustomizingStatementHandler.invoke(CustomizingStatementHandler.java:183)
at org.jdbi.v3.sqlobject.statement.internal.SqlUpdateHandler.invoke(SqlUpdateHandler.java:34)
at org.jdbi.v3.core.extension.ExtensionMetadata$ExtensionHandlerInvoker.lambda$invoke$0(ExtensionMetadata.java:330)

... zip ...

Caused by: java.sql.SQLException: Sorry, acquisition timeout!
at io.agroal.pool.ConnectionPool.handlerFromSharedCache(ConnectionPool.java:294)
at io.agroal.pool.ConnectionPool.getConnection(ConnectionPool.java:249)
at io.agroal.pool.DataSource.getConnection(DataSource.java:86)
at org.jdbi.v3.core.Jdbi.open(Jdbi.java:319)
... 41 more

@stevenschlansker
Copy link
Member

Thanks for giving this a try - unfortunately, the stack trace provided isn't enough to diagnose the problem. Any chance you could post your sample project?
What are the other connections that this thread failed to acquire doing at the time - are they blocked? A full thread stack dump might help.

@stevenschlansker
Copy link
Member

And it is expected that right now Jdbi would pin carrier threads potentially - we do use synchronized.
If there are a few hotspots we can fix individually, we would consider it.

If the project turns into rewriting all synchronized blocks to use Lock, we might instead wait for Project Loom to fix synchronized to work properly with threads.

@vszakd
Copy link

vszakd commented Jan 4, 2024

I will create a minimal project that reproduces the problem

@vszakd
Copy link

vszakd commented Jan 10, 2024

@stevenschlansker you can find a self-contained test project attached.

Running it with JMeter with 100 threads POSTing on localhost:8080 this request body JSON:

{
  "label": "test"
}

triggers the problem. If you remove the @RunOnVirtualThread annotation all is good.

jdbiquarkusdemo.zip

@stevenschlansker
Copy link
Member

stevenschlansker commented Jan 10, 2024

Hi @vszakd , I downloaded and imported your project into Eclipse, but I cannot figure out how to run it. None of the classes have a main method. Which class am I supposed to run?

@stevenschlansker
Copy link
Member

I created:
#2597
which shows that at least a basic use of bare virtual threads works OK. I am sure there are deeper problems to find, but I didn't see an easy way yet to boot the attached Quarkus app in Eclipse.

@stevenschlansker
Copy link
Member

Ok, I made a slightly more complex test that runs 100 virtual threads over 10 connections, and it doesn't work as well. So at least I have something to work on now.

@stevenschlansker
Copy link
Member

I pushed my failing test up to the branch.

@stevenschlansker
Copy link
Member

Well, the good news is the initial Loom programming experience is great, but unfortunately the observability / debuggability seemingly still sucks. I am able to see the program hang, but I do not see any pinned threads (tried using -Djdk.trackAllThreads=true -Djdk.tracePinnedThreads=full) or synchronized monitors that should hold carrier threads. Yet, the program hangs.

@vszakd
Copy link

vszakd commented Jan 11, 2024

Hi @vszakd , I downloaded and imported your project into Eclipse, but I cannot figure out how to run it. None of the classes have a main method. Which class am I supposed to run?

Hello @stevenschlansker, it's a Quarkus application so it does not have an explicit main, it should suffice to run ./gradlew quarkusDev, the DB will be launched automatically in Docker. For debugging, ./gradlew --console=plain quarkusDev -Ddebug=true will launch the application in debug mode and you can attach a debugger on port 5005 of localhost.

Before you can use the gradlew wrapper you will need to add the wrapper task and run gradle wrapper to generate it (https://stackoverflow.com/questions/25769536/how-when-to-generate-gradle-wrapper-files).

If you prefer, I can upload a maven project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants