// TODO: would it make sense to return buffers asynchronously?
@Override
  public void allocateMultiple(MemoryBuffer[] dest, int size)
      throws AllocatorOutOfMemoryException {
    assert size > 0 : "size is " + size;
    if (size > maxAllocation) {
      throw new RuntimeException("Trying to allocate " + size + "; max is " + maxAllocation);
    }
    int freeListIx = 31 - Integer.numberOfLeadingZeros(size);
    if (size != (1 << freeListIx)) ++freeListIx; // not a power of two, add one more
    freeListIx = Math.max(freeListIx - minAllocLog2, 0);
    int allocLog2 = freeListIx + minAllocLog2;
    int allocationSize = 1 << allocLog2;
    // TODO: reserving the entire thing is not ideal before we alloc anything. Interleave?
    memoryManager.reserveMemory(dest.length << allocLog2, true);
    int ix = 0;
    for (int i = 0; i < dest.length; ++i) {
      if (dest[i] != null) continue;
      dest[i] = createUnallocated(); // TODO: pool of objects?
    }
    // First try to quickly lock some of the correct-sized free lists and allocate from them.
    int arenaCount = allocatedArenas.get();
    if (arenaCount < 0) {
      arenaCount = -arenaCount - 1; // Next arena is being allocated.
    }
    long threadId = arenaCount > 1 ? Thread.currentThread().getId() : 0;
    {
      int startIndex = (int)(threadId % arenaCount), index = startIndex;
      do {
        int newIx = arenas[index].allocateFast(index, freeListIx, dest, ix, allocationSize);
        if (newIx == dest.length) return;
        if (newIx != -1) {  // TODO: check if it can still happen; count should take care of this.
          ix = newIx;
        }
        ix = newIx;
        if ((++index) == arenaCount) {
          index = 0;
        }
      } while (index != startIndex);
    }
    // TODO: this is very hacky.
    // We called reserveMemory so we know that somewhere in there, there's memory waiting for us.
    // However, we have a class of rare race conditions related to the order of locking/checking of
    // different allocation areas. Simple case - say we have 2 arenas, 256Kb available in arena 2.
    // We look at arena 1; someone deallocs 256Kb from arena 1 and allocs the same from arena 2;
    // we look at arena 2 and find no memory. Or, for single arena, 2 threads reserve 256k each,
    // and a single 1Mb block is available. When the 1st thread locks the 1Mb freelist, the 2nd one
    // might have already examined the 256k and 512k lists, finding nothing. Blocks placed by (1)
    // into smaller lists after its split is done will not be found by (2); given that freelist
    // locks don't overlap, (2) may even run completely between the time (1) takes out the 1Mb
    // block and the time it returns the remaining 768Kb.
    // Two solutions to this are some form of cross-thread helping (threads putting "demand"
    // into some sort of queues that deallocate and split will examine), or having and "actor"
    // allocator thread (or threads per arena).
    // The 2nd one is probably much simpler and will allow us to get rid of a lot of sync code.
    // But for now we will just retry 5 times 0_o
    for (int attempt = 0; attempt < 5; ++attempt) {
      // Try to split bigger blocks. TODO: again, ideally we would tryLock at least once
      for (int i = 0; i < arenaCount; ++i) {
        int newIx = arenas[i].allocateWithSplit(i, freeListIx, dest, ix, allocationSize);
        if (newIx == -1) break; // Shouldn't happen.
        if (newIx == dest.length) return;
        ix = newIx;
      }
      if (attempt == 0) {
        // Try to allocate memory if we haven't allocated all the way to maxSize yet; very rare.
        for (int i = arenaCount; i < arenas.length; ++i) {
          ix = arenas[i].allocateWithExpand(i, freeListIx, dest, ix, allocationSize);
          if (ix == dest.length) return;
        }
      }
      LlapIoImpl.LOG.warn("Failed to allocate despite reserved memory; will retry " + attempt);
    }
    String msg = "Failed to allocate " + size + "; at " + ix + " out of " + dest.length;
    LlapIoImpl.LOG.error(msg + "\nALLOCATOR STATE:\n" + debugDump()
        + "\nPARENT STATE:\n" + memoryManager.debugDumpForOom());
    throw new AllocatorOutOfMemoryException(msg);
  }
