package org.dcache.poolmanager;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import diskCacheV111.poolManager.CostModule;
import diskCacheV111.pools.PoolCostInfo.PoolSpaceInfo;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.CostException;
import org.dcache.pool.assumption.AvailableSpaceAssumption;
import org.dcache.vehicles.FileAttributes;
import static com.google.common.base.Preconditions.checkState;
import static java.util.stream.Collectors.toList;
/**
* Partition that selects the least recently used pool.
*
* Corresponds more or less to a round robin selection.
*/
public class LruPartition extends Partition
{
private static final long serialVersionUID = 2982471048144479008L;
static final String TYPE = "lru";
private static final AtomicLong _counter = new AtomicLong();
private static final Random random = new Random();
/**
* Pool name to access order. Making this static will mean that
* all instances of LruPartition will share this information. It
* will also ensure that this information is not serialized,
* meaning that multiple deserialized instances preserve this
* information.
*/
private static final ConcurrentMap<String,Long> _lastWrite =
Maps.newConcurrentMap();
private static final ConcurrentMap<String,Long> _lastRead =
Maps.newConcurrentMap();
public LruPartition(Map<String,String> inherited)
{
this(inherited, NO_PROPERTIES);
}
public LruPartition(Map<String,String> inherited,
Map<String,String> properties)
{
super(NO_PROPERTIES, inherited, properties);
}
@Override
protected Partition create(Map<String,String> inherited,
Map<String,String> properties)
{
return new LruPartition(inherited, properties);
}
@Override
public String getType()
{
return TYPE;
}
private static boolean canHoldFile(PoolInfo pool, long size)
{
PoolSpaceInfo space = pool.getCostInfo().getSpaceInfo();
long available = space.getFreeSpace() + space.getRemovableSpace();
return available - size > space.getGap();
}
private long next()
{
return _counter.getAndIncrement();
}
/* Thread safe lock-free algorithm for choosing the least recently
* used pool and updating the timestamp of the chosen pool.
*/
private PoolInfo select(List<PoolInfo> pools,
ConcurrentMap<String,Long> lastAccessed)
{
checkState(!pools.isEmpty());
PoolInfo oldest;
long current;
do {
oldest = null;
current = Long.MAX_VALUE;
for (PoolInfo pool: pools) {
Long value = lastAccessed.get(pool.getName());
/* A random negative value for unused pools ensures
* that the initial order in which we select pools is
* random.
*/
if (value == null) {
value = -random.nextLong();
}
if (value < current) {
current = value;
oldest = pool;
}
}
/* Repeat until we manage update the entry without concurrent
* modification.
*/
} while (current < 0 && lastAccessed.putIfAbsent(oldest.getName(), next()) != null ||
current >= 0 && !lastAccessed.replace(oldest.getName(), current, next()));
return oldest;
}
@Override
public SelectedPool selectWritePool(CostModule cm,
List<PoolInfo> pools,
FileAttributes attributes,
long preallocated)
throws CacheException
{
List<PoolInfo> freePools =
pools.stream().filter(pool -> canHoldFile(pool, preallocated)).collect(toList());
if (freePools.isEmpty()) {
throw new CostException("All pools are full", null, false, false);
}
return new SelectedPool(select(freePools, _lastWrite), new AvailableSpaceAssumption(preallocated));
}
@Override
public SelectedPool selectReadPool(CostModule cm,
List<PoolInfo> pools,
FileAttributes attributes)
throws CacheException
{
return new SelectedPool(select(pools, _lastRead));
}
@Override
public P2pPair selectPool2Pool(CostModule cm,
List<PoolInfo> src,
List<PoolInfo> dst,
FileAttributes attributes,
boolean force)
throws CacheException
{
return new P2pPair(new SelectedPool(select(src, _lastRead)),
selectWritePool(cm, dst, attributes, attributes.getSize()));
}
@Override
public SelectedPool selectStagePool(CostModule cm,
List<PoolInfo> pools,
String previousPool,
String previousHost,
FileAttributes attributes)
throws CacheException
{
return selectWritePool(cm, pools, attributes, attributes.getSize());
}
}