/*
* Copyright (C) 2011-2014 Chris Vest (mr.chrisvest@gmail.com)
*
* 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 stormpot;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
final class BAllocThread<T extends Poolable> implements Runnable {
/**
* The amount of time, in nanoseconds, to wait for more work when the
* shutdown process has deallocated all the dead and live slots it could
* get its hands on, but there are still (claimed) slots left.
*/
private final static long shutdownPauseNanos =
TimeUnit.MILLISECONDS.toNanos(10);
private final BlockingQueue<BSlot<T>> live;
private final DisregardBPile<T> disregardPile;
private final Reallocator<T> allocator;
private final BSlot<T> poisonPill;
private final MetricsRecorder metricsRecorder;
private final Expiration<? super T> expiration;
private final boolean backgroundExpirationEnabled;
private final PreciseLeakDetector leakDetector;
private final CountDownLatch completionLatch;
private final BlockingQueue<BSlot<T>> dead;
private final AtomicInteger poisonedSlots;
// Single reader: this. Many writers.
private volatile int targetSize;
private volatile boolean shutdown;
// Many readers. Single writer: this.
private volatile long allocationCount;
private volatile long failedAllocationCount;
private int size;
private boolean didAnythingLastIteration;
public BAllocThread(
BlockingQueue<BSlot<T>> live,
DisregardBPile<T> disregardPile,
Config<T> config,
BSlot<T> poisonPill) {
this.live = live;
this.disregardPile = disregardPile;
this.allocator = config.getAdaptedReallocator();
this.targetSize = config.getSize();
this.metricsRecorder = config.getMetricsRecorder();
this.poisonPill = poisonPill;
this.expiration = config.getExpiration();
this.backgroundExpirationEnabled = config.isBackgroundExpirationEnabled();
this.leakDetector = config.isPreciseLeakDetectionEnabled() ?
new PreciseLeakDetector() : null;
this.completionLatch = new CountDownLatch(1);
this.dead = new LinkedTransferQueue<>();
this.poisonedSlots = new AtomicInteger();
this.size = 0;
this.didAnythingLastIteration = true; // start out busy
}
@Override
public void run() {
continuouslyReplenishPool();
shutPoolDown();
completionLatch.countDown();
}
private void continuouslyReplenishPool() {
try {
while (!shutdown) {
replenishPool();
}
} catch (InterruptedException ignore) {
// This can only be thrown by the dead.poll() method call, because alloc
// catches exceptions and use them for poison.
}
// This means we've been shut down.
// let the poison-pill enter the system
poisonPill.dead2live();
live.offer(poisonPill);
}
private void replenishPool() throws InterruptedException {
boolean weHaveWorkToDo = size != targetSize || poisonedSlots.get() > 0;
long deadPollTimeout = weHaveWorkToDo?
(didAnythingLastIteration? 0 : 10) : 50;
didAnythingLastIteration = false;
if (size < targetSize) {
increaseSizeByAllocating();
}
BSlot<T> slot = dead.poll(deadPollTimeout, TimeUnit.MILLISECONDS);
if (size > targetSize) {
reduceSizeByDeallocating(slot);
} else if (slot != null) {
reallocateDeadSlot(slot);
}
if (shutdown) {
// Prior allocations might notice that we've been shut down. In that
// case, we need to skip the eager reallocation of poisoned slots.
return;
}
if (poisonedSlots.get() > 0) {
// Proactively seek out and try to heal poisoned slots
proactivelyHealPoison();
} else if (backgroundExpirationEnabled && !weHaveWorkToDo) {
backgroundExpirationCheck();
}
}
private void increaseSizeByAllocating() {
BSlot<T> slot = new BSlot<>(live, poisonedSlots);
alloc(slot);
registerWithLeakDetector(slot);
}
private void reduceSizeByDeallocating(BSlot<T> slot) {
slot = slot == null? live.poll() : slot;
if (slot != null) {
if (slot.isDead() || slot.live2dead()) {
dealloc(slot);
unregisterWithLeakDetector(slot);
} else {
live.offer(slot);
}
}
}
private void registerWithLeakDetector(BSlot<T> slot) {
if (leakDetector != null) {
leakDetector.register(slot);
}
}
private void unregisterWithLeakDetector(BSlot<T> slot) {
if (leakDetector != null) {
leakDetector.unregister(slot);
}
}
private void reallocateDeadSlot(BSlot<T> slot) {
realloc(slot);
}
private void proactivelyHealPoison() {
BSlot<T> slot = live.poll();
if (slot != null) {
if (slot.poison != null && (slot.isDead() || slot.live2dead())) {
realloc(slot);
} else {
live.offer(slot);
}
}
}
private void backgroundExpirationCheck() {
BSlot<T> slot = live.poll();
if (slot != null) {
if (slot.isLive() && slot.live2claim()) {
boolean expired;
try {
expired = slot.poison != null || expiration.hasExpired(slot);
} catch (Exception ignore) {
expired = true;
}
if (expired) {
slot.claim2dead(); // Not strictly necessary
dead.offer(slot);
didAnythingLastIteration = true;
} else {
slot.claim2live();
live.offer(slot);
}
} else {
live.offer(slot);
}
}
}
private void shutPoolDown() {
while (size > 0) {
BSlot<T> slot = dead.poll();
if (slot == null) {
slot = live.poll();
}
if (slot == poisonPill) {
live.offer(poisonPill);
slot = null;
}
if (slot == null) {
if (!disregardPile.refillQueue()) {
LockSupport.parkNanos(shutdownPauseNanos);
}
} else {
if (slot.isDead() || slot.live2dead()) {
dealloc(slot);
unregisterWithLeakDetector(slot);
} else {
live.offer(slot);
}
}
}
}
private void alloc(BSlot<T> slot) {
try {
slot.obj = allocator.allocate(slot);
if (slot.obj == null) {
poisonedSlots.getAndIncrement();
failedAllocationCount++;
slot.poison = new NullPointerException("Allocation returned null");
} else {
allocationCount++;
}
} catch (Exception e) {
poisonedSlots.getAndIncrement();
failedAllocationCount++;
slot.poison = e;
}
size++;
resetSlot(slot, System.currentTimeMillis());
live.offer(slot);
didAnythingLastIteration = true;
}
private void resetSlot(BSlot<T> slot, long now) {
slot.created = now;
slot.claims = 0;
slot.stamp = 0;
slot.dead2live();
}
private void dealloc(BSlot<T> slot) {
size--;
try {
if (slot.poison == null) {
long now = System.currentTimeMillis();
recordObjectLifetimeSample(now - slot.created);
allocator.deallocate(slot.obj);
} else {
poisonedSlots.getAndDecrement();
}
} catch (Exception ignore) { // NOPMD
// Ignored as per specification
}
slot.poison = null;
slot.obj = null;
didAnythingLastIteration = true;
}
private void realloc(BSlot<T> slot) {
if (slot.poison == BlazePool.EXPLICIT_EXPIRE_POISON) {
slot.poison = null;
poisonedSlots.getAndDecrement();
}
if (slot.poison == null) {
try {
slot.obj = allocator.reallocate(slot, slot.obj);
if (slot.obj == null) {
poisonedSlots.getAndIncrement();
failedAllocationCount++;
slot.poison = new NullPointerException("Reallocation returned null");
} else {
allocationCount++;
}
} catch (Exception e) {
poisonedSlots.getAndIncrement();
failedAllocationCount++;
slot.poison = e;
}
long now = System.currentTimeMillis();
recordObjectLifetimeSample(now - slot.created);
resetSlot(slot, now);
live.offer(slot);
} else {
dealloc(slot);
alloc(slot);
}
didAnythingLastIteration = true;
}
private void recordObjectLifetimeSample(long milliseconds) {
if (metricsRecorder != null) {
metricsRecorder.recordObjectLifetimeSampleMillis(milliseconds);
}
}
void setTargetSize(int size) {
this.targetSize = size;
}
int getTargetSize() {
return targetSize;
}
Completion shutdown(Thread allocatorThread) {
shutdown = true;
allocatorThread.interrupt();
return new LatchCompletion(completionLatch);
}
long getAllocationCount() {
return allocationCount;
}
long getFailedAllocationCount() {
return failedAllocationCount;
}
long countLeakedObjects() {
if (leakDetector != null) {
return leakDetector.countLeakedObjects();
}
return -1;
}
void offerDeadSlot(BSlot<T> slot) {
dead.offer(slot);
}
}