Hello everybody,

We're evaluating Neo4j for integration in two products we are developing.

One of the aspects we are interested in is making an embedded neo4j instance
participate in JTA/XA transactions with a postgresql datasource and possibly
JMS.

 

I'm sorry if this is a bit long but the matter is complex.

 

From what i understood after a bit of research, currently neo4j does not allow
to use an external TransactionManager out of the box. 

I then dug into the source code and simply patched the following classes to
allow me to pass an external JTA TransactionManager instance down to the
TxModule, replacing Neo4j own TxManager.

 

For our tests we used the following:

 * Neo4J 1.1

 * Atomikos TransactionEssentials JTA Manager 3.6.5

 * postgresql XA-enabled JDBC driver wrapped in an AtomikosDataSourceBean

 

We created a simple test app which creates a very minimal Spring
ApplicationContext which sets up the neo4j database, the JTA manager, the JDBC
datasource and a sample transactional service which interacts with both the
relational database and the graph database; we used Spring
"tx:annotation-driven" infrastructure to handle begin/commit/rollback of the
global transactions.

 

Upon starting it, we got the following exception:

 

org.neo4j.kernel.impl.transaction.LockNotFoundException: No transaction lock
element found for Placebo tx for thread
Thread[net.emaze.springexperiment.App.main(),5,net.emaze.springexperiment.App]

at org.neo4j.kernel.impl.transaction.RWLock.releaseWriteLock(RWLock.java:345)

at
org.neo4j.kernel.impl.transaction.LockManager.releaseWriteLock(LockManager.java:202)

at org.neo4j.kernel.impl.core.LockReleaser.releaseLocks(LockReleaser.java:337)

at
org.neo4j.kernel.impl.core.LockReleaser$ReadOnlyTxReleaser.afterCompletion(LockReleaser.java:713)

at com.atomikos.icatch.jta.Sync2Sync.afterCompletion(Sync2Sync.java:91)

at com.atomikos.icatch.imp.SynchToFSM.doAfterCompletion(SynchToFSM.java:38)

at com.atomikos.icatch.imp.SynchToFSM.entered(SynchToFSM.java:59)

at com.atomikos.finitestates.FSMImp.notifyListeners(FSMImp.java:197)

at com.atomikos.finitestates.FSMImp.setState(FSMImp.java:288)

at com.atomikos.icatch.imp.CoordinatorImp.setState(CoordinatorImp.java:498)

at
com.atomikos.icatch.imp.CoordinatorImp.setStateHandler(CoordinatorImp.java:328)

at
com.atomikos.icatch.imp.CoordinatorStateHandler.commit(CoordinatorStateHandler.java:730)

at
com.atomikos.icatch.imp.IndoubtStateHandler.commit(IndoubtStateHandler.java:225)

at com.atomikos.icatch.imp.CoordinatorImp.commit(CoordinatorImp.java:828)

at com.atomikos.icatch.imp.CoordinatorImp.terminate(CoordinatorImp.java:1127)

at
com.atomikos.icatch.imp.CompositeTerminatorImp.commit(CompositeTerminatorImp.java:151)

at com.atomikos.icatch.jta.TransactionImp.commit(TransactionImp.java:298)

at
com.atomikos.icatch.jta.TransactionManagerImp.commit(TransactionManagerImp.java:612)

at
com.atomikos.icatch.jta.UserTransactionImp.commit(UserTransactionImp.java:168)

at
org.springframework.transaction.jta.JtaTransactionManager.doCommit(JtaTransactionManager.java:1028)

at
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:732)

at
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:701)

at
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:321)

at
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:116)

at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)

at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)

at $Proxy9.success(Unknown Source)

at net.emaze.springexperiment.App.main(App.java:21)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

at java.lang.reflect.Method.invoke(Method.java:597)

at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:291)

at java.lang.Thread.run(Thread.java:619)

 

Further analysis resulted in the following hypothesis.

1. the external JTA TransactionManager correctly completes the transaction

2. the TransactionManager then calls the Synchronization.afterCompletion()
method on the LockReleaser$ReadOnlyTxReleaser for the (now finished)
transaction.

3. This causes LockReleaser.releaseLocks() to be called, which through various
calls invokes the RWLock.releaseWriteLock() method.

4. At this point RWLock.releaseWriteLock() calls
RagManager.getCurrentTransaction(), which delegates to the TransactionManager,
which correctly returns null, since the transaction is already completed.

5. RWLock.releaseWriteLock then creates a PlaceboTransaction and looks up the
txLockElementMap for the TxLockElement associated to the PlaceboTransaction.
Since the TxLockElement it is looking for was previously associated to the
actual global transaction which was just completed, this fails and the
LockNotFoundException is thrown.

 

The problem, IMHO, is caused by the fact that, in the execution flow of an
afterCompletion() callback, the TransactionManager is requested for the current
global transaction, which does not exist at this point.

To fix it, i hacked the following classes to allow the
LockReleaser$ReadOnlyTxReleaser to pass the transaction reference stored at
creation time down to the RWLock.releaseWriteLock() method, which now can look
up the correct TxLockElement in the txLockElementMap using the correct key.

 

Changed classes:

org.neo4j.kernel.core.LockReleaser

org.neo4j.kernel.impl.transaction.LockManager

org.neo4j.kernel.impl.transaction.RWLock

 

At this point, my doubts are the following:

 * I thought that injecting an external TransactionManager for neo4j to use
would be easier; is my approach valid in this regard? Is there a specific reason
why the TransactionManager implementation is not easily pluggable ?

 * Regarding the potential bug i found after injecting an external
TransactionManager, is my analysis correct ? Can it, or some other solution, be
integrated in a future release ?

 

Handling this scenario is pretty important for us in choosing if we should
proceed in using neo4j in our app.

I can send you the two patches i made fron neo4j-kernel trunk and the sample
app, if needed; i did not know if it was ok to attach it to the mailing list
post.

 

Thanks for your time.

 

Francesco Degrassi
_______________________________________________
Neo4j mailing list
User@lists.neo4j.org
https://lists.neo4j.org/mailman/listinfo/user

Reply via email to