Search in sources :

Example 1 with RelationshipModifications

use of org.neo4j.storageengine.api.txstate.RelationshipModifications in project neo4j by neo4j.

the class TxState method accept.

@Override
public void accept(final TxStateVisitor visitor) throws KernelException {
    if (nodes != null) {
        nodes.getAdded().each(visitor::visitCreatedNode);
    }
    if (relationships != null) {
        try (HeapTrackingArrayList<NodeRelationshipIds> sortedNodeRelState = HeapTrackingArrayList.newArrayList(nodeStatesMap.size(), memoryTracker)) {
            nodeStatesMap.forEachValue(nodeState -> {
                if (nodeState.isDeleted() && nodeState.isAddedInThisTx()) {
                    return;
                }
                if (nodeState.hasAddedRelationships() || nodeState.hasRemovedRelationships()) {
                    sortedNodeRelState.add(StateNodeRelationshipIds.createStateNodeRelationshipIds(nodeState, this::relationshipVisit, memoryTracker));
                }
            });
            sortedNodeRelState.sort(Comparator.comparingLong(NodeRelationshipIds::nodeId));
            // Visit relationships, this will grab all the locks needed to do the updates
            visitor.visitRelationshipModifications(new RelationshipModifications() {

                @Override
                public void forEachSplit(IdsVisitor visitor) {
                    sortedNodeRelState.forEach(visitor);
                }

                @Override
                public RelationshipBatch creations() {
                    return idsAsBatch(relationships.getAdded(), TxState.this::relationshipVisit);
                }

                @Override
                public RelationshipBatch deletions() {
                    return idsAsBatch(relationships.getRemoved());
                }
            });
        }
    }
    if (nodes != null) {
        nodes.getRemoved().each(visitor::visitDeletedNode);
    }
    for (NodeState node : modifiedNodes()) {
        if (node.hasPropertyChanges()) {
            visitor.visitNodePropertyChanges(node.getId(), node.addedProperties(), node.changedProperties(), node.removedProperties());
        }
        final LongDiffSets labelDiffSets = node.labelDiffSets();
        if (!labelDiffSets.isEmpty()) {
            visitor.visitNodeLabelChanges(node.getId(), labelDiffSets.getAdded(), labelDiffSets.getRemoved());
        }
    }
    for (RelationshipState rel : modifiedRelationships()) {
        visitor.visitRelPropertyChanges(rel.getId(), rel.addedProperties(), rel.changedProperties(), rel.removedProperties());
    }
    if (indexChanges != null) {
        for (IndexDescriptor indexDescriptor : indexChanges.getAdded()) {
            visitor.visitAddedIndex(indexDescriptor);
        }
        indexChanges.getRemoved().forEach(visitor::visitRemovedIndex);
    }
    if (constraintsChanges != null) {
        for (ConstraintDescriptor added : constraintsChanges.getAdded()) {
            visitor.visitAddedConstraint(added);
        }
        constraintsChanges.getRemoved().forEach(visitor::visitRemovedConstraint);
    }
    if (createdLabelTokens != null) {
        createdLabelTokens.forEachKeyValue((id, token) -> visitor.visitCreatedLabelToken(id, token.name, token.internal));
    }
    if (createdPropertyKeyTokens != null) {
        createdPropertyKeyTokens.forEachKeyValue((id, token) -> visitor.visitCreatedPropertyKeyToken(id, token.name, token.internal));
    }
    if (createdRelationshipTypeTokens != null) {
        createdRelationshipTypeTokens.forEachKeyValue((id, token) -> visitor.visitCreatedRelationshipTypeToken(id, token.name, token.internal));
    }
}
Also used : NodeRelationshipIds(org.neo4j.storageengine.api.txstate.RelationshipModifications.NodeRelationshipIds) RelationshipModifications(org.neo4j.storageengine.api.txstate.RelationshipModifications) NodeState(org.neo4j.storageengine.api.txstate.NodeState) ConstraintDescriptor(org.neo4j.internal.schema.ConstraintDescriptor) IndexBackedConstraintDescriptor(org.neo4j.internal.schema.constraints.IndexBackedConstraintDescriptor) RelationshipState(org.neo4j.storageengine.api.txstate.RelationshipState) IndexDescriptor(org.neo4j.internal.schema.IndexDescriptor) TrackableDiffSets.newMutableLongDiffSets(org.neo4j.kernel.impl.util.diffsets.TrackableDiffSets.newMutableLongDiffSets) MutableLongDiffSets(org.neo4j.kernel.impl.util.diffsets.MutableLongDiffSets) LongDiffSets(org.neo4j.storageengine.api.txstate.LongDiffSets)