@Override
public void allocateMultiple(MemoryBuffer[] dest, int size)
throws AllocatorOutOfMemoryException {
assert size > 0 : "size is " + size;
if (size > maxAllocation) {
throw new RuntimeException("Trying to allocate " + size + "; max is " + maxAllocation);
}
int freeListIx = 31 - Integer.numberOfLeadingZeros(size);
if (size != (1 << freeListIx)) ++freeListIx; // not a power of two, add one more
freeListIx = Math.max(freeListIx - minAllocLog2, 0);
int allocLog2 = freeListIx + minAllocLog2;
int allocationSize = 1 << allocLog2;
// TODO: reserving the entire thing is not ideal before we alloc anything. Interleave?
memoryManager.reserveMemory(dest.length << allocLog2, true);
int ix = 0;
for (int i = 0; i < dest.length; ++i) {
if (dest[i] != null) continue;
dest[i] = createUnallocated(); // TODO: pool of objects?
}
// First try to quickly lock some of the correct-sized free lists and allocate from them.
int arenaCount = allocatedArenas.get();
if (arenaCount < 0) {
arenaCount = -arenaCount - 1; // Next arena is being allocated.
}
long threadId = arenaCount > 1 ? Thread.currentThread().getId() : 0;
{
int startIndex = (int)(threadId % arenaCount), index = startIndex;
do {
int newIx = arenas[index].allocateFast(index, freeListIx, dest, ix, allocationSize);
if (newIx == dest.length) return;
if (newIx != -1) { // TODO: check if it can still happen; count should take care of this.
ix = newIx;
}
ix = newIx;
if ((++index) == arenaCount) {
index = 0;
}
} while (index != startIndex);
}
// TODO: this is very hacky.
// We called reserveMemory so we know that somewhere in there, there's memory waiting for us.
// However, we have a class of rare race conditions related to the order of locking/checking of
// different allocation areas. Simple case - say we have 2 arenas, 256Kb available in arena 2.
// We look at arena 1; someone deallocs 256Kb from arena 1 and allocs the same from arena 2;
// we look at arena 2 and find no memory. Or, for single arena, 2 threads reserve 256k each,
// and a single 1Mb block is available. When the 1st thread locks the 1Mb freelist, the 2nd one
// might have already examined the 256k and 512k lists, finding nothing. Blocks placed by (1)
// into smaller lists after its split is done will not be found by (2); given that freelist
// locks don't overlap, (2) may even run completely between the time (1) takes out the 1Mb
// block and the time it returns the remaining 768Kb.
// Two solutions to this are some form of cross-thread helping (threads putting "demand"
// into some sort of queues that deallocate and split will examine), or having and "actor"
// allocator thread (or threads per arena).
// The 2nd one is probably much simpler and will allow us to get rid of a lot of sync code.
// But for now we will just retry 5 times 0_o
for (int attempt = 0; attempt < 5; ++attempt) {
// Try to split bigger blocks. TODO: again, ideally we would tryLock at least once
for (int i = 0; i < arenaCount; ++i) {
int newIx = arenas[i].allocateWithSplit(i, freeListIx, dest, ix, allocationSize);
if (newIx == -1) break; // Shouldn't happen.
if (newIx == dest.length) return;
ix = newIx;
}
if (attempt == 0) {
// Try to allocate memory if we haven't allocated all the way to maxSize yet; very rare.
for (int i = arenaCount; i < arenas.length; ++i) {
ix = arenas[i].allocateWithExpand(i, freeListIx, dest, ix, allocationSize);
if (ix == dest.length) return;
}
}
LlapIoImpl.LOG.warn("Failed to allocate despite reserved memory; will retry " + attempt);
}
String msg = "Failed to allocate " + size + "; at " + ix + " out of " + dest.length;
LlapIoImpl.LOG.error(msg + "\nALLOCATOR STATE:\n" + debugDump()
+ "\nPARENT STATE:\n" + memoryManager.debugDumpForOom());
throw new AllocatorOutOfMemoryException(msg);
}
@Override
public void allocateMultiple(MemoryBuffer[] dest, int size)
throws AllocatorOutOfMemoryException {
assert size > 0 : "size is " + size;
if (size > maxAllocation) {
throw new RuntimeException("Trying to allocate " + size + "; max is " + maxAllocation);
}
int freeListIx = 31 - Integer.numberOfLeadingZeros(size);
if (size != (1 << freeListIx)) ++freeListIx; // not a power of two, add one more
freeListIx = Math.max(freeListIx - minAllocLog2, 0);
int allocLog2 = freeListIx + minAllocLog2;
int allocationSize = 1 << allocLog2;
// TODO: reserving the entire thing is not ideal before we alloc anything. Interleave?
memoryManager.reserveMemory(dest.length << allocLog2, true);
int ix = 0;
for (int i = 0; i < dest.length; ++i) {
if (dest[i] != null) continue;
dest[i] = createUnallocated(); // TODO: pool of objects?
}
// First try to quickly lock some of the correct-sized free lists and allocate from them.
int arenaCount = allocatedArenas.get();
if (arenaCount < 0) {
arenaCount = -arenaCount - 1; // Next arena is being allocated.
}
long threadId = arenaCount > 1 ? Thread.currentThread().getId() : 0;
{
int startIndex = (int)(threadId % arenaCount), index = startIndex;
do {
int newIx = arenas[index].allocateFast(index, freeListIx, dest, ix, allocationSize);
if (newIx == dest.length) return;
if (newIx != -1) { // TODO: check if it can still happen; count should take care of this.
ix = newIx;
}
ix = newIx;
if ((++index) == arenaCount) {
index = 0;
}
} while (index != startIndex);
}
// TODO: this is very hacky.
// We called reserveMemory so we know that somewhere in there, there's memory waiting for us.
// However, we have a class of rare race conditions related to the order of locking/checking of
// different allocation areas. Simple case - say we have 2 arenas, 256Kb available in arena 2.
// We look at arena 1; someone deallocs 256Kb from arena 1 and allocs the same from arena 2;
// we look at arena 2 and find no memory. Or, for single arena, 2 threads reserve 256k each,
// and a single 1Mb block is available. When the 1st thread locks the 1Mb freelist, the 2nd one
// might have already examined the 256k and 512k lists, finding nothing. Blocks placed by (1)
// into smaller lists after its split is done will not be found by (2); given that freelist
// locks don't overlap, (2) may even run completely between the time (1) takes out the 1Mb
// block and the time it returns the remaining 768Kb.
// Two solutions to this are some form of cross-thread helping (threads putting "demand"
// into some sort of queues that deallocate and split will examine), or having and "actor"
// allocator thread (or threads per arena).
// The 2nd one is probably much simpler and will allow us to get rid of a lot of sync code.
// But for now we will just retry 5 times 0_o
for (int attempt = 0; attempt < 5; ++attempt) {
// Try to split bigger blocks. TODO: again, ideally we would tryLock at least once
for (int i = 0; i < arenaCount; ++i) {
int newIx = arenas[i].allocateWithSplit(i, freeListIx, dest, ix, allocationSize);
if (newIx == -1) break; // Shouldn't happen.
if (newIx == dest.length) return;
ix = newIx;
}
if (attempt == 0) {
// Try to allocate memory if we haven't allocated all the way to maxSize yet; very rare.
for (int i = arenaCount; i < arenas.length; ++i) {
ix = arenas[i].allocateWithExpand(i, freeListIx, dest, ix, allocationSize);
if (ix == dest.length) return;
}
}
LlapIoImpl.LOG.warn("Failed to allocate despite reserved memory; will retry " + attempt);
}
String msg = "Failed to allocate " + size + "; at " + ix + " out of " + dest.length;
LlapIoImpl.LOG.error(msg + "\nALLOCATOR STATE:\n" + debugDump()
+ "\nPARENT STATE:\n" + memoryManager.debugDumpForOom());
throw new AllocatorOutOfMemoryException(msg);
}
@Override
public void allocateMultiple(MemoryBuffer[] dest, int size)
throws AllocatorOutOfMemoryException {
assert size > 0 : "size is " + size;
if (size > maxAllocation) {
throw new RuntimeException("Trying to allocate " + size + "; max is " + maxAllocation);
}
int freeListIx = 31 - Integer.numberOfLeadingZeros(size);
if (size != (1 << freeListIx)) ++freeListIx; // not a power of two, add one more
freeListIx = Math.max(freeListIx - minAllocLog2, 0);
int allocLog2 = freeListIx + minAllocLog2;
int allocationSize = 1 << allocLog2;
// TODO: reserving the entire thing is not ideal before we alloc anything. Interleave?
memoryManager.reserveMemory(dest.length << allocLog2, true);
int ix = 0;
for (int i = 0; i < dest.length; ++i) {
if (dest[i] != null) continue;
dest[i] = createUnallocated(); // TODO: pool of objects?
}
// First try to quickly lock some of the correct-sized free lists and allocate from them.
int arenaCount = allocatedArenas.get();
if (arenaCount < 0) {
arenaCount = -arenaCount - 1; // Next arena is being allocated.
}
long threadId = arenaCount > 1 ? Thread.currentThread().getId() : 0;
{
int startIndex = (int)(threadId % arenaCount), index = startIndex;
do {
int newIx = arenas[index].allocateFast(index, freeListIx, dest, ix, allocationSize);
if (newIx == dest.length) return;
if (newIx != -1) { // TODO: check if it can still happen; count should take care of this.
ix = newIx;
}
ix = newIx;
if ((++index) == arenaCount) {
index = 0;
}
} while (index != startIndex);
}
// TODO: this is very hacky.
// We called reserveMemory so we know that somewhere in there, there's memory waiting for us.
// However, we have a class of rare race conditions related to the order of locking/checking of
// different allocation areas. Simple case - say we have 2 arenas, 256Kb available in arena 2.
// We look at arena 1; someone deallocs 256Kb from arena 1 and allocs the same from arena 2;
// we look at arena 2 and find no memory. Or, for single arena, 2 threads reserve 256k each,
// and a single 1Mb block is available. When the 1st thread locks the 1Mb freelist, the 2nd one
// might have already examined the 256k and 512k lists, finding nothing. Blocks placed by (1)
// into smaller lists after its split is done will not be found by (2); given that freelist
// locks don't overlap, (2) may even run completely between the time (1) takes out the 1Mb
// block and the time it returns the remaining 768Kb.
// Two solutions to this are some form of cross-thread helping (threads putting "demand"
// into some sort of queues that deallocate and split will examine), or having and "actor"
// allocator thread (or threads per arena).
// The 2nd one is probably much simpler and will allow us to get rid of a lot of sync code.
// But for now we will just retry 5 times 0_o
for (int attempt = 0; attempt < 5; ++attempt) {
// Try to split bigger blocks. TODO: again, ideally we would tryLock at least once
for (int i = 0; i < arenaCount; ++i) {
int newIx = arenas[i].allocateWithSplit(i, freeListIx, dest, ix, allocationSize);
if (newIx == -1) break; // Shouldn't happen.
if (newIx == dest.length) return;
ix = newIx;
}
if (attempt == 0) {
// Try to allocate memory if we haven't allocated all the way to maxSize yet; very rare.
for (int i = arenaCount; i < arenas.length; ++i) {
ix = arenas[i].allocateWithExpand(i, freeListIx, dest, ix, allocationSize);
if (ix == dest.length) return;
}
}
LlapIoImpl.LOG.warn("Failed to allocate despite reserved memory; will retry " + attempt);
}
String msg = "Failed to allocate " + size + "; at " + ix + " out of " + dest.length;
LlapIoImpl.LOG.error(msg + "\nALLOCATOR STATE:\n" + debugDump()
+ "\nPARENT STATE:\n" + memoryManager.debugDumpForOom());
throw new AllocatorOutOfMemoryException(msg);
}