diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/IndexPartsCache.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/IndexPartsCache.java new file mode 100644 index 000000000000..4ad314304765 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/IndexPartsCache.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.index.schema; + +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +abstract class IndexPartsCache implements Iterable +{ + final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + final Lock instantiateCloseLock = new ReentrantLock(); + // guarded by instantiateCloseLock + private boolean closed; + + void assertOpen() + { + if ( closed ) + { + throw new IllegalStateException( this + " is already closed" ); + } + } + + void closeInstantiateCloseLock() + { + instantiateCloseLock.lock(); + closed = true; + instantiateCloseLock.unlock(); + } + + @Override + public Iterator iterator() + { + return cache.values().iterator(); + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialIndexCache.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialIndexCache.java index 1d9f264a5759..44e6c43506ea 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialIndexCache.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialIndexCache.java @@ -21,10 +21,6 @@ import java.io.IOException; import java.io.UncheckedIOException; -import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import org.neo4j.values.storable.CoordinateReferenceSystem; @@ -37,13 +33,9 @@ * * @param Type of parts */ -class SpatialIndexCache implements Iterable +class SpatialIndexCache extends IndexPartsCache { private final Factory factory; - private ConcurrentHashMap spatials = new ConcurrentHashMap<>(); - private final Lock instantiateCloseLock = new ReentrantLock(); - // guarded by instantiateCloseLock - private boolean closed; SpatialIndexCache( Factory factory ) { @@ -59,7 +51,7 @@ class SpatialIndexCache implements Iterable */ T uncheckedSelect( CoordinateReferenceSystem crs ) { - T existing = spatials.get( crs ); + T existing = cache.get( crs ); if ( existing != null ) { return existing; @@ -72,7 +64,7 @@ T uncheckedSelect( CoordinateReferenceSystem crs ) try { assertOpen(); - return spatials.computeIfAbsent( crs, key -> + return cache.computeIfAbsent( crs, key -> { try { @@ -90,21 +82,6 @@ T uncheckedSelect( CoordinateReferenceSystem crs ) } } - protected void assertOpen() - { - if ( closed ) - { - throw new IllegalStateException( this + " is already closed" ); - } - } - - void closeInstantiateCloseLock() - { - instantiateCloseLock.lock(); - closed = true; - instantiateCloseLock.unlock(); - } - /** * Select the part corresponding to the given CoordinateReferenceSystem. Creates the part if needed, * in which case an exception of type E might be thrown. @@ -136,7 +113,7 @@ T select( CoordinateReferenceSystem crs ) throws IOException */ RESULT selectOrElse( CoordinateReferenceSystem crs, Function function, RESULT orElse ) { - T part = spatials.get( crs ); + T part = cache.get( crs ); if ( part == null ) { return orElse; @@ -152,12 +129,6 @@ void loadAll() } } - @Override - public Iterator iterator() - { - return spatials.values().iterator(); - } - /** * Factory used by the SpatialIndexCache to create parts. * diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexAccessor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexAccessor.java index 214b898d8433..a4920e47d1de 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexAccessor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexAccessor.java @@ -96,7 +96,7 @@ public void refresh() @Override public void close() throws IOException { - shutInstantiateCloseLock(); + closeInstantiateCloseLock(); forAll( NativeSchemaIndexAccessor::close, this ); } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexCache.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexCache.java index 20263208bdab..5b577da47542 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexCache.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexCache.java @@ -21,10 +21,6 @@ import java.io.IOException; import java.io.UncheckedIOException; -import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import org.neo4j.function.ThrowingSupplier; @@ -46,7 +42,7 @@ * * @param Type of parts */ -class TemporalIndexCache implements Iterable +class TemporalIndexCache extends IndexPartsCache { private final Factory factory; @@ -60,11 +56,6 @@ enum Offset duration } - private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); - private final Lock instantiateCloseLock = new ReentrantLock(); - // guarded by instantiateCloseLock - private boolean closed; - TemporalIndexCache( Factory factory ) { this.factory = factory; @@ -163,21 +154,6 @@ RESULT selectOrElse( ValueGroup valueGroup, Function function return cachedValue != null ? function.apply( cachedValue ) : orElse; } - private void assertOpen() - { - if ( closed ) - { - throw new IllegalStateException( this + " is already closed" ); - } - } - - void shutInstantiateCloseLock() - { - instantiateCloseLock.lock(); - closed = true; - instantiateCloseLock.unlock(); - } - private T getOrCreatePart( Offset key, ThrowingSupplier factory ) throws UncheckedIOException { T existing = cache.get( key ); @@ -258,12 +234,6 @@ void loadAll() } } - @Override - public Iterator iterator() - { - return cache.values().iterator(); - } - /** * Factory used by the TemporalIndexCache to create parts. * diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulator.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulator.java index d150bc2b77b3..0b2fee3c0c98 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexPopulator.java @@ -109,7 +109,7 @@ public IndexUpdater newPopulatingUpdater( PropertyAccessor accessor ) @Override public synchronized void close( boolean populationCompletedSuccessfully ) throws IOException { - shutInstantiateCloseLock(); + closeInstantiateCloseLock(); for ( NativeSchemaIndexPopulator part : this ) { part.close( populationCompletedSuccessfully ); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexAccessor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexAccessor.java index 0b0a5a46f804..9127008d9918 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexAccessor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexAccessor.java @@ -62,7 +62,7 @@ class FusionIndexAccessor extends FusionIndexBase implements Inde @Override public void drop() throws IOException { - forAll( IndexAccessor::drop, instanceSelector ); + instanceSelector.forAll( IndexAccessor::drop ); dropAction.drop( indexId ); } @@ -77,19 +77,19 @@ public IndexUpdater newUpdater( IndexUpdateMode mode ) @Override public void force( IOLimiter ioLimiter ) throws IOException { - forAll( accessor -> accessor.force( ioLimiter ), instanceSelector ); + instanceSelector.forAll( accessor -> accessor.force( ioLimiter ) ); } @Override public void refresh() throws IOException { - forAll( IndexAccessor::refresh, instanceSelector ); + instanceSelector.forAll( IndexAccessor::refresh ); } @Override public void close() throws IOException { - forAll( IndexAccessor::close, instanceSelector ); + instanceSelector.close( IndexAccessor::close ); } @Override @@ -103,7 +103,7 @@ public IndexReader newReader() @Override public BoundedIterable newAllEntriesReader() { - BoundedIterable[] entries = instancesAs( new BoundedIterable[INSTANCE_COUNT], IndexAccessor::newAllEntriesReader ); + BoundedIterable[] entries = instanceSelector.instancesAs( new BoundedIterable[INSTANCE_COUNT], IndexAccessor::newAllEntriesReader ); return new BoundedIterable() { @Override @@ -149,7 +149,8 @@ public Iterator iterator() @Override public ResourceIterator snapshotFiles() throws IOException { - return concatResourceIterators( iterator( instancesAs( new ResourceIterator[INSTANCE_COUNT], accessor -> accessor.snapshotFiles() ) ) ); + return concatResourceIterators( + iterator( instanceSelector.instancesAs( new ResourceIterator[INSTANCE_COUNT], accessor -> accessor.snapshotFiles() ) ) ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexBase.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexBase.java index 81dea2696d09..9abada43564d 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexBase.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexBase.java @@ -22,16 +22,11 @@ import java.util.Arrays; import java.util.function.Function; -import org.neo4j.collection.primitive.PrimitiveIntCollections; import org.neo4j.function.ThrowingConsumer; -import org.neo4j.function.ThrowingFunction; import org.neo4j.helpers.Exceptions; -import org.neo4j.kernel.api.index.IndexProvider; import org.neo4j.values.storable.Value; import org.neo4j.values.storable.ValueGroup; -import static org.neo4j.kernel.impl.index.schema.fusion.SlotSelector.INSTANCE_COUNT; - /** * Acting as a simplifier for the multiplexing that is going in inside a fusion index. A fusion index consists of multiple parts, * each handling one or more value groups. Each instance, be it a reader, populator or accessor should extend this class @@ -52,67 +47,6 @@ public abstract class FusionIndexBase this.instanceSelector = instanceSelector; } - /** - * Short-hand for calling the static {@link #instancesAs(InstanceSelector, Object[], ThrowingFunction)}, here with the local {@link #instanceSelector}. - */ - R[] instancesAs( R[] target, ThrowingFunction converter ) throws E - { - return instancesAs( instanceSelector, target, converter ); - } - - /** - * Convenience method typically for calling a method on each of the sub-parts of a fusion entity, - * one which creates another instance. All those instances are returned as an array, or actually put into an array - * created by the caller to avoid reflection to instantiate the array. - * - * @param instanceSelector {@link InstanceSelector} to use as the source. - * @param target array to put the created instances into, also returned. - * @param converter {@link ThrowingFunction} which converts from the source to target instance. - * @param type of source instance. - * @param type of target instance. - * @param type of exception that converter may throw. - * @return the target array which was passed in, now populated. - * @throws E exception from converter. - */ - static T[] instancesAs( InstanceSelector instanceSelector, T[] target, ThrowingFunction converter ) throws E - { - for ( int slot = 0; slot < INSTANCE_COUNT; slot++ ) - { - target[slot] = converter.apply( instanceSelector.select( slot ) ); - } - return target; - } - - static void forInstantiated( ThrowingConsumer consumer, InstanceSelector instanceSelector ) throws E - { - E exception = null; - for ( int slot = 0; slot < INSTANCE_COUNT; slot++ ) - { - T instance = instanceSelector.getIfInstantiated( slot ); - if ( instance != null ) - { - exception = consume( exception, consumer, instance ); - } - } - if ( exception != null ) - { - throw exception; - } - } - - public static void forAll( ThrowingConsumer consumer, InstanceSelector instanceSelector ) throws E - { - E exception = null; - for ( int slot = 0; slot < INSTANCE_COUNT; slot++ ) - { - exception = consume( exception, consumer, instanceSelector.select( slot ) ); - } - if ( exception != null ) - { - throw exception; - } - } - /** * See {@link #forAll(ThrowingConsumer, Object[])} * diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexPopulator.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexPopulator.java index b735414d5413..118e81e41809 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexPopulator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexPopulator.java @@ -53,13 +53,13 @@ class FusionIndexPopulator extends FusionIndexBase implements In public void create() throws IOException { dropAction.drop( indexId, archiveFailedIndex ); - forAll( IndexPopulator::create, instanceSelector ); + instanceSelector.forAll( IndexPopulator::create ); } @Override public void drop() throws IOException { - forAll( IndexPopulator::drop, instanceSelector ); + instanceSelector.forAll( IndexPopulator::drop ); dropAction.drop( indexId ); } @@ -105,13 +105,13 @@ public IndexUpdater newPopulatingUpdater( PropertyAccessor accessor ) @Override public void close( boolean populationCompletedSuccessfully ) throws IOException { - forAll( populator -> populator.close( populationCompletedSuccessfully ), instanceSelector ); + instanceSelector.close( populator -> populator.close( populationCompletedSuccessfully ) ); } @Override public void markAsFailed( String failure ) throws IOException { - forAll( populator -> populator.markAsFailed( failure ), instanceSelector ); + instanceSelector.forAll( populator -> populator.markAsFailed( failure ) ); } @Override @@ -123,6 +123,6 @@ public void includeSample( IndexEntryUpdate update ) @Override public IndexSample sampleResult() { - return combineSamples( instancesAs( new IndexSample[INSTANCE_COUNT], IndexPopulator::sampleResult ) ); + return combineSamples( instanceSelector.instancesAs( new IndexSample[INSTANCE_COUNT], IndexPopulator::sampleResult ) ); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexProvider.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexProvider.java index 522797672be0..5aded0c65345 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexProvider.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexProvider.java @@ -43,8 +43,6 @@ import static org.neo4j.internal.kernel.api.InternalIndexState.FAILED; import static org.neo4j.internal.kernel.api.InternalIndexState.POPULATING; -import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexBase.forAll; -import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexBase.instancesAs; import static org.neo4j.kernel.impl.index.schema.fusion.SlotSelector.INSTANCE_COUNT; import static org.neo4j.kernel.impl.index.schema.fusion.SlotSelector.LUCENE; import static org.neo4j.kernel.impl.index.schema.fusion.SlotSelector.NUMBER; @@ -102,7 +100,7 @@ private void fillProvidersArray( IndexProvider[] providers, public IndexPopulator getPopulator( long indexId, SchemaIndexDescriptor descriptor, IndexSamplingConfig samplingConfig ) { IndexPopulator[] populators = - instancesAs( providers, new IndexPopulator[INSTANCE_COUNT], provider -> provider.getPopulator( indexId, descriptor, samplingConfig ) ); + providers.instancesAs( new IndexPopulator[INSTANCE_COUNT], provider -> provider.getPopulator( indexId, descriptor, samplingConfig ) ); return new FusionIndexPopulator( slotSelector, new InstanceSelector<>( populators ), indexId, dropAction, archiveFailedIndex ); } @@ -111,7 +109,7 @@ public IndexAccessor getOnlineAccessor( long indexId, SchemaIndexDescriptor desc IndexSamplingConfig samplingConfig ) throws IOException { IndexAccessor[] accessors = - instancesAs( providers, new IndexAccessor[INSTANCE_COUNT], provider -> provider.getOnlineAccessor( indexId, descriptor, samplingConfig ) ); + providers.instancesAs( new IndexAccessor[INSTANCE_COUNT], provider -> provider.getOnlineAccessor( indexId, descriptor, samplingConfig ) ); return new FusionIndexAccessor( slotSelector, new InstanceSelector<>( accessors ), indexId, descriptor, dropAction ); } @@ -119,7 +117,7 @@ public IndexAccessor getOnlineAccessor( long indexId, SchemaIndexDescriptor desc public String getPopulationFailure( long indexId, SchemaIndexDescriptor descriptor ) throws IllegalStateException { StringBuilder builder = new StringBuilder(); - forAll( p -> writeFailure( p.getClass().getSimpleName(), builder, p, indexId, descriptor ), providers ); + providers.forAll( p -> writeFailure( p.getClass().getSimpleName(), builder, p, indexId, descriptor ) ); String failure = builder.toString(); if ( !failure.isEmpty() ) { @@ -146,7 +144,7 @@ private void writeFailure( String indexName, StringBuilder builder, IndexProvide @Override public InternalIndexState getInitialState( long indexId, SchemaIndexDescriptor descriptor ) { - InternalIndexState[] states = instancesAs( providers, new InternalIndexState[INSTANCE_COUNT], p -> p.getInitialState( indexId, descriptor ) ); + InternalIndexState[] states = providers.instancesAs( new InternalIndexState[INSTANCE_COUNT], p -> p.getInitialState( indexId, descriptor ) ); if ( Arrays.stream( states ).anyMatch( state -> state == FAILED ) ) { // One of the state is FAILED, the whole state must be considered FAILED @@ -165,7 +163,7 @@ public InternalIndexState getInitialState( long indexId, SchemaIndexDescriptor d public IndexCapability getCapability( SchemaIndexDescriptor schemaIndexDescriptor ) { IndexCapability[] capabilities = - instancesAs( providers, new IndexCapability[INSTANCE_COUNT], provider -> provider.getCapability( schemaIndexDescriptor ) ); + providers.instancesAs( new IndexCapability[INSTANCE_COUNT], provider -> provider.getCapability( schemaIndexDescriptor ) ); return new UnionIndexCapability( capabilities ) { @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexReader.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexReader.java index 166e950f36a1..200c3e019280 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexReader.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexReader.java @@ -52,7 +52,7 @@ class FusionIndexReader extends FusionIndexBase implements IndexRea @Override public void close() { - forInstantiated( Resource::close, instanceSelector ); + instanceSelector.close( Resource::close ); } @Override @@ -64,15 +64,16 @@ public long countIndexedNodes( long nodeId, Value... propertyValues ) @Override public IndexSampler createSampler() { - return new FusionIndexSampler( instancesAs( new IndexSampler[INSTANCE_COUNT], IndexReader::createSampler ) ); + return new FusionIndexSampler( instanceSelector.instancesAs( new IndexSampler[INSTANCE_COUNT], IndexReader::createSampler ) ); } @Override public PrimitiveLongResourceIterator query( IndexQuery... predicates ) throws IndexNotApplicableKernelException { int slot = slotSelector.selectSlot( predicates, IndexQuery::valueGroup ); - return slot != UNKNOWN ? instanceSelector.select( slot ).query( predicates ) - : concat( instancesAs( new PrimitiveLongResourceIterator[INSTANCE_COUNT], reader -> reader.query( predicates ) ) ); + return slot != UNKNOWN + ? instanceSelector.select( slot ).query( predicates ) + : concat( instanceSelector.instancesAs( new PrimitiveLongResourceIterator[INSTANCE_COUNT], reader -> reader.query( predicates ) ) ); } @Override @@ -95,7 +96,7 @@ public void query( IndexProgressor.NodeValueClient cursor, IndexOrder indexOrder BridgingIndexProgressor multiProgressor = new BridgingIndexProgressor( cursor, descriptor.schema().getPropertyIds() ); cursor.initialize( descriptor, multiProgressor, predicates ); - forAll( reader -> reader.query( multiProgressor, indexOrder, predicates ), instanceSelector ); + instanceSelector.forAll( reader -> reader.query( multiProgressor, indexOrder, predicates ) ); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexUpdater.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexUpdater.java index f5f30d7524d1..bce9d040a986 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexUpdater.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexUpdater.java @@ -72,7 +72,7 @@ public void close() throws IOException, IndexEntryConflictException { try { - forInstantiated( IndexUpdater::close, instanceSelector ); + instanceSelector.close( IndexUpdater::close ); } catch ( IOException | IndexEntryConflictException | RuntimeException e ) { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/InstanceSelector.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/InstanceSelector.java index a8668a703bd3..ce6461b5dced 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/InstanceSelector.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/InstanceSelector.java @@ -21,6 +21,12 @@ import java.util.function.IntFunction; +import org.neo4j.function.ThrowingConsumer; +import org.neo4j.function.ThrowingFunction; +import org.neo4j.helpers.Exceptions; + +import static org.neo4j.kernel.impl.index.schema.fusion.SlotSelector.INSTANCE_COUNT; + /** * Selects an instance given a certain slot. * @param type of instance @@ -29,6 +35,7 @@ class InstanceSelector { private final T[] instances; private final IntFunction factory; + private boolean closed; /** * @param instances fully instantiated instances so that no factory is needed. @@ -51,17 +58,137 @@ class InstanceSelector this.factory = factory; } + /** + * Returns the instance at the given slot. Instantiating it if it hasn't already been instantiated. + * + * @param slot slot number to return instance for. + * @return the instance at the given {@code slot}. + */ T select( int slot ) { if ( instances[slot] == null ) { + assertOpen(); instances[slot] = factory.apply( slot ); } return instances[slot]; } + private void assertOpen() + { + if ( closed ) + { + throw new IllegalStateException( "This selector has been closed" ); + } + } + + /** + * Returns the instance at the given slot. If the instance at the given {@code slot} hasn't been instantiated yet, {@code null} is returned. + * + * @param slot slot number to return instance for. + * @return the instance at the given {@code slot}, or {@code null}. + */ T getIfInstantiated( int slot ) { return instances[slot]; } + + /** + * Convenience method typically for calling a method on each of the instances, a method that returns another type of instance. + * Even called on instances that haven't been instantiated yet. All those created instances are put into the provided array and returned. + * + * @param target array to put the created instances into, also returned. + * @param converter {@link ThrowingFunction} which converts from the source to target instance. + * @param type of returned instance. + * @param type of exception that converter may throw. + * @return the target array which was passed in, now populated. + * @throws E exception from converter. + */ + R[] instancesAs( R[] target, ThrowingFunction converter ) throws E + { + for ( int slot = 0; slot < INSTANCE_COUNT; slot++ ) + { + target[slot] = converter.apply( select( slot ) ); + } + return target; + } + + /** + * Convenience method for doing something to already instantiated instances. + * + * @param consumer {@link ThrowingConsumer} which performs some action on an instance. + * @param type of exception the action may throw. + * @throws E exception from action. + */ + void forInstantiated( ThrowingConsumer consumer ) throws E + { + E exception = null; + for ( int slot = 0; slot < INSTANCE_COUNT; slot++ ) + { + T instance = getIfInstantiated( slot ); + if ( instance != null ) + { + exception = consume( exception, consumer, instance ); + } + } + if ( exception != null ) + { + throw exception; + } + } + + /** + * Convenience method for doing something to all instances, even those that haven't already been instantiated. + * + * @param consumer {@link ThrowingConsumer} which performs some action on an instance. + * @param type of exception the action may throw. + * @throws E exception from action. + */ + void forAll( ThrowingConsumer consumer ) throws E + { + E exception = null; + for ( int slot = 0; slot < INSTANCE_COUNT; slot++ ) + { + exception = consume( exception, consumer, select( slot ) ); + } + if ( exception != null ) + { + throw exception; + } + } + + /** + * Perform a final action on instantiated instances and then closes this selector, preventing further instantiation. + * + * @param consumer {@link ThrowingConsumer} which performs some action on an instance. + * @param type of exception the action may throw. + * @throws E exception from action. + */ + void close( ThrowingConsumer consumer ) throws E + { + if ( !closed ) + { + try + { + forInstantiated( consumer ); + } + finally + { + closed = true; + } + } + } + + private static E consume( E exception, ThrowingConsumer consumer, T instance ) + { + try + { + consumer.accept( instance ); + } + catch ( Exception e ) + { + exception = Exceptions.chain( exception, (E) e ); + } + return exception; + } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/index/MultipleIndexPopulatorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/index/MultipleIndexPopulatorTest.java index c57d79d4f162..4a93933bf019 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/index/MultipleIndexPopulatorTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/index/MultipleIndexPopulatorTest.java @@ -27,6 +27,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.concurrent.Callable; import java.util.function.IntPredicate; @@ -53,9 +54,9 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -386,7 +387,7 @@ public void testMultiplePopulatorUpdater() throws IOException, IndexEntryConflic addPopulator( indexPopulator1, 1 ); addPopulator( indexPopulator2, 2 ); - doThrow( getPopulatorException() ).when( indexPopulator2 ) + doThrow( new UncheckedIOException( getPopulatorException() ) ).when( indexPopulator2 ) .newPopulatingUpdater( any( PropertyAccessor.class ) ); IndexUpdater multipleIndexUpdater = @@ -490,7 +491,7 @@ private IOException getPopulatorException() private void checkPopulatorFailure( IndexPopulator populator ) throws IOException { - verify( populator ).markAsFailed( startsWith( "java.io.IOException: something went wrong" ) ); + verify( populator ).markAsFailed( contains( "something went wrong" ) ); verify( populator ).close( false ); } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/TemporalIndexCacheTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/TemporalIndexCacheTest.java index 10711ed25557..666ad25bc9f2 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/TemporalIndexCacheTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/TemporalIndexCacheTest.java @@ -28,20 +28,16 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.neo4j.helpers.collection.Iterables; import org.neo4j.test.Race; -import org.neo4j.values.storable.CoordinateReferenceSystem; import org.neo4j.values.storable.ValueGroup; -import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.neo4j.helpers.collection.Iterables.count; @@ -132,7 +128,7 @@ public void stressInstantiationWithClose() throws Throwable }, 1 ); race.addContestant( () -> { - cache.shutInstantiateCloseLock(); + cache.closeInstantiateCloseLock(); instantiatedAtClose.setValue( count( cache ) ); }, 1 ); diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexAccessorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexAccessorTest.java index 07652c6d2000..a26c527ee80c 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexAccessorTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexAccessorTest.java @@ -251,20 +251,22 @@ public void closeMustCloseAll() throws Exception @Test public void closeMustThrowIfOneThrow() throws Exception { - for ( IndexAccessor accessor : aliveAccessors ) + for ( int i = 0; i < aliveAccessors.length; i++ ) { + IndexAccessor accessor = aliveAccessors[i]; verifyFusionCloseThrowOnSingleCloseThrow( accessor, fusionIndexAccessor ); - resetMocks(); + initiateMocks(); } } @Test public void closeMustCloseOthersIfOneThrow() throws Exception { - for ( IndexAccessor accessor : aliveAccessors ) + for ( int i = 0; i < aliveAccessors.length; i++ ) { + IndexAccessor accessor = aliveAccessors[i]; verifyOtherIsClosedOnSingleThrow( accessor, fusionIndexAccessor, without( aliveAccessors, accessor ) ); - resetMocks(); + initiateMocks(); } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexPopulatorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexPopulatorTest.java index 7ecae7c00eb0..3cb3baf2baa4 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexPopulatorTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexPopulatorTest.java @@ -301,9 +301,10 @@ private void closeAndVerifyPropagation( boolean populationCompletedSuccessfully @Test public void closeMustThrowIfCloseAnyThrow() throws Exception { - for ( IndexPopulator alivePopulator : alivePopulators ) + for ( int i = 0; i < alivePopulators.length; i++ ) { // given + IndexPopulator alivePopulator = alivePopulators[i]; IOException failure = new IOException( "fail" ); doThrow( failure ).when( alivePopulator ).close( anyBoolean() ); @@ -313,8 +314,7 @@ public void closeMustThrowIfCloseAnyThrow() throws Exception return null; } ); - // reset throw for testing of next populator - doAnswer( invocation -> null ).when( alivePopulator ).close( anyBoolean() ); + initiateMocks(); } } @@ -344,10 +344,11 @@ private void verifyOtherCloseOnThrow( IndexPopulator throwingPopulator ) throws @Test public void closeMustCloseOthersIfAnyThrow() throws Exception { - for ( IndexPopulator throwingPopulator : alivePopulators ) + for ( int i = 0; i < alivePopulators.length; i++ ) { + IndexPopulator throwingPopulator = alivePopulators[i]; verifyOtherCloseOnThrow( throwingPopulator ); - resetMocks(); + initiateMocks(); } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexUpdaterTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexUpdaterTest.java index 1830dcd05732..e3042ef86915 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexUpdaterTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/FusionIndexUpdaterTest.java @@ -327,20 +327,22 @@ public void closeMustCloseAll() throws Exception @Test public void closeMustThrowIfAnyThrow() throws Exception { - for ( IndexUpdater updater : aliveUpdaters ) + for ( int i = 0; i < aliveUpdaters.length; i++ ) { + IndexUpdater updater = aliveUpdaters[i]; FusionIndexTestHelp.verifyFusionCloseThrowOnSingleCloseThrow( updater, fusionIndexUpdater ); - resetMocks(); + initiateMocks(); } } @Test public void closeMustCloseOthersIfAnyThrow() throws Exception { - for ( IndexUpdater updater : aliveUpdaters ) + for ( int i = 0; i < aliveUpdaters.length; i++ ) { + IndexUpdater updater = aliveUpdaters[i]; FusionIndexTestHelp.verifyOtherIsClosedOnSingleThrow( updater, fusionIndexUpdater, without( aliveUpdaters, updater ) ); - resetMocks(); + initiateMocks(); } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/InstanceSelectorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/InstanceSelectorTest.java new file mode 100644 index 000000000000..9ba286da7e00 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/InstanceSelectorTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.index.schema.fusion; + +import org.junit.Test; + +import java.util.function.IntFunction; + +import org.neo4j.function.ThrowingConsumer; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.neo4j.kernel.impl.index.schema.fusion.SlotSelector.INSTANCE_COUNT; + +public class InstanceSelectorTest +{ + @Test + public void shouldInstantiateLazilyOnFirstSelect() + { + // given + IntFunction factory = mock( IntFunction.class ); + when( factory.apply( anyInt() ) ).then( invocationOnMock -> String.valueOf( (Integer) invocationOnMock.getArgument( 0 ) ) ); + InstanceSelector selector = new InstanceSelector<>( new String[INSTANCE_COUNT], factory ); + + // when + for ( int slot = 0; slot < INSTANCE_COUNT; slot++ ) + { + for ( int candidate = 0; candidate < INSTANCE_COUNT; candidate++ ) + { + // then + if ( candidate < slot ) + { + verify( factory, times( 1 ) ).apply( candidate ); + selector.select( candidate ); + verify( factory, times( 1 ) ).apply( candidate ); + } + else if ( candidate == slot ) + { + verify( factory, times( 0 ) ).apply( candidate ); + selector.select( candidate ); + verify( factory, times( 1 ) ).apply( candidate ); + } + else + { + assertNull( selector.getIfInstantiated( candidate ) ); + } + } + } + } + + @Test + public void shouldPerformActionOnInstantiated() + { + // given + IntFunction factory = mock( IntFunction.class ); + when( factory.apply( anyInt() ) ).then( invocationOnMock -> String.valueOf( (Integer) invocationOnMock.getArgument( 0 ) ) ); + InstanceSelector selector = new InstanceSelector<>( new String[INSTANCE_COUNT], factory ); + selector.select( 0 ); + selector.select( 2 ); + + // when + ThrowingConsumer consumer = mock( ThrowingConsumer.class ); + selector.forInstantiated( consumer ); + + // then + verify( consumer, times( 1 ) ).accept( "0" ); + verify( consumer, times( 1 ) ).accept( "2" ); + verifyNoMoreInteractions( consumer ); + } + + @Test + public void shouldPerformActionOnAll() + { + // given + IntFunction factory = mock( IntFunction.class ); + when( factory.apply( anyInt() ) ).then( invocationOnMock -> String.valueOf( (Integer) invocationOnMock.getArgument( 0 ) ) ); + InstanceSelector selector = new InstanceSelector<>( new String[INSTANCE_COUNT], factory ); + selector.select( 1 ); + + // when + ThrowingConsumer consumer = mock( ThrowingConsumer.class ); + selector.forAll( consumer ); + + // then + for ( int slot = 0; slot < INSTANCE_COUNT; slot++ ) + { + verify( consumer, times( 1 ) ).accept( String.valueOf( slot ) ); + } + verifyNoMoreInteractions( consumer ); + } + + @Test + public void shouldCloseAllInstantiated() + { + // given + IntFunction factory = mock( IntFunction.class ); + when( factory.apply( anyInt() ) ).then( invocationOnMock -> String.valueOf( (Integer) invocationOnMock.getArgument( 0 ) ) ); + InstanceSelector selector = new InstanceSelector<>( new String[INSTANCE_COUNT], factory ); + selector.select( 1 ); + selector.select( 3 ); + + // when + ThrowingConsumer consumer = mock( ThrowingConsumer.class ); + selector.close( consumer ); + + // then + verify( consumer, times( 1 ) ).accept( "1" ); + verify( consumer, times( 1 ) ).accept( "3" ); + verifyNoMoreInteractions( consumer ); + } + + @Test + public void shouldPreventInstantiationAfterClose() + { + // given + IntFunction factory = mock( IntFunction.class ); + when( factory.apply( anyInt() ) ).then( invocationOnMock -> String.valueOf( (Integer) invocationOnMock.getArgument( 0 ) ) ); + InstanceSelector selector = new InstanceSelector<>( new String[INSTANCE_COUNT], factory ); + selector.select( 1 ); + selector.select( 3 ); + + // when + selector.close( mock( ThrowingConsumer.class ) ); + + // then + try + { + selector.select( 0 ); + fail( "Should have failed" ); + } + catch ( IllegalStateException e ) + { + // then good + } + } +}