Example 2 with RelationshipModifications

use of org.neo4j.storageengine.api.txstate.RelationshipModifications in project neo4j by neo4j.

the class RelationshipModifier method acquireMostOfTheNodeAndGroupsLocks.

private void acquireMostOfTheNodeAndGroupsLocks(RelationshipModifications modifications, RecordAccessSet recordChanges, ResourceLocker locks, LockTracer lockTracer, MutableLongObjectMap<NodeContext> contexts, MappedNodeDataLookup nodeDataLookup) {
    /* Here we're going to figure out if we need to make changes to any node and/or relationship group records and lock them if we do. */
    // We check modifications for each node, it might need locking. The iteration here is always sorted by node id
    modifications.forEachSplit(byNode -> {
        long nodeId = byNode.nodeId();
        RecordProxy<NodeRecord, Void> nodeProxy = recordChanges.getNodeRecords().getOrLoad(nodeId, null, cursorContext);
        // optimistic (unlocked) read
        NodeRecord node = nodeProxy.forReadingLinkage();
        boolean nodeIsAddedInTx = node.isCreated();
        if (// we can not trust this as the node is not locked
        !node.isDense()) {
            if (// to avoid locking unnecessarily
            !nodeIsAddedInTx) {
                // lock and re-read, now we can trust it
                locks.acquireExclusive(lockTracer, NODE, nodeId);
                nodeProxy = recordChanges.getNodeRecords().getOrLoad(nodeId, null, cursorContext);
                node = nodeProxy.forReadingLinkage();
                if (node.isDense()) {
                    // another transaction just turned this node dense, unlock and let it be handled below
                    locks.releaseExclusive(NODE, nodeId);
                } else if (byNode.hasCreations()) {
                    // Sparse node with added relationships. We might turn this node dense, at which point the group lock will be needed, so lock it
                    locks.acquireExclusive(lockTracer, RELATIONSHIP_GROUP, nodeId);
                }
            }
        }
        if (// the node is not locked but the dense node is a one-way transform so we can trust it
        node.isDense()) {
            // Stabilize first in chains, in case they are deleted or needed for chain degrees.
            // We are preventing any changes to the group which in turn blocks any other relationship becomming the first in chain
            locks.acquireShared(lockTracer, RELATIONSHIP_GROUP, nodeId);
            // Creations
            NodeContext nodeContext = NodeContext.createNodeContext(nodeProxy, memoryTracker);
            contexts.put(nodeId, nodeContext);
            if (byNode.hasCreations()) {
                // We have some creations on a dense node. If the group exists we can use that, otherwise we create it
                byNode.forEachCreationSplit(byType -> {
                    RelationshipGroupGetter.RelationshipGroupPosition groupPosition = findRelationshipGroup(recordChanges, nodeContext, byType);
                    nodeContext.setCurrentGroup(groupPosition.group() != null ? groupPosition.group() : groupPosition.closestPrevious());
                    RecordProxy<RelationshipGroupRecord, Integer> groupProxy = groupPosition.group();
                    if (groupProxy == null) {
                        // The group did not exist
                        if (!nodeContext.hasExclusiveGroupLock()) {
                            // And we did not already have the lock, so we need to upgrade to exclusive create it
                            locks.releaseShared(RELATIONSHIP_GROUP, nodeId);
                            // Note the small window here where we dont hold any group lock, things might change so we can not trust previous group reads
                            locks.acquireExclusive(lockTracer, NODE, nodeId);
                            locks.acquireExclusive(lockTracer, RELATIONSHIP_GROUP, nodeId);
                        }
                        nodeContext.setNode(recordChanges.getNodeRecords().getOrLoad(nodeId, null, cursorContext));
                        long groupStartingId = nodeContext.node().forReadingLinkage().getNextRel();
                        long groupStartingPrevId = NULL_REFERENCE.longValue();
                        if (groupPosition.closestPrevious() != null) {
                            groupStartingId = groupPosition.closestPrevious().getKey();
                            groupStartingPrevId = groupPosition.closestPrevious().forReadingLinkage().getPrev();
                        }
                        // At this point the group is locked so we can create it
                        groupProxy = relGroupGetter.getOrCreateRelationshipGroup(nodeContext.node(), byType.type(), recordChanges.getRelGroupRecords(), groupStartingPrevId, groupStartingId);
                        // another transaction might beat us at this point, so we are not guaranteed to be the creator but we can trust it to exist
                        if (!nodeContext.hasExclusiveGroupLock()) {
                            nodeContext.markExclusiveGroupLock();
                        } else if (groupProxy.isCreated()) {
                            // When a new group is created we can no longer trust the pointers of the cache
                            nodeContext.clearDenseContext();
                        }
                    }
                    nodeContext.denseContext(byType.type()).setGroup(groupProxy);
                });
                if (!nodeContext.hasExclusiveGroupLock()) {
                    // No other path has given us the exclusive lock yet
                    byNode.forEachCreationSplitInterruptible(byType -> {
                        // But if we are creating relationships to a chain that does not exist on the group
                        // or we might need to flip the external degrees flag
                        RelationshipGroupRecord group = nodeContext.denseContext(byType.type()).group().forReadingLinkage();
                        if (byType.hasOut() && (!group.hasExternalDegreesOut() || isNull(group.getFirstOut())) || byType.hasIn() && (!group.hasExternalDegreesIn() || isNull(group.getFirstIn())) || byType.hasLoop() && (!group.hasExternalDegreesLoop() || isNull(group.getFirstLoop()))) {
                            // Then we need the exclusive lock to change it
                            locks.releaseShared(RELATIONSHIP_GROUP, nodeId);
                            // Note the small window here where we dont hold any group lock, things might change so we can not trust previous group reads
                            locks.acquireExclusive(lockTracer, RELATIONSHIP_GROUP, nodeId);
                            nodeContext.markExclusiveGroupLock();
                            // And we can abort the iteration as the group lock is protecting all relationship group records of the node
                            return true;
                        }
                        return false;
                    });
                }
            }
            // Deletions
            if (byNode.hasDeletions()) {
                if (// no need to do anything if it is already locked by additions
                !nodeContext.hasExclusiveGroupLock()) {
                    byNode.forEachDeletionSplitInterruptible(byType -> {
                        NodeContext.DenseContext denseContext = nodeContext.denseContext(byType.type());
                        RelationshipGroupRecord group = denseContext.getOrLoadGroup(relGroupGetter, nodeContext.node().forReadingLinkage(), byType.type(), recordChanges.getRelGroupRecords(), cursorContext);
                        // here we have the shared lock, so we can trust the read
                        if (byType.hasOut() && !group.hasExternalDegreesOut() || byType.hasIn() && !group.hasExternalDegreesIn() || byType.hasLoop() && !group.hasExternalDegreesLoop()) {
                            // We have deletions but without external degrees, we might need to flip that so we lock it
                            locks.releaseShared(RELATIONSHIP_GROUP, nodeId);
                            // Note the small window here where we dont hold any group lock, things might change so we can not trust previous group reads
                            locks.acquireExclusive(lockTracer, RELATIONSHIP_GROUP, nodeId);
                            nodeContext.markExclusiveGroupLock();
                            return true;
                        } else {
                            // We have deletions and only external degrees
                            boolean hasAnyFirst = batchContains(byType.out(), group.getFirstOut()) || batchContains(byType.in(), group.getFirstIn()) || batchContains(byType.loop(), group.getFirstLoop());
                            if (hasAnyFirst) {
                                // But we're deleting the first in the chain so the group needs to be updated
                                locks.releaseShared(RELATIONSHIP_GROUP, nodeId);
                                // Note the small window here where we dont hold any group lock, things might change so we can not trust previous group reads
                                locks.acquireExclusive(lockTracer, RELATIONSHIP_GROUP, nodeId);
                                nodeContext.markExclusiveGroupLock();
                                return true;
                            }
                        }
                        return false;
                    });
                }
            }
            // Look for an opportunity to delete empty groups that we noticed while looking for groups above
            if (nodeContext.hasExclusiveGroupLock() && nodeContext.hasAnyEmptyGroup()) {
                // There may be one or more empty groups that we can delete
                if (locks.tryExclusiveLock(NODE_RELATIONSHIP_GROUP_DELETE, nodeId)) {
                    // We got the EXCLUSIVE group lock so we can go ahead and try to remove any potentially empty groups
                    if (!nodeContext.hasEmptyFirstGroup() || locks.tryExclusiveLock(NODE, nodeId)) {
                        if (nodeContext.hasEmptyFirstGroup()) {
                            // It's possible that we need to delete the first group, i.e. we just now locked the node and therefore need to re-read it
                            nodeContext.setNode(recordChanges.getNodeRecords().getOrLoad(nodeId, null, cursorContext));
                        }
                        Predicate<RelationshipGroupRecord> canDeleteGroup = group -> !byNode.hasCreations(group.getType());
                        if (RelationshipGroupGetter.deleteEmptyGroups(nodeContext.node(), canDeleteGroup, nodeDataLookup)) {
                            nodeContext.clearDenseContext();
                        }
                    }
                }
            }
        }
    });
}
Also used : RelationshipModifications(org.neo4j.storageengine.api.txstate.RelationshipModifications) RelationshipLockHelper.findAndLockInsertionPoint(org.neo4j.internal.recordstorage.RelationshipLockHelper.findAndLockInsertionPoint) Record.isNull(org.neo4j.kernel.impl.store.record.Record.isNull) HeapTrackingCollections.newLongObjectMap(org.neo4j.collection.trackable.HeapTrackingCollections.newLongObjectMap) RelationshipCreator.relCount(org.neo4j.internal.recordstorage.RelationshipCreator.relCount) CursorContext(org.neo4j.io.pagecache.context.CursorContext) LongLists(org.eclipse.collections.impl.factory.primitive.LongLists) MutableLongObjectMap(org.eclipse.collections.api.map.primitive.MutableLongObjectMap) MutableLongList(org.eclipse.collections.api.list.primitive.MutableLongList) DIR_OUT(org.neo4j.internal.recordstorage.RelationshipCreator.NodeDataLookup.DIR_OUT) RelationshipLockHelper.lockRelationshipsInOrder(org.neo4j.internal.recordstorage.RelationshipLockHelper.lockRelationshipsInOrder) NODE_RELATIONSHIP_GROUP_DELETE(org.neo4j.lock.ResourceTypes.NODE_RELATIONSHIP_GROUP_DELETE) MemoryTracker(org.neo4j.memory.MemoryTracker) RELATIONSHIP_GROUP(org.neo4j.lock.ResourceTypes.RELATIONSHIP_GROUP) RELATIONSHIP(org.neo4j.lock.ResourceTypes.RELATIONSHIP) LockTracer(org.neo4j.lock.LockTracer) RelationshipGroupDegreesStore(org.neo4j.internal.counts.RelationshipGroupDegreesStore) Predicate(java.util.function.Predicate) NULL_REFERENCE(org.neo4j.kernel.impl.store.record.Record.NULL_REFERENCE) RelationshipBatch(org.neo4j.storageengine.api.txstate.RelationshipModifications.RelationshipBatch) ResourceLocker(org.neo4j.lock.ResourceLocker) DIR_IN(org.neo4j.internal.recordstorage.RelationshipCreator.NodeDataLookup.DIR_IN) DIR_LOOP(org.neo4j.internal.recordstorage.RelationshipCreator.NodeDataLookup.DIR_LOOP) RelationshipGroupRecord(org.neo4j.kernel.impl.store.record.RelationshipGroupRecord) HeapTrackingLongObjectHashMap(org.neo4j.collection.trackable.HeapTrackingLongObjectHashMap) NodeRecord(org.neo4j.kernel.impl.store.record.NodeRecord) ALWAYS(org.neo4j.kernel.impl.store.record.RecordLoad.ALWAYS) NODE(org.neo4j.lock.ResourceTypes.NODE) RelationshipRecord(org.neo4j.kernel.impl.store.record.RelationshipRecord) RecordProxy(org.neo4j.internal.recordstorage.RecordAccess.RecordProxy) RelationshipGroupRecord(org.neo4j.kernel.impl.store.record.RelationshipGroupRecord) NodeRecord(org.neo4j.kernel.impl.store.record.NodeRecord)

