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