Our Craft

Making it better

The versioning isn’t happening, part 2: beforeCompletion

Posted by danielmeyer on December 4, 2008

We’re trying to figure out why the envers versioning records are not created when our project uses a JTA transaction manager — it creates the versioning records just fine when we’re using local Hibernate transactions.

Our approach last time was to see what differences there might be in the Hibernate configuration.  I ended up that post with

Perhaps next I should capture the values of the rest of AnnotationConfiguration’s 47 fields, and compare them all between jta mode and Hibernate mode.

-1. The Other 47 Fields

After I stopped writing last time, I did go on to capture the 47 AnnotationConfiguration fields’ values when running in Hibernate mode and when running in JTA mode, and… they were the same!  (Keith suspected they would be.)

0. No Other Apparent Magic During JBoss Startup

Still running in Hibernate mode, I followed the control flow after the creation of the Hibernate SessionFactory bean, but I didn’t see anything interesting-looking.  It’s hard to let the idea go that something’s different at startup time, but I think next I need to take a closer look at what happens during a save to the database.

1. A list of work to do later

Let’s see where the envers onPostInsert() hook gets called from, in hibernate mode (i.e., where things are working correctly). Here’s the call stack:


Daemon Thread [http-127.0.0.1-8080-1] (Suspended (entry into method onPostInsert in VersionsEventListener))
VersionsEventListener.onPostInsert(PostInsertEvent) line: 88
EntityInsertAction.postInsert() line: 131
EntityInsertAction.execute() line: 110
ActionQueue.execute(Executable) line: 279
ActionQueue.executeActions(List) line: 263
ActionQueue.executeActions() line: 167
DefaultFlushEventListener(AbstractFlushingEventListener).performExecutions(EventSource) line: 298
DefaultFlushEventListener.onFlush(FlushEvent) line: 27
SessionImpl.flush() line: 1000
GreetingDAO(GenericDAO<PO,PK>).create(PO) line: 66
GreetingDBSvcImpl.saveGreeting(GreetingPTO) line: 25
GreetingSvcImpl.saveGreeting(GreetingDTO) line: 21
HelloWorldSvcImpl.writeGreetingToDatabase(Greeting) line: 24
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39

1.1. It’s all the same in JTA mode

I set breakpoints at most of these places.  Let’s try it in jta mode:

  • We get to the session.flush() in GreetingDAO(GenericDAO<PO,PK>).create(PO) line: 66
  • We get to DefaultFlushEventListener.onFlush(FlushEvent) line: 27
  • We get to DefaultFlushEventListener(AbstractFlushingEventListener).performExecutions(EventSource) line: 298
  • We get to ActionQueue.executeActions() line: 167 with 1 element in our insertions ArrayList to do, just like in hibernate mode
  • We get to EntityInsertAction.execute() line: 110
  • We get into EntityInsertAction.postInsert(), with a org.jboss.envers.event.VersionsEventListener and a org.hibernate.search.event.FullTextIndexEventListenerin the postInsertEventListener[] array
  • We even get into VersionsEventListener.onPostInsert(PostInsertEvent)!

So why doesn’t the versioning happen?

It turns out that VersionsEventListener.onPostInsert() isn’t where the real work is done.

1.2. What VersionsEventListener.onPostInsert() is responsible for

Summary:

VersionsEventListener.onPostInsert() adds a VersionsWorkUnit to a list, to be picked up and performed later.  It doesn’t actually do the work.

Details:

org.jboss.envers.event.VersionsEventListener.onPostInsert(PostInsertEvent) does is to add the org.jboss.envers.synchronization.work.VersionsWorkUnit to the org.jboss.envers.synchronization.VersionsSync.workUnits linked list (VersionsSync is the class that implements javax.transaction.Synchronization).

3. Who picks up the work unit?

Who is responsible for picking up the work unit, then?  I notice that the javax.transaction.Synchronization specifies two methods: beforeCompletion() and afterCompletion(int).  Let’s see if anyone calls beforeCompletion() when we’re in Hibernate mode… setting breakpoints on these two methods and re-running my hibernate mode test, we break at beforeCompletion() with this call stack:


