Neo4j Java API Concurrency v2.0M3:在其他线程同时创建新关系时迭代关系时出现异常

时间:2013-07-25 23:25:11

标签: api exception concurrency neo4j iteration

我在这里尝试实现的是获取特定节点的关系数量,而其他线程同时向其添加新关系。我在单元测试中运行我的代码 TestGraphDatabaseFactory()。newImpermanentDatabase()图形服务。

我的代码由大约50个线程执行,它看起来像这样:

int numOfRels = 0;
try {
    Iterable<Relationship> rels = parentNode.getRelationships(RelTypes.RUNS, Direction.OUTGOING);
    while (rels.iterator().hasNext()) {
        numOfRels++;
        rels.iterator().next();
    }
}
catch(Exception e) {
    throw e;
}

// Enforce relationship limit
if (numOfRels > 10) {
    // do something
}

Transaction tx = graph.beginTx();
try {
    Node node = createMyNodeAndConnectToParentNode(...);

    tx.success();

    return node;
}
catch (Exception e) {
    tx.failure();
}
finally {
    tx.finish();
}

问题是我在上面的try-catch块(围绕 getRelationships()的那个)中得到“ArrayIndexOutOfBoundsException:1”。如果我理解正确,Iterable不是线程安全的,并导致此问题。

我的问题是使用Neo4j的Java API迭代不断变化的关系和节点的最佳方法是什么?

我收到以下错误:

Exception in thread "Thread-14" org.neo4j.helpers.ThisShouldNotHappenError: Developer: Stefan/Jake claims that: A property key id disappeared under our feet
    at org.neo4j.kernel.impl.core.NodeProxy.setProperty(NodeProxy.java:188)
    at com.inbiza.connio.neo4j.server.extensions.graph.AppEntity.createMyNodeAndConnectToParentNode(AppEntity.java:546)
    at com.inbiza.connio.neo4j.server.extensions.graph.AppEntity.create(AppEntity.java:305)
    at com.inbiza.connio.neo4j.server.extensions.TestEmbeddedConnioGraph$appCreatorThread.run(TestEmbeddedConnioGraph.java:61)
    at java.lang.Thread.run(Thread.java:722)
Exception in thread "Thread-92" java.lang.ArrayIndexOutOfBoundsException: 1
    at org.neo4j.kernel.impl.core.RelationshipIterator.fetchNextOrNull(RelationshipIterator.java:72)
    at org.neo4j.kernel.impl.core.RelationshipIterator.fetchNextOrNull(RelationshipIterator.java:36)
    at org.neo4j.helpers.collection.PrefetchingIterator.hasNext(PrefetchingIterator.java:55)
    at com.inbiza.connio.neo4j.server.extensions.graph.AppEntity.create(AppEntity.java:243)
    at com.inbiza.connio.neo4j.server.extensions.TestEmbeddedConnioGraph$appCreatorThread.run(TestEmbeddedConnioGraph.java:61)
    at java.lang.Thread.run(Thread.java:722)
Exception in thread "Thread-12" java.lang.ArrayIndexOutOfBoundsException: 1
    at org.neo4j.kernel.impl.core.RelationshipIterator.fetchNextOrNull(RelationshipIterator.java:72)
    at org.neo4j.kernel.impl.core.RelationshipIterator.fetchNextOrNull(RelationshipIterator.java:36)
    at org.neo4j.helpers.collection.PrefetchingIterator.hasNext(PrefetchingIterator.java:55)
    at com.inbiza.connio.neo4j.server.extensions.graph.AppEntity.create(AppEntity.java:243)
    at com.inbiza.connio.neo4j.server.extensions.TestEmbeddedConnioGraph$appCreatorThread.run(TestEmbeddedConnioGraph.java:61)
    at java.lang.Thread.run(Thread.java:722)
Exception in thread "Thread-93" java.lang.ArrayIndexOutOfBoundsException
Exception in thread "Thread-90" java.lang.ArrayIndexOutOfBoundsException

以下是负责节点创建的方法:

