org.apache.bookkeeper.mledger.util.RangeCache Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.bookkeeper.mledger.util;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.Lists;
import io.netty.util.ReferenceCounted;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang3.tuple.Pair;
/**
* Special type of cache where get() and delete() operations can be done over a range of keys.
*
* @param
* Cache key. Needs to be Comparable
* @param
* Cache value
*/
public class RangeCache, Value extends ReferenceCounted> {
// Map from key to nodes inside the linked list
private final ConcurrentNavigableMap entries;
private AtomicLong size; // Total size of values stored in cache
private final Weighter weighter; // Weighter object used to extract the size from values
/**
* Construct a new RangeLruCache with default Weighter.
*/
public RangeCache() {
this(new DefaultWeighter());
}
/**
* Construct a new RangeLruCache.
*
* @param weighter
* a custom weighter to compute the size of each stored value
*/
public RangeCache(Weighter weighter) {
this.size = new AtomicLong(0);
this.entries = new ConcurrentSkipListMap<>();
this.weighter = weighter;
}
/**
* Insert.
*
* @param key
* @param value
* ref counted value with at least 1 ref to pass on the cache
* @return whether the entry was inserted in the cache
*/
public boolean put(Key key, Value value) {
if (entries.putIfAbsent(key, value) == null) {
size.addAndGet(weighter.getSize(value));
return true;
} else {
return false;
}
}
public Value get(Key key) {
Value value = entries.get(key);
if (value == null) {
return null;
} else {
try {
value.retain();
return value;
} catch (Throwable t) {
// Value was already destroyed between get() and retain()
return null;
}
}
}
/**
*
* @param first
* the first key in the range
* @param last
* the last key in the range (inclusive)
* @return a collections of the value found in cache
*/
public Collection getRange(Key first, Key last) {
List values = Lists.newArrayList();
// Return the values of the entries found in cache
for (Value value : entries.subMap(first, true, last, true).values()) {
try {
value.retain();
values.add(value);
} catch (Throwable t) {
// Value was already destroyed between get() and retain()
}
}
return values;
}
/**
*
* @param first
* @param last
* @param lastInclusive
* @return an pair of ints, containing the number of removed entries and the total size
*/
public Pair removeRange(Key first, Key last, boolean lastInclusive) {
Map subMap = entries.subMap(first, true, last, lastInclusive);
int removedEntries = 0;
long removedSize = 0;
for (Key key : subMap.keySet()) {
Value value = entries.remove(key);
if (value == null) {
continue;
}
removedSize += weighter.getSize(value);
value.release();
++removedEntries;
}
size.addAndGet(-removedSize);
return Pair.of(removedEntries, removedSize);
}
/**
*
* @param minSize
* @return a pair containing the number of entries evicted and their total size
*/
public Pair evictLeastAccessedEntries(long minSize) {
checkArgument(minSize > 0);
long removedSize = 0;
int removedEntries = 0;
while (removedSize < minSize) {
Map.Entry entry = entries.pollFirstEntry();
if (entry == null) {
break;
}
Value value = entry.getValue();
++removedEntries;
removedSize += weighter.getSize(value);
value.release();
}
size.addAndGet(-removedSize);
return Pair.of(removedEntries, removedSize);
}
/**
* Just for testing. Getting the number of entries is very expensive on the conncurrent map
*/
protected long getNumberOfEntries() {
return entries.size();
}
public long getSize() {
return size.get();
}
/**
* Remove all the entries from the cache.
*
* @return the old size
*/
public synchronized long clear() {
long removedSize = 0;
while (true) {
Map.Entry entry = entries.pollFirstEntry();
if (entry == null) {
break;
}
Value value = entry.getValue();
removedSize += weighter.getSize(value);
value.release();
}
entries.clear();
return size.getAndAdd(-removedSize);
}
/**
* Interface of a object that is able to the extract the "weight" (size/cost/space) of the cached values.
*
* @param
*/
public interface Weighter {
long getSize(ValueT value);
}
/**
* Default cache weighter, every value is assumed the same cost.
*
* @param
*/
private static class DefaultWeighter implements Weighter {
public long getSize(Value value) {
return 1;
}
}
}