ebean: Datasource connection deadlock

Expected behavior

It is hoped that the new query caused by the previous query process should use the same jdbc connection.

Actual behavior

When the previous jdbc connection is not closed, hashcode triggers lazy loading and re-acquires a new jdbc connection, which may cause mutual exclusion of connections and deadlock in concurrent situations. For example, the maximum available connection pool is 2, when two threads trigger lazy loading at the same time, because the previous two connections are not released and get new connections through the connection pool, the connection pool finds that there is no available connection and suspends the thread to wait for other threads to release the connection, and both threads are stuck in getting new connections, resulting in deadlock.

Steps to reproduce

java.lang.RuntimeException
	at test.system.Ebean$TConnection.invoke(Ebean.java:369)
	at jdk.proxy2/jdk.proxy2.$Proxy161.getAutoCommit(Unknown Source)
	at io.ebeaninternal.server.transaction.JdbcTransaction.checkAutoCommit(JdbcTransaction.java:282)
	at io.ebeaninternal.server.transaction.JdbcTransaction.<init>(JdbcTransaction.java:223)
	at io.ebeaninternal.server.transaction.TransactionManager.createTransaction(TransactionManager.java:397)
	at io.ebeaninternal.server.transaction.TransactionFactoryBasic.create(TransactionFactoryBasic.java:55)
	at io.ebeaninternal.server.transaction.TransactionFactoryBasic.createReadOnlyTransaction(TransactionFactoryBasic.java:29)
	at io.ebeaninternal.server.transaction.TransactionManager.createReadOnlyTransaction(TransactionManager.java:390)
	at io.ebeaninternal.server.core.DefaultServer.createReadOnlyTransaction(DefaultServer.java:2231)
	at io.ebeaninternal.server.core.OrmQueryRequest.initTransIfRequired(OrmQueryRequest.java:278)
	at io.ebeaninternal.server.core.DefaultServer.findList(DefaultServer.java:1535)
	at io.ebeaninternal.server.core.DefaultServer.findList(DefaultServer.java:1515)
	at io.ebeaninternal.server.core.DefaultBeanLoader.executeQuery(DefaultBeanLoader.java:180)
	at io.ebeaninternal.server.core.DefaultBeanLoader.loadBean(DefaultBeanLoader.java:163)
	at io.ebeaninternal.server.core.DefaultServer.loadBean(DefaultServer.java:561)
	at io.ebeaninternal.server.loadcontext.DLoadBeanContext$LoadBuffer.loadBean(DLoadBeanContext.java:208)
	at io.ebean.bean.EntityBeanIntercept.loadBeanInternal(EntityBeanIntercept.java:852)
	at io.ebean.bean.EntityBeanIntercept.loadBean(EntityBeanIntercept.java:829)
	at io.ebean.bean.EntityBeanIntercept.preGetter(EntityBeanIntercept.java:923)
	at test.domain.entity.Root._ebean_get_reference(Root.java:6)
	at test.domain.entity.Root.hashCode(Root.java:657)
	at java.base/java.util.HashMap.hash(HashMap.java:338)
	at java.base/java.util.HashMap.put(HashMap.java:610)
	at java.base/java.util.HashSet.add(HashSet.java:221)
	at io.ebean.common.BeanSet.internalAdd(BeanSet.java:84)
	at io.ebean.common.BeanSet.internalAddWithCheck(BeanSet.java:74)
	at io.ebeaninternal.server.deploy.BaseCollectionHelp.add(BaseCollectionHelp.java:39)
	at io.ebeaninternal.server.deploy.BeanSetHelp.add(BeanSetHelp.java:19)
	at io.ebeaninternal.server.deploy.BeanPropertyAssocMany.addBeanToCollectionWithCreate(BeanPropertyAssocMany.java:271)
	at io.ebeaninternal.server.query.CQuery.setLazyLoadedChildBean(CQuery.java:417)
	at io.ebeaninternal.server.query.SqlTreeNodeBean$Load.complete(SqlTreeNodeBean.java:421)
	at io.ebeaninternal.server.query.SqlTreeNodeBean$Load.perform(SqlTreeNodeBean.java:447)
	at io.ebeaninternal.server.query.SqlTreeNodeBean.load(SqlTreeNodeBean.java:464)
	at io.ebeaninternal.server.query.SqlTreeNodeRoot.load(SqlTreeNodeRoot.java:45)
	at io.ebeaninternal.server.query.CQuery.readNextBean(CQuery.java:447)
	at io.ebeaninternal.server.query.CQuery.hasNext(CQuery.java:531)
	at io.ebeaninternal.server.query.CQuery.readCollection(CQuery.java:564)
	at io.ebeaninternal.server.query.CQueryEngine.findMany(CQueryEngine.java:379)
	at io.ebeaninternal.server.query.DefaultOrmQueryEngine.findMany(DefaultOrmQueryEngine.java:131)
	at io.ebeaninternal.server.core.OrmQueryRequest.findList(OrmQueryRequest.java:470)
	at io.ebeaninternal.server.core.DefaultServer.findList(DefaultServer.java:1536)
	at io.ebeaninternal.server.core.DefaultServer.findList(DefaultServer.java:1515)
	at io.ebeaninternal.server.core.DefaultBeanLoader.executeQuery(DefaultBeanLoader.java:180)
	at io.ebeaninternal.server.core.DefaultBeanLoader.loadMany(DefaultBeanLoader.java:45)
	at io.ebeaninternal.server.core.DefaultServer.loadMany(DefaultServer.java:546)
	at io.ebeaninternal.server.loadcontext.DLoadManyContext$LoadBuffer.loadMany(DLoadManyContext.java:228)
	at io.ebean.common.AbstractBeanCollection.lazyLoadCollection(AbstractBeanCollection.java:101)
	at io.ebean.common.BeanSet.init(BeanSet.java:137)
	at io.ebean.common.BeanSet.iterator(BeanSet.java:283)
	at java.base/java.util.Spliterators$IteratorSpliterator.estimateSize(Spliterators.java:1865)
	at java.base/java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:414)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:508)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
	at test.domain.entity.Student.getCuDetail(Student.java:634)
	at test.domain.entity.Student.getCuDetail(Student.java:627)
	at test.dto.response.StudentRespDTO.<init>(StudentRespDTO.java:79)
	at test.repository.StudentUnavailabilityTimeRepositoryTest.lambda$test$1(StudentUnavailabilityTimeRepositoryTest.java:145)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
	at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 19 (9 by maintainers)

Commits related to this issue

Most upvoted comments

ebean version 12.16.2 has been released with the back ported patch https://github.com/ebean-orm/ebean/pull/3218

Note: The branch maintain-v12 is the maintenance branch for 12.x releases going forward.

I have completed the test code but I don’t have permission to push it up. What should I do?

Did you “create a fork”?

You won’t have permission to push to the repo directly but if you have your own fork you should be able to push to that.

Thank you, I have submitted the test code to this address: https://github.com/lbsoft-lwsoft/example-minimal/blob/test-case-3173/src/test/java/org/example/domain/ConnectionLeakTest.java

I think we posted at the same time there.

solve this problem directly at the bottom layer

Yes, it looks like we can fix it in OrmQueryRequest in initTransIfRequired() and endTransIfRequired() for the read-only case.