Skip to content

Commit

Permalink
Merge branch '3.0' into 3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
tinwelint committed Sep 22, 2016
2 parents d206f96 + 6bc48f6 commit ebf79ab
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
*/
package org.neo4j.kernel.impl.api;

import org.apache.commons.lang3.mutable.MutableInt;

import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;

Expand Down Expand Up @@ -275,17 +276,16 @@ public void nodeDelete( KernelStatement state, long nodeId )
}

@Override
public int nodeDetachDelete( final KernelStatement state, final long nodeId )
throws EntityNotFoundException, AutoIndexingKernelException, InvalidTransactionTypeKernelException, KernelException
public int nodeDetachDelete( final KernelStatement state, final long nodeId ) throws KernelException
{
final AtomicInteger count = new AtomicInteger();
final MutableInt count = new MutableInt();
TwoPhaseNodeForRelationshipLocking locking = new TwoPhaseNodeForRelationshipLocking( entityReadDelegate,
relId -> {
state.assertOpen();
try
{
entityWriteDelegate.relationshipDelete( state, relId );
count.incrementAndGet();
count.increment();
}
catch ( EntityNotFoundException e )
{
Expand All @@ -296,7 +296,7 @@ public int nodeDetachDelete( final KernelStatement state, final long nodeId )
locking.lockAllNodesAndConsumeRelationships( nodeId, state );
state.assertOpen();
entityWriteDelegate.nodeDetachDelete( state, nodeId );
return count.get();
return count.intValue();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1006,8 +1006,7 @@ public void nodeDelete( long nodeId )
}

@Override
public int nodeDetachDelete( long nodeId ) throws EntityNotFoundException, InvalidTransactionTypeKernelException,
AutoIndexingKernelException, KernelException
public int nodeDetachDelete( long nodeId ) throws KernelException
{
statement.assertOpen();
return dataWrite().nodeDetachDelete( statement, nodeId );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
class TwoPhaseNodeForRelationshipLocking
{
private final PrimitiveLongSet nodeIds = Primitive.longSet();
private final EntityReadOperations reads;
private final EntityReadOperations entityReadOperations;
private final ThrowingConsumer<Long,KernelException> relIdAction;

private boolean retry = true;
Expand Down Expand Up @@ -73,45 +73,44 @@ public void visit( long relId, int type, long startNode, long endNode ) throws K
}
};

TwoPhaseNodeForRelationshipLocking( EntityReadOperations reads, ThrowingConsumer<Long,KernelException> relIdAction )
TwoPhaseNodeForRelationshipLocking( EntityReadOperations entityReadOperations, ThrowingConsumer<Long,KernelException> relIdAction )
{
this.reads = reads;
this.entityReadOperations = entityReadOperations;
this.relIdAction = relIdAction;
}

void lockAllNodesAndConsumeRelationships( long nodeId, final KernelStatement state ) throws KernelException
{
nodeIds.add( nodeId );
while ( retry )
{
retry = false;
first = true;
firstRelId = -1;

// lock all the nodes involved by following the node id ordering
try ( Cursor<NodeItem> cursor = reads.nodeCursorById( state, nodeId ) )
try ( Cursor<NodeItem> cursor = entityReadOperations.nodeCursorById( state, nodeId ) )
{
RelationshipIterator relationships = cursor.get().getRelationships( Direction.BOTH );
while ( relationships.hasNext() )
{
reads.relationshipVisit( state, relationships.next(), collectNodeIdVisitor );
entityReadOperations.relationshipVisit( state, relationships.next(), collectNodeIdVisitor );
}
}

PrimitiveLongIterator nodeIdIterator = nodeIds.iterator();
while ( nodeIdIterator.hasNext() )
{
PrimitiveLongIterator iterator = nodeIds.iterator();
while ( iterator.hasNext() )
{
state.locks().optimistic().acquireExclusive( ResourceTypes.NODE, iterator.next() );
}
state.locks().optimistic().acquireExclusive( ResourceTypes.NODE, nodeIdIterator.next() );
}

// perform the action on each relationship, we will retry if the the relationship iterator contains new relationships
try ( Cursor<NodeItem> cursor = reads.nodeCursorById( state, nodeId ) )
try ( Cursor<NodeItem> cursor = entityReadOperations.nodeCursorById( state, nodeId ) )
{
RelationshipIterator relationships = cursor.get().getRelationships( Direction.BOTH );
while ( relationships.hasNext() )
{
reads.relationshipVisit( state, relationships.next(), relationshipConsumingVisitor );
entityReadOperations.relationshipVisit( state, relationships.next(), relationshipConsumingVisitor );
if ( retry )
{
PrimitiveLongIterator iterator = nodeIds.iterator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.neo4j.kernel.api.constraints.UniquenessConstraint;
import org.neo4j.kernel.api.exceptions.EntityNotFoundException;
import org.neo4j.kernel.api.exceptions.InvalidTransactionTypeKernelException;
import org.neo4j.kernel.api.exceptions.KernelException;
import org.neo4j.kernel.api.exceptions.legacyindex.AutoIndexingKernelException;
import org.neo4j.kernel.api.index.IndexDescriptor;
import org.neo4j.kernel.api.properties.DefinedProperty;
Expand All @@ -43,22 +44,32 @@
import org.neo4j.kernel.impl.api.operations.SchemaReadOperations;
import org.neo4j.kernel.impl.api.operations.SchemaStateOperations;
import org.neo4j.kernel.impl.api.operations.SchemaWriteOperations;
import org.neo4j.kernel.impl.api.state.StubCursors;
import org.neo4j.kernel.impl.api.state.TxState;
import org.neo4j.kernel.impl.factory.CanWrite;
import org.neo4j.kernel.impl.api.store.CursorRelationshipIterator;
import org.neo4j.kernel.impl.api.store.RelationshipIterator;
import org.neo4j.kernel.impl.api.store.StoreSingleNodeCursor;
import org.neo4j.kernel.impl.locking.Locks;
import org.neo4j.kernel.impl.locking.ResourceTypes;
import org.neo4j.kernel.impl.locking.SimpleStatementLocks;
import org.neo4j.kernel.impl.proc.Procedures;
import org.neo4j.kernel.impl.util.Cursors;
import org.neo4j.storageengine.api.Direction;
import org.neo4j.storageengine.api.NodeItem;
import org.neo4j.storageengine.api.RelationshipItem;
import org.neo4j.storageengine.api.StorageStatement;

import static org.junit.Assert.assertSame;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static org.neo4j.kernel.impl.locking.ResourceTypes.schemaResource;

Expand Down Expand Up @@ -478,6 +489,56 @@ public void shouldNotAcquireEntityWriteLockBeforeSettingPropertyOnJustCreatedRel
order.verify( entityWriteOps ).relationshipSetProperty( state, 123, property );
}

@Test
public void detachDeleteNodeWithoutRelationshipsExclusivelyLockNode() throws KernelException
{
long nodeId = 1L;

NodeItem nodeItem = mock( NodeItem.class );
when( nodeItem.getRelationships( Direction.BOTH ) ).thenReturn( RelationshipIterator.EMPTY );
StoreSingleNodeCursor nodeCursor = mock( StoreSingleNodeCursor.class );
when( nodeCursor.get() ).thenReturn( nodeItem );
when( entityReadOps.nodeCursorById( state, nodeId ) ).thenReturn( nodeCursor );

lockingOps.nodeDetachDelete( state, nodeId );

order.verify( locks ).acquireExclusive( ResourceTypes.NODE, nodeId );
order.verify( locks, times( 0 ) ).releaseExclusive( ResourceTypes.NODE, nodeId );
order.verify( entityWriteOps ).nodeDetachDelete( state, nodeId );
}

@Test
public void detachDeleteNodeExclusivelyLockNodes() throws KernelException
{
long startNodeId = 1L;
long endNodeId = 2L;

RelationshipItem relationshipItem = StubCursors.asRelationship( 1L, 0, startNodeId, endNodeId, null );
CursorRelationshipIterator relationshipIterator =
new CursorRelationshipIterator( Cursors.cursor( relationshipItem ) );

NodeItem nodeItem = mock( NodeItem.class );
when( nodeItem.getRelationships( Direction.BOTH ) ).thenReturn( relationshipIterator );
StoreSingleNodeCursor nodeCursor = mock( StoreSingleNodeCursor.class );
when( nodeCursor.get() ).thenReturn( nodeItem );
when( entityReadOps.nodeCursorById( state, startNodeId ) ).thenReturn( nodeCursor );
doAnswer( invocation ->
{
RelationshipVisitor visitor = invocation.getArgumentAt( 2, RelationshipVisitor.class );
visitor.visit( relationshipItem.id(), relationshipItem.type(), relationshipItem.startNode(),
relationshipItem.endNode() );
return null;
} ).when( entityReadOps ).relationshipVisit( eq(state), anyLong(), any() );

lockingOps.nodeDetachDelete( state, startNodeId );

order.verify( locks ).acquireExclusive( ResourceTypes.NODE, startNodeId );
order.verify( locks ).acquireExclusive( ResourceTypes.NODE, endNodeId );
order.verify( locks, times( 0 ) ).releaseExclusive( ResourceTypes.NODE, startNodeId );
order.verify( locks, times( 0 ) ).releaseExclusive( ResourceTypes.NODE, endNodeId );
order.verify( entityWriteOps ).nodeDetachDelete( state, startNodeId );
}

private static class SimpleTxStateHolder implements TxStateHolder
{
private final TxState txState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.neo4j.helpers.collection.Iterators.set;

Expand Down Expand Up @@ -120,6 +122,19 @@ public void shouldLockNodesInOrderAndConsumeTheRelationshipsAndRetryIfTheNewRela
assertEquals( set( 21L, 22L, 23L ), collector.set );
}

@Test
public void lockNodeWithoutRelationships() throws Exception
{
Collector collector = new Collector();
TwoPhaseNodeForRelationshipLocking locking = new TwoPhaseNodeForRelationshipLocking( ops, collector );
returnRelationships( nodeId, false );

locking.lockAllNodesAndConsumeRelationships( nodeId, state );

verify( locks ).acquireExclusive( ResourceTypes.NODE, nodeId );
verifyNoMoreInteractions( locks );
}

private void returnNodesForRelationship( final long relId, final long startNodeId, final long endNodeId )
throws Exception
{
Expand Down

0 comments on commit ebf79ab

Please sign in to comment.