/*
* Copyright 2013 Ben Manes. All Rights Reserved.
*
* 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 com.github.benmanes.multiway;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import com.google.common.base.Ticker;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A policy that enforces that a resource may reside idle in the pool for up to a specified time
* limit. A resource is considered idle when it is available to be borrowed, resulting in expiration
* if the duration since the previous release exceeds a threshold.
*
* @author Ben Manes (ben.manes@gmail.com)
*/
@ThreadSafe
final class TimeToIdlePolicy<K, R> {
static final int AMORTIZED_THRESHOLD = 16;
final LinkedDeque<ResourceKey<K>> idleQueue;
final EvictionListener<K> evictionListener;
final EliminationStack<Runnable> taskStack;
final long expireAfterAccessNanos;
final Lock idleLock;
final Ticker ticker;
TimeToIdlePolicy(long expireAfterAccessNanos,
Ticker ticker, EvictionListener<K> evictionListener) {
this.ticker = ticker;
this.idleLock = new ReentrantLock();
this.idleQueue = new LinkedDeque<>();
this.taskStack = new EliminationStack<>();
this.expireAfterAccessNanos = expireAfterAccessNanos;
this.evictionListener = checkNotNull(evictionListener);
}
/** Adds an idle resource to be tracked for expiration. */
void add(ResourceKey<K> key) {
key.setAccessTime(ticker.read());
schedule(key, key.getAddTask());
}
/** Removes a resource that is no longer idle. */
void invalidate(ResourceKey<K> key) {
schedule(key, key.getRemovalTask());
}
/** Schedules the task to be applied to the idle policy. */
void schedule(ResourceKey<K> key, Runnable task) {
taskStack.push(task);
cleanUp(AMORTIZED_THRESHOLD);
}
/** Determines whether the resource has expired. */
boolean hasExpired(ResourceKey<K> resourceKey) {
return hasExpired(resourceKey, ticker.read());
}
/** Determines whether the resource has expired. */
boolean hasExpired(ResourceKey<K> resourceKey, long currentTimeNanos) {
return (currentTimeNanos - resourceKey.getAccessTime()) >= expireAfterAccessNanos;
}
/** Performs any pending maintenance operations needed by the policy. */
void cleanUp(int threshold) {
if (idleLock.tryLock()) {
try {
drainTaskStack(threshold);
evict(threshold);
} finally {
idleLock.unlock();
}
}
}
/** Applies the pending operations, up to the threshold limit, in the task queue. */
@GuardedBy("idleLock")
void drainTaskStack(int threshold) {
for (int ran = 0; ran < threshold; ran++) {
Runnable task = taskStack.pop();
if (task == null) {
return;
}
task.run();
}
}
/** Evicts the resources that have exceeded the threshold for remaining idle. */
@GuardedBy("idleLock")
void evict(int threshold) {
long now = ticker.read();
for (int i = 0; i < threshold; i++) {
ResourceKey<K> resourceKey = idleQueue.peekFirst();
if ((resourceKey == null) || !hasExpired(resourceKey, now)) {
break;
}
idleQueue.remove();
evictionListener.onEviction(resourceKey);
}
}
/** A listener that is invoked when the policy has evicted an expired resource. */
interface EvictionListener<K> {
/** A call-back notification that the entry was evicted. */
void onEviction(ResourceKey<K> resourceKey);
}
}