Search in sources :

Example 1 with RecordProxy

use of org.neo4j.internal.recordstorage.RecordAccess.RecordProxy 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 2 with RecordProxy

use of org.neo4j.internal.recordstorage.RecordAccess.RecordProxy in project neo4j by neo4j.

the class WriteTransactionCommandOrderingTest method injectAllPossibleCommands.

private static TransactionRecordState injectAllPossibleCommands() {
    RecordChangeSet recordChangeSet = mock(RecordChangeSet.class);
    RecordChanges<LabelTokenRecord, Void> labelTokenChanges = mock(RecordChanges.class);
    RecordChanges<RelationshipTypeTokenRecord, Void> relationshipTypeTokenChanges = mock(RecordChanges.class);
    RecordChanges<PropertyKeyTokenRecord, Void> propertyKeyTokenChanges = mock(RecordChanges.class);
    RecordChanges<NodeRecord, Void> nodeRecordChanges = mock(RecordChanges.class);
    RecordChanges<RelationshipRecord, Void> relationshipRecordChanges = mock(RecordChanges.class);
    RecordChanges<PropertyRecord, PrimitiveRecord> propertyRecordChanges = mock(RecordChanges.class);
    RecordChanges<RelationshipGroupRecord, Integer> relationshipGroupChanges = mock(RecordChanges.class);
    RecordChanges<SchemaRecord, SchemaRule> schemaRuleChanges = mock(RecordChanges.class);
    when(recordChangeSet.getLabelTokenChanges()).thenReturn(labelTokenChanges);
    when(recordChangeSet.getRelationshipTypeTokenChanges()).thenReturn(relationshipTypeTokenChanges);
    when(recordChangeSet.getPropertyKeyTokenChanges()).thenReturn(propertyKeyTokenChanges);
    when(recordChangeSet.getNodeRecords()).thenReturn(nodeRecordChanges);
    when(recordChangeSet.getRelRecords()).thenReturn(relationshipRecordChanges);
    when(recordChangeSet.getPropertyRecords()).thenReturn(propertyRecordChanges);
    when(recordChangeSet.getRelGroupRecords()).thenReturn(relationshipGroupChanges);
    when(recordChangeSet.getSchemaRuleChanges()).thenReturn(schemaRuleChanges);
    List<RecordProxy<NodeRecord, Void>> nodeChanges = new LinkedList<>();
    RecordChange<NodeRecord, Void> deletedNode = mock(RecordChange.class);
    when(deletedNode.getBefore()).thenReturn(inUseNode());
    when(deletedNode.forReadingLinkage()).thenReturn(missingNode());
    nodeChanges.add(deletedNode);
    RecordChange<NodeRecord, Void> createdNode = mock(RecordChange.class);
    when(createdNode.getBefore()).thenReturn(missingNode());
    when(createdNode.forReadingLinkage()).thenReturn(createdNode());
    nodeChanges.add(createdNode);
    RecordChange<NodeRecord, Void> updatedNode = mock(RecordChange.class);
    when(updatedNode.getBefore()).thenReturn(inUseNode());
    when(updatedNode.forReadingLinkage()).thenReturn(inUseNode());
    nodeChanges.add(updatedNode);
    when(nodeRecordChanges.changes()).thenReturn(nodeChanges);
    when(nodeRecordChanges.changeSize()).thenReturn(3);
    when(recordChangeSet.changeSize()).thenReturn(3);
    when(labelTokenChanges.changes()).thenReturn(Collections.emptyList());
    when(relationshipTypeTokenChanges.changes()).thenReturn(Collections.emptyList());
    when(propertyKeyTokenChanges.changes()).thenReturn(Collections.emptyList());
    when(relationshipRecordChanges.changes()).thenReturn(Collections.emptyList());
    when(propertyRecordChanges.changes()).thenReturn(Collections.emptyList());
    when(relationshipGroupChanges.changes()).thenReturn(Collections.emptyList());
    when(schemaRuleChanges.changes()).thenReturn(Collections.emptyList());
    NeoStores neoStores = mock(NeoStores.class);
    NodeStore store = mock(NodeStore.class);
    when(neoStores.getNodeStore()).thenReturn(store);
    RelationshipGroupStore relationshipGroupStore = mock(RelationshipGroupStore.class);
    when(neoStores.getRelationshipGroupStore()).thenReturn(relationshipGroupStore);
    RelationshipStore relationshipStore = mock(RelationshipStore.class);
    when(neoStores.getRelationshipStore()).thenReturn(relationshipStore);
    return new TransactionRecordState(neoStores, mock(IntegrityValidator.class), recordChangeSet, 0, null, LockTracer.NONE, null, null, null, NULL, INSTANCE, LATEST_LOG_SERIALIZATION);
}
Also used : RelationshipTypeTokenRecord(org.neo4j.kernel.impl.store.record.RelationshipTypeTokenRecord) RelationshipRecord(org.neo4j.kernel.impl.store.record.RelationshipRecord) SchemaRule(org.neo4j.internal.schema.SchemaRule) PropertyKeyTokenRecord(org.neo4j.kernel.impl.store.record.PropertyKeyTokenRecord) NodeRecord(org.neo4j.kernel.impl.store.record.NodeRecord) PropertyRecord(org.neo4j.kernel.impl.store.record.PropertyRecord) NodeStore(org.neo4j.kernel.impl.store.NodeStore) SchemaRecord(org.neo4j.kernel.impl.store.record.SchemaRecord) LabelTokenRecord(org.neo4j.kernel.impl.store.record.LabelTokenRecord) PrimitiveRecord(org.neo4j.kernel.impl.store.record.PrimitiveRecord) RecordProxy(org.neo4j.internal.recordstorage.RecordAccess.RecordProxy) RelationshipGroupRecord(org.neo4j.kernel.impl.store.record.RelationshipGroupRecord) RelationshipGroupStore(org.neo4j.kernel.impl.store.RelationshipGroupStore) LinkedList(java.util.LinkedList) NeoStores(org.neo4j.kernel.impl.store.NeoStores) RelationshipStore(org.neo4j.kernel.impl.store.RelationshipStore)

Aggregations

RecordProxy (org.neo4j.internal.recordstorage.RecordAccess.RecordProxy)2 NodeRecord (org.neo4j.kernel.impl.store.record.NodeRecord)2 RelationshipGroupRecord (org.neo4j.kernel.impl.store.record.RelationshipGroupRecord)2 RelationshipRecord (org.neo4j.kernel.impl.store.record.RelationshipRecord)2 LinkedList (java.util.LinkedList)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 HeapTrackingCollections.newLongObjectMap (org.neo4j.collection.trackable.HeapTrackingCollections.newLongObjectMap)1 HeapTrackingLongObjectHashMap (org.neo4j.collection.trackable.HeapTrackingLongObjectHashMap)1 RelationshipGroupDegreesStore (org.neo4j.internal.counts.RelationshipGroupDegreesStore)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 SchemaRule (org.neo4j.internal.schema.SchemaRule)1 CursorContext (org.neo4j.io.pagecache.context.CursorContext)1