cayenne-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Hugi Thordarson <h...@karlmenn.is>
Subject Re: Exception/Transaction problems when performing DB-access in a thread inside a CommitLogListener — but only if using a 3rd party Connection Pool
Date Mon, 27 Nov 2017 14:35:14 GMT
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 <ntimofeev@objectstyle.com> 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 <hugi@karlmenn.is> 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


Mime
View raw message