Example 3 with RelationshipModifications

use of org.neo4j.storageengine.api.txstate.RelationshipModifications in project neo4j by neo4j.

the class RelationshipModifierTest method shouldCreateAndDelete.

// ... other known cases ...
@RepeatedTest(20)
void shouldCreateAndDelete() {
    // given and initial state
    long node = createEmptyNode();
    IntSupplier typeStrategy = randomTypes(10);
    Supplier<RelationshipDirection> directionStrategy = RANDOM_DIRECTION;
    LongSupplier otherNodeStrategy = this::createEmptyNode;
    int maxRelationships = 100;
    List<RelationshipData> expectedRelationships = generateRelationshipData(random.nextInt(0, maxRelationships), node, typeStrategy, otherNodeStrategy, directionStrategy);
    createRelationships(expectedRelationships);
    // ... a set of relationships to create and delete
    List<RelationshipData> relationshipsToCreate = generateRelationshipData((int) random.among(new long[] { 0, 1, 10, 100 }), node, typeStrategy, otherNodeStrategy, directionStrategy);
    int numRelationshipsToDelete = min((int) random.among(new long[] { 0, 1, 10, maxRelationships }), expectedRelationships.size());
    RelationshipData[] relationshipsToDelete = random.selection(expectedRelationships.toArray(RelationshipData[]::new), numRelationshipsToDelete, numRelationshipsToDelete, false);
    // ... and rules for how the world changes "concurrently" while we perform these modifications
    // on locked
    monitors.addMonitorListener(new ResourceTypeLockOrderVerifier());
    // on read
    monitors.addMonitorListener(new ChangeWorldOnReadMonitor(node, typeStrategy, otherNodeStrategy, directionStrategy, expectedRelationships));
    // when
    RelationshipModifications modifications = modifications(relationshipsToCreate.toArray(RelationshipData[]::new), relationshipsToDelete);
    modify(modifications);
    applyModificationsToExpectedRelationships(modifications, expectedRelationships);
    // then
    assertThat(readRelationshipsFromStore(node, store)).isEqualTo(asSet(expectedRelationships));
}
Also used : RelationshipData(org.neo4j.internal.recordstorage.FlatRelationshipModifications.RelationshipData) RelationshipModifications(org.neo4j.storageengine.api.txstate.RelationshipModifications) RelationshipDirection(org.neo4j.storageengine.api.RelationshipDirection) IntSupplier(java.util.function.IntSupplier) LongSupplier(java.util.function.LongSupplier) RepeatedTest(org.junit.jupiter.api.RepeatedTest)

