ARTEMIS-2602 Improve Journal loading heap usage

This commit is contained in:
Francesco Nigro 2020-01-16 14:04:40 +01:00 committed by Clebert Suconic
parent 2f68f002a8
commit 4cc6464ddd
6 changed files with 1825 additions and 18 deletions

View File

@ -81,6 +81,12 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,622 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.utils.collections;
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A hash set implementation of {@link Set<Long>} that uses open addressing values.
* To minimize the memory footprint, this class uses open addressing rather than chaining.
* Collisions are resolved using linear probing. Deletions implement compaction, so cost of
* remove can approach O(N) for full maps, which makes a small loadFactor recommended.
*
* The implementation is based on <a href=https://github.com/real-logic/agrona/blob/master/agrona/src/main/java/org/agrona/collections/IntHashSet.java>Agrona IntHashSet</a>
* but uses long primitive keys and a different {@link #MISSING_VALUE} to account for {@link Long#hashCode} being 0 for -1.
*/
public class LongHashSet extends AbstractSet<Long> implements Serializable {
/**
* The initial capacity used when none is specified in the constructor.
*/
public static final int DEFAULT_INITIAL_CAPACITY = 8;
public static final float DEFAULT_LOAD_FACTOR = 0.5f;
static final long MISSING_VALUE = -2;
private boolean containsMissingValue;
private final float loadFactor;
private int resizeThreshold;
// NB: excludes missing value
private int sizeOfArrayValues;
private long[] values;
/**
* Construct a hash set with {@link #DEFAULT_INITIAL_CAPACITY} and {@link #DEFAULT_LOAD_FACTOR}.
*/
public LongHashSet() {
this(DEFAULT_INITIAL_CAPACITY);
}
/**
* Construct a hash set with a proposed capacity and {@link #DEFAULT_LOAD_FACTOR}.
*
* @param proposedCapacity for the initial capacity of the set.
*/
public LongHashSet(final int proposedCapacity) {
this(proposedCapacity, DEFAULT_LOAD_FACTOR);
}
private static int hashIndex(long value, int mask) {
return hashCode(value) & mask;
}
private static int hashCode(long value) {
long hash = value * 31;
hash = (int) hash ^ (int) (hash >>> 32);
return (int) hash;
}
/**
* Construct a hash set with a proposed initial capacity and load factor.
*
* @param proposedCapacity for the initial capacity of the set.
* @param loadFactor to be used for resizing.
*/
public LongHashSet(final int proposedCapacity, final float loadFactor) {
if (loadFactor < 0.1f || loadFactor > 0.9f) {
throw new IllegalArgumentException("load factor must be in the range of 0.1 to 0.9: " + loadFactor);
}
this.loadFactor = loadFactor;
sizeOfArrayValues = 0;
final int capacity = findNextPositivePowerOfTwo(Math.max(DEFAULT_INITIAL_CAPACITY, proposedCapacity));
resizeThreshold = (int) (capacity * loadFactor);
values = new long[capacity];
Arrays.fill(values, MISSING_VALUE);
}
/**
* Get the load factor beyond which the set will increase size.
*
* @return load factor for when the set should increase size.
*/
public float loadFactor() {
return loadFactor;
}
/**
* Get the total capacity for the set to which the load factor with be a fraction of.
*
* @return the total capacity for the set.
*/
public int capacity() {
return values.length;
}
/**
* Get the actual threshold which when reached the map will resize.
* This is a function of the current capacity and load factor.
*
* @return the threshold when the map will resize.
*/
public int resizeThreshold() {
return resizeThreshold;
}
/**
* {@inheritDoc}
*/
@Override
public boolean add(final Long value) {
return add(value.longValue());
}
/**
* Primitive specialised overload of {this#add(Long)}
*
* @param value the value to add
* @return true if the collection has changed, false otherwise
* @throws IllegalArgumentException if value is missingValue
*/
public boolean add(final long value) {
if (value == MISSING_VALUE) {
final boolean previousContainsMissingValue = this.containsMissingValue;
containsMissingValue = true;
return !previousContainsMissingValue;
}
final long[] values = this.values;
final int mask = values.length - 1;
int index = hashIndex(value, mask);
while (values[index] != MISSING_VALUE) {
if (values[index] == value) {
return false;
}
index = next(index, mask);
}
values[index] = value;
sizeOfArrayValues++;
if (sizeOfArrayValues > resizeThreshold) {
increaseCapacity();
}
return true;
}
private void increaseCapacity() {
final int newCapacity = values.length * 2;
if (newCapacity < 0) {
throw new IllegalStateException("max capacity reached at size=" + size());
}
rehash(newCapacity);
}
private void rehash(final int newCapacity) {
final int capacity = newCapacity;
final int mask = newCapacity - 1;
resizeThreshold = (int) (newCapacity * loadFactor);
final long[] tempValues = new long[capacity];
Arrays.fill(tempValues, MISSING_VALUE);
for (final long value : values) {
if (value != MISSING_VALUE) {
int newHash = hashIndex(value, mask);
while (tempValues[newHash] != MISSING_VALUE) {
newHash = ++newHash & mask;
}
tempValues[newHash] = value;
}
}
values = tempValues;
}
/**
* {@inheritDoc}
*/
@Override
public boolean remove(final Object value) {
return value instanceof Long && remove(((Long) value).longValue());
}
/**
* An int specialised version of {this#remove(Object)}.
*
* @param value the value to remove
* @return true if the value was present, false otherwise
*/
public boolean remove(final long value) {
if (value == MISSING_VALUE) {
final boolean previousContainsMissingValue = this.containsMissingValue;
containsMissingValue = false;
return previousContainsMissingValue;
}
final long[] values = this.values;
final int mask = values.length - 1;
int index = hashIndex(value, mask);
while (values[index] != MISSING_VALUE) {
if (values[index] == value) {
values[index] = MISSING_VALUE;
compactChain(index);
sizeOfArrayValues--;
return true;
}
index = next(index, mask);
}
return false;
}
private static int next(final int index, final int mask) {
return (index + 1) & mask;
}
@SuppressWarnings("FinalParameters")
private void compactChain(int deleteIndex) {
final long[] values = this.values;
final int mask = values.length - 1;
int index = deleteIndex;
while (true) {
index = next(index, mask);
if (values[index] == MISSING_VALUE) {
return;
}
final int hash = hashIndex(values[index], mask);
if ((index < hash && (hash <= deleteIndex || deleteIndex <= index)) || (hash <= deleteIndex && deleteIndex <= index)) {
values[deleteIndex] = values[index];
values[index] = MISSING_VALUE;
deleteIndex = index;
}
}
}
/**
* Compact the backing arrays by rehashing with a capacity just larger than current size
* and giving consideration to the load factor.
*/
public void compact() {
final int idealCapacity = (int) Math.round(size() * (1.0 / loadFactor));
rehash(findNextPositivePowerOfTwo(Math.max(DEFAULT_INITIAL_CAPACITY, idealCapacity)));
}
/**
* {@inheritDoc}
*/
@Override
public boolean contains(final Object value) {
return value instanceof Long && contains(((Long) value).longValue());
}
/**
* Contains method that does not box values.
*
* @param value to be check for if the set contains it.
* @return true if the value is contained in the set otherwise false.
* @see Collection#contains(Object)
*/
public boolean contains(final long value) {
if (value == MISSING_VALUE) {
return containsMissingValue;
}
final long[] values = this.values;
final int mask = values.length - 1;
int index = hashIndex(value, mask);
while (values[index] != MISSING_VALUE) {
if (values[index] == value) {
return true;
}
index = next(index, mask);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
return sizeOfArrayValues + (containsMissingValue ? 1 : 0);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty() {
return size() == 0;
}
/**
* {@inheritDoc}
*/
@Override
public void clear() {
if (size() > 0) {
Arrays.fill(values, MISSING_VALUE);
sizeOfArrayValues = 0;
containsMissingValue = false;
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean addAll(final Collection<? extends Long> coll) {
boolean added = false;
for (final Long value : coll) {
added |= add(value);
}
return added;
}
/**
* {@inheritDoc}
*/
@Override
public boolean removeAll(final Collection<?> coll) {
boolean removed = false;
for (final Object value : coll) {
removed |= remove(value);
}
return removed;
}
/**
* {@inheritDoc}
*/
@Override
public LongIterator iterator() {
return new LongIterator().reset();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append('{');
for (final long value : values) {
if (value != MISSING_VALUE) {
sb.append(value).append(", ");
}
}
if (containsMissingValue) {
sb.append(MISSING_VALUE).append(", ");
}
if (sb.length() > 1) {
sb.setLength(sb.length() - 2);
}
sb.append('}');
return sb.toString();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(final T[] a) {
final Class<?> componentType = a.getClass().getComponentType();
if (!componentType.isAssignableFrom(Long.class)) {
throw new ArrayStoreException("cannot store Longs in array of type " + componentType);
}
final int size = size();
final T[] arrayCopy = a.length >= size ? a : (T[]) Array.newInstance(componentType, size);
copyValues(arrayCopy);
return arrayCopy;
}
/**
* {@inheritDoc}
*/
@Override
public Object[] toArray() {
final Object[] arrayCopy = new Object[size()];
copyValues(arrayCopy);
return arrayCopy;
}
private void copyValues(final Object[] arrayCopy) {
int i = 0;
final long[] values = this.values;
for (final long value : values) {
if (MISSING_VALUE != value) {
arrayCopy[i++] = value;
}
}
if (containsMissingValue) {
arrayCopy[sizeOfArrayValues] = MISSING_VALUE;
}
}
/**
* LongHashSet specialised variant of {this#containsAll(Collection)}.
*
* @param other int hash set to compare against.
* @return true if every element in other is in this.
*/
public boolean containsAll(final LongHashSet other) {
for (final long value : other.values) {
if (value != MISSING_VALUE && !contains(value)) {
return false;
}
}
return !other.containsMissingValue || this.containsMissingValue;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(final Object other) {
if (other == this) {
return true;
}
if (other instanceof LongHashSet) {
final LongHashSet otherSet = (LongHashSet) other;
return otherSet.containsMissingValue == containsMissingValue && otherSet.sizeOfArrayValues == sizeOfArrayValues && containsAll(otherSet);
}
if (!(other instanceof Set)) {
return false;
}
final Set<?> c = (Set<?>) other;
if (c.size() != size()) {
return false;
}
try {
return containsAll(c);
} catch (final ClassCastException | NullPointerException ignore) {
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hashCode = 0;
for (final long value : values) {
if (value != MISSING_VALUE) {
hashCode += Long.hashCode(value);
}
}
if (containsMissingValue) {
// Account negative hashcode
final int code = Long.hashCode(MISSING_VALUE);
hashCode += code;
}
return hashCode;
}
/**
* Iterator which supports unboxed access to values.
*/
public final class LongIterator implements Iterator<Long>, Serializable {
private int remaining;
private int positionCounter;
private int stopCounter;
private boolean isPositionValid = false;
LongIterator reset() {
remaining = size();
final long[] values = LongHashSet.this.values;
final int length = values.length;
int i = length;
if (values[length - 1] != LongHashSet.MISSING_VALUE) {
for (i = 0; i < length; i++) {
if (values[i] == LongHashSet.MISSING_VALUE) {
break;
}
}
}
stopCounter = i;
positionCounter = i + length;
isPositionValid = false;
return this;
}
@Override
public boolean hasNext() {
return remaining > 0;
}
public int remaining() {
return remaining;
}
@Override
public Long next() {
return nextValue();
}
/**
* Strongly typed alternative of {@link Iterator#next()} to avoid boxing.
*
* @return the next int value.
*/
public long nextValue() {
if (remaining == 1 && containsMissingValue) {
remaining = 0;
isPositionValid = true;
return LongHashSet.MISSING_VALUE;
}
findNext();
final long[] values = LongHashSet.this.values;
return values[position(values)];
}
@Override
public void remove() {
if (isPositionValid) {
if (0 == remaining && containsMissingValue) {
containsMissingValue = false;
} else {
final long[] values = LongHashSet.this.values;
final int position = position(values);
values[position] = MISSING_VALUE;
--sizeOfArrayValues;
compactChain(position);
}
isPositionValid = false;
} else {
throw new IllegalStateException();
}
}
private void findNext() {
final long[] values = LongHashSet.this.values;
final int mask = values.length - 1;
isPositionValid = true;
for (int i = positionCounter - 1; i >= stopCounter; i--) {
final int index = i & mask;
if (values[index] != LongHashSet.MISSING_VALUE) {
positionCounter = i;
--remaining;
return;
}
}
isPositionValid = false;
throw new NoSuchElementException();
}
private int position(final long[] values) {
return positionCounter & (values.length - 1);
}
}
}

View File

@ -0,0 +1,243 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.utils.collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* This list share the same motivation and structure of https://en.wikipedia.org/wiki/Unrolled_linked_list:
* it's a linked list of arrays/chunks of {@code T}.<br>
* Differently from an {@code UnrolledLinkedList} this list doesn't optimize addition and removal to achieve a balanced
* utilization among chunks ie a chunk is removed only if empty and chunks can't be merged.
* This list has been optimized for small-sized chunks (ideally <= 32 elements): this allow search/removal to
* be performed with a greedy approach despite a sparse chunk utilization (ie chunks contains few sparse elements)
*/
public final class SparseArrayLinkedList<T> {
// the whole chunk fit into 1 or 2 cache lines depending if JVM COOPS are used
private static final int SPARSE_ARRAY_DEFAULT_CAPACITY = 16;
private static final class SparseArray<T> {
private final Object[] elements;
private int size;
// index next to the last non null element
private int tail;
private SparseArray(int capacity) {
elements = new Object[capacity];
size = 0;
tail = 0;
}
private boolean add(T e) {
final int capacity = elements.length;
if (tail == capacity) {
return false;
}
elements[tail] = (T) e;
tail++;
size++;
return true;
}
private int remove(Predicate<? super T> filter) {
if (size == 0) {
// this shouldn't happen: the chunk should be removed if empty
return 0;
}
// this is allowed to make holes
// to save System::arrayCopy while removing elements
int removed = 0;
final Object[] elements = this.elements;
int visited = 0;
final int originalSize = size;
for (int i = 0, capacity = elements.length; i < capacity; i++) {
final T e = (T) elements[i];
if (e != null) {
if (filter.test(e)) {
elements[i] = null;
removed++;
} else {
// allows a weak form of compaction: incoming elements
// will be placed right after it
tail = i + 1;
}
visited++;
if (visited == originalSize) {
break;
}
}
}
size -= removed;
return removed;
}
public int clear(Consumer<? super T> consumer) {
final int originalSize = size;
if (originalSize == 0) {
return 0;
}
int visited = 0;
final Object[] elements = this.elements;
for (int i = 0, capacity = elements.length; i < capacity; i++) {
final T e = (T) elements[i];
if (e != null) {
if (consumer != null) {
consumer.accept(e);
}
elements[i] = null;
size--;
visited++;
if (visited == originalSize) {
break;
}
}
}
assert size == 0;
tail = 0;
return originalSize;
}
private int size() {
return size;
}
}
public static <T> long removeFromSparseArrayList(List<SparseArray<T>> sparseArrayList, Predicate<? super T> filter) {
if (filter == null) {
return 0;
}
long removed = 0;
Iterator<SparseArray<T>> iter = sparseArrayList.iterator();
while (iter.hasNext()) {
final SparseArray<T> sparseArray = iter.next();
final int justRemoved = sparseArray.remove(filter);
removed += justRemoved;
if (justRemoved > 0) {
// remove RecordInfo only if empty:
// it means that there is a chance of fragmentation
// proportional with the capacity of chunk
if (sparseArray.size() == 0) {
iter.remove();
}
}
}
return removed;
}
public static <T> void addToSparseArrayList(List<SparseArray<T>> sparseArrayList, T e, int sparseArrayCapacity) {
final int size = sparseArrayList.size();
// LinkedList::get(size-1) is fast as LinkedList::getLast
if (size == 0 || !sparseArrayList.get(size - 1).add(e)) {
final SparseArray<T> sparseArray = new SparseArray<>(sparseArrayCapacity);
sparseArray.add(e);
sparseArrayList.add(sparseArray);
}
}
public static <T> long clearSparseArrayList(List<SparseArray<T>> sparseArrayList, Consumer<? super T> consumer) {
final int size = sparseArrayList.size();
long count = 0;
if (size > 0) {
for (int i = 0; i < size; i++) {
// LinkedList::remove(0) is fast as LinkedList::getFirst
final SparseArray<T> removed = sparseArrayList.remove(0);
count += removed.clear(consumer);
}
}
return count;
}
private final LinkedList<SparseArray<T>> list;
private final int sparseArrayCapacity;
private long size;
public SparseArrayLinkedList() {
this(SPARSE_ARRAY_DEFAULT_CAPACITY);
}
public SparseArrayLinkedList(int sparseArrayCapacity) {
if (sparseArrayCapacity <= 0) {
throw new IllegalArgumentException("sparseArrayCapacity must be > 0");
}
list = new LinkedList<>();
size = 0;
this.sparseArrayCapacity = sparseArrayCapacity;
}
/**
* Appends {@code e} to the end of this list.
*/
public void add(T e) {
Objects.requireNonNull(e, "e cannot be null");
addToSparseArrayList(list, e, sparseArrayCapacity);
size++;
}
/**
* Removes any element of the list matching the given predicate.
*/
public long remove(Predicate<? super T> filter) {
if (size == 0) {
return 0;
}
final long removed = removeFromSparseArrayList(list, filter);
size -= removed;
assert size >= 0;
return removed;
}
/**
* Clear while consuming (using the given {@code consumer} all the elements of this list.
*/
public long clear(Consumer<? super T> consumer) {
if (size == 0) {
return 0;
}
final long removed = clearSparseArrayList(list, consumer);
assert removed == size;
size = 0;
return removed;
}
/**
* Returns the number of elements of this list.
*/
public long size() {
return size;
}
/**
* Returns the configured capacity of each sparse array/chunk.
*/
public int sparseArrayCapacity() {
return sparseArrayCapacity;
}
/**
* Returns the number of sparse arrays/chunks of this list.
*/
public int sparseArraysCount() {
return list.size();
}
}

View File

@ -0,0 +1,778 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.utils.collections;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsEqual.equalTo;
/**
* These tests are based on <a href="https://github.com/real-logic/agrona/blob/master/agrona/src/test/java/org/agrona/collections/IntHashSetTest.java">Agrona IntHashSetTest</a>
* to guarantee a similar coverage to what's provided for a similar collection.
*/
public class LongHashSetTest {
private static final int INITIAL_CAPACITY = 100;
private final LongHashSet testSet = new LongHashSet(INITIAL_CAPACITY);
@Test
public void initiallyContainsNoElements() {
for (long i = 0; i < 10_000; i++) {
Assert.assertFalse(testSet.contains(i));
}
}
@Test
public void initiallyContainsNoBoxedElements() {
for (long i = 0; i < 10_000; i++) {
Assert.assertFalse(testSet.contains(Long.valueOf(i)));
}
}
@Test
public void containsAddedElement() {
Assert.assertTrue(testSet.add(1L));
Assert.assertTrue(testSet.contains(1L));
}
@Test
public void addingAnElementTwiceDoesNothing() {
Assert.assertTrue(testSet.add(1L));
Assert.assertFalse(testSet.add(1L));
}
@Test
public void containsAddedBoxedElements() {
Assert.assertTrue(testSet.add(1L));
Assert.assertTrue(testSet.add(Long.valueOf(2L)));
Assert.assertTrue(testSet.contains(Long.valueOf(1L)));
Assert.assertTrue(testSet.contains(2L));
}
@Test
public void removingAnElementFromAnEmptyListDoesNothing() {
Assert.assertFalse(testSet.remove(0L));
}
@Test
public void removingAPresentElementRemovesIt() {
Assert.assertTrue(testSet.add(1L));
Assert.assertTrue(testSet.remove(1L));
Assert.assertFalse(testSet.contains(1L));
}
@Test
public void sizeIsInitiallyZero() {
Assert.assertEquals(0, testSet.size());
}
@Test
public void sizeIncrementsWithNumberOfAddedElements() {
addTwoElements(testSet);
Assert.assertEquals(2, testSet.size());
}
@Test
public void sizeContainsNumberOfNewElements() {
testSet.add(1L);
testSet.add(1L);
Assert.assertEquals(1, testSet.size());
}
@Test
public void iteratorsListElements() {
addTwoElements(testSet);
assertIteratorHasElements();
}
@Test
public void iteratorsStartFromTheBeginningEveryTime() {
iteratorsListElements();
assertIteratorHasElements();
}
@Test
public void iteratorsListElementsWithoutHasNext() {
addTwoElements(testSet);
assertIteratorHasElementsWithoutHasNext();
}
@Test
public void iteratorsStartFromTheBeginningEveryTimeWithoutHasNext() {
iteratorsListElementsWithoutHasNext();
assertIteratorHasElementsWithoutHasNext();
}
@Test(expected = NoSuchElementException.class)
public void iteratorsThrowNoSuchElementException() {
addTwoElements(testSet);
exhaustIterator();
}
@Test(expected = NoSuchElementException.class)
public void iteratorsThrowNoSuchElementExceptionFromTheBeginningEveryTime() {
addTwoElements(testSet);
try {
exhaustIterator();
} catch (final NoSuchElementException ignore) {
}
exhaustIterator();
}
@Test
public void iteratorHasNoElements() {
Assert.assertFalse(testSet.iterator().hasNext());
}
@Test(expected = NoSuchElementException.class)
public void iteratorThrowExceptionForEmptySet() {
testSet.iterator().next();
}
@Test
public void clearRemovesAllElementsOfTheSet() {
addTwoElements(testSet);
testSet.clear();
Assert.assertEquals(0, testSet.size());
Assert.assertFalse(testSet.contains(1L));
Assert.assertFalse(testSet.contains(1001L));
}
@Test
public void twoEmptySetsAreEqual() {
final LongHashSet other = new LongHashSet(100);
Assert.assertEquals(testSet, other);
}
@Test
public void setsWithTheSameValuesAreEqual() {
final LongHashSet other = new LongHashSet(100);
addTwoElements(testSet);
addTwoElements(other);
Assert.assertEquals(testSet, other);
}
@Test
public void setsWithTheDifferentSizesAreNotEqual() {
final LongHashSet other = new LongHashSet(100);
addTwoElements(testSet);
other.add(1001L);
Assert.assertNotEquals(testSet, other);
}
@Test
public void setsWithTheDifferentValuesAreNotEqual() {
final LongHashSet other = new LongHashSet(100);
addTwoElements(testSet);
other.add(2L);
other.add(1001L);
Assert.assertNotEquals(testSet, other);
}
@Test
public void twoEmptySetsHaveTheSameHashcode() {
Assert.assertEquals(testSet.hashCode(), new LongHashSet(100).hashCode());
}
@Test
public void setsWithTheSameValuesHaveTheSameHashcode() {
final LongHashSet other = new LongHashSet(100);
addTwoElements(testSet);
addTwoElements(other);
Assert.assertEquals(testSet.hashCode(), other.hashCode());
}
@Test
public void reducesSizeWhenElementRemoved() {
addTwoElements(testSet);
testSet.remove(1001L);
Assert.assertEquals(1, testSet.size());
}
@Test(expected = ArrayStoreException.class)
public void toArrayThrowsArrayStoreExceptionForWrongType() {
testSet.toArray(new String[1]);
}
@Test(expected = NullPointerException.class)
public void toArrayThrowsNullPointerExceptionForNullArgument() {
final Long[] into = null;
testSet.toArray(into);
}
@Test
public void toArrayCopiesElementsIntoSufficientlySizedArray() {
addTwoElements(testSet);
final Long[] result = testSet.toArray(new Long[testSet.size()]);
assertArrayContainingElements(result);
}
@Test
public void toArrayCopiesElementsIntoNewArray() {
addTwoElements(testSet);
final Long[] result = testSet.toArray(new Long[testSet.size()]);
assertArrayContainingElements(result);
}
@Test
public void toArraySupportsEmptyCollection() {
final Long[] result = testSet.toArray(new Long[testSet.size()]);
Assert.assertArrayEquals(result, new Long[]{});
}
// Test case from usage bug.
@Test
public void chainCompactionShouldNotCauseElementsToBeMovedBeforeTheirHash() {
final LongHashSet requiredFields = new LongHashSet(14);
requiredFields.add(8L);
requiredFields.add(9L);
requiredFields.add(35L);
requiredFields.add(49L);
requiredFields.add(56L);
Assert.assertTrue("Failed to remove 8", requiredFields.remove(8L));
Assert.assertTrue("Failed to remove 9", requiredFields.remove(9L));
assertThat(requiredFields, containsInAnyOrder(35L, 49L, 56L));
}
@Test
public void shouldResizeWhenItHitsCapacity() {
for (long i = 0; i < 2 * INITIAL_CAPACITY; i++) {
Assert.assertTrue(testSet.add(i));
}
for (long i = 0; i < 2 * INITIAL_CAPACITY; i++) {
Assert.assertTrue(testSet.contains(i));
}
}
@Test
public void containsEmptySet() {
final LongHashSet other = new LongHashSet(100);
Assert.assertTrue(testSet.containsAll(other));
Assert.assertTrue(testSet.containsAll((Collection<?>) other));
}
@Test
public void containsSubset() {
addTwoElements(testSet);
final LongHashSet subset = new LongHashSet(100);
subset.add(1L);
Assert.assertTrue(testSet.containsAll(subset));
Assert.assertTrue(testSet.containsAll((Collection<?>) subset));
}
@Test
public void doesNotContainDisjointSet() {
addTwoElements(testSet);
final LongHashSet other = new LongHashSet(100);
other.add(1L);
other.add(1002L);
Assert.assertFalse(testSet.containsAll(other));
Assert.assertFalse(testSet.containsAll((Collection<?>) other));
}
@Test
public void doesNotContainSuperset() {
addTwoElements(testSet);
final LongHashSet superset = new LongHashSet(100);
addTwoElements(superset);
superset.add(15L);
Assert.assertFalse(testSet.containsAll(superset));
Assert.assertFalse(testSet.containsAll((Collection<?>) superset));
}
@Test
public void addingEmptySetDoesNothing() {
addTwoElements(testSet);
Assert.assertFalse(testSet.addAll(new LongHashSet(100)));
Assert.assertFalse(testSet.addAll(new HashSet<>()));
assertContainsElements(testSet);
}
@Test
public void addingSubsetDoesNothing() {
addTwoElements(testSet);
final LongHashSet subset = new LongHashSet(100);
subset.add(1L);
final HashSet<Long> subSetCollection = new HashSet<>(subset);
Assert.assertFalse(testSet.addAll(subset));
Assert.assertFalse(testSet.addAll(subSetCollection));
assertContainsElements(testSet);
}
@Test
public void addingEqualSetDoesNothing() {
addTwoElements(testSet);
final LongHashSet equal = new LongHashSet(100);
addTwoElements(equal);
final HashSet<Long> equalCollection = new HashSet<>(equal);
Assert.assertFalse(testSet.addAll(equal));
Assert.assertFalse(testSet.addAll(equalCollection));
assertContainsElements(testSet);
}
@Test
public void containsValuesAddedFromDisjointSetPrimitive() {
addTwoElements(testSet);
final LongHashSet disjoint = new LongHashSet(100);
disjoint.add(2L);
disjoint.add(1002L);
Assert.assertTrue(testSet.addAll(disjoint));
Assert.assertTrue(testSet.contains(1L));
Assert.assertTrue(testSet.contains(1001L));
Assert.assertTrue(testSet.containsAll(disjoint));
}
@Test
public void containsValuesAddedFromDisjointSet() {
addTwoElements(testSet);
final HashSet<Long> disjoint = new HashSet<>();
disjoint.add(2L);
disjoint.add(1002L);
Assert.assertTrue(testSet.addAll(disjoint));
Assert.assertTrue(testSet.contains(1L));
Assert.assertTrue(testSet.contains(1001L));
Assert.assertTrue(testSet.containsAll(disjoint));
}
@Test
public void containsValuesAddedFromIntersectingSetPrimitive() {
addTwoElements(testSet);
final LongHashSet intersecting = new LongHashSet(100);
intersecting.add(1L);
intersecting.add(1002L);
Assert.assertTrue(testSet.addAll(intersecting));
Assert.assertTrue(testSet.contains(1L));
Assert.assertTrue(testSet.contains(1001L));
Assert.assertTrue(testSet.containsAll(intersecting));
}
@Test
public void containsValuesAddedFromIntersectingSet() {
addTwoElements(testSet);
final HashSet<Long> intersecting = new HashSet<>();
intersecting.add(1L);
intersecting.add(1002L);
Assert.assertTrue(testSet.addAll(intersecting));
Assert.assertTrue(testSet.contains(1L));
Assert.assertTrue(testSet.contains(1001L));
Assert.assertTrue(testSet.containsAll(intersecting));
}
@Test
public void removingEmptySetDoesNothing() {
addTwoElements(testSet);
Assert.assertFalse(testSet.removeAll(new LongHashSet(100)));
Assert.assertFalse(testSet.removeAll(new HashSet<Long>()));
assertContainsElements(testSet);
}
@Test
public void removingDisjointSetDoesNothing() {
addTwoElements(testSet);
final LongHashSet disjoint = new LongHashSet(100);
disjoint.add(2L);
disjoint.add(1002L);
Assert.assertFalse(testSet.removeAll(disjoint));
Assert.assertFalse(testSet.removeAll(new HashSet<Long>()));
assertContainsElements(testSet);
}
@Test
public void doesNotContainRemovedIntersectingSetPrimitive() {
addTwoElements(testSet);
final LongHashSet intersecting = new LongHashSet(100);
intersecting.add(1L);
intersecting.add(1002L);
Assert.assertTrue(testSet.removeAll(intersecting));
Assert.assertTrue(testSet.contains(1001L));
Assert.assertFalse(testSet.containsAll(intersecting));
}
@Test
public void doesNotContainRemovedIntersectingSet() {
addTwoElements(testSet);
final HashSet<Long> intersecting = new HashSet<>();
intersecting.add(1L);
intersecting.add(1002L);
Assert.assertTrue(testSet.removeAll(intersecting));
Assert.assertTrue(testSet.contains(1001L));
Assert.assertFalse(testSet.containsAll(intersecting));
}
@Test
public void isEmptyAfterRemovingEqualSetPrimitive() {
addTwoElements(testSet);
final LongHashSet equal = new LongHashSet(100);
addTwoElements(equal);
Assert.assertTrue(testSet.removeAll(equal));
Assert.assertTrue(testSet.isEmpty());
}
@Test
public void isEmptyAfterRemovingEqualSet() {
addTwoElements(testSet);
final HashSet<Long> equal = new HashSet<>();
addTwoElements(equal);
Assert.assertTrue(testSet.removeAll(equal));
Assert.assertTrue(testSet.isEmpty());
}
@Test
public void removeElementsFromIterator() {
addTwoElements(testSet);
final LongHashSet.LongIterator iterator = testSet.iterator();
while (iterator.hasNext()) {
if (iterator.nextValue() == 1L) {
iterator.remove();
}
}
assertThat(testSet, contains(1001L));
assertThat(testSet, hasSize(1));
}
@Test
public void shouldNotContainMissingValueInitially() {
Assert.assertFalse(testSet.contains(LongHashSet.MISSING_VALUE));
}
@Test
public void shouldAllowMissingValue() {
Assert.assertTrue(testSet.add(LongHashSet.MISSING_VALUE));
Assert.assertTrue(testSet.contains(LongHashSet.MISSING_VALUE));
Assert.assertFalse(testSet.add(LongHashSet.MISSING_VALUE));
}
@Test
public void shouldAllowRemovalOfMissingValue() {
Assert.assertTrue(testSet.add(LongHashSet.MISSING_VALUE));
Assert.assertTrue(testSet.remove(LongHashSet.MISSING_VALUE));
Assert.assertFalse(testSet.contains(LongHashSet.MISSING_VALUE));
Assert.assertFalse(testSet.remove(LongHashSet.MISSING_VALUE));
}
@Test
public void sizeAccountsForMissingValue() {
testSet.add(1L);
testSet.add(LongHashSet.MISSING_VALUE);
Assert.assertEquals(2, testSet.size());
}
@Test
public void toArrayCopiesElementsIntoNewArrayIncludingMissingValue() {
addTwoElements(testSet);
testSet.add(LongHashSet.MISSING_VALUE);
final Long[] result = testSet.toArray(new Long[testSet.size()]);
assertThat(result, arrayContainingInAnyOrder(1L, 1001L, LongHashSet.MISSING_VALUE));
}
@Test
public void toObjectArrayCopiesElementsIntoNewArrayIncludingMissingValue() {
addTwoElements(testSet);
testSet.add(LongHashSet.MISSING_VALUE);
final Object[] result = testSet.toArray();
assertThat(result, arrayContainingInAnyOrder(1L, 1001L, LongHashSet.MISSING_VALUE));
}
@Test
public void equalsAccountsForMissingValue() {
addTwoElements(testSet);
testSet.add(LongHashSet.MISSING_VALUE);
final LongHashSet other = new LongHashSet(100);
addTwoElements(other);
Assert.assertNotEquals(testSet, other);
other.add(LongHashSet.MISSING_VALUE);
Assert.assertEquals(testSet, other);
testSet.remove(LongHashSet.MISSING_VALUE);
Assert.assertNotEquals(testSet, other);
}
@Test
public void consecutiveValuesShouldBeCorrectlyStored() {
for (long i = 0; i < 10_000; i++) {
testSet.add(i);
}
assertThat(testSet, hasSize(10_000));
int distinctElements = 0;
for (final long ignore : testSet) {
distinctElements++;
}
assertThat(distinctElements, is(10_000));
}
@Test
public void hashCodeAccountsForMissingValue() {
addTwoElements(testSet);
testSet.add(LongHashSet.MISSING_VALUE);
final LongHashSet other = new LongHashSet(100);
addTwoElements(other);
Assert.assertNotEquals(testSet.hashCode(), other.hashCode());
other.add(LongHashSet.MISSING_VALUE);
Assert.assertEquals(testSet.hashCode(), other.hashCode());
testSet.remove(LongHashSet.MISSING_VALUE);
Assert.assertNotEquals(testSet.hashCode(), other.hashCode());
}
@Test
public void iteratorAccountsForMissingValue() {
addTwoElements(testSet);
testSet.add(LongHashSet.MISSING_VALUE);
int missingValueCount = 0;
final LongHashSet.LongIterator iterator = testSet.iterator();
while (iterator.hasNext()) {
if (iterator.nextValue() == LongHashSet.MISSING_VALUE) {
missingValueCount++;
}
}
Assert.assertEquals(1, missingValueCount);
}
@Test
public void iteratorCanRemoveMissingValue() {
addTwoElements(testSet);
testSet.add(LongHashSet.MISSING_VALUE);
final LongHashSet.LongIterator iterator = testSet.iterator();
while (iterator.hasNext()) {
if (iterator.nextValue() == LongHashSet.MISSING_VALUE) {
iterator.remove();
}
}
Assert.assertFalse(testSet.contains(LongHashSet.MISSING_VALUE));
}
@Test
public void shouldGenerateStringRepresentation() {
final long[] testEntries = {3L, 1L, -2L, 19L, 7L, 11L, 12L, 7L};
for (final long testEntry : testEntries) {
testSet.add(testEntry);
}
final String mapAsAString = "{1, 19, 11, 7, 3, 12, -2}";
assertThat(testSet.toString(), equalTo(mapAsAString));
}
@Test
public void shouldRemoveMissingValueWhenCleared() {
Assert.assertTrue(testSet.add(LongHashSet.MISSING_VALUE));
testSet.clear();
Assert.assertFalse(testSet.contains(LongHashSet.MISSING_VALUE));
}
@Test
public void shouldHaveCompatibleEqualsAndHashcode() {
final HashSet<Long> compatibleSet = new HashSet<>();
final long seed = System.nanoTime();
final Random r = new Random(seed);
for (long i = 0; i < 1024; i++) {
final long value = r.nextLong();
compatibleSet.add(value);
testSet.add(value);
}
if (r.nextBoolean()) {
compatibleSet.add(LongHashSet.MISSING_VALUE);
testSet.add(LongHashSet.MISSING_VALUE);
}
Assert.assertEquals("Fail with seed:" + seed, testSet, compatibleSet);
Assert.assertEquals("Fail with seed:" + seed, compatibleSet, testSet);
Assert.assertEquals("Fail with seed:" + seed, compatibleSet.hashCode(), testSet.hashCode());
}
private static void addTwoElements(final LongHashSet obj) {
obj.add(1L);
obj.add(1001L);
}
private static void addTwoElements(final HashSet<Long> obj) {
obj.add(1L);
obj.add(1001L);
}
private void assertIteratorHasElements() {
final Iterator<Long> iter = testSet.iterator();
final Set<Long> values = new HashSet<>();
Assert.assertTrue(iter.hasNext());
values.add(iter.next());
Assert.assertTrue(iter.hasNext());
values.add(iter.next());
Assert.assertFalse(iter.hasNext());
assertContainsElements(values);
}
private void assertIteratorHasElementsWithoutHasNext() {
final Iterator<Long> iter = testSet.iterator();
final Set<Long> values = new HashSet<>();
values.add(iter.next());
values.add(iter.next());
assertContainsElements(values);
}
private static void assertArrayContainingElements(final Long[] result) {
assertThat(result, arrayContainingInAnyOrder(1L, 1001L));
}
private static void assertContainsElements(final Set<Long> other) {
assertThat(other, containsInAnyOrder(1L, 1001L));
}
private void exhaustIterator() {
final Iterator iterator = testSet.iterator();
iterator.next();
iterator.next();
iterator.next();
}
}

View File

@ -0,0 +1,158 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.activemq.artemis.utils.collections;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
public class SparseArrayLinkedListTest {
private static final int SPARSE_ARRAY_CAPACITY = 4;
private static final int ELEMENTS = SPARSE_ARRAY_CAPACITY * 4;
private final SparseArrayLinkedList<Integer> list;
public SparseArrayLinkedListTest() {
list = new SparseArrayLinkedList<>(SPARSE_ARRAY_CAPACITY);
}
@Test(expected = IllegalArgumentException.class)
public void shouldFailToCreateZeroArrayCapacityCollection() {
new SparseArrayLinkedList<>(0);
}
@Test(expected = IllegalArgumentException.class)
public void shouldFailToCreateNegativeArrayCapacityCollection() {
new SparseArrayLinkedList<>(-1);
}
@Test(expected = NullPointerException.class)
public void shouldFailToAddNull() {
list.add(null);
}
@Test
public void shouldNumberOfElementsBeTheSameOfTheAddedElements() {
final int elements = ELEMENTS;
for (int i = 0; i < elements; i++) {
Assert.assertEquals(i, list.size());
list.add(i);
}
Assert.assertEquals(elements, list.size());
}
@Test
public void shouldClearConsumeElementsInOrder() {
final int elements = ELEMENTS;
Assert.assertEquals(0, list.clear(null));
final ArrayList<Integer> expected = new ArrayList<>(elements);
for (int i = 0; i < elements; i++) {
final Integer added = i;
list.add(added);
expected.add(added);
}
final List<Integer> removed = new ArrayList<>(elements);
Assert.assertEquals(elements, list.clear(removed::add));
Assert.assertEquals(0, list.sparseArraysCount());
Assert.assertEquals(0, list.size());
Assert.assertThat(removed, is(expected));
}
@Test
public void shouldRemoveMatchingElements() {
final int elements = ELEMENTS;
for (int i = 0; i < elements; i++) {
list.add(i);
}
Assert.assertEquals(1, list.remove(e -> e.intValue() == 0));
Assert.assertEquals(elements - 1, list.size());
Assert.assertEquals(0, list.remove(e -> e.intValue() == 0));
Assert.assertEquals(elements - 1, list.size());
Assert.assertEquals(elements - 1, list.remove(e -> true));
Assert.assertEquals(0, list.size());
Assert.assertEquals(0, list.remove(e -> true));
Assert.assertEquals(0, list.sparseArraysCount());
}
@Test
public void shouldRemoveDetachSparseArrays() {
final int elements = list.sparseArrayCapacity() * 3;
for (int i = 0; i < elements; i++) {
list.add(i);
}
// remove elements in the middle
final int startInclusiveMiddle = list.sparseArrayCapacity();
final int endNotInclusiveMiddle = startInclusiveMiddle + list.sparseArrayCapacity();
Assert.assertEquals(list.sparseArrayCapacity(),
list.remove(e -> e.intValue() >= startInclusiveMiddle && e.intValue() < endNotInclusiveMiddle));
Assert.assertEquals(2, list.sparseArraysCount());
// remove elements at the beginning
final int startInclusiveFirst = 0;
final int endNotInclusiveFirst = startInclusiveMiddle;
Assert.assertEquals(list.sparseArrayCapacity(),
list.remove(e -> e.intValue() >= startInclusiveFirst && e.intValue() < endNotInclusiveFirst));
Assert.assertEquals(1, list.sparseArraysCount());
// remove all elements at the end
final int startInclusiveLast = endNotInclusiveMiddle;
final int endNotInclusiveLast = elements;
Assert.assertEquals(list.sparseArrayCapacity(),
list.remove(e -> e.intValue() >= startInclusiveLast && e.intValue() < endNotInclusiveLast));
Assert.assertEquals(0, list.sparseArraysCount());
}
@Test
public void shouldAddAfterRemoveAtTheEndReusingTheAvailableSpace() {
final int elements = list.sparseArrayCapacity();
for (int i = 0; i < elements; i++) {
list.add(i);
}
Assert.assertEquals(1, list.sparseArraysCount());
// removing last element
Assert.assertEquals(1, list.remove(e -> e.intValue() == elements - 1));
list.add(elements - 1);
Assert.assertEquals(1, list.sparseArraysCount());
Assert.assertEquals(1, list.remove(e -> e.intValue() == 0));
list.add(elements);
Assert.assertEquals(2, list.sparseArraysCount());
}
@Test
public void shouldClearConsumeRemainingElementsInOrder() {
final int elements = ELEMENTS;
final Integer zero = 0;
list.add(zero);
for (int i = 1; i < elements; i++) {
list.add(i);
}
Assert.assertEquals(elements - 1, list.remove(e -> e != zero));
final ArrayList<Integer> remaining = new ArrayList<>();
Assert.assertEquals(1, list.clear(remaining::add));
Assert.assertEquals(0, list.size());
Assert.assertEquals(1, remaining.size());
Assert.assertEquals(zero, remaining.get(0));
}
}

View File

@ -24,13 +24,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
@ -44,6 +40,8 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
@ -82,6 +80,8 @@ import org.apache.activemq.artemis.utils.actors.OrderedExecutorFactory;
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet;
import org.apache.activemq.artemis.utils.collections.ConcurrentLongHashMap;
import org.apache.activemq.artemis.utils.collections.ConcurrentLongHashSet;
import org.apache.activemq.artemis.utils.collections.LongHashSet;
import org.apache.activemq.artemis.utils.collections.SparseArrayLinkedList;
import org.jboss.logging.Logger;
import static org.apache.activemq.artemis.core.journal.impl.Reclaimer.scan;
@ -1449,9 +1449,11 @@ public class JournalImpl extends JournalBase implements TestableJournal, Journal
final List<PreparedTransactionInfo> preparedTransactions,
final TransactionFailureCallback failureCallback,
final boolean fixBadTX) throws Exception {
final Set<Long> recordsToDelete = new HashSet<>();
final LongHashSet recordsToDelete = new LongHashSet(1024);
final Predicate<RecordInfo> toDeleteFilter = recordInfo -> recordsToDelete.contains(recordInfo.id);
// ArrayList was taking too long to delete elements on checkDeleteSize
final List<RecordInfo> records = new LinkedList<>();
// and LinkedList<RecordInfo> creates too many nodes
final SparseArrayLinkedList<RecordInfo> records = new SparseArrayLinkedList<>();
final int DELETE_FLUSH = 20000;
@ -1461,18 +1463,15 @@ public class JournalImpl extends JournalBase implements TestableJournal, Journal
private void checkDeleteSize() {
// HORNETQ-482 - Flush deletes only if memory is critical
if (recordsToDelete.size() > DELETE_FLUSH && runtime.freeMemory() < runtime.maxMemory() * 0.2) {
logger.debug("Flushing deletes during loading, deleteCount = " + recordsToDelete.size());
if (logger.isDebugEnabled()) {
logger.debugf("Flushing deletes during loading, deleteCount = %d", recordsToDelete.size());
}
// Clean up when the list is too large, or it won't be possible to load large sets of files
// Done as part of JBMESSAGING-1678
Iterator<RecordInfo> iter = records.iterator();
while (iter.hasNext()) {
RecordInfo record = iter.next();
if (recordsToDelete.contains(record.id)) {
iter.remove();
final long removed = records.remove(toDeleteFilter);
if (logger.isDebugEnabled()) {
logger.debugf("Removed records during loading = %d", removed);
}
}
recordsToDelete.clear();
logger.debug("flush delete done");
@ -1513,12 +1512,13 @@ public class JournalImpl extends JournalBase implements TestableJournal, Journal
}
}, fixBadTX, null);
for (RecordInfo record : records) {
final Consumer<RecordInfo> fillCommittedRecord = record -> {
if (!recordsToDelete.contains(record.id)) {
committedRecords.add(record);
}
}
};
// it helps GC by cleaning up each SparseArray too
records.clear(fillCommittedRecord);
return info;
}