/**
* 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 org.apache.aurora.scheduler.preemptor;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Inject;
import com.google.common.base.Optional;
import com.google.common.base.Ticker;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import org.apache.aurora.common.quantity.Amount;
import org.apache.aurora.common.quantity.Time;
import org.apache.aurora.common.stats.StatsProvider;
import org.apache.aurora.common.util.Clock;
import static java.util.Objects.requireNonNull;
/**
* A bi-directional cache of items. Entries are purged from cache after
* {@link BiCacheSettings#expireAfter}.
*
* @param <K> Key type.
* @param <V> Value type.
*/
public class BiCache<K, V> {
public static class BiCacheSettings {
private final Amount<Long, Time> expireAfter;
private final String cacheName;
public BiCacheSettings(Amount<Long, Time> expireAfter, String cacheName) {
this.expireAfter = requireNonNull(expireAfter);
this.cacheName = requireNonNull(cacheName);
}
}
private final Cache<K, V> cache;
private final Multimap<V, K> inverse = HashMultimap.create();
private final AtomicLong removalCounter;
private final AtomicLong expirationCounter;
private final AtomicLong explictRemovalCounter;
@Inject
public BiCache(
StatsProvider statsProvider,
BiCacheSettings settings,
final Clock clock) {
requireNonNull(clock);
this.cache = CacheBuilder.newBuilder()
.expireAfterWrite(settings.expireAfter.as(Time.MINUTES), TimeUnit.MINUTES)
.ticker(new Ticker() {
@Override
public long read() {
return clock.nowNanos();
}
})
.removalListener(new RemovalListener<K, V>() {
@Override
public void onRemoval(RemovalNotification<K, V> notification) {
removalCounter.getAndIncrement();
if (notification.wasEvicted()) {
expirationCounter.incrementAndGet();
}
inverse.remove(notification.getValue(), notification.getKey());
}
})
.build();
statsProvider.makeGauge(settings.cacheName + "_cache_size", cache::size);
removalCounter = statsProvider.makeCounter(settings.cacheName + "_cache_removals");
expirationCounter = statsProvider.makeCounter(
settings.cacheName + "_cache_expiration_removals");
explictRemovalCounter = statsProvider.makeCounter(
settings.cacheName + "_cache_explicit_removals");
}
/**
* Puts a new key/value pair.
*
* @param key Key to add.
* @param value Value to add.
*/
public synchronized void put(K key, V value) {
requireNonNull(key);
requireNonNull(value);
cache.put(key, value);
inverse.put(value, key);
}
/**
* Gets a cached value by key.
*
* @param key Key to get value for.
* @return Optional of value.
*/
public synchronized Optional<V> get(K key) {
return Optional.fromNullable(cache.getIfPresent(key));
}
/**
* Gets a set of keys for a given value.
*
* @param value Value to get all keys for.
* @return An {@link Iterable} of keys or empty if value does not exist.
*/
public synchronized Set<K> getByValue(V value) {
// Cache items are lazily removed by routine maintenance operations during get/write access.
// Forcing cleanup here to ensure proper data integrity.
cache.cleanUp();
return ImmutableSet.copyOf(inverse.get(value));
}
/**
* Removes a key/value pair from cache.
*
* @param key Key to remove.
* @param value Value to remove.
*/
public synchronized void remove(K key, V value) {
explictRemovalCounter.getAndIncrement();
inverse.remove(value, key);
cache.invalidate(key);
}
/**
* Returns an immutable copy of entries stored in this cache.
*
* @return Immutable map of cache entries.
*/
public synchronized Map<K, V> asMap() {
return ImmutableMap.copyOf(cache.asMap());
}
}