Uploaded image for project: 'Confluence Data Center'
  1. Confluence Data Center
  2. CONFSERVER-58663

Concurrently purging all items from the trash may throw an error with HibernateOptimisticLockingFailureException

XMLWordPrintable

      Issue Summary

      The Space administrator can purge all pages that are in the Space Trash as described in Empty the trash or permanently delete a page.
      Depending on the amount of content to be purged, this operation may take some time. This depends on how many pages, history versions, attachments, images and other items that are associated to deleted content.

      If the purge all functionality is triggered again for the same set of content (by the same user or a different one), then a system error with a stack trace is sent to the space administrator.

      Steps to Reproduce

      1. Install a vanilla instance of Confluence.
        • This was validated in Confluence Server 6.15.4.
      2. Create two users as part of the confluence-administrators group – say it admin and admin1.
        • It is expected you run the following steps as a Confluence administrator.
      3. Create a sample blank Space.
        • Space Name: Purge Content; Space Key: PC
      4. Under the Purge Content Home page, create a new blank page.
        • Page name: Purge Content - Root Page
      5. Create 1985 pages as child of Purge Content - Root Page.
        • Since we are dealing with a vanilla instance, then we are reproducing the issue with an exaggerated number of pages; in a production environment this could happen with hundreds of pages considering history versions, attachments and so on.
        • The following shell script may help you to create the child pages.
          USRNAME=admin
          USRPWD=admin
          CONFBASEURL=http://localhost:26154/c6154
          SPACE_KEY=PC
          PARENT_PAGEID=1476569
          PAGE_TITLE_PRE=RootPage
          MAX_PAGES=1985
            
          for PAGE_NUM in $(seq 1 $MAX_PAGES); do
           curl --user $USRNAME:$USRPWD -H "Content-Type: application/json" -H "Accept: application/json" -d '{"type":"page","title":"'$PAGE_TITLE_PRE$PAGE_NUM'", "ancestors":[{"type":"page","id":'$PARENT_PAGEID'}], "space":{"key":"'$SPACE_KEY'"},"body":{"storage":{"value":"<p>This is <br/> another new page</p>","representation": "storage"}}}' -X POST $CONFBASEURL/rest/api/content >/dev/null 2>&1
           echo "$PAGE_TITLE_PRE$PAGE_NUM created"
          done
          
      6. Delete Purge Content - Root Page and all of its child pages.
        1. Go to Purge Content - Root Page > ... > Delete
        2. Choose Also delete child pages and then click on Next.
        3. Click on Delete on the confirmation page.
      7. Simultaneously access <Confluence Base URL>/pages/viewtrash.action?key=PC using both administrators users created a few steps before.
        • You may do this using different browsers.
      8. As admin, click on the Purge All link.
        • Don't click on the OK button yet – wait for it.
      9. As admin1, click on the Purge All link.
      10. As admin1, click on the OK button to confirm the purge operation.
        • The delete operation starts, but there's no meaningful information about it.
      11. Quickly switch to the admin browser window and click on the OK button to confirm the purge operation.

      Expected Results

      On the second time the operation is confirmed (in this case triggered by admin, Confluence would notify the user the same operation is already occurring, gracefully dealing with concurrent (same) tasks.

      To admin1, it would be expected to have a meaningful feedback in the UI to advise the operation was ongoing (and maybe lock the UI). Maybe something similar when a large set of pages are being deleted as in the image below.

      Actual Results

      Confluence sends admin to a System Error page with a stack trace similar to the below.

      org.springframework.orm.hibernate5.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
      	at org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:283)
      	at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:368)
      	at org.springframework.orm.hibernate5.HibernateTemplate.execute(HibernateTemplate.java:315)
      	at com.atlassian.confluence.mail.notification.persistence.dao.hibernate.HibernateNotificationDao.findNotificationsByContent(HibernateNotificationDao.java:117)
      	at com.atlassian.confluence.internal.notification.persistence.DelegatingNotificationDaoInternal.findNotificationsByContent(DelegatingNotificationDaoInternal.java:99)
      	at com.atlassian.confluence.mail.notification.DefaultNotificationManager.getNotificationsByContent(DefaultNotificationManager.java:124)
      	at sun.reflect.GeneratedMethodAccessor1067.invoke(Unknown Source)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
      	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
      	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
      	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at com.atlassian.spring.interceptors.SpringProfilingInterceptor.invoke(SpringProfilingInterceptor.java:16)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at com.atlassian.confluence.util.profiling.ConfluenceMonitoringMethodInterceptor.invoke(ConfluenceMonitoringMethodInterceptor.java:34)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
      	at com.sun.proxy.$Proxy72.getNotificationsByContent(Unknown Source)
      	at com.atlassian.confluence.core.DefaultContentEntityManager.removeContentEntityInternal(DefaultContentEntityManager.java:227)
      	at com.atlassian.confluence.core.DefaultContentEntityManager.removeContentEntity(DefaultContentEntityManager.java:259)
      	at sun.reflect.GeneratedMethodAccessor1532.invoke(Unknown Source)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
      	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
      	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
      	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at com.atlassian.spring.interceptors.SpringProfilingInterceptor.invoke(SpringProfilingInterceptor.java:16)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at com.atlassian.confluence.util.profiling.ConfluenceMonitoringMethodInterceptor.invoke(ConfluenceMonitoringMethodInterceptor.java:34)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
      	at com.sun.proxy.$Proxy118.removeContentEntity(Unknown Source)
      	at com.atlassian.confluence.pages.AbstractPage.remove(AbstractPage.java:92)
      	at com.atlassian.confluence.pages.Page.remove(Page.java:231)
      	at com.atlassian.confluence.pages.DefaultTrashManager.deleteContentEntity(DefaultTrashManager.java:159)
      	at com.atlassian.confluence.pages.DefaultTrashManager.access$100(DefaultTrashManager.java:27)
      	at com.atlassian.confluence.pages.DefaultTrashManager$1.doInTransaction(DefaultTrashManager.java:145)
      	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
      	at com.atlassian.confluence.pages.DefaultTrashManager.deleteBlock(DefaultTrashManager.java:126)
      	at com.atlassian.confluence.pages.DefaultTrashManager.emptyTrash(DefaultTrashManager.java:54)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
      	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
      	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
      	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at com.atlassian.spring.interceptors.SpringProfilingInterceptor.invoke(SpringProfilingInterceptor.java:16)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at com.atlassian.confluence.util.profiling.ConfluenceMonitoringMethodInterceptor.invoke(ConfluenceMonitoringMethodInterceptor.java:34)
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
      	at com.sun.proxy.$Proxy202.emptyTrash(Unknown Source)
      	at com.atlassian.confluence.pages.actions.EmptyTrashAction.execute(EmptyTrashAction.java:11)
      (...)
      	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
      	at java.lang.Thread.run(Thread.java:748)
      Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
      	at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:67)
      	at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:54)
      	at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:46)
      	at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3315)
      	at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3552)
      	at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:99)
      	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:586)
      	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:460)
      	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
      	at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:50)
      	at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1389)
      	at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1474)
      	at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1441)
      	at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1410)
      	at com.atlassian.confluence.mail.notification.persistence.dao.hibernate.HibernateNotificationDao.lambda$findNotificationsByContent$1(HibernateNotificationDao.java:121)
      	at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:361)
      	... 440 more
      

      Notes

      The purge operation will not fail, since it would be running in the background. However, the second user might feel it failed because the System Error.

      Although the operation and the stack trace error is similar, this issue is different from bug CONFSERVER-57823"Purge All" space trash fails with "org.hibernate.StaleStateException" due to Confluence's handling of CustomContentEntity.

      Workaround

      Currently there is no known workaround for this behavior. A workaround will be added here when available

              346a06beb37e ajoshi7 (Inactive)
              tmasutti Thiago Masutti
              Votes:
              18 Vote for this issue
              Watchers:
              16 Start watching this issue

                Created:
                Updated:
                Resolved: