package com.tesora.dve.common;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import java.util.Timer;
// this is an object pool that is tuned for environments with lots of threads - currently used for the result
// collector pool. after about 100 concurrent connections, the commons GenericObjectPool causes nontrivial lock
// contention on borrowObject. this class is meant to rectify that problem.
public class PEObjectPool<T extends PEPoolableObject> {
// we're going to need two locks: one on the buf list, and one on the free list.
private final Object bufLock = new Object();
private final Object freeLock = new Object();
private final PEPoolableObjectFactory<T> factory;
private FreeEntry[] freeList;
// i.e. the index into the freeList of the last inuse item
private int lastFree;
// the backing objects
private PEPoolableObject[] bufList;
// this is the index of the last object
private int last;
// expiration parameters
// how long is something idle before we toss it
private int threshold;
private int minIdle;
private Timer timer;
public PEObjectPool(PEPoolableObjectFactory<T> fact, int minIdle, int initBufSize, int checkInterval, int idleThreshold) {
factory = fact;
freeList = new FreeEntry[initBufSize];
for(int i = 0; i < initBufSize; i++)
freeList[i] = new FreeEntry(-1);
// nothing is in use initially
lastFree = -1;
bufList = new PEPoolableObject[initBufSize];
last = -1;
threshold = idleThreshold;
this.minIdle = minIdle;
timer = new Timer();
timer.schedule(new ExpireTask(this), checkInterval, checkInterval);
}
// used in tests and what not
@SuppressWarnings("unchecked")
public void clear() {
synchronized(freeLock) {
for(int i = 0; i < freeList.length; i++)
// this will clear the entry
freeList[i].use();
lastFree = -1;
}
List<PEPoolableObject> out = new ArrayList<PEPoolableObject>();
synchronized(bufLock) {
for(int i = 0; i < bufList.length; i++) {
if (bufList[i] != null) {
out.add(bufList[i]);
bufList[i] = null;
}
}
}
for(PEPoolableObject so : out)
factory.destroy((T) so);
}
@SuppressWarnings("unchecked")
private T getNextFree() throws Exception {
synchronized(freeLock) {
T obj = null;
if (lastFree < 0 || lastFree >= freeList.length) return null;
FreeEntry fet = freeList[lastFree--];
if (fet.getIndex() == -1)
throw new Exception("Found unused slot, expected next free at index " + lastFree + ", total freelist length " + freeList.length);
obj = (T) bufList[fet.use()];
return obj;
}
}
@SuppressWarnings("unchecked")
private T makeNewObject() throws Exception {
PEPoolableObject sot = factory.makeNew();
synchronized(bufLock) {
int index = ++last;
if (index >= bufList.length) {
// nasty case
index = -1;
for(int i = 0; i < bufList.length; i++) {
if (bufList[i] == null) {
index = i;
break;
}
}
if (index == -1) {
// reallocate
PEPoolableObject[] newBuf = new PEPoolableObject[bufList.length * 2];
System.arraycopy(bufList, 0, newBuf, 0, bufList.length);
index = bufList.length;
bufList = newBuf;
}
}
bufList[index] = sot;
sot.setPoolIndex(index);
}
return (T) sot;
}
public T allocate() throws Exception {
// try {
T candidate = getNextFree();
if (candidate == null)
candidate = makeNewObject();
return candidate;
// } catch (Exception e) {
// e.printStackTrace();
// throw e;
// }
}
public void deallocate(T obj) throws Exception {
// try {
factory.passivate(obj);
if (obj.getPoolIndex() == -1)
throw new Exception("Attempt to deallocate an unpooled object");
synchronized(freeLock) {
if (++lastFree >= freeList.length) {
lastFree = freeList.length - 1;
// reallocate free list
FreeEntry[] nl = new FreeEntry[freeList.length * 2];
System.arraycopy(freeList, 0, nl, 0, freeList.length);
// add new entries at the end
for(int i = freeList.length; i < nl.length; i++)
nl[i] = new FreeEntry(-1);
freeList = nl;
}
freeList[lastFree].setIndex(obj.getPoolIndex());
}
// } catch (Exception e) {
// e.printStackTrace();
// throw e;
// }
}
@SuppressWarnings("unchecked")
public void expire() {
if (lastFree < minIdle) return;
long now = System.currentTimeMillis();
// anything whose timestamp is less than this is a candidate
long cutoff = now - threshold;
int[] toDestroy = null;
// this might not always be that efficient - that's ok - we'll get the next tranche later
synchronized(freeLock) {
FreeEntry[] a = new FreeEntry[freeList.length];
FreeEntry[] b = new FreeEntry[freeList.length];
int ac = -1;
int bc = -1;
for(int i = 0; i < freeList.length; i++) {
if (i > lastFree) {
if (ac == -1)
// really quick exit
return;
b[++bc] = freeList[i];
}
else if ((lastFree - ac) > minIdle && freeList[i].getTimestamp() < cutoff) {
a[++ac] = freeList[i];
// System.out.println("To be destroyed: entry " + i + ", index " + freeList[i].getIndex() + ", lastFree " + lastFree);
} else {
b[++bc] = freeList[i];
}
}
if (ac == -1)
// quick exit - found nothing
return;
// now a has all the entries we are modifying, and b has all the entries we won't modify
// we're going to move all the b entries to the beginning of the freeList, and all the a
// entries to the end. but first, copy out all the a indexes
toDestroy = new int[ac+1];
for(int i = 0; i < toDestroy.length; i++) {
toDestroy[i] = a[i].getIndex();
a[i].setIndex(-1);
}
// int oldLastFree = lastFree;
int fc = -1;
lastFree = -1;
for(int i = 0; i <= bc; i++) {
freeList[++fc] = b[i];
if (b[i].getIndex() != -1)
lastFree = fc;
}
for(int i = 0; i <= ac; i++) {
freeList[++fc] = a[i];
}
// System.out.println("Evicted " + toDestroy.length + " entries. Old lastFree " + oldLastFree + ", new lastFree " + lastFree + ", freelist length " + freeList.length);
}
PEPoolableObject[] destroyObjs = null;
synchronized(bufLock) {
destroyObjs = new PEPoolableObject[toDestroy.length];
for(int i = 0; i < toDestroy.length; i++) {
int index = toDestroy[i];
destroyObjs[i] = bufList[index];
bufList[index] = null;
}
}
for(PEPoolableObject so : destroyObjs)
factory.destroy((T) so);
}
private static class FreeEntry {
// when -1 - not in use
private int index;
private long timestamp;
public FreeEntry(int ind) {
setIndex(ind);
}
public void setIndex(int ind) {
index = ind;
timestamp = System.currentTimeMillis();
}
public long getTimestamp() {
return timestamp;
}
public int getIndex() {
return index;
}
public int use() {
int ret = index;
index = -1;
return ret;
}
}
private static class ExpireTask extends TimerTask {
private final PEObjectPool<?> myPool;
public ExpireTask(PEObjectPool<?> sop) {
myPool = sop;
}
@Override
public void run() {
myPool.expire();
}
}
}