From 57318de345a363008d03a63dd5da1541e0d154b4 Mon Sep 17 00:00:00 2001 From: MishaDemianenko Date: Tue, 3 Oct 2017 18:17:29 +0200 Subject: [PATCH] Atomic schema cache state updates Previously schema state update was non atomic and readers where observing partial cache updates. PR changes that behaviour by preventing concurrent updates of cache, performing atomic cache state update. --- .../kernel/impl/api/store/SchemaCache.java | 326 +++++++++++------- .../impl/api/store/SchemaCacheTest.java | 132 +++++-- 2 files changed, 306 insertions(+), 152 deletions(-) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/SchemaCache.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/SchemaCache.java index 9ddb5414ac960..19532066a4125 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/SchemaCache.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/SchemaCache.java @@ -19,13 +19,15 @@ */ package org.neo4j.kernel.impl.api.store; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.StampedLock; import java.util.function.Function; import org.neo4j.helpers.collection.Iterators; @@ -40,209 +42,279 @@ import org.neo4j.storageengine.api.schema.SchemaRule; import static java.util.Collections.emptyIterator; -import static java.util.Collections.newSetFromMap; /** * A cache of {@link SchemaRule schema rules} as well as enforcement of schema consistency. * Will always reflect the committed state of the schema store. - * - * Assume synchronization/locking is done outside, with locks. - * - * @author Mattias Persson - * @author Stefan Plantikow */ public class SchemaCache { - private final Map indexRuleById = new ConcurrentHashMap<>(); - private final Map constraintRuleById = new ConcurrentHashMap<>(); - private final Set constraints = newSetFromMap( new ConcurrentHashMap<>() ); - - private final Map indexDescriptors = new ConcurrentHashMap<>(); - private final Map> indexDescriptorsByLabel = new ConcurrentHashMap<>(); - private final ConstraintSemantics constraintSemantics; - - private final Map,Object> dependantState = new ConcurrentHashMap<>(); - private final Map> indexByProperty = new ConcurrentHashMap<>(); + private final Lock cacheUpdateLock = new StampedLock().asWriteLock(); + private volatile SchemaCacheState schemaCacheState; public SchemaCache( ConstraintSemantics constraintSemantics, Iterable initialRules ) { - this.constraintSemantics = constraintSemantics; - splitUpInitialRules( initialRules ); - } - - private void splitUpInitialRules( Iterable initialRules ) - { - for ( SchemaRule rule : initialRules ) - { - addSchemaRule( rule ); - } + this.schemaCacheState = new SchemaCacheState( constraintSemantics, initialRules ); } - // SCHEMA RULES - public Iterable indexRules() { - return indexRuleById.values(); + return schemaCacheState.indexRules(); } public Iterable constraintRules() { - return constraintRuleById.values(); + return schemaCacheState.constraintRules(); } public boolean hasConstraintRule( ConstraintDescriptor descriptor ) { - for ( ConstraintRule rule : constraintRuleById.values() ) - { - if ( rule.getConstraintDescriptor().equals( descriptor ) ) - { - return true; - } - } - return false; + return schemaCacheState.hasConstraintRule( descriptor ); } public boolean hasIndexRule( SchemaDescriptor descriptor ) { - for ( IndexRule rule : indexRuleById.values() ) - { - if ( rule.schema().equals( descriptor ) ) - { - return true; - } - } - return false; + return schemaCacheState.hasIndexRule( descriptor ); } - // CONSTRAINTS - public Iterator constraints() { - return constraints.iterator(); + return schemaCacheState.constraints(); } public Iterator constraintsForLabel( final int label ) { - return Iterators.filter( SchemaDescriptorPredicates.hasLabel( label ), constraints.iterator() ); + return Iterators.filter( SchemaDescriptorPredicates.hasLabel( label ), constraints() ); } public Iterator constraintsForRelationshipType( final int relTypeId ) { - return Iterators.filter( SchemaDescriptorPredicates.hasRelType( relTypeId ), constraints.iterator() ); + return Iterators.filter( SchemaDescriptorPredicates.hasRelType( relTypeId ), constraints() ); } public Iterator constraintsForSchema( SchemaDescriptor descriptor ) { - return Iterators.filter( SchemaDescriptor.equalTo( descriptor ), constraints.iterator() ); + return Iterators.filter( SchemaDescriptor.equalTo( descriptor ), constraints() ); } public T getOrCreateDependantState( Class type, Function factory, P parameter ) { - return type.cast( dependantState.computeIfAbsent( type, key -> factory.apply( parameter ) ) ); + return schemaCacheState.getOrCreateDependantState( type, factory, parameter ); } - public void addSchemaRule( SchemaRule rule ) + public void load( Iterable rules ) { - dependantState.clear(); - if ( rule instanceof ConstraintRule ) + cacheUpdateLock.lock(); + try { - ConstraintRule constraintRule = (ConstraintRule) rule; - constraintRuleById.put( constraintRule.getId(), constraintRule ); - constraints.add( constraintSemantics.readConstraint( constraintRule ) ); + ConstraintSemantics constraintSemantics = schemaCacheState.constraintSemantics; + this.schemaCacheState = new SchemaCacheState( constraintSemantics, rules ); } - else if ( rule instanceof IndexRule ) + finally { - IndexRule indexRule = (IndexRule) rule; - indexRuleById.put( indexRule.getId(), indexRule ); - LabelSchemaDescriptor schema = indexRule.schema(); - indexDescriptors.put( schema, indexRule.getIndexDescriptor() ); - - Set forLabel = indexDescriptorsByLabel.get( schema.getLabelId() ); - if ( forLabel == null ) - { - forLabel = new HashSet<>(); - indexDescriptorsByLabel.put( schema.getLabelId(), forLabel ); - } - forLabel.add( indexRule.getIndexDescriptor() ); - - for ( int propertyId : indexRule.schema().getPropertyIds() ) - { - List indexesForProperty = indexByProperty.get( propertyId ); - if ( indexesForProperty == null ) - { - indexesForProperty = new LinkedList<>(); - indexByProperty.put( propertyId, indexesForProperty ); - } - indexesForProperty.add( indexRule.getIndexDescriptor() ); - } + cacheUpdateLock.unlock(); } } - public void clear() + public void addSchemaRule( SchemaRule rule ) { - indexRuleById.clear(); - constraintRuleById.clear(); - constraints.clear(); - indexDescriptors.clear(); - indexByProperty.clear(); + cacheUpdateLock.lock(); + try + { + SchemaCacheState updatedSchemaState = new SchemaCacheState( schemaCacheState ); + updatedSchemaState.addSchemaRule( rule ); + this.schemaCacheState = updatedSchemaState; + } + finally + { + cacheUpdateLock.unlock(); + } } - public void load( List schemaRuleIterator ) + public void removeSchemaRule( long id ) { - clear(); - for ( SchemaRule schemaRule : schemaRuleIterator ) + cacheUpdateLock.lock(); + try + { + SchemaCacheState updatedSchemaState = new SchemaCacheState( schemaCacheState ); + updatedSchemaState.removeSchemaRule( id ); + this.schemaCacheState = updatedSchemaState; + } + finally { - addSchemaRule( schemaRule ); + cacheUpdateLock.unlock(); } } - public void removeSchemaRule( long id ) + public IndexDescriptor indexDescriptor( LabelSchemaDescriptor descriptor ) + { + return schemaCacheState.indexDescriptor( descriptor ); + } + + public Iterator indexDescriptorsForLabel( int labelId ) { - dependantState.clear(); - if ( constraintRuleById.containsKey( id ) ) + return schemaCacheState.indexDescriptorsForLabel( labelId ); + } + + public Iterator indexesByProperty( int propertyId ) + { + return schemaCacheState.indexesByProperty( propertyId ); + } + + private static class SchemaCacheState + { + private final ConstraintSemantics constraintSemantics; + private final Set constraints; + private final Map indexRuleById; + private final Map constraintRuleById; + + private final Map indexDescriptors; + private final Map> indexDescriptorsByLabel; + + private final Map,Object> dependantState; + private final Map> indexByProperty; + + SchemaCacheState( ConstraintSemantics constraintSemantics, Iterable rules ) { - ConstraintRule rule = constraintRuleById.remove( id ); - constraints.remove( rule.getConstraintDescriptor() ); + this.constraintSemantics = constraintSemantics; + this.constraints = new HashSet<>(); + this.indexRuleById = new HashMap<>(); + this.constraintRuleById = new HashMap<>(); + + this.indexDescriptors = new HashMap<>(); + this.indexDescriptorsByLabel = new HashMap<>(); + this.dependantState = new HashMap<>(); + this.indexByProperty = new HashMap<>(); + load( rules ); } - else if ( indexRuleById.containsKey( id ) ) + + SchemaCacheState( SchemaCacheState schemaCacheState ) { - IndexRule rule = indexRuleById.remove( id ); - LabelSchemaDescriptor schema = rule.schema(); - indexDescriptors.remove( schema ); + this.constraintSemantics = schemaCacheState.constraintSemantics; + this.indexRuleById = new HashMap<>( schemaCacheState.indexRuleById ); + this.constraintRuleById = new HashMap<>( schemaCacheState.constraintRuleById ); + this.constraints = new HashSet<>( schemaCacheState.constraints ); - Set forLabel = indexDescriptorsByLabel.get( schema.getLabelId() ); - forLabel.remove( rule.getIndexDescriptor() ); - if ( forLabel.isEmpty() ) + this.indexDescriptors = new HashMap<>( schemaCacheState.indexDescriptors ); + this.indexDescriptorsByLabel = new HashMap<>( schemaCacheState.indexDescriptorsByLabel ); + this.dependantState = new HashMap<>(); + this.indexByProperty = new HashMap<>( schemaCacheState.indexByProperty ); + } + + public void load( Iterable schemaRuleIterator ) + { + for ( SchemaRule schemaRule : schemaRuleIterator ) { - indexDescriptorsByLabel.remove( schema.getLabelId() ); + addSchemaRule( schemaRule ); } + } + + Iterable indexRules() + { + return indexRuleById.values(); + } + + Iterable constraintRules() + { + return constraintRuleById.values(); + } + + boolean hasConstraintRule( ConstraintDescriptor descriptor ) + { + return constraints.contains( descriptor ); + } + + boolean hasIndexRule( SchemaDescriptor descriptor ) + { + return indexDescriptors.containsKey( descriptor ); + } + + Iterator constraints() + { + return constraints.iterator(); + } + + IndexDescriptor indexDescriptor( LabelSchemaDescriptor descriptor ) + { + return indexDescriptors.get( descriptor ); + } + + Iterator indexesByProperty( int propertyId ) + { + List indexes = indexByProperty.get( propertyId ); + return (indexes == null) ? emptyIterator() : indexes.iterator(); + } + + Iterator indexDescriptorsForLabel( int labelId ) + { + Set forLabel = indexDescriptorsByLabel.get( labelId ); + return forLabel == null ? emptyIterator() : forLabel.iterator(); + } - for ( int propertyId : rule.schema().getPropertyIds() ) + T getOrCreateDependantState( Class type, Function factory, P parameter ) + { + return type.cast( dependantState.computeIfAbsent( type, key -> factory.apply( parameter ) ) ); + } + + void addSchemaRule( SchemaRule rule ) + { + if ( rule instanceof ConstraintRule ) { - List forProperty = indexByProperty.get( propertyId ); - forProperty.remove( rule.getIndexDescriptor() ); - if ( forProperty.isEmpty() ) + ConstraintRule constraintRule = (ConstraintRule) rule; + constraintRuleById.put( constraintRule.getId(), constraintRule ); + constraints.add( constraintSemantics.readConstraint( constraintRule ) ); + } + else if ( rule instanceof IndexRule ) + { + IndexRule indexRule = (IndexRule) rule; + indexRuleById.put( indexRule.getId(), indexRule ); + LabelSchemaDescriptor schemaDescriptor = indexRule.schema(); + IndexDescriptor indexDescriptor = indexRule.getIndexDescriptor(); + indexDescriptors.put( schemaDescriptor, indexDescriptor ); + + Set forLabel = + indexDescriptorsByLabel.computeIfAbsent( schemaDescriptor.getLabelId(), k -> new HashSet<>() ); + forLabel.add( indexDescriptor ); + + for ( int propertyId : indexRule.schema().getPropertyIds() ) { - indexByProperty.remove( propertyId ); + List indexesForProperty = + indexByProperty.computeIfAbsent( propertyId, k -> new ArrayList<>() ); + indexesForProperty.add( indexDescriptor ); } } } - } - public IndexDescriptor indexDescriptor( LabelSchemaDescriptor descriptor ) - { - return indexDescriptors.get( descriptor ); - } + void removeSchemaRule( long id ) + { + if ( constraintRuleById.containsKey( id ) ) + { + ConstraintRule rule = constraintRuleById.remove( id ); + constraints.remove( rule.getConstraintDescriptor() ); + } + else if ( indexRuleById.containsKey( id ) ) + { + IndexRule rule = indexRuleById.remove( id ); + LabelSchemaDescriptor schema = rule.schema(); + indexDescriptors.remove( schema ); - public Iterator indexDescriptorsForLabel( int labelId ) - { - Set forLabel = indexDescriptorsByLabel.get( labelId ); - return forLabel == null ? emptyIterator() : forLabel.iterator(); - } + Set forLabel = indexDescriptorsByLabel.get( schema.getLabelId() ); + forLabel.remove( rule.getIndexDescriptor() ); + if ( forLabel.isEmpty() ) + { + indexDescriptorsByLabel.remove( schema.getLabelId() ); + } - public Iterator indexesByProperty( int propertyId ) - { - List indexes = indexByProperty.get( propertyId ); - return (indexes == null) ? emptyIterator() : indexes.iterator(); + for ( int propertyId : rule.schema().getPropertyIds() ) + { + List forProperty = indexByProperty.get( propertyId ); + forProperty.remove( rule.getIndexDescriptor() ); + if ( forProperty.isEmpty() ) + { + indexByProperty.remove( propertyId ); + } + } + } + } } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/SchemaCacheTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/SchemaCacheTest.java index f01dc25cbd441..1afddf248f0d5 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/SchemaCacheTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/store/SchemaCacheTest.java @@ -29,7 +29,6 @@ import org.neo4j.helpers.collection.Iterables; import org.neo4j.helpers.collection.Iterators; import org.neo4j.kernel.api.schema.LabelSchemaDescriptor; -import org.neo4j.kernel.api.schema.SchemaDescriptorFactory; import org.neo4j.kernel.api.schema.constaints.ConstraintDescriptor; import org.neo4j.kernel.api.schema.constaints.ConstraintDescriptorFactory; import org.neo4j.kernel.api.schema.index.IndexDescriptor; @@ -38,6 +37,7 @@ import org.neo4j.kernel.impl.store.record.ConstraintRule; import org.neo4j.kernel.impl.store.record.IndexRule; import org.neo4j.storageengine.api.schema.SchemaRule; +import org.neo4j.test.Race; import static java.util.Arrays.asList; import static java.util.Collections.singleton; @@ -46,28 +46,50 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.neo4j.helpers.collection.Iterators.asSet; +import static org.neo4j.kernel.api.schema.SchemaDescriptorFactory.forLabel; +import static org.neo4j.kernel.api.schema.constaints.ConstraintDescriptorFactory.uniqueForLabel; import static org.neo4j.kernel.impl.api.index.TestSchemaIndexProviderDescriptor.PROVIDER_DESCRIPTOR; +import static org.neo4j.kernel.impl.store.record.ConstraintRule.constraintRule; public class SchemaCacheTest { - final SchemaRule hans = newIndexRule( 1, 0, 5 ); - final SchemaRule witch = nodePropertyExistenceConstraintRule( 2, 3, 6 ); - final SchemaRule gretel = newIndexRule( 3, 0, 7 ); + private final SchemaRule hans = newIndexRule( 1, 0, 5 ); + private final SchemaRule witch = nodePropertyExistenceConstraintRule( 2, 3, 6 ); + private final SchemaRule gretel = newIndexRule( 3, 0, 7 ); + private final ConstraintRule robot = relPropertyExistenceConstraintRule( 7L, 8, 9 ); @Test public void should_construct_schema_cache() { // GIVEN - Collection rules = asList( hans, witch, gretel ); + Collection rules = asList( hans, witch, gretel, robot ); SchemaCache cache = new SchemaCache( new ConstraintSemantics(), rules ); // THEN assertEquals( asSet( hans, gretel ), Iterables.asSet( cache.indexRules() ) ); - assertEquals( asSet( witch ), Iterables.asSet( cache.constraintRules() ) ); + assertEquals( asSet( witch, robot ), Iterables.asSet( cache.constraintRules() ) ); } @Test - public void should_add_schema_rules() + public void addRemoveIndexes() + { + Collection rules = asList( hans, witch, gretel, robot ); + SchemaCache cache = new SchemaCache( new ConstraintSemantics(), rules ); + + IndexRule rule1 = newIndexRule( 10, 11, 12 ); + IndexRule rule2 = newIndexRule( 13, 14, 15 ); + cache.addSchemaRule( rule1 ); + cache.addSchemaRule( rule2 ); + + cache.removeSchemaRule( hans.getId() ); + cache.removeSchemaRule( witch.getId() ); + + assertEquals( asSet( gretel, rule1, rule2 ), Iterables.asSet( cache.indexRules() ) ); + assertEquals( asSet( robot ), Iterables.asSet( cache.constraintRules() ) ); + } + + @Test + public void addSchemaRules() { // GIVEN SchemaCache cache = newSchemaCache(); @@ -76,10 +98,11 @@ public void should_add_schema_rules() cache.addSchemaRule( hans ); cache.addSchemaRule( gretel ); cache.addSchemaRule( witch ); + cache.addSchemaRule( robot ); // THEN assertEquals( asSet( hans, gretel ), Iterables.asSet( cache.indexRules() ) ); - assertEquals( asSet( witch ), Iterables.asSet( cache.constraintRules() ) ); + assertEquals( asSet( witch, robot ), Iterables.asSet( cache.constraintRules() ) ); } @Test @@ -95,8 +118,8 @@ public void should_list_constraints() cache.addSchemaRule( nodePropertyExistenceConstraintRule( 3L, 7, 8 ) ); // THEN - ConstraintDescriptor unique1 = ConstraintDescriptorFactory.uniqueForLabel( 1, 2 ); - ConstraintDescriptor unique2 = ConstraintDescriptorFactory.uniqueForLabel( 3, 4 ); + ConstraintDescriptor unique1 = uniqueForLabel( 1, 2 ); + ConstraintDescriptor unique2 = uniqueForLabel( 3, 4 ); ConstraintDescriptor existsRel = ConstraintDescriptorFactory.existsForRelType( 5, 6 ); ConstraintDescriptor existsNode = ConstraintDescriptorFactory.existsForLabel( 7, 8 ); @@ -114,7 +137,7 @@ public void should_list_constraints() assertEquals( asSet(), - asSet( cache.constraintsForSchema( SchemaDescriptorFactory.forLabel( 1, 3 ) ) ) ); + asSet( cache.constraintsForSchema( forLabel( 1, 3 ) ) ) ); assertEquals( asSet( existsRel ), @@ -134,8 +157,8 @@ public void should_remove_constraints() cache.removeSchemaRule( 0L ); // THEN - ConstraintDescriptor dropped = ConstraintDescriptorFactory.uniqueForLabel( 1, 1 ); - ConstraintDescriptor unique = ConstraintDescriptorFactory.uniqueForLabel( 3, 4 ); + ConstraintDescriptor dropped = uniqueForLabel( 1, 1 ); + ConstraintDescriptor unique = uniqueForLabel( 3, 4 ); assertEquals( asSet( unique ), asSet( cache.constraints() ) ); @@ -162,7 +185,7 @@ public void adding_constraints_should_be_idempotent() throws Exception // then assertEquals( - asList( ConstraintDescriptorFactory.uniqueForLabel( 1, 2 ) ), + asList( uniqueForLabel( 1, 2 ) ), Iterators.asList( cache.constraints() ) ); } @@ -177,7 +200,7 @@ public void shouldResolveIndexDescriptor() throws Exception cache.addSchemaRule( newIndexRule( 3L, 2, 2 ) ); // When - LabelSchemaDescriptor schema = SchemaDescriptorFactory.forLabel( 1, 3 ); + LabelSchemaDescriptor schema = forLabel( 1, 3 ); IndexDescriptor descriptor = cache.indexDescriptor( schema ); // Then @@ -191,7 +214,7 @@ public void shouldReturnNullWhenNoIndexExists() SchemaCache schemaCache = newSchemaCache(); // When - IndexDescriptor indexDescriptor = schemaCache.indexDescriptor( SchemaDescriptorFactory.forLabel( 1, 1 ) ); + IndexDescriptor indexDescriptor = schemaCache.indexDescriptor( forLabel( 1, 1 ) ); // Then assertNull( indexDescriptor ); @@ -205,7 +228,10 @@ public void shouldListConstraintsForLabel() ConstraintRule rule2 = uniquenessConstraintRule( 1, 2, 1, 0 ); ConstraintRule rule3 = nodePropertyExistenceConstraintRule( 2, 1, 2 ); - SchemaCache cache = newSchemaCache( rule1, rule2, rule3 ); + SchemaCache cache = newSchemaCache(); + cache.addSchemaRule( rule1 ); + cache.addSchemaRule( rule2 ); + cache.addSchemaRule( rule3 ); // When Set listed = asSet( cache.constraintsForLabel( 1 ) ); @@ -225,7 +251,10 @@ public void shouldListConstraintsForSchema() ConstraintRule rule2 = uniquenessConstraintRule( 1, 2, 1, 0 ); ConstraintRule rule3 = nodePropertyExistenceConstraintRule( 2, 1, 2 ); - SchemaCache cache = newSchemaCache( rule1, rule2, rule3 ); + SchemaCache cache = newSchemaCache(); + cache.addSchemaRule( rule1 ); + cache.addSchemaRule( rule2 ); + cache.addSchemaRule( rule3 ); // When Set listed = asSet( cache.constraintsForSchema( rule3.schema() ) ); @@ -242,7 +271,10 @@ public void shouldListConstraintsForRelationshipType() ConstraintRule rule2 = relPropertyExistenceConstraintRule( 0, 2, 1 ); ConstraintRule rule3 = relPropertyExistenceConstraintRule( 0, 1, 2 ); - SchemaCache cache = newSchemaCache( rule1, rule2, rule3 ); + SchemaCache cache = newSchemaCache(); + cache.addSchemaRule( rule1 ); + cache.addSchemaRule( rule2 ); + cache.addSchemaRule( rule3 ); // When Set listed = asSet( cache.constraintsForRelationshipType( 1 ) ); @@ -252,6 +284,59 @@ public void shouldListConstraintsForRelationshipType() assertEquals( expected, listed ); } + @Test + public void concurrentSchemaRuleAdd() throws Throwable + { + SchemaCache cache = newSchemaCache(); + Race race = new Race(); + int indexNumber = 10; + for ( int i = 0; i < indexNumber; i++ ) + { + int id = i; + race.addContestant( () -> cache.addSchemaRule( newIndexRule( id, id, id ) ) ); + } + race.go(); + + assertEquals( indexNumber, Iterables.count( cache.indexRules() ) ); + for ( int labelId = 0; labelId < indexNumber; labelId++ ) + { + assertEquals( 1, Iterators.count( cache.indexDescriptorsForLabel( labelId ) ) ); + } + for ( int propertyId = 0; propertyId < indexNumber; propertyId++ ) + { + assertEquals( 1, Iterators.count( cache.indexesByProperty( propertyId ) ) ); + } + } + + @Test + public void concurrentSchemaRuleRemove() throws Throwable + { + SchemaCache cache = newSchemaCache(); + int indexNumber = 20; + for ( int i = 0; i < indexNumber; i++ ) + { + cache.addSchemaRule( newIndexRule( i, i, i ) ); + } + Race race = new Race(); + int numberOfDeletions = 10; + for ( int i = 0; i < numberOfDeletions; i++ ) + { + int indexId = i; + race.addContestant( () -> cache.removeSchemaRule( indexId ) ); + } + race.go(); + + assertEquals( indexNumber - numberOfDeletions, Iterables.count( cache.indexRules() ) ); + for ( int labelId = numberOfDeletions; labelId < indexNumber; labelId++ ) + { + assertEquals( 1, Iterators.count( cache.indexDescriptorsForLabel( labelId ) ) ); + } + for ( int propertyId = numberOfDeletions; propertyId < indexNumber; propertyId++ ) + { + assertEquals( 1, Iterators.count( cache.indexesByProperty( propertyId ) ) ); + } + } + private IndexRule newIndexRule( long id, int label, int propertyKey ) { return IndexRule.indexRule( id, IndexDescriptorFactory.forLabel( label, propertyKey ), PROVIDER_DESCRIPTOR ); @@ -259,20 +344,17 @@ private IndexRule newIndexRule( long id, int label, int propertyKey ) private ConstraintRule nodePropertyExistenceConstraintRule( long ruleId, int labelId, int propertyId ) { - return ConstraintRule.constraintRule( - ruleId, ConstraintDescriptorFactory.existsForLabel( labelId, propertyId ) ); + return constraintRule( ruleId, ConstraintDescriptorFactory.existsForLabel( labelId, propertyId ) ); } private ConstraintRule relPropertyExistenceConstraintRule( long ruleId, int relTypeId, int propertyId ) { - return ConstraintRule.constraintRule( - ruleId, ConstraintDescriptorFactory.existsForRelType( relTypeId, propertyId ) ); + return constraintRule( ruleId, ConstraintDescriptorFactory.existsForRelType( relTypeId, propertyId ) ); } private ConstraintRule uniquenessConstraintRule( long ruleId, int labelId, int propertyId, long indexId ) { - return ConstraintRule.constraintRule( - ruleId, ConstraintDescriptorFactory.uniqueForLabel( labelId, propertyId ), indexId ); + return constraintRule( ruleId, uniqueForLabel( labelId, propertyId ), indexId ); } private static SchemaCache newSchemaCache( SchemaRule... rules )