Aggregations

RelationshipModifications (org.neo4j.storageengine.api.txstate.RelationshipModifications)3 IntSupplier (java.util.function.IntSupplier)1 LongSupplier (java.util.function.LongSupplier)1 Predicate (java.util.function.Predicate)1 MutableLongList (org.eclipse.collections.api.list.primitive.MutableLongList)1 MutableLongObjectMap (org.eclipse.collections.api.map.primitive.MutableLongObjectMap)1 LongLists (org.eclipse.collections.impl.factory.primitive.LongLists)1 RepeatedTest (org.junit.jupiter.api.RepeatedTest)1 HeapTrackingCollections.newLongObjectMap (org.neo4j.collection.trackable.HeapTrackingCollections.newLongObjectMap)1 HeapTrackingLongObjectHashMap (org.neo4j.collection.trackable.HeapTrackingLongObjectHashMap)1 RelationshipGroupDegreesStore (org.neo4j.internal.counts.RelationshipGroupDegreesStore)1 RelationshipData (org.neo4j.internal.recordstorage.FlatRelationshipModifications.RelationshipData)1 RecordProxy (org.neo4j.internal.recordstorage.RecordAccess.RecordProxy)1 DIR_IN (org.neo4j.internal.recordstorage.RelationshipCreator.NodeDataLookup.DIR_IN)1 DIR_LOOP (org.neo4j.internal.recordstorage.RelationshipCreator.NodeDataLookup.DIR_LOOP)1 DIR_OUT (org.neo4j.internal.recordstorage.RelationshipCreator.NodeDataLookup.DIR_OUT)1 RelationshipCreator.relCount (org.neo4j.internal.recordstorage.RelationshipCreator.relCount)1 RelationshipLockHelper.findAndLockInsertionPoint (org.neo4j.internal.recordstorage.RelationshipLockHelper.findAndLockInsertionPoint)1 RelationshipLockHelper.lockRelationshipsInOrder (org.neo4j.internal.recordstorage.RelationshipLockHelper.lockRelationshipsInOrder)1 ConstraintDescriptor (org.neo4j.internal.schema.ConstraintDescriptor)1