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();
}
}
}
}
}
});
}
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);
}
Aggregations