Weird behaviour with enhanced classes and embedded entities

#1

Hi,

I've found some weird behaviour in my project (www.tinymediamanager.org): Sometimes some entries of my ArrayLists with embedded entities are missing. I've hunted the problem down to an enhanced/proxied list in my entity which contains embedded entities and some .add(obj) are simply failing.

Here is a small sscce which reproduces the issue:

package objectdbsscce;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.persistence.CascadeType;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Persistence;

public class Main {

  @Entity
  static class MyEntity {
    @Id
    int    id;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<MyEmbeddedEntity>                embeddedEntities          = new ArrayList<MyEmbeddedEntity>(0);

    public MyEntity(int id) {
      this.id = id;
    }
   
    public void addEmbeddedEntity(MyEmbeddedEntity embedded){
      embeddedEntities.add(embedded);
    }
   
    public List<MyEmbeddedEntity> getEmbeddedEntities(){
      return embeddedEntities;
    }
  }
 
  @Embeddable
  static class MyEmbeddedEntity { 
    String title;
   
    List<MySecondEmbeddedEntity> embeddedEntities = new ArrayList<MySecondEmbeddedEntity>(0);
   
    public MyEmbeddedEntity(String title){
      this.title = title;
    }
   
    public void addSecondEmbeddedEntitiy(MySecondEmbeddedEntity entity){
      this.embeddedEntities.add(entity);
    }
  }
 
  @Embeddable
  static class MySecondEmbeddedEntity {   
    String title;
       
    public MySecondEmbeddedEntity(String title){
      this.title = title;
    }
  } 
 
  private static EntityManager entityManager;
 
  public static void main(String[] args) {
    // enhance entities
    com.objectdb.Enhancer.enhance("objectdbsscce.*");
   
    // open db
    EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("objectdbsscce.odb");
    entityManager = entityManagerFactory.createEntityManager();
    
    // start 3 threads which are filling its own entity with embedded entities
    Runnable task1 = new Runnable() {     
      @Override
      public void run() {
        addEntities(1);       
      }
    };
   
    Runnable task2 = new Runnable() {     
      @Override
      public void run() {
        addEntities(2);       
      }
    };
   
    Runnable task3 = new Runnable() {     
      @Override
      public void run() {
        addEntities(3);       
      }
    };
     
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    executor.execute(task1);
    executor.execute(task2);
    executor.execute(task3);
   
    executor.shutdown();
    while(!executor.isTerminated()){
    }
   
    entityManager.close();
    entityManagerFactory.close();
    System.out.println("Done");
  }
 
  private static void addEntities(int id){
    MyEntity entity = new MyEntity(id);
    for(int i = 0; i < 1000; i++){
      try {
        // wait 1ms to simulate a "real" application, where some code comes between adding embeddables ;)
        Thread.sleep(1);
      }
      catch (InterruptedException e) {
      }
      MyEmbeddedEntity embedded = new MyEmbeddedEntity("foo" + i);
      embedded.addSecondEmbeddedEntitiy(new MySecondEmbeddedEntity("oof"+i));
      entity.addEmbeddedEntity(embedded);
      if(!entity.getEmbeddedEntities().contains(embedded)){
        System.out.println("Not added index " + i + " in Thread " + id);
      }
      
      // every 10 rounds we create some saving points
      if(i % 10 == 0){
        synchronized (entityManager) {
          entityManager.getTransaction().begin();
          entityManager.persist(entity);
          entityManager.getTransaction().commit();
        }
      }
    } 
  }
}

As you can see there is my main entity (MyEntity) which contains a list of embedded entities (MyEmbeddedEntity) which also contains a list of embedded entities (MySecondEmbeddedEntity).

If I fill one entity (per thread) with embedded entities, sometimes the newly added object is NOT in the list.. I suggest that the expansion of the proxied list isn't threadsafe, because the same code works with only one thread (or without enhancing the classes).

What I can say so far:

  • Enhancing & multi threading breaks .add() to the proxied list (I am pretty sure I do the locking/synchronizing the right way)
  • Without enhancing the sscce works (3 threads)
  • Without multi threading the sscce works (enhanced classes)

Am I doing anything wrong or is there a problem inside ObjectDB? I've used the latest version for the sscce.

If there are any open questions, please feel free to ask!

Kind regards
Manuel Laggner

#2

An EntityManager is not designed to be used concurrently by multiple threads.

The recommended way to implement your test case is to use 3 separate EntityManager instances, representing 3 independent connections to the database, one for each working thread.

If you must share an EntityManager between threads you will have to synchronize them better. For example, your test case works well if the synchronized block wraps the entire loop content:

    for(int i = 0; i < 1000; i++) {
        synchronized (entityManager) {

This will prevent the current situation in which during commit by one thread another thread is trying to work with a managed entity object and update it.

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
#3

thanks for clarification; I thought it would be safe enough to synchronize the explicit access of the EntityManager.

I could solve the problem by adding some synchronization to all relevant list-access in my program


Post Reply

To post a reply and/or subscribe to update notifications - please login