/* * 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.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; final class QAllocThread<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 CountDownLatch completionLatch; private final BlockingQueue<QSlot<T>> live; private final BlockingQueue<QSlot<T>> dead; private final Reallocator<T> allocator; private final QSlot<T> poisonPill; private final MetricsRecorder metricsRecorder; private final boolean backgroundExpirationEnabled; private final Expiration<? super T> expiration; private final PreciseLeakDetector leakDetector; 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 QAllocThread( BlockingQueue<QSlot<T>> live, BlockingQueue<QSlot<T>> dead, Config<T> config, QSlot<T> poisonPill) { this.targetSize = config.getSize(); completionLatch = new CountDownLatch(1); this.allocator = config.getAdaptedReallocator(); this.size = 0; this.didAnythingLastIteration = true; // start out busy this.live = live; this.dead = dead; this.poisonPill = poisonPill; this.metricsRecorder = config.getMetricsRecorder(); this.backgroundExpirationEnabled = config.isBackgroundExpirationEnabled(); this.expiration = config.getExpiration(); this.leakDetector = config.isPreciseLeakDetectionEnabled() ? new PreciseLeakDetector() : null; this.poisonedSlots = new AtomicInteger(); } @Override public void run() { continuouslyReplenishPool(); shutPoolDown(); completionLatch.countDown(); } private void continuouslyReplenishPool() { try { //noinspection InfiniteLoopStatement for (;;) { boolean weHaveWorkToDo = size != targetSize || poisonedSlots.get() > 0; long deadPollTimeout = weHaveWorkToDo? (didAnythingLastIteration? 0 : 10) : 50; didAnythingLastIteration = false; if (size < targetSize) { QSlot<T> slot = new QSlot<T>(live, poisonedSlots); alloc(slot); registerWithLeakDetector(slot); didAnythingLastIteration = true; } QSlot<T> slot = dead.poll(deadPollTimeout, TimeUnit.MILLISECONDS); if (size > targetSize) { slot = slot == null? live.poll() : slot; if (slot != null) { dealloc(slot); unregisterWithLeakDetector(slot); didAnythingLastIteration = true; } } else if (slot != null) { realloc(slot); didAnythingLastIteration = true; } if (shutdown) { break; } if (poisonedSlots.get() > 0) { // Proactively seek out and try to heal poisoned slots slot = live.poll(); if (slot != null) { if (slot.poison == null && !slot.expired) { live.offer(slot); } else { realloc(slot); didAnythingLastIteration = true; } } } else if (backgroundExpirationEnabled && !weHaveWorkToDo) { slot = live.poll(); try { if (slot != null) { if (slot.expired || expiration.hasExpired(slot)) { dead.offer(slot); didAnythingLastIteration = true; } else { live.offer(slot); } } } catch (Exception e) { dead.offer(slot); didAnythingLastIteration = true; } } } } 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 live.offer(poisonPill); } private void registerWithLeakDetector(QSlot<T> slot) { if (leakDetector != null) { leakDetector.register(slot); } } private void unregisterWithLeakDetector(QSlot<T> slot) { if (leakDetector != null) { leakDetector.unregister(slot); } } private void shutPoolDown() { while (size > 0) { QSlot<T> slot = dead.poll(); if (slot == null) { slot = live.poll(); } if (slot == poisonPill) { live.offer(poisonPill); slot = null; } if (slot == null) { LockSupport.parkNanos(shutdownPauseNanos); } else { dealloc(slot); unregisterWithLeakDetector(slot); } } } private void alloc(QSlot<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++; slot.created = System.currentTimeMillis(); slot.claims = 0; slot.stamp = 0; slot.expired = false; slot.claimed.set(true); slot.release(slot.obj); } private void dealloc(QSlot<T> slot) { size--; try { if (slot.poison == null) { recordObjectLifetimeSample(System.currentTimeMillis() - slot.created); allocator.deallocate(slot.obj); if (slot.expired) { poisonedSlots.getAndDecrement(); } } else { poisonedSlots.getAndDecrement(); } } catch (Exception ignore) { // NOPMD // Ignored as per specification } slot.poison = null; slot.obj = null; } private void realloc(QSlot<T> slot) { if (slot.poison == null) { if (slot.expired) { poisonedSlots.getAndDecrement(); } 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); slot.created = now; slot.claims = 0; slot.stamp = 0; slot.expired = false; live.offer(slot); } else { dealloc(slot); alloc(slot); } } private void recordObjectLifetimeSample(long milliseconds) { if (metricsRecorder != null) { metricsRecorder.recordObjectLifetimeSampleMillis(milliseconds); } } void setTargetSize(int size) { this.targetSize = size; } int getTargetSize() { return targetSize; } LatchCompletion 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; } }