orientdb icon indicating copy to clipboard operation
orientdb copied to clipboard

ConcurrentModificationException with Transactions

Open mmacfadden opened this issue 5 years ago • 4 comments

OrientDB Version: 3.0.31

Java Version: JDK 11

OS: macos

Issue

We are getting a ConcurrentModificationException when we wrap a particular function in a transaction. If we do not use a transaction the code works fine. If we do use a transaction we get the exception. The intention is to use a transaction. We had the code working without a transaction and realized that one was needed. When we tried to add it we ran into the below issue.

Expected behavior

The code should work with a transaction.

Actual behavior

The following exception is thrown:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1493)
	at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1526)
	at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1524)
	at com.orientechnologies.orient.core.db.record.ORecordLazySet$1.next(ORecordLazySet.java:80)
	at com.orientechnologies.orient.core.db.record.ORecordLazySet$1.next(ORecordLazySet.java:65)
	at com.orientechnologies.orient.core.db.record.OLazyRecordIterator.next(OLazyRecordIterator.java:69)
	at com.orientechnologies.orient.core.db.record.OLazyRecordIterator.next(OLazyRecordIterator.java:39)
	at com.orientechnologies.orient.core.sql.parser.OArrayConcatExpression.apply(OArrayConcatExpression.java:74)
	at com.orientechnologies.orient.core.sql.parser.OArrayConcatExpression.execute(OArrayConcatExpression.java:103)
	at com.orientechnologies.orient.core.sql.parser.OExpression.execute(OExpression.java:113)
	at com.orientechnologies.orient.core.sql.parser.OUpdateItem.applyUpdate(OUpdateItem.java:119)
	at com.orientechnologies.orient.core.sql.executor.UpdateSetStep$1.next(UpdateSetStep.java:36)
	at com.orientechnologies.orient.core.sql.executor.SaveElementStep$1.next(SaveElementStep.java:37)
	at com.orientechnologies.orient.core.sql.executor.CountStep.syncPull(CountStep.java:53)
	at com.orientechnologies.orient.core.sql.executor.OSelectExecutionPlan.fetchNext(OSelectExecutionPlan.java:37)
	at com.orientechnologies.orient.core.sql.executor.OUpdateExecutionPlan.executeInternal(OUpdateExecutionPlan.java:46)
	at com.orientechnologies.orient.core.sql.parser.OUpdateStatement.execute(OUpdateStatement.java:169)
	at com.orientechnologies.orient.core.sql.parser.OStatement.execute(OStatement.java:87)
	at com.orientechnologies.orient.core.db.document.ODatabaseDocumentEmbedded.command(ODatabaseDocumentEmbedded.java:591)
	at OrientTest.addPermissions(OrientTest.java:109)
	at OrientTest.main(OrientTest.java:74)

Steps to reproduce

We have extracted the relevant code from our code base to reproduce the error:

