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