Daemon Thread [http-127.0.0.1-8080-1] (Suspended (entry into method beforeCompletion in VersionsSync))
    VersionsSync.beforeCompletion() line: 125
    JDBCTransaction.notifyLocalSynchsBeforeTransactionCompletion() line: 228
    JDBCTransaction.commit() line: 109
    HibernateTransactionManager.doCommit(DefaultTransactionStatus) line: 655
    HibernateTransactionManager(AbstractPlatformTransactionManager).processCommit(DefaultTransactionStatus) line: 709
    HibernateTransactionManager(AbstractPlatformTransactionManager).commit(TransactionStatus) line: 678
    TransactionInterceptor(TransactionAspectSupport).commitTransactionAfterReturning(TransactionAspectSupport$TransactionInfo)line: 321
    TransactionInterceptor.invoke(MethodInvocation) line: 116
    ReflectiveMethodInvocation.proceed() line: 171
    ExposeInvocationInterceptor.invoke(MethodInvocation) line: 89
    ReflectiveMethodInvocation.proceed() line: 171
    JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
    $Proxy68.writeGreetingToDatabase(Greeting) line: not available
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
    Method.invoke(Object, Object...) line: 585
    JAXWSMethodInvoker(AbstractInvoker).performInvocation(Exchange, Object, Method, Object[]) line: 136
    JAXWSMethodInvoker(AbstractInvoker).invoke(Exchange, Object, Method, List<Object>) line: 82
    JAXWSMethodInvoker.invoke(Exchange, Object, Method, List<Object>) line: 97
    JAXWSMethodInvoker(AbstractInvoker).invoke(Exchange, Object) line: 68
    ServiceInvokerInterceptor$1.run() line: 56
    SynchronousExecutor.execute(Runnable) line: 37
    ServiceInvokerInterceptor.handleMessage(Message) line: 92
    PhaseInterceptorChain.doIntercept(Message) line: 221
    ChainInitiationObserver.onMessage(Message) line: 78
    ServletDestination.invoke(ServletContext, HttpServletRequest, HttpServletResponse) line: 92
    ServletController.invokeDestination(HttpServletRequest, HttpServletResponse, ServletDestination) line: 279
    ServletController.invoke(HttpServletRequest, HttpServletResponse) line: 161
    CXFServlet(AbstractCXFServlet).invoke(HttpServletRequest, HttpServletResponse) line: 174
    CXFServlet(AbstractCXFServlet).doPost(HttpServletRequest, HttpServletResponse) line: 152
    CXFServlet(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 710
    CXFServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 803
    ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 290
    ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
    ReplyHeaderFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 96
    ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 235
    ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
    StandardWrapperValve.invoke(Request, Response) line: 230
    StandardContextValve.invoke(Request, Response) line: 175
    SecurityAssociationValve.invoke(Request, Response) line: 179
    JaccContextValve.invoke(Request, Response) line: 84
    StandardHostValve.invoke(Request, Response) line: 127
    ErrorReportValve.invoke(Request, Response) line: 102
    CachedConnectionValve.invoke(Request, Response) line: 157
    StandardEngineValve.invoke(Request, Response) line: 109
    CoyoteAdapter.service(Request, Response) line: 262
    Http11Processor.process(Socket) line: 844
    Http11Protocol$Http11ConnectionHandler.process(Socket) line: 583
    JIoEndpoint$Worker.run() line: 446
    Thread.run() line: 595

So VersionsSync.beforeCompletion() does indeed get called when we’re in Hibernate mode.

3.1. Sidenote: The Point Where the Records Appear in the Tables

After some more lines of code, we get to org.hibernate.transaction.JDBCTransaction.commitAndResetAutoCommit(), which does a  jdbcContext.connection().commit().  Before this line is executed, neither the main record nor the versioning record appear in the table; after that line, the records do appear.

4. JTA mode: no beforeCompletion call made

Next we’ll re-run the test we did in step 3 with the jta version deployed and with all the same breakpoints, to see if org.jboss.envers.synchronization.VersionsSync.beforeCompletion() gets called…

It doesn’t.

Tune in next time for an exploration of why VersionsSync.beforeCompletion() isn’t getting called in JTA mode!

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>