OpenJPA
  1. OpenJPA
  2. OPENJPA-913

A deadlock issue happens when DirtyListener is used

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 1.2.0
    • Fix Version/s: 1.1.1, 2.0.0-M2
    • Component/s: kernel
    • Labels:
      None
    • Environment:
      Linux x86

      Description

      A deadlock issue happens when OpenJPA entity manager is
      concurrently called and DirtyListener is used and
      DirtyListener.beforeDirty calls an entity manager related
      operation. To call OpenJPA entity manager concurrently,
      we need to define openjpa.MulthThreaded option as true.

      Following is test scenario.

      1. Thread A calls entityMangaer.refresh() repeatedly.
      In refresh() method, entityManager acquires BrokerImpl
      lock. And then, entityManager acquires LifecycleEventManager
      lock to call lifecycle callback.
      2. Thread B calls persistedObject.getAItems() (getting
      collection items).
      3. In enhanced getItems() method, entityManager tries
      to mark it as "dirty". Before marking, callback listener
      DirtyListener.beforeDirty is called. In this point,
      LifecycleEventManager lock is acquired without acquiring
      BrokerImpl lock.
      4. In the testcase, beforeDirty calls persistedObject.getAItems().
      And then, entity manager tries to acquire BrokerImpl lock.
      But, sometimes BrokerImpl lock is already acquired by Thread B.
      So, a deadlock issue happens. deadlock stack is as follows.

      ====
      [java] "Thread-1" prio=1 tid=0x09e98b28 nid=0x7fcc waiting on condition [0xb15f3000..0xb15f4130]
      [java] at sun.misc.Unsafe.park(Native Method)
      [java] at java.util.concurrent.locks.LockSupport.park(LockSupport.java:118)
      [java] at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:716)
      [java] at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:746)
      [java] at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1076)
      [java] at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:184)
      [java] at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:256)
      [java] at org.apache.openjpa.kernel.BrokerImpl.lock(BrokerImpl.java:4168)
      [java] at org.apache.openjpa.kernel.BrokerImpl.beginOperation(BrokerImpl.java:1770)
      [java] at org.apache.openjpa.kernel.BrokerImpl.isActive(BrokerImpl.java:1742)
      [java] at org.apache.openjpa.kernel.StateManagerImpl.beforeRead(StateManagerImpl.java:964)
      [java] at org.apache.openjpa.kernel.StateManagerImpl.accessingField(StateManagerImpl.java:1501)
      [java] at model.A.getAItems(A.java)
      [java] at model.ADirtyListener.beforeDirty(ADirtyListener.java:24)
      [java] at org.apache.openjpa.event.LifecycleEventManager.fireEvent(LifecycleEventManager.java:423)
      [java] at org.apache.openjpa.event.LifecycleEventManager.fireEvent(LifecycleEventManager.java:289)
      [java] - locked <0x51b4e4b0> (a org.apache.openjpa.event.LifecycleEventManager)
      [java] at org.apache.openjpa.kernel.BrokerImpl.fireLifecycleEvent(BrokerImpl.java:693)
      [java] at org.apache.openjpa.kernel.StateManagerImpl.fireLifecycleEvent(StateManagerImpl.java:364)
      [java] at org.apache.openjpa.kernel.StateManagerImpl.dirty(StateManagerImpl.java:1596)
      [java] at org.apache.openjpa.kernel.StateManagerImpl.dirty(StateManagerImpl.java:1539)
      [java] at org.apache.openjpa.util.Proxies.dirty(Proxies.java:66)
      [java] at org.apache.openjpa.util.ProxyCollections.beforeAdd(ProxyCollections.java:57)
      [java] at org.apache.openjpa.util.java$util$HashSet$proxy.add(Unknown Source)
      [java] at business.Test$2.run(Test.java:80)
      [java] at java.lang.Thread.run(Thread.java:595)

      [java] "Thread-0" prio=1 tid=0x09e9d010 nid=0x7fcb waiting for monitor entry [0xb1674000..0xb1674db0]
      [java] at org.apache.openjpa.event.LifecycleEventManager.fireEvent(LifecycleEventManager.java:272)
      [java] - waiting to lock <0x51b4e4b0> (a org.apache.openjpa.event.LifecycleEventManager)
      [java] at org.apache.openjpa.kernel.BrokerImpl.fireLifecycleEvent(BrokerImpl.java:693)
      [java] at org.apache.openjpa.kernel.StateManagerImpl.fireLifecycleEvent(StateManagerImpl.java:364)
      [java] at org.apache.openjpa.kernel.StateManagerImpl.clearFields(StateManagerImpl.java:2647)
      [java] at org.apache.openjpa.kernel.StateManagerImpl.beforeRefresh(StateManagerImpl.java:1239)
      [java] at org.apache.openjpa.kernel.BrokerImpl.refreshInternal(BrokerImpl.java:2835)
      [java] at org.apache.openjpa.kernel.BrokerImpl.refresh(BrokerImpl.java:2781)
      [java] at org.apache.openjpa.kernel.DelegatingBroker.refresh(DelegatingBroker.java:1078)
      [java] at org.apache.openjpa.persistence.EntityManagerImpl.refresh(EntityManagerImpl.java:694)
      [java] at business.Test$1.run(Test.java:64)
      [java] at java.lang.Thread.run(Thread.java:595)
      ====

      Intially the problem is reproduced on OpenJPA 1.x. But,
      I verified the problem could be reproduced with latest
      OpenJPA head.

      1. proposed-fix.patch
        0.9 kB
        Hiroki Tateno
      2. testcase.zip
        5.26 MB
        Hiroki Tateno

        Activity

        Hide
        Hiroki Tateno added a comment -

        Abe's change resolved the issue.

        Show
        Hiroki Tateno added a comment - Abe's change resolved the issue.
        Hide
        Hiroki Tateno added a comment -

        Also, I attached possible fix of the issue.
        The root cause of the issue is sometimes BrokerImpl.fireLifecycleEvent
        is called without BrokerImpl lock. So, I added lock()/unlock() to
        BrokerImpl.fireLifecycleEvent. It may be overkill fix. If it's overkill,
        it's better that adding broker.lock()/broker.unlock() to around
        calling fireLifecycleEvent() in StateManagerImpl.dirty().

        Show
        Hiroki Tateno added a comment - Also, I attached possible fix of the issue. The root cause of the issue is sometimes BrokerImpl.fireLifecycleEvent is called without BrokerImpl lock. So, I added lock()/unlock() to BrokerImpl.fireLifecycleEvent. It may be overkill fix. If it's overkill, it's better that adding broker.lock()/broker.unlock() to around calling fireLifecycleEvent() in StateManagerImpl.dirty().
        Hide
        Hiroki Tateno added a comment -

        I uploaded a simple testcase testcase.zip.

        ====
        step by step test procedure)
        1. update database configuration in persistence.xml
        2. run "ant test"
        ====

        Show
        Hiroki Tateno added a comment - I uploaded a simple testcase testcase.zip. ==== step by step test procedure) 1. update database configuration in persistence.xml 2. run "ant test" ====

          People

          • Assignee:
            Unassigned
            Reporter:
            Hiroki Tateno
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development