orientdb
orientdb copied to clipboard
ConcurrentModificationException with Transactions
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();
}
}
}
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).
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();
@laa Are we just closing all issues? I haven't seen anything that suggests that this was fix.
Hi @mmacfadden . I am closing old issues. If this issue is still actual for you I will lreopen it.