cayenne-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Nikita Timofeev <ntimof...@objectstyle.com>
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:20:03 GMT
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