diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/id/FreeIdKeeper.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/id/FreeIdKeeper.java index a4f982f56b8e4..8f575f469aea8 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/id/FreeIdKeeper.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/id/FreeIdKeeper.java @@ -22,9 +22,8 @@ import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayDeque; -import java.util.Deque; +import org.neo4j.collection.primitive.PrimitiveLongArrayQueue; import org.neo4j.collection.primitive.PrimitiveLongCollections; import org.neo4j.io.fs.StoreChannel; import org.neo4j.kernel.impl.store.UnderlyingStorageException; @@ -50,8 +49,8 @@ public class FreeIdKeeper implements Closeable { private static final int ID_ENTRY_SIZE = Long.BYTES; - private final Deque freeIds = new ArrayDeque<>(); - private final Deque readFromDisk = new ArrayDeque<>(); + private final PrimitiveLongArrayQueue freeIds = new PrimitiveLongArrayQueue(); + private final PrimitiveLongArrayQueue readFromDisk = new PrimitiveLongArrayQueue(); private final StoreChannel channel; private final int batchSize; private final boolean aggressiveMode; @@ -103,7 +102,7 @@ static long countFreeIds( StoreChannel channel ) throws IOException public void freeId( long id ) { - freeIds.add( id ); + freeIds.enqueue( id ); freeIdCount++; if ( freeIds.size() >= batchSize ) @@ -133,7 +132,7 @@ public long getId() long result; if ( freeIds.size() > 0 && aggressiveMode ) { - result = freeIds.removeFirst(); + result = freeIds.dequeue(); freeIdCount--; } else @@ -158,7 +157,7 @@ public long[] getIds( int numberOfIds ) int cursor = 0; while ( (cursor < reusableIds) && !freeIds.isEmpty() ) { - ids[cursor++] = freeIds.removeFirst(); + ids[cursor++] = freeIds.dequeue(); } while ( cursor < reusableIds ) { @@ -176,7 +175,7 @@ private long getIdFromDisk() } if ( !readFromDisk.isEmpty() ) { - return readFromDisk.removeFirst(); + return readFromDisk.dequeue(); } else { @@ -224,7 +223,7 @@ private void readIdBatch0() throws IOException for ( int i = 0; i < idsRead; i++ ) { long id = readBuffer.getLong(); - readFromDisk.add( id ); + readFromDisk.enqueue( id ); } if ( aggressiveMode ) { @@ -253,7 +252,7 @@ private long flushFreeIds0( ByteBuffer writeBuffer ) throws IOException writeBuffer.clear(); while ( !freeIds.isEmpty() ) { - long id = freeIds.removeFirst(); + long id = freeIds.dequeue(); if ( id == NO_RESULT ) { continue; diff --git a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/PrimitiveLongArrayQueue.java b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/PrimitiveLongArrayQueue.java new file mode 100644 index 0000000000000..b0c49b55f1c72 --- /dev/null +++ b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/PrimitiveLongArrayQueue.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2002-2017 "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.collection.primitive; + +import java.util.NoSuchElementException; + +/** + * Simple array based FIFO queue for primitive longs. + * Newly enqueued element is added into the end of the queue, and dequeue will return + * element from the head of the queue. (See CLRS 10.1 for more detailed description) + * + * Queue capacity should always be power of two to be able to use + * '&' mask operation with {@link #values} length. + */ +public class PrimitiveLongArrayQueue implements PrimitiveLongCollection +{ + private static final int DEFAULT_CAPACITY = 16; + private long[] values; + private int head; + private int tail; + + public PrimitiveLongArrayQueue() + { + this( DEFAULT_CAPACITY ); + } + + PrimitiveLongArrayQueue( int capacity ) + { + assert (capacity != 0) && ((capacity & (capacity - 1)) == 0) : "Capacity should be power of 2."; + initValues( capacity ); + } + + @Override + public void visitKeys( PrimitiveLongVisitor visitor ) throws E + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() + { + return head == tail; + } + + @Override + public void clear() + { + initValues( DEFAULT_CAPACITY ); + } + + @Override + public int size() + { + return (tail - head) & (values.length - 1); + } + + @Override + public void close() + { + values = PrimitiveLongCollections.EMPTY_LONG_ARRAY; + } + + @Override + public PrimitiveLongIterator iterator() + { + return new PrimitiveLongArrayQueueIterator(); + } + + public long dequeue() + { + if ( isEmpty() ) + { + throw new IllegalStateException( "Fail to poll first element. Queue is empty." ); + } + long value = values[head]; + head = (head + 1) & (values.length - 1); + return value; + } + + public void enqueue( long value ) + { + values[tail] = value; + tail = (tail + 1) & (values.length - 1); + if ( tail == head ) + { + ensureCapacity(); + } + } + + public void addAll( PrimitiveLongArrayQueue otherQueue ) + { + while ( !otherQueue.isEmpty() ) + { + enqueue( otherQueue.dequeue() ); + } + } + + private void initValues( int capacity ) + { + values = new long[capacity]; + head = 0; + tail = 0; + } + + private void ensureCapacity() + { + int newCapacity = values.length << 1; + if ( newCapacity < 0 ) + { + throw new IllegalStateException( "Fail to increase capacity queue capacity." ); + } + long[] newValues = new long[newCapacity]; + int elementsFromHeadTillEnd = values.length - head; + System.arraycopy( values, head, newValues, 0, elementsFromHeadTillEnd ); + System.arraycopy( values, 0, newValues, elementsFromHeadTillEnd, head ); + tail = values.length; + head = 0; + values = newValues; + } + + private class PrimitiveLongArrayQueueIterator implements PrimitiveLongIterator + { + private int position; + + PrimitiveLongArrayQueueIterator() + { + this.position = head; + } + + @Override + public boolean hasNext() + { + return position != tail; + } + + @Override + public long next() + { + if ( hasNext() ) + { + long value = values[position]; + position = (position + 1) & (values.length - 1); + return value; + } + throw new NoSuchElementException(); + } + } +} diff --git a/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/PrimitiveLongArrayQueueTest.java b/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/PrimitiveLongArrayQueueTest.java new file mode 100644 index 0000000000000..4a70d44e2b4d8 --- /dev/null +++ b/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/PrimitiveLongArrayQueueTest.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2002-2017 "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.collection.primitive; + +import org.junit.Test; + +import java.util.NoSuchElementException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PrimitiveLongArrayQueueTest +{ + + @Test + public void newQueueIsEmpty() + { + assertTrue( createQueue().isEmpty() ); + } + + @Test + public void growQueueOnElementOffer() + { + PrimitiveLongArrayQueue longArrayQueue = createQueue(); + for ( int i = 1; i < 1000; i++ ) + { + longArrayQueue.enqueue( i ); + assertEquals( i, longArrayQueue.size() ); + } + } + + @Test + public void addRemoveElementKeepQueueEmpty() + { + PrimitiveLongArrayQueue longArrayQueue = createQueue(); + for ( int i = 0; i < 1000; i++ ) + { + longArrayQueue.enqueue( i ); + assertEquals( i, longArrayQueue.dequeue() ); + assertTrue( longArrayQueue.isEmpty() ); + } + } + + @Test + public void offerLessThenQueueCapacityElements() + { + PrimitiveLongArrayQueue arrayQueue = createQueue(); + for ( int i = 1; i < 16; i++ ) + { + arrayQueue.enqueue( i ); + assertEquals( i, arrayQueue.size() ); + } + } + + @Test( expected = IllegalStateException.class ) + public void failToRemoveElementFromNewEmptyQueue() + { + createQueue().dequeue(); + } + + @Test + public void offerMoreThenQueueCapacityElements() + { + PrimitiveLongArrayQueue arrayQueue = createQueue(); + for ( int i = 1; i < 1234; i++ ) + { + arrayQueue.enqueue( i ); + } + int currentValue = 1; + while ( !arrayQueue.isEmpty() ) + { + assertEquals( currentValue++, arrayQueue.dequeue() ); + } + } + + @Test + public void emptyQueueAfterClear() + { + PrimitiveLongArrayQueue queue = createQueue(); + queue.enqueue( 2 ); + queue.enqueue( 3 ); + assertFalse( queue.isEmpty() ); + assertEquals( 2, queue.size() ); + + queue.clear(); + + assertTrue( queue.isEmpty() ); + } + + @Test + public void tailBeforeHeadCorrectSize() + { + PrimitiveLongArrayQueue queue = createQueue(); + for ( int i = 0; i < 14; i++ ) + { + queue.enqueue( i ); + } + for ( int i = 0; i < 10; i++ ) + { + assertEquals( i, queue.dequeue() ); + } + for ( int i = 14; i < 24 ; i++ ) + { + queue.enqueue( i ); + } + + assertEquals( 14, queue.size() ); + } + + @Test + public void tailBeforeHeadCorrectResize() + { + PrimitiveLongArrayQueue queue = createQueue(); + for ( int i = 0; i < 14; i++ ) + { + queue.enqueue( i ); + } + for ( int i = 0; i < 10; i++ ) + { + assertEquals( i, queue.dequeue() ); + } + for ( int i = 14; i < 34 ; i++ ) + { + queue.enqueue( i ); + } + + assertEquals( 24, queue.size() ); + for ( int j = 10; j < 34; j++ ) + { + assertEquals( j, queue.dequeue() ); + } + } + + @Test + public void tailBeforeHeadCorrectIteration() + { + PrimitiveLongArrayQueue queue = createQueue(); + for ( int i = 0; i < 14; i++ ) + { + queue.enqueue( i ); + } + for ( int i = 0; i < 10; i++ ) + { + assertEquals( i, queue.dequeue() ); + } + for ( int i = 14; i < 24 ; i++ ) + { + queue.enqueue( i ); + } + + assertEquals( 14, queue.size() ); + PrimitiveLongIterator iterator = queue.iterator(); + for ( int j = 10; j < 24; j++ ) + { + assertTrue( iterator.hasNext() ); + assertEquals( j, iterator.next() ); + } + assertFalse( iterator.hasNext() ); + } + + @Test( expected = NoSuchElementException.class ) + public void failToGetNextOnEmptyQueueIterator() + { + createQueue().iterator().next(); + } + + @Test + public void addAllElementsFromOtherQueue() + { + PrimitiveLongArrayQueue queue = createQueue(); + queue.enqueue( 1 ); + queue.enqueue( 2 ); + PrimitiveLongArrayQueue otherQueue = createQueue(); + otherQueue.enqueue( 3 ); + otherQueue.enqueue( 4 ); + queue.addAll( otherQueue ); + + assertTrue( otherQueue.isEmpty() ); + assertEquals( 0, otherQueue.size() ); + assertEquals( 4, queue.size() ); + for ( int value = 1; value <= 4; value++ ) + { + assertEquals( value, queue.dequeue() ); + } + assertTrue( queue.isEmpty() ); + } + + @Test( expected = AssertionError.class ) + public void doNotAllowCreationOfQueueWithRandomCapacity() + { + new PrimitiveLongArrayQueue( 7 ); + } + + private PrimitiveLongArrayQueue createQueue() + { + return new PrimitiveLongArrayQueue(); + } +} diff --git a/tools/src/test/java/org/neo4j/tools/dump/TransactionLogAnalyzerTest.java b/tools/src/test/java/org/neo4j/tools/dump/TransactionLogAnalyzerTest.java index a8a2890241043..3d2ab6fc95d6c 100644 --- a/tools/src/test/java/org/neo4j/tools/dump/TransactionLogAnalyzerTest.java +++ b/tools/src/test/java/org/neo4j/tools/dump/TransactionLogAnalyzerTest.java @@ -27,13 +27,12 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import org.neo4j.collection.primitive.PrimitiveLongArrayQueue; import org.neo4j.io.fs.DefaultFileSystemAbstraction; import org.neo4j.kernel.impl.store.record.NodeRecord; import org.neo4j.kernel.impl.transaction.SimpleLogVersionRepository; @@ -272,12 +271,12 @@ private static class VerifyingMonitor implements Monitor private int checkpoints; private int logFiles; private long nextExpectedTxId = BASE_TX_ID + 1; - private final Deque expectedCheckpointsAt = new ArrayDeque<>(); + private final PrimitiveLongArrayQueue expectedCheckpointsAt = new PrimitiveLongArrayQueue(); private long nextExpectedLogVersion = BASE_TX_LOG_VERSION; void expectCheckpointAfter( long txId ) { - expectedCheckpointsAt.addLast( txId ); + expectedCheckpointsAt.enqueue( txId ); } @Override @@ -298,7 +297,7 @@ public void transaction( LogEntry[] transactionEntries ) public void checkpoint( CheckPoint checkpoint, LogPosition checkpointEntryPosition ) { checkpoints++; - Long expected = expectedCheckpointsAt.poll(); + Long expected = expectedCheckpointsAt.dequeue(); assertNotNull( "Unexpected checkpoint", expected ); assertEquals( expected.longValue(), nextExpectedTxId - 1 ); }