blob: de2e3fcfda97f280514468d1704ea3f7659d4003 [file] [log] [blame]
/*
* 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.geode.internal.offheap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.apache.logging.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.apache.geode.OutOfOffHeapMemoryException;
import org.apache.geode.distributed.internal.DistributionConfig;
public class FreeListManagerTest {
private final int DEFAULT_SLAB_SIZE = 1024 * 1024 * 5;
private final MemoryAllocatorImpl ma = mock(MemoryAllocatorImpl.class);
private final OffHeapMemoryStats stats = mock(OffHeapMemoryStats.class);
private TestableFreeListManager freeListManager;
@Before
public void setUp() throws Exception {
when(ma.getStats()).thenReturn(stats);
}
@After
public void tearDown() throws Exception {
if (this.freeListManager != null) {
this.freeListManager.freeSlabs();
}
}
private static TestableFreeListManager createFreeListManager(MemoryAllocatorImpl ma,
Slab[] slabs) {
return new TestableFreeListManager(ma, slabs);
}
private static TestableFreeListManager createFreeListManager(MemoryAllocatorImpl ma, Slab[] slabs,
int maxCombine) {
return new TestableFreeListManager(ma, slabs, maxCombine);
}
private void setUpSingleSlabManager() {
setUpSingleSlabManager(DEFAULT_SLAB_SIZE);
}
private void setUpSingleSlabManager(int slabSize) {
Slab slab = new SlabImpl(slabSize);
this.freeListManager = createFreeListManager(ma, new Slab[] {slab});
}
@Test
public void usedMemoryIsZeroOnDefault() {
setUpSingleSlabManager();
assertThat(this.freeListManager.getUsedMemory()).isZero();
}
@Test
public void freeMemoryIsSlabSizeOnDefault() {
setUpSingleSlabManager();
assertThat(this.freeListManager.getFreeMemory()).isEqualTo(DEFAULT_SLAB_SIZE);
}
@Test
public void totalMemoryIsSlabSizeOnDefault() {
setUpSingleSlabManager();
assertThat(this.freeListManager.getTotalMemory()).isEqualTo(DEFAULT_SLAB_SIZE);
}
@Test
public void allocateTinyChunkHasCorrectSize() {
setUpSingleSlabManager();
int tinySize = 10;
OffHeapStoredObject c = this.freeListManager.allocate(tinySize);
validateChunkSizes(c, tinySize);
}
private void validateChunkSizes(OffHeapStoredObject c, int dataSize) {
assertThat(c).isNotNull();
assertThat(c.getDataSize()).isEqualTo(dataSize);
assertThat(c.getSize()).isEqualTo(computeExpectedSize(dataSize));
}
@Test
public void allocateTinyChunkFromFreeListHasCorrectSize() {
setUpSingleSlabManager();
int tinySize = 10;
OffHeapStoredObject c = this.freeListManager.allocate(tinySize);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
c = this.freeListManager.allocate(tinySize);
validateChunkSizes(c, tinySize);
}
@Test
public void allocateTinyChunkFromEmptyFreeListHasCorrectSize() {
setUpSingleSlabManager();
int dataSize = 10;
OffHeapStoredObject c = this.freeListManager.allocate(dataSize);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
this.freeListManager.allocate(dataSize);
// free list will now be empty
c = this.freeListManager.allocate(dataSize);
validateChunkSizes(c, dataSize);
}
@Test
public void allocateHugeChunkHasCorrectSize() {
setUpSingleSlabManager();
int hugeSize = FreeListManager.MAX_TINY + 1;
OffHeapStoredObject c = this.freeListManager.allocate(hugeSize);
validateChunkSizes(c, hugeSize);
}
@Test
public void allocateHugeChunkFromEmptyFreeListHasCorrectSize() {
setUpSingleSlabManager();
int dataSize = FreeListManager.MAX_TINY + 1;
OffHeapStoredObject c = this.freeListManager.allocate(dataSize);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
this.freeListManager.allocate(dataSize);
// free list will now be empty
c = this.freeListManager.allocate(dataSize);
validateChunkSizes(c, dataSize);
}
@Test
public void allocateHugeChunkFromFragmentWithItemInFreeListHasCorrectSize() {
setUpSingleSlabManager();
int dataSize = FreeListManager.MAX_TINY + 1 + 1024;
OffHeapStoredObject c = this.freeListManager.allocate(dataSize);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
dataSize = FreeListManager.MAX_TINY + 1;
c = this.freeListManager.allocate(dataSize);
validateChunkSizes(c, dataSize);
}
@Test
public void freeTinyMemoryDefault() {
setUpSingleSlabManager();
assertThat(this.freeListManager.getFreeTinyMemory()).isZero();
}
@Test
public void freeTinyMemoryEqualToChunkSize() {
setUpSingleSlabManager();
int dataSize = 10;
OffHeapStoredObject c = this.freeListManager.allocate(dataSize);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
assertThat(this.freeListManager.getFreeTinyMemory()).isEqualTo(computeExpectedSize(dataSize));
}
@Test
public void freeTinyMemoryWithTwoTinyFreeListsEqualToChunkSize() {
setUpSingleSlabManager();
int dataSize = 10;
OffHeapStoredObject c = this.freeListManager.allocate(dataSize);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
int dataSize2 = 100;
OffHeapStoredObject c2 = this.freeListManager.allocate(dataSize2);
OffHeapStoredObject.release(c2.getAddress(), this.freeListManager);
assertThat(this.freeListManager.getFreeTinyMemory())
.isEqualTo(computeExpectedSize(dataSize) + computeExpectedSize(dataSize2));
}
@Test
public void freeHugeMemoryDefault() {
setUpSingleSlabManager();
assertThat(this.freeListManager.getFreeHugeMemory()).isZero();
}
@Test
public void freeHugeMemoryEqualToChunkSize() {
setUpSingleSlabManager();
int dataSize = FreeListManager.MAX_TINY + 1 + 1024;
OffHeapStoredObject c = this.freeListManager.allocate(dataSize);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
assertThat(this.freeListManager.getFreeHugeMemory()).isEqualTo(computeExpectedSize(dataSize));
}
@Test
public void freeFragmentMemoryDefault() {
setUpSingleSlabManager();
assertThat(this.freeListManager.getFreeFragmentMemory()).isEqualTo(DEFAULT_SLAB_SIZE);
}
@Test
public void freeFragmentMemorySomeOfFragmentAllocated() {
setUpSingleSlabManager();
OffHeapStoredObject c = this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 4 - 8);
assertThat(this.freeListManager.getFreeFragmentMemory()).isEqualTo((DEFAULT_SLAB_SIZE / 4) * 3);
}
@Test
public void freeFragmentMemoryMostOfFragmentAllocated() {
setUpSingleSlabManager();
OffHeapStoredObject c = this.freeListManager.allocate(DEFAULT_SLAB_SIZE - 8 - 8);
assertThat(this.freeListManager.getFreeFragmentMemory()).isZero();
}
private int computeExpectedSize(int dataSize) {
return ((dataSize + OffHeapStoredObject.HEADER_SIZE + 7) / 8) * 8;
}
@Test(expected = AssertionError.class)
public void allocateZeroThrowsAssertion() {
setUpSingleSlabManager();
this.freeListManager.allocate(0);
}
@Test
public void allocateFromMultipleSlabs() {
int SMALL_SLAB = 16;
int MEDIUM_SLAB = 128;
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
this.freeListManager = createFreeListManager(ma, new Slab[] {new SlabImpl(SMALL_SLAB),
new SlabImpl(SMALL_SLAB), new SlabImpl(MEDIUM_SLAB), slab});
this.freeListManager.allocate(SMALL_SLAB - 8 + 1);
this.freeListManager.allocate(DEFAULT_SLAB_SIZE - 8);
this.freeListManager.allocate(SMALL_SLAB - 8 + 1);
assertThat(this.freeListManager.getFreeMemory())
.isEqualTo(SMALL_SLAB * 2 + MEDIUM_SLAB - ((SMALL_SLAB + 8) * 2));
assertThat(this.freeListManager.getUsedMemory())
.isEqualTo(DEFAULT_SLAB_SIZE + (SMALL_SLAB + 8) * 2);
assertThat(this.freeListManager.getTotalMemory())
.isEqualTo(DEFAULT_SLAB_SIZE + MEDIUM_SLAB + SMALL_SLAB * 2);
}
@Test
public void defragmentWithLargeChunkSizeReturnsFalse() {
int SMALL_SLAB = 16;
int MEDIUM_SLAB = 128;
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
this.freeListManager = createFreeListManager(ma, new Slab[] {new SlabImpl(SMALL_SLAB),
new SlabImpl(SMALL_SLAB), new SlabImpl(MEDIUM_SLAB), slab});
ArrayList<OffHeapStoredObject> chunks = new ArrayList<>();
chunks.add(this.freeListManager.allocate(SMALL_SLAB - 8 + 1));
chunks.add(this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 2 - 8));
chunks.add(this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 2 - 8));
chunks.add(this.freeListManager.allocate(SMALL_SLAB - 8 + 1));
for (OffHeapStoredObject c : chunks) {
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
}
this.freeListManager.firstDefragmentation = false;
assertThat(this.freeListManager.defragment(DEFAULT_SLAB_SIZE + 1)).isFalse();
}
@Test
public void testSlabImplToString() {
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
String slabAsString = slab.toString();
assertThat(slabAsString.contains("MemoryAddress=" + slab.getMemoryAddress()));
assertThat(slabAsString.contains("Size=" + DEFAULT_SLAB_SIZE));
}
@Test
public void defragmentWithChunkSizeOfMaxSlabReturnsTrue() {
int SMALL_SLAB = 16;
int MEDIUM_SLAB = 128;
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE, true);
this.freeListManager = createFreeListManager(ma, new Slab[] {new SlabImpl(SMALL_SLAB, true),
new SlabImpl(SMALL_SLAB, true), new SlabImpl(MEDIUM_SLAB, true), slab});
ArrayList<OffHeapStoredObject> chunks = new ArrayList<>();
chunks.add(this.freeListManager.allocate(SMALL_SLAB - 8 + 1));
chunks.add(this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 2 - 8));
chunks.add(this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 2 - 8));
chunks.add(this.freeListManager.allocate(SMALL_SLAB - 8 + 1));
for (OffHeapStoredObject c : chunks) {
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
}
this.freeListManager.firstDefragmentation = false;
assertThat(this.freeListManager.defragment(DEFAULT_SLAB_SIZE)).isTrue();
assertThat(this.freeListManager.getFragmentList()).hasSize(4);
}
@Test
public void defragmentWithLiveChunks() {
int SMALL_SLAB = 16;
int MEDIUM_SLAB = 128;
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
this.freeListManager = createFreeListManager(ma, new Slab[] {new SlabImpl(SMALL_SLAB),
new SlabImpl(SMALL_SLAB), new SlabImpl(MEDIUM_SLAB), slab});
ArrayList<OffHeapStoredObject> chunks = new ArrayList<>();
chunks.add(this.freeListManager.allocate(SMALL_SLAB - 8 + 1));
this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 2 - 8);
chunks.add(this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 2 - 8));
this.freeListManager.allocate(SMALL_SLAB - 8 + 1);
for (OffHeapStoredObject c : chunks) {
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
}
this.freeListManager.firstDefragmentation = false;
assertThat(this.freeListManager.defragment(DEFAULT_SLAB_SIZE / 2)).isTrue();
}
@Test
public void defragmentWhenDisallowingCombine() {
int SMALL_SLAB = 16;
int MEDIUM_SLAB = 128;
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
this.freeListManager = createFreeListManager(ma, new Slab[] {new SlabImpl(SMALL_SLAB),
new SlabImpl(SMALL_SLAB), new SlabImpl(MEDIUM_SLAB), slab}, DEFAULT_SLAB_SIZE / 2);
ArrayList<OffHeapStoredObject> chunks = new ArrayList<>();
chunks.add(this.freeListManager.allocate(SMALL_SLAB - 8 + 1));
chunks.add(this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 2 - 8));
chunks.add(this.freeListManager.allocate(DEFAULT_SLAB_SIZE / 2 - 8));
this.freeListManager.allocate(SMALL_SLAB - 8 + 1);
for (OffHeapStoredObject c : chunks) {
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
}
this.freeListManager.firstDefragmentation = false;
assertThat(this.freeListManager.defragment((DEFAULT_SLAB_SIZE / 2) + 1)).isFalse();
assertThat(this.freeListManager.defragment(DEFAULT_SLAB_SIZE / 2)).isTrue();
}
@Test
public void defragmentAfterAllocatingAll() {
setUpSingleSlabManager();
OffHeapStoredObject c = freeListManager.allocate(DEFAULT_SLAB_SIZE - 8);
this.freeListManager.firstDefragmentation = false;
assertThat(this.freeListManager.defragment(1)).isFalse();
// call defragment twice for extra code coverage
assertThat(this.freeListManager.defragment(1)).isFalse();
assertThat(this.freeListManager.getFragmentList()).isEmpty();
}
@Test
public void afterAllocatingAllOneSizeDefragmentToAllocateDifferentSize() {
setUpSingleSlabManager();
ArrayList<OffHeapStoredObject> chunksToFree = new ArrayList<>();
ArrayList<OffHeapStoredObject> chunksToFreeLater = new ArrayList<>();
int ALLOCATE_COUNT = 1000;
OffHeapStoredObject bigChunk =
freeListManager.allocate(DEFAULT_SLAB_SIZE - 8 - (ALLOCATE_COUNT * 32) - 256 - 256);
for (int i = 0; i < ALLOCATE_COUNT; i++) {
OffHeapStoredObject c = freeListManager.allocate(24);
if (i % 3 != 2) {
chunksToFree.add(c);
} else {
chunksToFreeLater.add(c);
}
}
OffHeapStoredObject c1 = freeListManager.allocate(64 - 8);
OffHeapStoredObject c2 = freeListManager.allocate(64 - 8);
OffHeapStoredObject c3 = freeListManager.allocate(64 - 8);
OffHeapStoredObject c4 = freeListManager.allocate(64 - 8);
OffHeapStoredObject mediumChunk1 = freeListManager.allocate(128 - 8);
OffHeapStoredObject mediumChunk2 = freeListManager.allocate(128 - 8);
OffHeapStoredObject.release(bigChunk.getAddress(), freeListManager);
int s = chunksToFree.size();
for (int i = s / 2; i >= 0; i--) {
OffHeapStoredObject c = chunksToFree.get(i);
OffHeapStoredObject.release(c.getAddress(), freeListManager);
}
for (int i = (s / 2) + 1; i < s; i++) {
OffHeapStoredObject c = chunksToFree.get(i);
OffHeapStoredObject.release(c.getAddress(), freeListManager);
}
OffHeapStoredObject.release(c3.getAddress(), freeListManager);
OffHeapStoredObject.release(c1.getAddress(), freeListManager);
OffHeapStoredObject.release(c2.getAddress(), freeListManager);
OffHeapStoredObject.release(c4.getAddress(), freeListManager);
OffHeapStoredObject.release(mediumChunk1.getAddress(), freeListManager);
OffHeapStoredObject.release(mediumChunk2.getAddress(), freeListManager);
this.freeListManager.firstDefragmentation = false;
assertThat(freeListManager.defragment(DEFAULT_SLAB_SIZE - (ALLOCATE_COUNT * 32))).isFalse();
for (int i = 0; i < ((256 * 2) / 96); i++) {
OffHeapStoredObject.release(chunksToFreeLater.get(i).getAddress(), freeListManager);
}
assertThat(freeListManager.defragment(DEFAULT_SLAB_SIZE - (ALLOCATE_COUNT * 32))).isTrue();
}
@Test
public void afterAllocatingAndFreeingDefragment() {
int slabSize = 1024 * 3;
setUpSingleSlabManager(slabSize);
OffHeapStoredObject bigChunk1 = freeListManager.allocate(slabSize / 3 - 8);
OffHeapStoredObject bigChunk2 = freeListManager.allocate(slabSize / 3 - 8);
OffHeapStoredObject bigChunk3 = freeListManager.allocate(slabSize / 3 - 8);
this.freeListManager.firstDefragmentation = false;
assertThat(freeListManager.defragment(1)).isFalse();
OffHeapStoredObject.release(bigChunk3.getAddress(), freeListManager);
OffHeapStoredObject.release(bigChunk2.getAddress(), freeListManager);
OffHeapStoredObject.release(bigChunk1.getAddress(), freeListManager);
assertThat(freeListManager.defragment(slabSize)).isTrue();
}
@Test
public void defragmentWithEmptyTinyFreeList() {
setUpSingleSlabManager();
Fragment originalFragment = this.freeListManager.getFragmentList().get(0);
OffHeapStoredObject c = freeListManager.allocate(16);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
c = freeListManager.allocate(16);
this.freeListManager.firstDefragmentation = false;
assertThat(this.freeListManager.defragment(1)).isTrue();
assertThat(this.freeListManager.getFragmentList()).hasSize(1);
Fragment defragmentedFragment = this.freeListManager.getFragmentList().get(0);
assertThat(defragmentedFragment.getSize()).isEqualTo(originalFragment.getSize() - (16 + 8));
assertThat(defragmentedFragment.getAddress())
.isEqualTo(originalFragment.getAddress() + (16 + 8));
}
@Test
public void allocationsThatLeaveLessThanMinChunkSizeFreeInAFragment() {
int SMALL_SLAB = 16;
int MEDIUM_SLAB = 128;
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
this.freeListManager = createFreeListManager(ma, new Slab[] {new SlabImpl(SMALL_SLAB),
new SlabImpl(SMALL_SLAB), new SlabImpl(MEDIUM_SLAB), slab});
this.freeListManager.allocate(DEFAULT_SLAB_SIZE - 8 - (OffHeapStoredObject.MIN_CHUNK_SIZE - 1));
this.freeListManager.allocate(MEDIUM_SLAB - 8 - (OffHeapStoredObject.MIN_CHUNK_SIZE - 1));
this.freeListManager.firstDefragmentation = false;
assertThat(this.freeListManager.defragment(SMALL_SLAB)).isTrue();
}
@Test
public void maxAllocationUsesAllMemory() {
setUpSingleSlabManager();
this.freeListManager.allocate(DEFAULT_SLAB_SIZE - 8);
assertThat(this.freeListManager.getFreeMemory()).isZero();
assertThat(this.freeListManager.getUsedMemory()).isEqualTo(DEFAULT_SLAB_SIZE);
}
@Test
public void overMaxAllocationFails() {
setUpSingleSlabManager();
OutOfOffHeapMemoryListener ooohml = mock(OutOfOffHeapMemoryListener.class);
when(this.ma.getOutOfOffHeapMemoryListener()).thenReturn(ooohml);
Throwable thrown = catchThrowable(() -> this.freeListManager.allocate(DEFAULT_SLAB_SIZE - 7));
verify(ooohml).outOfOffHeapMemory((OutOfOffHeapMemoryException) thrown);
}
@Test(expected = AssertionError.class)
public void allocateNegativeThrowsAssertion() {
setUpSingleSlabManager();
this.freeListManager.allocate(-123);
}
@Test
public void hugeMultipleLessThanZeroIsIllegal() {
try {
FreeListManager.verifyHugeMultiple(-1);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected.getMessage()).contains(
"HUGE_MULTIPLE must be >= 0 and <= " + FreeListManager.HUGE_MULTIPLE + " but it was -1");
}
}
@Test
public void hugeMultipleGreaterThan256IsIllegal() {
try {
FreeListManager.verifyHugeMultiple(257);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected.getMessage())
.contains("HUGE_MULTIPLE must be >= 0 and <= 256 but it was 257");
}
}
@Test
public void hugeMultipleof256IsLegal() {
FreeListManager.verifyHugeMultiple(256);
}
@Test
public void offHeapFreeListCountLessThanZeroIsIllegal() {
try {
FreeListManager.verifyOffHeapFreeListCount(-1);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected.getMessage())
.contains(DistributionConfig.GEMFIRE_PREFIX + "OFF_HEAP_FREE_LIST_COUNT must be >= 1.");
}
}
@Test
public void offHeapFreeListCountOfZeroIsIllegal() {
try {
FreeListManager.verifyOffHeapFreeListCount(0);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected.getMessage())
.contains(DistributionConfig.GEMFIRE_PREFIX + "OFF_HEAP_FREE_LIST_COUNT must be >= 1.");
}
}
@Test
public void offHeapFreeListCountOfOneIsLegal() {
FreeListManager.verifyOffHeapFreeListCount(1);
}
@Test
public void offHeapAlignmentLessThanZeroIsIllegal() {
try {
FreeListManager.verifyOffHeapAlignment(-1);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected.getMessage()).contains(
DistributionConfig.GEMFIRE_PREFIX + "OFF_HEAP_ALIGNMENT must be a multiple of 8");
}
}
@Test
public void offHeapAlignmentNotAMultipleOf8IsIllegal() {
try {
FreeListManager.verifyOffHeapAlignment(9);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected.getMessage()).contains(
DistributionConfig.GEMFIRE_PREFIX + "OFF_HEAP_ALIGNMENT must be a multiple of 8");
}
}
@Test
public void offHeapAlignmentGreaterThan256IsIllegal() {
try {
FreeListManager.verifyOffHeapAlignment(256 + 8);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertThat(expected.getMessage())
.contains(DistributionConfig.GEMFIRE_PREFIX + "OFF_HEAP_ALIGNMENT must be <= 256");
}
}
@Test
public void offHeapAlignmentOf256IsLegal() {
FreeListManager.verifyOffHeapAlignment(256);
}
@Test
public void okToReuseNull() {
setUpSingleSlabManager();
assertThat(this.freeListManager.okToReuse(null)).isTrue();
}
@Test
public void okToReuseSameSlabs() {
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
Slab[] slabs = new Slab[] {slab};
this.freeListManager = createFreeListManager(ma, slabs);
assertThat(this.freeListManager.okToReuse(slabs)).isTrue();
}
@Test
public void notOkToReuseDifferentSlabs() {
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
Slab[] slabs = new Slab[] {slab};
this.freeListManager = createFreeListManager(ma, slabs);
Slab[] slabs2 = new Slab[] {slab};
assertThat(this.freeListManager.okToReuse(slabs2)).isFalse();
}
@Test
public void firstSlabAlwaysLargest() {
this.freeListManager =
createFreeListManager(ma, new Slab[] {new SlabImpl(10), new SlabImpl(100)});
assertThat(this.freeListManager.getLargestSlabSize()).isEqualTo(10);
}
@Test
public void findSlab() {
Slab chunk = new SlabImpl(10);
long address = chunk.getMemoryAddress();
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
assertThat(this.freeListManager.findSlab(address)).isEqualTo(0);
assertThat(this.freeListManager.findSlab(address + 9)).isEqualTo(0);
Throwable thrown = catchThrowable(() -> this.freeListManager.findSlab(address - 1));
assertThat(thrown).isExactlyInstanceOf(IllegalStateException.class)
.hasMessage("could not find a slab for addr " + (address - 1));
thrown = catchThrowable(() -> this.freeListManager.findSlab(address + 10));
assertThat(thrown).isExactlyInstanceOf(IllegalStateException.class)
.hasMessage("could not find a slab for addr " + (address + 10));
}
@Test
public void findSecondSlab() {
Slab chunk = new SlabImpl(10);
long address = chunk.getMemoryAddress();
Slab slab = new SlabImpl(DEFAULT_SLAB_SIZE);
this.freeListManager = createFreeListManager(ma, new Slab[] {slab, chunk});
assertThat(this.freeListManager.findSlab(address)).isEqualTo(1);
assertThat(this.freeListManager.findSlab(address + 9)).isEqualTo(1);
Throwable thrown = catchThrowable(() -> this.freeListManager.findSlab(address - 1));
assertThat(thrown).isExactlyInstanceOf(IllegalStateException.class)
.hasMessage("could not find a slab for addr " + (address - 1));
thrown = catchThrowable(() -> this.freeListManager.findSlab(address + 10));
assertThat(thrown).isExactlyInstanceOf(IllegalStateException.class)
.hasMessage("could not find a slab for addr " + (address + 10));
}
@Test
public void validateAddressWithinSlab() {
Slab chunk = new SlabImpl(10);
long address = chunk.getMemoryAddress();
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
assertThat(this.freeListManager.validateAddressAndSizeWithinSlab(address, -1)).isTrue();
assertThat(this.freeListManager.validateAddressAndSizeWithinSlab(address + 9, -1)).isTrue();
assertThat(this.freeListManager.validateAddressAndSizeWithinSlab(address - 1, -1)).isFalse();
assertThat(this.freeListManager.validateAddressAndSizeWithinSlab(address + 10, -1)).isFalse();
}
@Test
public void validateAddressAndSizeWithinSlab() {
Slab chunk = new SlabImpl(10);
long address = chunk.getMemoryAddress();
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
assertThat(this.freeListManager.validateAddressAndSizeWithinSlab(address, 1)).isTrue();
assertThat(this.freeListManager.validateAddressAndSizeWithinSlab(address, 10)).isTrue();
Throwable thrown =
catchThrowable(() -> this.freeListManager.validateAddressAndSizeWithinSlab(address, 0));
assertThat(thrown).isExactlyInstanceOf(IllegalStateException.class)
.hasMessage(" address 0x" + Long.toString(address + 0 - 1, 16)
+ " does not address the original slab memory");
thrown =
catchThrowable(() -> this.freeListManager.validateAddressAndSizeWithinSlab(address, 11));
assertThat(thrown).isExactlyInstanceOf(IllegalStateException.class)
.hasMessage(" address 0x" + Long.toString(address + 11 - 1, 16)
+ " does not address the original slab memory");
}
@Test
public void descriptionOfOneSlab() {
Slab chunk = new SlabImpl(10);
long address = chunk.getMemoryAddress();
long endAddress = address + 10;
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
StringBuilder sb = new StringBuilder();
this.freeListManager.getSlabDescriptions(sb);
assertThat(sb.toString())
.isEqualTo("[" + Long.toString(address, 16) + ".." + Long.toString(endAddress, 16) + "] ");
}
@Test
public void orderBlocksContainsFragment() {
Slab chunk = new SlabImpl(10);
long address = chunk.getMemoryAddress();
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
List<MemoryBlock> ob = this.freeListManager.getOrderedBlocks();
assertThat(ob).hasSize(1);
assertThat(ob.get(0).getAddress()).isEqualTo(address);
assertThat(ob.get(0).getBlockSize()).isEqualTo(10);
}
@Test
public void orderBlocksContainsTinyFree() {
Slab chunk = new SlabImpl(96);
long address = chunk.getMemoryAddress();
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
OffHeapStoredObject c = this.freeListManager.allocate(24);
OffHeapStoredObject c2 = this.freeListManager.allocate(24);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
List<MemoryBlock> ob = this.freeListManager.getOrderedBlocks();
assertThat(ob).hasSize(3);
}
@Test
public void allocatedBlocksEmptyIfNoAllocations() {
Slab chunk = new SlabImpl(10);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
List<MemoryBlock> ob = this.freeListManager.getAllocatedBlocks();
assertThat(ob).hasSize(0);
}
@Test
public void allocatedBlocksEmptyAfterFree() {
Slab chunk = new SlabImpl(96);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
OffHeapStoredObject c = this.freeListManager.allocate(24);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
List<MemoryBlock> ob = this.freeListManager.getAllocatedBlocks();
assertThat(ob).hasSize(0);
}
@Test
public void allocatedBlocksHasAllocatedChunk() {
Slab chunk = new SlabImpl(96);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
OffHeapStoredObject c = this.freeListManager.allocate(24);
List<MemoryBlock> ob = this.freeListManager.getAllocatedBlocks();
assertThat(ob).hasSize(1);
assertThat(ob.get(0).getAddress()).isEqualTo(c.getAddress());
}
@Test
public void allocatedBlocksHasBothAllocatedChunks() {
Slab chunk = new SlabImpl(96);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
OffHeapStoredObject c = this.freeListManager.allocate(24);
OffHeapStoredObject c2 = this.freeListManager.allocate(33);
List<MemoryBlock> ob = this.freeListManager.getAllocatedBlocks();
assertThat(ob).hasSize(2);
}
@Test
public void allocateFromFragmentWithBadIndexesReturnsNull() {
Slab chunk = new SlabImpl(96);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
assertThat(this.freeListManager.allocateFromFragment(-1, 32)).isNull();
assertThat(this.freeListManager.allocateFromFragment(1, 32)).isNull();
}
@Test
public void testLogging() {
Slab chunk = new SlabImpl(32);
Slab chunk2 = new SlabImpl(1024 * 1024 * 5);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk, chunk2});
OffHeapStoredObject c = this.freeListManager.allocate(24);
OffHeapStoredObject c2 = this.freeListManager.allocate(1024 * 1024);
OffHeapStoredObject.release(c.getAddress(), this.freeListManager);
OffHeapStoredObject.release(c2.getAddress(), this.freeListManager);
Logger lw = mock(Logger.class);
this.freeListManager.logOffHeapState(lw, 1024);
}
@Test
public void fragmentationShouldBeZeroIfNumberOfFragmentsIsZero() {
SlabImpl chunk = new SlabImpl(10);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
FreeListManager spy = spy(this.freeListManager);
when(spy.getFragmentCount()).thenReturn(0);
assertThat(spy.getFragmentation()).isZero();
}
@Test
public void fragmentationShouldBeZeroIfNumberOfFragmentsIsOne() {
SlabImpl chunk = new SlabImpl(10);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
FreeListManager spy = spy(this.freeListManager);
when(spy.getFragmentCount()).thenReturn(1);
assertThat(spy.getFragmentation()).isZero();
}
@Test
public void fragmentationShouldBeZeroIfUsedMemoryIsZero() {
SlabImpl chunk = new SlabImpl(10);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
FreeListManager spy = spy(this.freeListManager);
when(spy.getUsedMemory()).thenReturn(0L);
assertThat(spy.getFragmentation()).isZero();
}
@Test
public void fragmentationShouldBe100IfAllFreeMemoryIsFragmentedAsMinChunks() {
SlabImpl chunk = new SlabImpl(10);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
FreeListManager spy = spy(this.freeListManager);
when(spy.getUsedMemory()).thenReturn(1L);
when(spy.getFragmentCount()).thenReturn(2);
when(spy.getFreeMemory()).thenReturn((long) OffHeapStoredObject.MIN_CHUNK_SIZE * 2);
assertThat(spy.getFragmentation()).isEqualTo(100);
}
@Test
public void fragmentationShouldBeRoundedToNearestInteger() {
SlabImpl chunk = new SlabImpl(10);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
FreeListManager spy = spy(this.freeListManager);
when(spy.getUsedMemory()).thenReturn(1L);
when(spy.getFragmentCount()).thenReturn(4);
when(spy.getFreeMemory()).thenReturn((long) OffHeapStoredObject.MIN_CHUNK_SIZE * 8);
assertThat(spy.getFragmentation()).isEqualTo(50); // Math.rint(50.0)
when(spy.getUsedMemory()).thenReturn(1L);
when(spy.getFragmentCount()).thenReturn(3);
when(spy.getFreeMemory()).thenReturn((long) OffHeapStoredObject.MIN_CHUNK_SIZE * 8);
assertThat(spy.getFragmentation()).isEqualTo(38); // Math.rint(37.5)
when(spy.getUsedMemory()).thenReturn(1L);
when(spy.getFragmentCount()).thenReturn(6);
when(spy.getFreeMemory()).thenReturn((long) OffHeapStoredObject.MIN_CHUNK_SIZE * 17);
assertThat(spy.getFragmentation()).isEqualTo(35); // Math.rint(35.29)
when(spy.getUsedMemory()).thenReturn(1L);
when(spy.getFragmentCount()).thenReturn(6);
when(spy.getFreeMemory()).thenReturn((long) OffHeapStoredObject.MIN_CHUNK_SIZE * 9);
assertThat(spy.getFragmentation()).isEqualTo(67); // Math.rint(66.66)
}
@Test
public void isAdjacentBoundaryConditions() {
SlabImpl chunk = new SlabImpl(10);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
assertThat(!this.freeListManager.isAdjacent(Long.MAX_VALUE - 4, 4, Long.MAX_VALUE + 1));
assertThat(this.freeListManager.isAdjacent(Long.MAX_VALUE - 4, 4, Long.MAX_VALUE));
assertThat(this.freeListManager.isAdjacent(-8L, 4, -4L));
long lowAddr = Long.MAX_VALUE;
long highAddr = lowAddr + 4;
assertThat(this.freeListManager.isAdjacent(lowAddr, 4, highAddr));
assertThat(!this.freeListManager.isAdjacent(lowAddr, 4, highAddr - 1));
assertThat(!this.freeListManager.isAdjacent(lowAddr, 4, highAddr + 1));
lowAddr = highAddr;
highAddr = lowAddr + 4;
assertThat(this.freeListManager.isAdjacent(lowAddr, 4, highAddr));
assertThat(!this.freeListManager.isAdjacent(highAddr, 4, lowAddr));
}
@Test
public void isSmallEnoughBoundaryConditions() {
SlabImpl chunk = new SlabImpl(10);
this.freeListManager = createFreeListManager(ma, new Slab[] {chunk});
assertThat(this.freeListManager.isSmallEnough(Integer.MAX_VALUE));
assertThat(this.freeListManager.isSmallEnough(Integer.MAX_VALUE - 1));
assertThat(!this.freeListManager.isSmallEnough(Integer.MAX_VALUE + 1L));
assertThat(!this.freeListManager.isSmallEnough(Long.MAX_VALUE));
}
/**
* Just like Fragment except that the first time allocate is called it returns false indicating
* that the allocate failed. In a real system this would only happen if a concurrent allocate
* happened. This allows better code coverage.
*/
private static class TestableFragment extends Fragment {
private boolean allocateCalled = false;
public TestableFragment(long addr, int size) {
super(addr, size);
}
@Override
public boolean allocate(int oldOffset, int newOffset) {
if (!allocateCalled) {
allocateCalled = true;
return false;
}
return super.allocate(oldOffset, newOffset);
}
}
private static class TestableFreeListManager extends FreeListManager {
private boolean firstTime = true;
private boolean firstDefragmentation = true;
private final int maxCombine;
public TestableFreeListManager(MemoryAllocatorImpl ma, Slab[] slabs) {
this(ma, slabs, 0);
}
public TestableFreeListManager(MemoryAllocatorImpl ma, Slab[] slabs, int maxCombine) {
super(ma, slabs);
this.maxCombine = maxCombine;
}
@Override
protected Fragment createFragment(long addr, int size) {
return new TestableFragment(addr, size);
}
@Override
protected OffHeapStoredObjectAddressStack createFreeListForEmptySlot(
AtomicReferenceArray<OffHeapStoredObjectAddressStack> freeLists, int idx) {
if (this.firstTime) {
this.firstTime = false;
OffHeapStoredObjectAddressStack clq = super.createFreeListForEmptySlot(freeLists, idx);
if (!freeLists.compareAndSet(idx, null, clq)) {
fail("this should never happen. Indicates a concurrent modification");
}
}
return super.createFreeListForEmptySlot(freeLists, idx);
}
@Override
protected void afterDefragmentationCountFetched() {
if (this.firstDefragmentation) {
this.firstDefragmentation = false;
// Force defragmentation into thinking a concurrent defragmentation happened.
this.defragmentationCount.incrementAndGet();
} else {
super.afterDefragmentationCountFetched();
}
}
@Override
boolean isSmallEnough(long size) {
if (this.maxCombine != 0) {
return size <= this.maxCombine;
} else {
return super.isSmallEnough(size);
}
}
}
}