pessimistic lock not released on commit

#1

we are protecting object access via pessimistic lock of objects. in a multi thread environment (each with it's own entityManager) we do this:

- one thread is setting the lock
- another thread tries to set the lock, too. On exception we wait and retry
- the first thread commits but the second thread is not able to lock the formerly by thread one locked object

#2

The following example shows how pessimistic locking should work in ObjectDB (and JPA):

public class F2992 {
    private static final String DB = "objectdb:my.odb";
    private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory(DB);

    public static void main(String[] args) throws Exception {
        initDB();
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(F2992::threadA);
        Thread.sleep(200);
        executor.submit(F2992::threadB);
        executor.shutdown();
        executor.awaitTermination(5, TimeUnit.SECONDS);
        emf.close();
    }

    private static void initDB() {
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        if (em.find(MyEntity.class, 1L) == null) {
            em.persist(new MyEntity(1L, "initial"));
        }
        em.getTransaction().commit();
        em.close();
    }

    private static void threadA() {
        EntityManager em = emf.createEntityManager();
        try {
            em.getTransaction().begin();
            System.out.println("Thread A: trying to lock...");
            MyEntity e = em.find(MyEntity.class, 1L, LockModeType.PESSIMISTIC_WRITE);
            System.out.println("Thread A: got lock, updating...");
            e.setName("by A");
            Thread.sleep(1000);
            em.getTransaction().commit();
            System.out.println("Thread A: committed");
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            em.close();
        }
    }

    private static void threadB() {
        EntityManager em = emf.createEntityManager();
        boolean locked = false;
        try {
            while (!locked) {
                try {
                    em.getTransaction().begin();
                    System.out.println("Thread B: trying to lock...");
                    MyEntity e = em.find(MyEntity.class, 1L, LockModeType.PESSIMISTIC_WRITE);
                    System.out.println("Thread B: got lock, updating...");
                    e.setName("by B");
                    em.getTransaction().commit();
                    System.out.println("Thread B: committed");
                    locked = true;
                } catch (Exception ex) {
                    System.out.println("Thread B: failed to lock, retrying...");
                    em.getTransaction().rollback();
                    Thread.sleep(500);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            em.close();
        }
    }

    @Entity
    static class MyEntity {
        @Id
        private long id;
        private String name;

        public MyEntity() {}
        public MyEntity(long id, String name) {
            this.id = id;
            this.name = name;
        }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }
}

The output is:

Thread A: trying to lock...
Thread A: got lock, updating...
Thread B: trying to lock...
Thread B: failed to lock, retrying...
Thread B: trying to lock...
Thread B: failed to lock, retrying...
Thread A: committed
Thread B: trying to lock...
Thread B: got lock, updating...
Thread B: committed

As expected, thread B first fails while thread A holds the lock, then succeeds after thread A commits.

However, if you omit the call to  rollback (or put it in a comment) in threadB, you get an infinite loop:

Thread A: trying to lock...
Thread A: got lock, updating...
Thread B: trying to lock...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread A: committed
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...
Thread B: failed to lock, retrying...

In this case, thread B enters an infinite loop of failed attempts. This does not mean ObjectDB fails to release the lock. The problem is that once a lock exception occurs, JPA marks the transaction as rollback-only. Unless you explicitly call rollback() and begin a new transaction, retries will always fail, even after the lock is released. This is the expected behaviour in JPA.

ObjectDB Support

Reply