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

Fix virtual thread pinning caused by synchronized blocks in EclipseLinkJpaDialect #33546

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceException;
Expand All @@ -29,6 +31,7 @@
import org.springframework.orm.jpa.DefaultJpaDialect;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.util.ConcurrentReferenceHashMap;

/**
* {@link org.springframework.orm.jpa.JpaDialect} implementation for Eclipse
Expand Down Expand Up @@ -56,6 +59,11 @@
@SuppressWarnings("serial")
public class EclipseLinkJpaDialect extends DefaultJpaDialect {

/**
* Locks for virtual thread friendly "synchronization" on the {@link DatabaseLogin} instance.
*/
private static final Map<DatabaseLogin, ReentrantLock> LOCKS = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);

private boolean lazyDatabaseTransaction = false;


Expand Down Expand Up @@ -100,7 +108,9 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio
DatabaseLogin databaseLogin = uow.getLogin();
// Synchronize on shared DatabaseLogin instance for consistent isolation level
// set and reset in case of concurrent transactions with different isolation.
synchronized (databaseLogin) {
ReentrantLock lock = LOCKS.computeIfAbsent(databaseLogin, k -> new ReentrantLock());
lock.lock();
try {
int originalIsolationLevel = databaseLogin.getTransactionIsolation();
// Apply current isolation level value, if necessary.
if (currentIsolationLevel != originalIsolationLevel) {
Expand All @@ -115,6 +125,8 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio
if (currentIsolationLevel != originalIsolationLevel) {
databaseLogin.setTransactionIsolation(originalIsolationLevel);
}
} finally {
lock.unlock();
}
}
else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
Expand All @@ -125,10 +137,14 @@ else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
// Synchronize on shared DatabaseLogin instance for consistently picking up
// the default isolation level even in case of concurrent transactions with
// a custom isolation level (see above), as of 6.0.10
synchronized (databaseLogin) {
ReentrantLock lock = LOCKS.computeIfAbsent(databaseLogin, k -> new ReentrantLock());
lock.lock();
try {
entityManager.getTransaction().begin();
uow.beginEarlyTransaction();
entityManager.unwrap(Connection.class);
} finally {
lock.unlock();
}
}
else {
Expand Down