import com.orientechnologies.orient.core.db.ODatabaseType;
import com.orientechnologies.orient.core.db.OrientDB;
import com.orientechnologies.orient.core.db.OrientDBConfig;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OSchema;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.OElement;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.executor.OResultSet;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class OrientTest {
    private static final boolean transactions = true;

    public static void main(String[] args) {
        String url = "memory:target/orientdb/PersistenceStoreSpec/OrientTest";
        OrientDB orientDB = new OrientDB(url, OrientDBConfig.defaultConfig());

        String dbName = "OrientTest";

        try {
            System.out.println("Creating database");
            orientDB.create(dbName, ODatabaseType.MEMORY);
            ODatabaseDocument db = orientDB.open(dbName, "admin", "admin");

            System.out.println("Creating schema");
            OSchema schema = db.getMetadata().getSchema();

            OClass permissionClass = schema.createClass("Permission");
            OClass targetClass = schema.createClass("Target");

            permissionClass.createProperty("id", OType.STRING);
            permissionClass.createProperty("target", OType.LINKSET, targetClass);

            targetClass.createProperty("id", OType.STRING);
            targetClass.createProperty("permissions", OType.LINKSET, permissionClass);
            targetClass.createIndex("target.id", OClass.INDEX_TYPE.UNIQUE, "id");

            OElement target1 = db.newElement("Target");
            target1.setProperty("id", "t1");
            target1.save();
            System.out.println("Done creating schema");

            // First Run
            Set<String> permissions1 = new HashSet<>();
            permissions1.add("p1");
            permissions1.add("p2");
            addPermissions(db, permissions1, "t1");

            // Second Run
            Set<String> permissions2 = new HashSet<>();
            permissions2.add("p3");
            permissions2.add("p4");
            addPermissions(db, permissions2, "t1");

        } finally {
            orientDB.drop(dbName);
        }
    }

    private static void addPermissions(ODatabaseDocument db, Set<String> permissions, String targetId) {

        if (transactions) {
            db.begin();
        }

        System.out.println("Adding permissions to target: " + targetId);
        OIndex<?> index = db.getMetadata().getIndexManager().getIndex("target.id");

        OIdentifiable doc = (OIdentifiable) index.get(targetId);
        ORID targetRid = doc.getIdentity();

        Set<ORID> permissionRids = permissions
                .stream()
                .map(permission -> {
                    ODocument newPermission = db.newInstance("Permission");
                    newPermission.setProperty("permission", permission);
                    newPermission.setProperty("target", targetRid);
                    db.save(newPermission);
                    return newPermission.getIdentity();
                })
                .collect(Collectors.toSet());

        String command = "UPDATE :target SET permissions = permissions || :permissions";
        Map<String, Object> params = new HashMap<>();
        params.put("target", targetRid);
        params.put("permissions", permissionRids);

        OResultSet results = db.command(command, params);
        OElement result = results.next().toElement();
        long count = result.getProperty("count");
        System.out.println("Mutated targets: " + count);

        results.close();

        if (transactions) {
            db.commit();
        }
    }
}

mmacfadden avatar Jun 19 '20 17:06 mmacfadden

As another update the same issue arrises if se get the target document as an OElement and then get the set (as an OTrackedSet) manipulate it, and then call save() on the document (or database).

mmacfadden avatar Jun 29 '20 13:06 mmacfadden

the same issue arrises when run the code as follow.

        final OrientGraph memoryGraph = new OrientGraphFactory("memory:orient").getTx();
        final CyclicBarrier barrier = new CyclicBarrier(2);
        memoryGraph.createVertexClass("person");
        new Thread(() -> {
            for (; ; ) {
                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                try {
                    memoryGraph.addVertex(T.label, "person", "prototype", new Person("bofa1ex" + ThreadLocalRandom.current().nextLong(), 23));
                    memoryGraph.commit();
                    break;
                } catch (ONeedRetryException | OTransactionException e) {
                    memoryGraph.rollback();
                    memoryGraph.begin();
                } catch (Exception e) {
                    throw OException.wrapException(new ODatabaseException("Error during tx retry"), e);
                } finally {
                    countDownLatch.countDown();
                }
            }
        }).start();
        new Thread(() -> {
            for (; ; ) {
                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                try {
                    memoryGraph.addVertex(T.label, "person", "prototype", new Person("bofa2ex" + ThreadLocalRandom.current().nextLong(), 24));
                    memoryGraph.commit();
                    break;
                } catch (ONeedRetryException | OTransactionException  e) {
                    memoryGraph.rollback();
                    memoryGraph.begin();
                } catch (Exception e) {
                    throw OException.wrapException(new ODatabaseException("Error during tx retry"), e);
                } finally {
                    countDownLatch.countDown();
                }
            }
        }).start();

BOFA1ex avatar Oct 23 '20 10:10 BOFA1ex

@laa Are we just closing all issues? I haven't seen anything that suggests that this was fix.

mmacfadden avatar Aug 06 '21 18:08 mmacfadden

Hi @mmacfadden . I am closing old issues. If this issue is still actual for you I will lreopen it.

andrii0lomakin avatar Aug 07 '21 12:08 andrii0lomakin