Hi Nikita, Thanks for replying. I just found out that for this specific case, CommitLogFilter has a nice little method called "excludeFromTransaction()" that handles exactly this case, i.e. makes onPostCommit do it's work inside it's own transaction. (boy, do I ever wish I knew this earlier)
But thanks for the code example, quite interesting and might be useful in a later situation! Cheers, - hugi > On 27 Nov 2017, at 14:20, Nikita Timofeev <[email protected]> wrote: > > Hi, > > The problem seems in InheritableThreadLocal that stores current > transaction [1]. When you start new Thread from the one that already > have bound transaction it will grab that transaction. In your case > this leads to exception as transaction already committed (exactly as > error say). > Probably we should rethink this behavior. > Right now this logic is not configurable as it is completely static, > so it can't be easily changed. > > However you can try to manually start new transaction with a code > similar to one found in DefaultTransactionManager [2]. > > Following method fixes your demo project: > > static <T> T runInNewTransaction(TransactionalOperation<T> operation) { > TransactionFactory factory = > serverRuntime().getInjector().getInstance(TransactionFactory.class); > > Transaction tx = factory.createTransaction(); > Transaction oldTransaction = BaseTransaction.getThreadTransaction(); > > BaseTransaction.bindThreadTransaction(tx); > try { > T result = operation.perform(); > tx.commit(); > return result; > } catch (CayenneRuntimeException ex) { > tx.setRollbackOnly(); > throw ex; > } catch (Exception ex) { > tx.setRollbackOnly(); > throw new CayenneRuntimeException(ex); > } finally { > BaseTransaction.bindThreadTransaction(oldTransaction); > > if (tx.isRollbackOnly()) { > try { > tx.rollback(); > } catch (Exception e) { > //e.printStackTrace(); > } > } > } > } > > ... > > @Override > public void onPostCommit( ObjectContext originatingContext, ChangeMap > changes ) { > new Thread( () -> > runInNewTransaction(() -> { > serverRuntime().newContext().select( new > SelectQuery<>( "Person" ) ); > return null; > }), "afterUpdateThread" ).start(); > } > > > [1] > https://github.com/apache/cayenne/blob/bd1b109a943307a83078399c7a4d6aa53631a065/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java#L41 > [2] > https://github.com/apache/cayenne/blob/bd1b109a943307a83078399c7a4d6aa53631a065/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionManager.java#L55 > > On Sat, Nov 25, 2017 at 7:11 PM, Hugi Thordarson <[email protected]> wrote: >> Hi all. >> I've been fighting a bug that's been a pain to replicate. Here's a small >> self-contained project that demonstrates the issue: >> >> https://github.com/hugith/concurrencytest-simple/ >> >> Just run Main.java to see it happen: >> >> https://github.com/hugith/concurrencytest-simple/blob/master/src/main/java/concurrencytest/Main.java >> >> The subject basically says it all: If I touch the DB in a Thread inside a >> CommitLogListener AND am using a connection pool, it will fail with the >> Exception/trace shown below. But the real kicker: This only happens if I'm >> using a 3rd party connection pool (I've tried both HikariCP and c3p0 so I >> assume it's generic). If I just have Cayenne handle the DB connection for >> me, everything works fine. >> >> I've been attempting to figure out what the issue is but I'm somewhat at a >> loss. Any ideas what might be happening? >> >> Cheers, >> - hugi >> >> ---------------------------------- >> >> java.lang.IllegalStateException: Transaction must have 'STATUS_ACTIVE' to >> add a connection. Current status: STATUS_COMMITTED >> at >> org.apache.cayenne.tx.BaseTransaction.connectionAdded(BaseTransaction.java:246) >> at >> org.apache.cayenne.tx.CayenneTransaction.connectionAdded(CayenneTransaction.java:49) >> at >> org.apache.cayenne.tx.BaseTransaction.addConnection(BaseTransaction.java:231) >> at >> org.apache.cayenne.tx.BaseTransaction.getOrCreateConnection(BaseTransaction.java:203) >> at >> org.apache.cayenne.access.DataNode$TransactionDataSource.getConnection(DataNode.java:446) >> at >> org.apache.cayenne.access.DataNode.performQueries(DataNode.java:273) >> at >> org.apache.cayenne.access.DataDomainQueryAction.runQuery(DataDomainQueryAction.java:471) >> at >> org.apache.cayenne.access.DataDomainQueryAction.access$000(DataDomainQueryAction.java:72) >> at >> org.apache.cayenne.access.DataDomainQueryAction$2.perform(DataDomainQueryAction.java:446) >> at >> org.apache.cayenne.tx.DefaultTransactionManager.performInTransaction(DefaultTransactionManager.java:87) >> at >> org.apache.cayenne.tx.DefaultTransactionManager.performInTransaction(DefaultTransactionManager.java:51) >> at >> org.apache.cayenne.tx.DefaultTransactionManager.performInTransaction(DefaultTransactionManager.java:40) >> at >> org.apache.cayenne.access.DataDomainQueryAction.runQueryInTransaction(DataDomainQueryAction.java:443) >> at >> org.apache.cayenne.access.DataDomainQueryAction.execute(DataDomainQueryAction.java:122) >> at >> org.apache.cayenne.access.DataDomain.onQueryNoFilters(DataDomain.java:564) >> at >> org.apache.cayenne.access.DataDomain$DataDomainQueryFilterChain.onQuery(DataDomain.java:748) >> at >> org.apache.cayenne.commitlog.CommitLogFilter.onQuery(CommitLogFilter.java:61) >> at >> org.apache.cayenne.access.DataDomain$DataDomainQueryFilterChain.onQuery(DataDomain.java:748) >> at >> org.apache.cayenne.tx.TransactionFilter.onQuery(TransactionFilter.java:49) >> at >> org.apache.cayenne.access.DataDomain$DataDomainQueryFilterChain.onQuery(DataDomain.java:748) >> at org.apache.cayenne.access.DataDomain.onQuery(DataDomain.java:556) >> at >> org.apache.cayenne.util.ObjectContextQueryAction.runQuery(ObjectContextQueryAction.java:406) >> at >> org.apache.cayenne.util.ObjectContextQueryAction.executePostCache(ObjectContextQueryAction.java:107) >> at >> org.apache.cayenne.util.ObjectContextQueryAction.execute(ObjectContextQueryAction.java:94) >> at org.apache.cayenne.access.DataContext.onQuery(DataContext.java:965) >> at >> org.apache.cayenne.access.DataContext.performQuery(DataContext.java:954) >> at org.apache.cayenne.BaseContext.select(BaseContext.java:307) >> at org.apache.cayenne.query.FluentSelect.select(FluentSelect.java:157) >> at concurrencytest.Main$AfterUpdateListener.lambda$0(Main.java:61) >> at java.lang.Thread.run(Thread.java:748) >> Exception in thread "afterUpdateThread" >> org.apache.cayenne.CayenneRuntimeException: [v.4.1.M1 Oct 06 2017 09:23:31] >> Global exception. >> at >> org.apache.cayenne.access.DataDomainQueryAction.nextGlobalException(DataDomainQueryAction.java:619) >> at >> org.apache.cayenne.access.DataNode.performQueries(DataNode.java:282) >> at >> org.apache.cayenne.access.DataDomainQueryAction.runQuery(DataDomainQueryAction.java:471) >> at >> org.apache.cayenne.access.DataDomainQueryAction.access$000(DataDomainQueryAction.java:72) >> at >> org.apache.cayenne.access.DataDomainQueryAction$2.perform(DataDomainQueryAction.java:446) >> at >> org.apache.cayenne.tx.DefaultTransactionManager.performInTransaction(DefaultTransactionManager.java:87) >> at >> org.apache.cayenne.tx.DefaultTransactionManager.performInTransaction(DefaultTransactionManager.java:51) >> at >> org.apache.cayenne.tx.DefaultTransactionManager.performInTransaction(DefaultTransactionManager.java:40) >> at >> org.apache.cayenne.access.DataDomainQueryAction.runQueryInTransaction(DataDomainQueryAction.java:443) >> at >> org.apache.cayenne.access.DataDomainQueryAction.execute(DataDomainQueryAction.java:122) >> at >> org.apache.cayenne.access.DataDomain.onQueryNoFilters(DataDomain.java:564) >> at >> org.apache.cayenne.access.DataDomain$DataDomainQueryFilterChain.onQuery(DataDomain.java:748) >> at >> org.apache.cayenne.commitlog.CommitLogFilter.onQuery(CommitLogFilter.java:61) >> at >> org.apache.cayenne.access.DataDomain$DataDomainQueryFilterChain.onQuery(DataDomain.java:748) >> at >> org.apache.cayenne.tx.TransactionFilter.onQuery(TransactionFilter.java:49) >> at >> org.apache.cayenne.access.DataDomain$DataDomainQueryFilterChain.onQuery(DataDomain.java:748) >> at org.apache.cayenne.access.DataDomain.onQuery(DataDomain.java:556) >> at >> org.apache.cayenne.util.ObjectContextQueryAction.runQuery(ObjectContextQueryAction.java:406) >> at >> org.apache.cayenne.util.ObjectContextQueryAction.executePostCache(ObjectContextQueryAction.java:107) >> at >> org.apache.cayenne.util.ObjectContextQueryAction.execute(ObjectContextQueryAction.java:94) >> at org.apache.cayenne.access.DataContext.onQuery(DataContext.java:965) >> at >> org.apache.cayenne.access.DataContext.performQuery(DataContext.java:954) >> at org.apache.cayenne.BaseContext.select(BaseContext.java:307) >> at org.apache.cayenne.query.FluentSelect.select(FluentSelect.java:157) >> at concurrencytest.Main$AfterUpdateListener.lambda$0(Main.java:61) >> at java.lang.Thread.run(Thread.java:748) >> Caused by: java.lang.IllegalStateException: Transaction must have >> 'STATUS_ACTIVE' to add a connection. Current status: STATUS_COMMITTED >> at >> org.apache.cayenne.tx.BaseTransaction.connectionAdded(BaseTransaction.java:246) >> at >> org.apache.cayenne.tx.CayenneTransaction.connectionAdded(CayenneTransaction.java:49) >> at >> org.apache.cayenne.tx.BaseTransaction.addConnection(BaseTransaction.java:231) >> at >> org.apache.cayenne.tx.BaseTransaction.getOrCreateConnection(BaseTransaction.java:203) >> at >> org.apache.cayenne.access.DataNode$TransactionDataSource.getConnection(DataNode.java:446) >> at >> org.apache.cayenne.access.DataNode.performQueries(DataNode.java:273) >> ... 24 more >> > > > > -- > Best regards, > Nikita Timofeev
