Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #805 from jakewins/no-lowlevel-exception-leakage

InvalidRecordException -> NotFoundException
  • Loading branch information...
commit 50b3f9a4daeecc0aa4cfbd39c5999a84b659d001 2 parents ce4d2b8 + e347ac5
Mattias Persson tinwelint authored
2  kernel/src/main/java/org/neo4j/kernel/impl/core/IntArrayIterator.java
@@ -145,7 +145,7 @@ else if ( fromNode.getMoreRelationships( nodeManager ) ||
145 145 break;
146 146 }
147 147 }
148   - } while ( currentTypeIterator.hasNext() );
  148 + } while ( currentTypeIterator.hasNext() );
149 149 // no next element found
150 150 return null;
151 151 }
41 kernel/src/main/java/org/neo4j/kernel/impl/core/NodeImpl.java
@@ -38,6 +38,7 @@
38 38 import org.neo4j.kernel.impl.cache.SizeOfs;
39 39 import org.neo4j.kernel.impl.core.LockReleaser.CowEntityElement;
40 40 import org.neo4j.kernel.impl.core.LockReleaser.PrimitiveElement;
  41 +import org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
41 42 import org.neo4j.kernel.impl.nioneo.store.PropertyData;
42 43 import org.neo4j.kernel.impl.nioneo.store.Record;
43 44 import org.neo4j.kernel.impl.transaction.LockType;
@@ -90,7 +91,7 @@ public int size()
90 91 }
91 92 return size;
92 93 }
93   -
  94 +