static Node createMyNodeAndConnectToParentNode(GraphDatabaseService graph, final Node ownerAccountNode, final String suggestedName, Map properties) {

  final String accountId = checkNotNull((String)ownerAccountNode.getProperty("account_id"));

  Node appNode = graph.createNode();
  appNode.setProperty("urn_name", App.composeUrnName(accountId, suggestedName.toLowerCase().trim()));

  int nextId = nodeId.addAndGet(1); // I normally use getOrCreate idiom but to simplify I replaced it with an atomic int - that would do for testing 

  String urn = App.composeUrnUid(accountId,  nextId);
  appNode.setProperty("urn_uid", urn);
  appNode.setProperty("id", nextId);
  appNode.setProperty("name", suggestedName);

  Index<Node> indexUid =  graph.index().forNodes("EntityUrnUid");
  indexUid.add(appNode, "urn_uid", urn);

  appNode.addLabel(LabelTypes.App);

  appNode.setProperty("version", properties.get("version"));
  appNode.setProperty("description", properties.get("description"));

  Relationship rel = ownerAccountNode.createRelationshipTo(appNode, RelTypes.RUNS);
  rel.setProperty("date_created", fmt.print(new DateTime()));

  return appNode;
}

我在看org.neo4j.kernel.impl.core.RelationshipIterator.fetchNextOrNull()

看起来我的测试生成了一个条件,其中 else if((status = fromNode.getMoreRelationships(nodeManager))。loaded()|| lastTimeILookedThereWasMoreToLoad)未执行,其中 currentTypeIterator 状态在两者之间发生变化。

RelIdIterator currentTypeIterator = rels[currentTypeIndex];  //<-- this is where is crashes
do
{
  if ( currentTypeIterator.hasNext() )
  ...
  ... 

  while ( !currentTypeIterator.hasNext() )
  {
    if ( ++currentTypeIndex < rels.length )
    {
        currentTypeIterator = rels[currentTypeIndex];
    }
    else if ( (status = fromNode.getMoreRelationships( nodeManager )).loaded()
            // This is here to guard for that someone else might have loaded
            // stuff in this relationship chain (and exhausted it) while I
            // iterated over my batch of relationships. It will only happen
            // for nodes which have more than <grab size> relationships and
            // isn't fully loaded when starting iterating.
            || lastTimeILookedThereWasMoreToLoad )
    {
        ....
    }
  }
} while ( currentTypeIterator.hasNext() );

我还测试了几种锁定方案。下面的一个解决了这个问题。每次我基于此迭代关系时都不确定是否应该使用锁。

Transaction txRead = graph.beginTx();
try {
  txRead.acquireReadLock(parentNode);

  long numOfRels = 0L;
  Iterable<Relationship> rels = parentNode.getRelationships(RelTypes.RUNS, Direction.OUTGOING);
  while (rels.iterator().hasNext()) {
    numOfRels++;
    rels.iterator().next();
  }

  txRead.success();
}
finally {
  txRead.finish();
}

我对Neo4j及其来源基础很新;只是作为我们产品的潜在数据存储测试。如果有人知道里面的Neo4j,我会很感激。 out解释了这里发生了什么。

2 个答案:

答案 0 :(得分:2)

这是一个错误。此拉取请求中捕获了此修复程序:https://github.com/neo4j/neo4j/pull/1011

答案 1 :(得分:0)

嗯,我认为这是一个错误。 Iterable返回的getRelationships()意味着不可变。调用此方法时,迭代器中将提供所有可用的Nodes,直到该时刻。 (您可以从org.neo4j.kernel.IntArrayIterator)验证这一点

我尝试通过让250个线程尝试从节点插入关系到其他节点来复制它。并且有一个主线程循环遍历第一个节点的迭代器。经过仔细分析,迭代器仅包含上次调用getRelationship()时添加的关系。这个问题从来没有出现过。

请你把你的完整代码,IMO可能有些愚蠢的错误。它不可能发生的原因是在添加关系时写锁定就位,因此读取同步。