package core.framework.impl.resource;
import core.framework.api.util.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.Instant;
import java.util.Iterator;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
/**
* this is for internal use only,
* <p>
* the reason not using template/lambda pattern but classic design
* is to keep original exception, and simplify context variable access (read or write var within method),
* <p>
* the downside is boilerplate code, so to keep it only for internal
*
* @author neo
*/
public final class Pool<T> {
final BlockingDeque<PoolItem<T>> idleItems = new LinkedBlockingDeque<>();
private final Logger logger = LoggerFactory.getLogger(Pool.class);
private final AtomicInteger total = new AtomicInteger(0);
private final Supplier<T> factory;
private final ResourceCloseHandler<T> closeHandler;
private String name;
private int minSize = 1;
private int maxSize = 50;
private Duration maxIdleTime = Duration.ofMinutes(30);
private long checkoutTimeoutInMs = Duration.ofSeconds(30).toMillis();
public Pool(Supplier<T> factory, ResourceCloseHandler<T> closeHandler) {
this.factory = factory;
this.closeHandler = closeHandler;
}
public void name(String name) {
this.name = name;
}
public void size(int minSize, int maxSize) {
this.minSize = minSize;
this.maxSize = maxSize;
}
public void maxIdleTime(Duration maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
public void checkoutTimeout(Duration checkoutTimeout) {
checkoutTimeoutInMs = checkoutTimeout.toMillis();
}
public PoolItem<T> borrowItem() {
PoolItem<T> item = idleItems.poll();
if (item != null) return item;
if (total.get() < maxSize) {
return createNewItem();
} else {
return waitNextAvailableItem();
}
}
public void returnItem(PoolItem<T> item) {
if (item.broken) {
recycleItem(item);
} else {
item.returnTime = System.currentTimeMillis();
idleItems.push(item);
}
}
private void recycleItem(PoolItem<T> item) {
StopWatch watch = new StopWatch();
int total = this.total.decrementAndGet();
try {
closeResource(item.resource);
} finally {
logger.debug("recycle resource, pool={}, total={}, elapsed={}", name, total, watch.elapsedTime());
}
}
private PoolItem<T> waitNextAvailableItem() {
StopWatch watch = new StopWatch();
try {
PoolItem<T> item = idleItems.poll(checkoutTimeoutInMs, TimeUnit.MILLISECONDS);
if (item == null) throw new PoolException("timeout to wait for next available resource", "POOL_TIME_OUT");
return item;
} catch (InterruptedException e) {
throw new Error("interrupted during waiting for next available resource", e);
} finally {
logger.debug("wait for next available resource, pool={}, total={}, elapsed={}", name, total.get(), watch.elapsedTime());
}
}
private PoolItem<T> createNewItem() {
StopWatch watch = new StopWatch();
total.incrementAndGet();
try {
return new PoolItem<>(factory.get());
} catch (Throwable e) {
total.getAndDecrement();
throw e;
} finally {
logger.debug("create new resource, pool={}, total={}, elapsed={}", name, total.get(), watch.elapsedTime());
}
}
public void refresh() {
logger.info("refresh resource pool, pool={}", name);
recycleIdleItems();
replenish();
}
private void recycleIdleItems() {
Iterator<PoolItem<T>> iterator = idleItems.descendingIterator();
long maxIdleTimeInSeconds = maxIdleTime.getSeconds();
Instant now = Instant.now();
while (iterator.hasNext()) {
PoolItem<T> item = iterator.next();
if (Duration.between(Instant.ofEpochMilli(item.returnTime), now).getSeconds() > maxIdleTimeInSeconds) {
boolean removed = idleItems.remove(item);
if (!removed) return;
recycleItem(item);
} else {
return;
}
}
}
private void replenish() {
while (total.get() < minSize) {
returnItem(createNewItem());
}
}
private void closeResource(T resource) {
try {
closeHandler.close(resource);
} catch (Exception e) {
logger.warn("failed to close resource, pool={}", name, e);
}
}
public void close() {
total.set(maxSize); // make sure no more new resource will be created
while (true) {
PoolItem<T> item = idleItems.poll();
if (item == null) return;
closeResource(item.resource);
}
}
}