Subject: [pgjdbc/pgjdbc] issue #3835: Virtual Thread pinned in PostgreSQL JDBC Driver 42.7.8

From: "mschorsch (@mschorsch)" <[email-masked]>
Date: Tue, 30 Sep 2025 14:58:13 +0000

Thread pinning occurs in the Postgres JDBC driver and has been occurring since version [42.7.8](https://github.com/pgjdbc/pgjdbc/releases/tag/REL42.7.8); previous versions were fine.

We suspect that this change is causing the problem: https://github.com/pgjdbc/pgjdbc/pull/3703

```
VirtualThread[#81,quarkus-virtual-thread-14]/runnable@ForkJoinPool-1-worker-3 reason:MONITOR
    java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:199)
    java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393)
    java.base/java.lang.VirtualThread.park(VirtualThread.java:596)
    java.base/java.lang.System$2.parkVirtualThread(System.java:2643)
    java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:54)
    java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:219)
    java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:754)
    java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:990)
    java.base/java.util.concurrent.locks.ReentrantLock$Sync.lock(ReentrantLock.java:153)
    java.base/java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:322)
    org.postgresql.jdbc.ResourceLock.obtain(ResourceLock.java:28)
    org.postgresql.core.QueryExecutorBase.getStandardConformingStrings(QueryExecutorBase.java:302)
    org.postgresql.core.CachedQueryCreateAction.create(CachedQueryCreateAction.java:43)
    org.postgresql.core.CachedQueryCreateAction.create(CachedQueryCreateAction.java:19)
    org.postgresql.util.LruCache.borrow(LruCache.java:130) <== monitors:1
    org.postgresql.core.QueryExecutorBase.borrowQuery(QueryExecutorBase.java:335)
    org.postgresql.jdbc.PgConnection.borrowQuery(PgConnection.java:228)
    org.postgresql.jdbc.PgPreparedStatement.<init>(PgPreparedStatement.java:93)
    org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:1426)
    org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:1878)
    org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:541)
    io.agroal.pool.wrapper.ConnectionWrapper.prepareStatement(ConnectionWrapper.java:649)
    org.jooq.impl.ProviderEnabledConnection.prepareStatement(ProviderEnabledConnection.java:109)
    org.jooq.impl.SettingsEnabledConnection.prepareStatement(SettingsEnabledConnection.java:91)
    org.jooq.impl.AbstractResultQuery.prepare(AbstractResultQuery.java:189)
    org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:316)
    org.jooq.impl.AbstractResultQuery.fetch(AbstractResultQuery.java:263)
    org.jooq.impl.SelectImpl.fetch(SelectImpl.java:3217)
    com.example.database.client.DSLContextExtensionsKt$fetchAwait$2.invokeSuspend(DSLContextExtensions.kt:39)
    kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
    kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
    io.quarkus.virtual.threads.ContextPreservingExecutorService$ContextPreservingRunnable.run(ContextPreservingExecutorService.java:50)
    java.base/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314)
    java.base/java.lang.VirtualThread.run(VirtualThread.java:329)
```

see also https://github.com/quarkusio/quarkus/issues/50345

---

From: "vlsi (@vlsi)" <[email-masked]>
Date: Tue, 30 Sep 2025 18:06:56 +0000

Just wondering: do you share the same connection across concurrent execute threads?

`QueryExecutorBase.getStandardConformingStrings` acquires a connection-scoped lock, so if you got pinning, then it looks like the same connection was used in a different thread which looks like an anti-pattern.

Do you have a reproducer for the issue?

In any case, it makes sense to move `createAction.create()` out of the synchronized block, however, I believe it will just mask the true issue of "sharing the same connection between concurrent execute threads" which you probably need to address in the app.

---

From: "mschorsch (@mschorsch)" <[email-masked]>
Date: Sat, 18 Oct 2025 07:51:08 +0000

Hi @vlsi, sorry for the late reply. 

> Do you have a reproducer for the issue?

I do not have a reproducer. We were only able to find the problem in production.

> QueryExecutorBase.getStandardConformingStrings acquires a connection-scoped lock, so if you got pinning, then it looks like the same connection was used in a different thread which looks like an anti-pattern.

Thanks for the hint. We will check that again.

> In any case, it makes sense to move createAction.create() out of the synchronized block, however, I believe it will just mask the true issue of "sharing the same connection between concurrent execute threads" which you probably need to address in the app.

👍

---

From: "Sanne (@Sanne)" <[email-masked]>
Date: Wed, 29 Oct 2025 09:53:10 +0000

hi @vlsi , I was looking into this issue as well. I agree with your assesment and I'm looking forward to more details from @mschorsch .

However as I was looking into the code, just out of curiosity - I'm wondering: why are methods `getStandardConformingStrings` and `setStandardConformingStrings` using such a lock rather than a simple volatile boolean?

Specifically:

https://github.com/pgjdbc/pgjdbc/blob/d1ddc0f0c2895abf8d87a845c8940463ea8c594e/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java#L61-L62

https://github.com/pgjdbc/pgjdbc/blob/d1ddc0f0c2895abf8d87a845c8940463ea8c594e/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java#L294-L305

(there seem to be no other uses of field `standardConformingStrings` )

---

From: "vlsi (@vlsi)" <[email-masked]>
Date: Wed, 29 Oct 2025 10:11:57 +0000

@Sanne , I believe it is only for historical reasons like in "it was under synchronized lock previously, so we blindly converted it to the lock so keep the semantics". It might indeed be worth converting the field to a non-locked volatile one.

---

From: "Sanne (@Sanne)" <[email-masked]>
Date: Wed, 29 Oct 2025 10:18:02 +0000

Cool, thanks for the quick reply! Shall I send a PR or would you prefer to do it yourself?

---

From: "Sanne (@Sanne)" <[email-masked]>
Date: Thu, 30 Oct 2025 15:56:24 +0000

it's trivial enough, I went ahead and sent a PR:
 - https://github.com/pgjdbc/pgjdbc/pull/3849