/*
* Copyright 2015 Terracotta, Inc., a Software AG company.
*
* Licensed 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.terracotta.offheapstore.disk.storage;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.terracotta.offheapstore.util.AATreeSet;
import static org.terracotta.offheapstore.util.Validation.shouldValidate;
import static org.terracotta.offheapstore.util.Validation.validate;
/**
* An augmented AA tree based allocator.
* <p>
* This allocator maintains an augmented AA tree of free regions. Tree nodes
* are augmented with the size of the maximum and minimum contiguous free region
* linked beneath them in the tree. Regions being freed are merged with
* adjacent free regions and the tree structure updated to reflect the resultant
* region.
* <p>
* Allocations are performed in a very approximate best fit manner. Assuming
* that there is a large enough free region in the tree, tree navigation
* decisions proceed as follows:
* <ol>
* <li>if the requested size is smaller than or equal to the smallest contiguous
* region then find the smallest contiguous region and use it</li>
* <li>if the current node is perfectly sized then use it</li>
* <li>pick the child with the smallest contiguous subnode that will hold us - and then go to 2</li>
* <li>if no such child exists use the current node</li>
* </ol>
* <p>
* This allocator will experience bad fragmentation affects when not used with
* uniformly sized allocations calls. Since the AA Tree is stored in the Java
* object heap this can lead to excessive heap usage.
*
* @author Chris Dennis
*/
public class AATreeFileAllocator extends AATreeSet<Region> {
private static final boolean VALIDATING = shouldValidate(AATreeFileAllocator.class);
private static final int MAGIC = 0x43485249;
private static final int MAGIC_REGION = 0x53544f50;
private final long capacity;
private long occupied;
/**
* Create an abstract allocator using the given buffer source and initial
* size.
* <p>
* This initial size will be used to size the buffer returned from the clear
* call.
*
* @param size initial buffer size
*/
public AATreeFileAllocator(long size) {
super();
this.capacity = size;
add(new Region(0, capacity - 1));
}
public AATreeFileAllocator(long size, DataInput input) throws IOException {
super();
this.capacity = size;
this.occupied = size;
if (input.readInt() != MAGIC) {
throw new IOException("Invalid magic number");
}
while (true) {
int magic = input.readInt();
if (magic == -1) {
break;
} else if (magic != MAGIC_REGION) {
throw new IOException("Invalid magic number");
}
long start = input.readLong();
long end = input.readLong();
Region r = new Region(start, end);
add(r);
freed(r);
}
}
public long allocate(long size) {
if (Long.bitCount(size) != 1) {
size = Long.highestOneBit(size) << 1;
}
final Region r = find(size);
if (r == null) {
return -1;
}
Region current = removeAndReturn(Long.valueOf(r.start()));
Region newRange = current.remove(r);
if (newRange != null) {
add(current);
add(newRange);
} else if (!current.isNull()) {
add(current);
}
allocated(r);
return r.start();
}
public void free(long address, long length) {
if (Long.bitCount(length) != 1) {
length = Long.highestOneBit(length) << 1;
}
if (length != 0) {
Region r = new Region(address, address + length - 1);
free(r);
freed(r);
}
}
@Override
public Region removeAndReturn(Object o) {
Region r = super.removeAndReturn(o);
if (r != null) {
return new Region(r);
} else {
return null;
}
}
@Override
public Region find(Object o) {
Region r = super.find(o);
if (r != null) {
return new Region(r);
} else {
return null;
}
}
public long occupied() {
return occupied;
}
public long capacity() {
return capacity;
}
private void allocated(Region r) {
occupied += r.size();
}
private void freed(Region r) {
occupied -= r.size();
}
private void free(Region r) {
// Step 1 : Check if the previous number is present, if so add to the same Range.
Region prev = removeAndReturn(Long.valueOf(r.start() - 1));
if (prev != null) {
prev.merge(r);
Region next = removeAndReturn(Long.valueOf(r.end() + 1));
if (next != null) {
prev.merge(next);
}
add(prev);
return;
}
// Step 2 : Check if the next number is present, if so add to the same Range.
Region next = removeAndReturn(Long.valueOf(r.end() + 1));
if (next != null) {
next.merge(r);
add(next);
return;
}
// Step 3: Add a new range for just this number.
add(r);
}
/**
* Find a region of the given size.
*/
private Region find(long size) {
validate(!VALIDATING || Long.bitCount(size) == 1);
Node<Region> currentNode = getRoot();
Region currentRegion = currentNode.getPayload();
if (currentRegion == null || (currentRegion.available() & size) == 0) {
//no region big enough for us...
return null;
} else {
while (true) {
Region left = currentNode.getLeft().getPayload();
if (left != null && (left.available() & size) != 0) {
currentNode = currentNode.getLeft();
currentRegion = currentNode.getPayload();
} else if ((currentRegion.availableHere() & size) != 0) {
long mask = size - 1;
long a = (currentRegion.start() + mask) & ~mask;
return new Region(a, a + size - 1);
} else {
Region right = currentNode.getRight().getPayload();
if (right != null && (right.available() & size) != 0) {
currentNode = currentNode.getRight();
currentRegion = currentNode.getPayload();
} else {
throw new AssertionError();
}
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "RegionSet = { " + super.toString() + " }";
}
void persist(DataOutput output) throws IOException {
output.writeInt(MAGIC);
for (Region r : this) {
persist(output, r);
}
output.writeInt(-1);
}
void persist(DataOutput output, Region r) throws IOException {
output.writeInt(MAGIC_REGION);
output.writeLong(r.start());
output.writeLong(r.end());
}
}