94 95 @Override
95 96 public int hashCode()
96 97 {
@@ -347,7 +348,16 @@ private void loadInitialRelationships( NodeManager nodeManager )
347 348 {
348 349 if ( relationships == null )
349 350 {
350   - relChainPosition = nodeManager.getRelationshipChainPosition( this );
  351 + try
  352 + {
  353 + relChainPosition = nodeManager.getRelationshipChainPosition( this );
  354 + }
  355 + catch ( InvalidRecordException e )
  356 + {
  357 + throw new NotFoundException( asProxy( nodeManager ) +
  358 + " concurrently deleted while loading its relationships?", e );
  359 + }
  360 +
351 361 ArrayMap<String,RelIdArray> tmpRelMap = new ArrayMap<String,RelIdArray>();
352 362 rels = getMoreRelationships( nodeManager, tmpRelMap );
353 363 this.relationships = toRelIdArray( tmpRelMap );
@@ -386,7 +396,6 @@ protected void updateSize( NodeManager nodeManager )
386 396 return result;
387 397 }
388 398
389   -// private Triplet<ArrayMap<String,RelIdArray>,Map<Long,RelationshipImpl>,Long> getMoreRelationships(
390 399 // NodeManager nodeManager, ArrayMap<String,RelIdArray> tmpRelMap )
391 400 private Triplet<ArrayMap<String,RelIdArray>,List<RelationshipImpl>,Long> getMoreRelationships(
392 401 NodeManager nodeManager, ArrayMap<String,RelIdArray> tmpRelMap )
@@ -395,10 +404,10 @@ protected void updateSize( NodeManager nodeManager )
395 404 {
396 405 return null;
397 406 }
398   -// Triplet<ArrayMap<String,RelIdArray>,Map<Long,RelationshipImpl>,Long> rels =
399   -// nodeManager.getMoreRelationships( this );
400   - Triplet<ArrayMap<String,RelIdArray>,List<RelationshipImpl>,Long> rels =
401   - nodeManager.getMoreRelationships( this );
  407 + Triplet<ArrayMap<String,RelIdArray>,List<RelationshipImpl>,Long> rels;
  408 +
  409 + rels = loadMoreRelationshipsFromNodeManager(nodeManager);
  410 +
402 411 ArrayMap<String,RelIdArray> addMap = rels.first();
403 412 if ( addMap.size() == 0 )
404 413 {
@@ -433,7 +442,6 @@ boolean hasMoreRelationshipsToLoad()
433 442
434 443 boolean getMoreRelationships( NodeManager nodeManager )
435 444 {
436   -// Triplet<ArrayMap<String,RelIdArray>,Map<Long,RelationshipImpl>,Long> rels;
437 445 Triplet<ArrayMap<String,RelIdArray>,List<RelationshipImpl>,Long> rels;
438 446 if ( !hasMoreRelationshipsToLoad() )
439 447 {
@@ -445,7 +453,7 @@ boolean getMoreRelationships( NodeManager nodeManager )
445 453 {
446 454 return false;
447 455 }
448   - rels = nodeManager.getMoreRelationships( this );
  456 + rels = loadMoreRelationshipsFromNodeManager(nodeManager);
449 457 ArrayMap<String,RelIdArray> addMap = rels.first();
450 458 if ( addMap.size() == 0 )
451 459 {
@@ -477,6 +485,19 @@ boolean getMoreRelationships( NodeManager nodeManager )
477 485 return true;
478 486 }
479 487
  488 + private Triplet<ArrayMap<String, RelIdArray>, List<RelationshipImpl>, Long>
  489 + loadMoreRelationshipsFromNodeManager( NodeManager nodeManager )
  490 + {
  491 + try
  492 + {
  493 + return nodeManager.getMoreRelationships( this );
  494 + } catch(InvalidRecordException e)
  495 + {
  496 + throw new NotFoundException( "Unable to load one or more relationships from " + asProxy( nodeManager ) +
  497 + ". This usually happens when relationships are deleted by someone else just as we are about to load them. Please try again.", e );
  498 + }
  499 + }
  500 +
480 501 private RelIdArray getRelIdArray( String type )
481 502 {
482 503 // Concurrency-wise it's ok even if the relationships variable
@@ -496,7 +517,7 @@ private void putRelIdArray( RelIdArray addRels )
496 517 {
497 518 // we don't do size update here, instead performed in lockRelaser
498 519 // when calling commitRelationshipMaps and in getMoreRelationships
499   -
  520 +
500 521 // precondition: called under synchronization
501 522
502 523 // make a local reference to the array to avoid multiple read barrier hits
29 kernel/src/main/java/org/neo4j/kernel/impl/core/Primitive.java
@@ -26,6 +26,7 @@
26 26 import org.neo4j.graphdb.PropertyContainer;
27 27 import org.neo4j.kernel.impl.core.LockReleaser.CowEntityElement;
28 28 import org.neo4j.kernel.impl.core.LockReleaser.PrimitiveElement;
  29 +import org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
29 30 import org.neo4j.kernel.impl.nioneo.store.PropertyData;
30 31 import org.neo4j.kernel.impl.transaction.LockType;
31 32 import org.neo4j.kernel.impl.util.ArrayMap;
@@ -64,7 +65,7 @@ protected abstract void removeProperty( NodeManager nodeManager,
64 65 protected abstract void commitPropertyMaps(
65 66 ArrayMap<Integer,PropertyData> cowPropertyAddMap,
66 67 ArrayMap<Integer,PropertyData> cowPropertyRemoveMap, long firstProp, NodeManager nodeManager );
67   -
  68 +
68 69 @Override
69 70 public int hashCode()
70 71 {
@@ -563,19 +564,33 @@ private Object getPropertyValue( NodeManager nodeManager, PropertyData property
563 564
564 565 private void ensureFullProperties( NodeManager nodeManager )
565 566 {
566   - // double checked locking
567   - if ( allProperties() == null ) synchronized ( this )
568   - {
569   - if ( allProperties() == null ) setProperties( loadProperties( nodeManager, false ), nodeManager );
570   - }
  567 + ensureFullProperties( nodeManager, /* light = */ false );
571 568 }
572 569
573 570 private void ensureFullLightProperties( NodeManager nodeManager )
574 571 {
  572 + ensureFullProperties( nodeManager, /* light = */ true );
  573 + }
  574 +
  575 + private void ensureFullProperties(NodeManager nodeManager, boolean light )
  576 + {
575 577 // double checked locking
576 578 if ( allProperties() == null ) synchronized ( this )
577 579 {
578   - if ( allProperties() == null ) setProperties( loadProperties( nodeManager, true ), nodeManager );
  580 + if ( allProperties() == null )
  581 + {
  582 + try
  583 + {
  584 + ArrayMap<Integer, PropertyData> loadedProperties = loadProperties( nodeManager, light );
  585 + setProperties( loadedProperties, nodeManager );
  586 + }
  587 + catch ( InvalidRecordException e )
  588 + {
  589 + throw new NotFoundException( asProxy( nodeManager ) + " not found. This can be because someone " +
  590 + "else deleted this entity while we were trying to read properties from it, or because of " +
  591 + "concurrent modification of other properties on this entity. The problem should be temporary.", e );
  592 + }
  593 + }
579 594 }
580 595 }
581 596
25 kernel/src/main/java/org/neo4j/kernel/impl/util/DebugUtil.java
@@ -92,10 +92,11 @@ public static boolean currentStackTraceContains( Class<?> cls, String method )
92 92 public static class StackTracer
93 93 {
94 94 private final Map<Stack, AtomicInteger> uniqueStackTraces = new HashMap<Stack, AtomicInteger>();
  95 + private boolean considerMessages = true;
95 96
96 97 public void add( Throwable t )
97 98 {
98   - Stack key = new Stack( t );
  99 + Stack key = new Stack( t, considerMessages );
99 100 AtomicInteger count = uniqueStackTraces.get( key );
100 101 if ( count == null )
101 102 {
@@ -130,23 +131,32 @@ public void run()
130 131 } );
131 132 return this;
132 133 }
  134 +
  135 + public StackTracer ignoreMessages()
  136 + {
  137 + considerMessages = false;
  138 + return this;
  139 + }
133 140 }
134 141
135 142 private static class Stack
136 143 {
137 144 private final Throwable stackTrace;
138 145 private final StackTraceElement[] elements;
  146 + private boolean considerMessage;
139 147
140   - Stack( Throwable stackTrace )
  148 + Stack( Throwable stackTrace, boolean considerMessage )
141 149 {
142 150 this.stackTrace = stackTrace;
  151 + this.considerMessage = considerMessage;
143 152 this.elements = stackTrace.getStackTrace();
144 153 }
145 154
146 155 @Override
147 156 public int hashCode()
148 157 {
149   - int hashCode = stackTrace.getMessage().hashCode();
  158 + int hashCode = stackTrace.getMessage() == null || !considerMessage ? 31 :
  159 + stackTrace.getMessage().hashCode();
150 160 for ( StackTraceElement element : stackTrace.getStackTrace() )
151 161 hashCode = hashCode * 9 + element.hashCode();
152 162 return hashCode;
@@ -158,7 +168,14 @@ public boolean equals( Object obj )
158 168 if ( !( obj instanceof Stack) ) return false;
159 169
160 170 Stack o = (Stack) obj;
161   - if ( !stackTrace.getMessage().equals( o.stackTrace.getMessage() ) ) return false;
  171 + if ( considerMessage )
  172 + {
  173 + if ( stackTrace.getMessage() == null )
  174 + {
  175 + if ( o.stackTrace.getMessage() != null ) return false;
  176 + }
  177 + else if ( !stackTrace.getMessage().equals( o.stackTrace.getMessage() ) ) return false;
  178 + }
162 179 if ( elements.length != o.elements.length ) return false;
163 180 for ( int i = 0; i < elements.length; i++ )
164 181 if ( !elements[i].equals( o.elements[i] ) ) return false;
189 kernel/src/test/java/org/neo4j/kernel/impl/core/TestOperationsOnDeletedPrimitive.java
... ... @@ -0,0 +1,189 @@
  1 +/**
  2 + * Copyright (c) 2002-2012 "Neo Technology,"
  3 + * Network Engine for Objects in Lund AB [http://neotechnology.com]
  4 + *
  5 + * This file is part of Neo4j.
  6 + *
  7 + * Neo4j is free software: you can redistribute it and/or modify
  8 + * it under the terms of the GNU General Public License as published by
  9 + * the Free Software Foundation, either version 3 of the License, or
  10 + * (at your option) any later version.
  11 + *
  12 + * This program is distributed in the hope that it will be useful,
  13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 + * GNU General Public License for more details.
  16 + *
  17 + * You should have received a copy of the GNU General Public License
  18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 + */
  20 +package org.neo4j.kernel.impl.core;
  21 +
  22 +import static org.mockito.Mockito.*;
  23 +
  24 +import org.junit.Test;
  25 +import org.neo4j.graphdb.NotFoundException;
  26 +import org.neo4j.graphdb.PropertyContainer;
  27 +import org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
  28 +import org.neo4j.kernel.impl.nioneo.store.PropertyData;
  29 +import org.neo4j.kernel.impl.util.ArrayMap;
  30 +
  31 +/**
  32 + * To cover cases where either the primitive that a property belongs to has
  33 + * been removed, as well as cases where we are reading a property chain and
  34 + * someone suddenly removes items in the chain.
  35 + *
  36 + * The second case, with inconsistent chains, should not be handled like this later on,
  37 + * we don't want to throw NotFoundException to the user. Rather, we want to introduce appropriate
  38 + * read locks such that we never read a property chain in an inconsistent state.
  39 + *
  40 + * The same thing applies to reading relationship chains.
  41 + *
  42 + * Once we modify HA to take appropriate locks while applying transactions to slaves,
  43 + * this should be ready to be implemented.
  44 + */
  45 +public class TestOperationsOnDeletedPrimitive
  46 +{
  47 + private NodeManager nodeManager = mock(NodeManager.class);
  48 + private PropertyContainer propertyContainer = mock( PropertyContainer.class );
  49 + Primitive primitive = new PrimitiveThatHasActuallyBeenDeleted( false );
  50 +
  51 + @Test(expected = NotFoundException.class)
  52 + public void shouldThrowNotFoundOnGetPropertyWithDefaultOnDeletedEntity() throws Exception
  53 + {
  54 + primitive.getProperty( nodeManager, "the_property", new Object() );
  55 + }
  56 +
  57 + @Test(expected = NotFoundException.class)
  58 + public void shouldThrowNotFoundExceptionOnGetPropertyOnDeletedEntity() throws Exception
  59 + {
  60 + primitive.getProperty( nodeManager, "the_property" );
  61 + }
  62 +
  63 + @Test(expected = NotFoundException.class)
  64 + public void shouldThrowNotFoundExceptionOnSetPropertyOnDeletedEntity() throws Exception
  65 + {
  66 + primitive.setProperty( nodeManager, propertyContainer, "the_property", "the value" );
  67 + }
  68 +
  69 + @Test(expected = NotFoundException.class)
  70 + public void shouldThrowNotFoundExceptionOnRemovePropertyOnDeletedEntity() throws Exception
  71 + {
  72 + primitive.removeProperty( nodeManager, propertyContainer, "the_property" );
  73 + }
  74 +
  75 + @Test(expected = NotFoundException.class)
  76 + public void shouldThrowNotFoundExceptionOnGetPropertyKeysOnDeletedEntity() throws Exception
  77 + {
  78 + primitive.getPropertyKeys( nodeManager );
  79 + }
  80 +
  81 + @Test(expected = NotFoundException.class)
  82 + public void shouldThrowNotFoundExceptionOnGetPropertyValuesOnDeletedEntity() throws Exception
  83 + {
  84 + primitive.getPropertyValues( nodeManager );
  85 + }
  86 +
  87 + @Test(expected = NotFoundException.class)
  88 + public void shouldThrowNotFoundExceptionOnHasPropertyOnDeletedEntity() throws Exception
  89 + {
  90 + primitive.hasProperty( nodeManager, "the_property" );
  91 + }
  92 +
  93 + @Test(expected = NotFoundException.class)
  94 + public void shouldThrowNotFoundExceptionOnGetAllCommittedPropertiesOnDeletedEntity() throws Exception
  95 + {
  96 + primitive.getAllCommittedProperties( nodeManager );
  97 + }
  98 +
  99 + @Test(expected = NotFoundException.class)
  100 + public void shouldThrowNotFoundExceptionOnGetCommittedPropertyValueOnDeletedEntity() throws Exception
  101 + {
  102 + primitive.getCommittedPropertyValue( nodeManager, "the_key" );
  103 + }
  104 +
  105 + // Test utils
  106 +
  107 +
  108 + private class PrimitiveThatHasActuallyBeenDeleted extends Primitive
  109 + {
  110 +
  111 + PrimitiveThatHasActuallyBeenDeleted( boolean newPrimitive )
  112 + {
  113 + super( newPrimitive );
  114 + }
  115 +
  116 + // Because we have been deleted, this always throws invalidRecordException
  117 + @Override
  118 + protected ArrayMap<Integer, PropertyData> loadProperties( NodeManager nodeManager, boolean light )
  119 + {
  120 + throw new InvalidRecordException( "I have been deleted, remember!" );
  121 + }
  122 +
  123 + @Override
  124 + protected PropertyData changeProperty( NodeManager nodeManager, PropertyData property, Object value )
  125 + {
  126 + return null;
  127 + }
  128 +
  129 + @Override
  130 + protected PropertyData addProperty( NodeManager nodeManager, PropertyIndex index, Object value )
  131 + {
  132 + return null;
  133 + }
  134 +
  135 + @Override
  136 + protected void removeProperty( NodeManager nodeManager, PropertyData property )
  137 + {
  138 + }
  139 +
  140 + @Override
  141 + public long getId()
  142 + {
  143 + return 0;
  144 + }
  145 +
  146 + @Override
  147 + protected void setEmptyProperties()
  148 + {
  149 + }
  150 +
  151 + @Override
  152 + protected PropertyData[] allProperties()
  153 + {
  154 + return null;
  155 + }
  156 +
  157 + @Override
  158 + protected PropertyData getPropertyForIndex( int keyId )
  159 + {
  160 + return null;
  161 + }
  162 +
  163 + @Override
  164 + protected void setProperties( ArrayMap<Integer, PropertyData> properties, NodeManager nodeManager )
  165 + {
  166 + }
  167 +
  168 + @Override
  169 + protected void commitPropertyMaps( ArrayMap<Integer, PropertyData> cowPropertyAddMap, ArrayMap<Integer,
  170 + PropertyData> cowPropertyRemoveMap, long firstProp, NodeManager nodeManager )
  171 + {
  172 + }
  173 +
  174 + @Override
  175 + public LockReleaser.CowEntityElement getEntityElement( LockReleaser.PrimitiveElement element, boolean create )
  176 + {
  177 + return null;
  178 + }
  179 +
  180 + @Override
  181 + PropertyContainer asProxy( NodeManager nm )
  182 + {
  183 + PropertyContainer mockContainer = mock(PropertyContainer.class);
  184 + when( mockContainer.toString() ).thenReturn( "MockedEntity[1337]" );
  185 + return mockContainer;
  186 + }
  187 + }
  188 +
  189 +}
93 kernel/src/test/java/org/neo4j/kernel/impl/core/TestOperationsOnDeletedRelationships.java
... ... @@ -0,0 +1,93 @@
  1 +/**
  2 + * Copyright (c) 2002-2012 "Neo Technology,"
  3 + * Network Engine for Objects in Lund AB [http://neotechnology.com]
  4 + *
  5 + * This file is part of Neo4j.
  6 + *
  7 + * Neo4j is free software: you can redistribute it and/or modify
  8 + * it under the terms of the GNU General Public License as published by
  9 + * the Free Software Foundation, either version 3 of the License, or
  10 + * (at your option) any later version.
  11 + *
  12 + * This program is distributed in the hope that it will be useful,
  13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 + * GNU General Public License for more details.
  16 + *
  17 + * You should have received a copy of the GNU General Public License
  18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 + */
  20 +package org.neo4j.kernel.impl.core;
  21 +
  22 +import static org.hamcrest.CoreMatchers.is;
  23 +import static org.hamcrest.CoreMatchers.nullValue;
  24 +import static org.hamcrest.core.IsNot.not;
  25 +import static org.junit.Assert.*;
  26 +import static org.mockito.Matchers.any;
  27 +import static org.mockito.Mockito.*;
  28 +
  29 +import org.junit.Test;
  30 +import org.neo4j.graphdb.NotFoundException;
  31 +import org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
  32 +import org.neo4j.kernel.impl.util.RelIdArray;
  33 +
  34 +public class TestOperationsOnDeletedRelationships
  35 +{
  36 +
  37 + // Should it really do this? Wouldn't it be better if we could recover from a a relationship suddenly
  38 + // missing in the chain? Perhaps that is really hard to do though.
  39 + @Test
  40 + public void shouldThrowNotFoundOnGetAllRelationshipsWhenRelationshipConcurrentlyDeleted() throws Exception
  41 + {
  42 + // Given
  43 + NodeImpl nodeImpl = new NodeImpl( 1337l, 0l, 0l, false );
  44 + NodeManager nodeManager = mock(NodeManager.class);
  45 + Throwable exceptionCaught = null;
  46 +
  47 + // Given something tries to load relationships, throw InvalidRecordException
  48 + when( nodeManager.getMoreRelationships( any( NodeImpl.class ) ) ).thenThrow( new InvalidRecordException( "LURING!" ) );
  49 +
  50 + // When
  51 + try {
  52 + nodeImpl.getAllRelationships( nodeManager, RelIdArray.DirectionWrapper.BOTH );
  53 + } catch(Throwable e)
  54 + {
  55 + exceptionCaught = e;
  56 + }
  57 +
  58 + // Then
  59 + assertThat(exceptionCaught, not(nullValue()));
  60 + assertThat( exceptionCaught, is( NotFoundException.class) );
  61 + }
  62 +
  63 + @Test
  64 + public void shouldThrowNotFoundWhenIteratingOverDeletedRelationship() throws Exception
  65 + {
  66 + // Given
  67 + NodeImpl fromNode = new NodeImpl( 1337l, 0l, 0l, false );
  68 + NodeManager nodeManager = mock(NodeManager.class);
  69 + Throwable exceptionCaught = null;
  70 +
  71 + // This makes fromNode think there are more relationships to be loaded
  72 + fromNode.setRelChainPosition( 1337l );
  73 +
  74 + // This makes nodeManager pretend that relationships have been deleted
  75 + when( nodeManager.getMoreRelationships( any( NodeImpl.class ) ) ).thenThrow( new InvalidRecordException(
  76 + "LURING!" ) );
  77 +
  78 +
  79 + // When
  80 + try
  81 + {
  82 + fromNode.getMoreRelationships( nodeManager );
  83 + } catch(Throwable e)
  84 + {
  85 + exceptionCaught = e;
  86 + }
  87 +
  88 + // Then
  89 + assertThat( exceptionCaught, not( nullValue() ) );
  90 + assertThat( exceptionCaught, is( NotFoundException.class) );
  91 + }
  92 +
  93 +}

0 comments on commit 50b3f9a

Please sign in to comment.
Something went wrong with that request. Please try again.