Cache FileStoreAttributeCache per directory
Cache FileStoreAttributeCache entries since looking up FileStore for a file may be expensive on some platforms. Implement a simple LRU cache based on ConcurrentHashMap using a simple long counter to order access to cache entries. Change-Id: I4881fa938ad2f17712c05da857838073a2fc4ddb Signed-off-by: Matthias Sohn <matthias.sohn@sap.com> Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com> Also-By: Marc Strapetz <marc.strapetz@syntevo.com>
This commit is contained in:
parent
275f3da783
commit
6857138e19
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SimpleLruCacheTest {
|
||||||
|
|
||||||
|
private Path trash;
|
||||||
|
|
||||||
|
private SimpleLruCache<String, String> cache;
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws IOException {
|
||||||
|
trash = Files.createTempDirectory("tmp_");
|
||||||
|
cache = new SimpleLruCache<>(100, 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
FileUtils.delete(trash.toFile(),
|
||||||
|
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutGet() {
|
||||||
|
cache.put("a", "A");
|
||||||
|
cache.put("z", "Z");
|
||||||
|
assertEquals("A", cache.get("a"));
|
||||||
|
assertEquals("Z", cache.get("z"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testPurgeFactorTooLarge() {
|
||||||
|
cache.configure(5, 1.01f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testPurgeFactorTooLarge2() {
|
||||||
|
cache.configure(5, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testPurgeFactorTooSmall() {
|
||||||
|
cache.configure(5, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testPurgeFactorTooSmall2() {
|
||||||
|
cache.configure(5, -100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMissing() {
|
||||||
|
assertEquals(null, cache.get("a"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPurge() {
|
||||||
|
for (int i = 0; i < 101; i++) {
|
||||||
|
cache.put("a" + i, "a" + i);
|
||||||
|
}
|
||||||
|
assertEquals(80, cache.size());
|
||||||
|
assertNull(cache.get("a0"));
|
||||||
|
assertNull(cache.get("a20"));
|
||||||
|
assertNotNull(cache.get("a21"));
|
||||||
|
assertNotNull(cache.get("a99"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConfigure() {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
cache.put("a" + i, "a" + i);
|
||||||
|
}
|
||||||
|
assertEquals(100, cache.size());
|
||||||
|
cache.configure(10, 0.3f);
|
||||||
|
assertEquals(7, cache.size());
|
||||||
|
assertNull(cache.get("a0"));
|
||||||
|
assertNull(cache.get("a92"));
|
||||||
|
assertNotNull(cache.get("a93"));
|
||||||
|
assertNotNull(cache.get("a99"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -240,6 +240,14 @@
|
||||||
</message_arguments>
|
</message_arguments>
|
||||||
</filter>
|
</filter>
|
||||||
</resource>
|
</resource>
|
||||||
|
<resource path="src/org/eclipse/jgit/util/SimpleLruCache.java" type="org.eclipse.jgit.util.SimpleLruCache">
|
||||||
|
<filter id="1109393411">
|
||||||
|
<message_arguments>
|
||||||
|
<message_argument value="5.1.9"/>
|
||||||
|
<message_argument value="org.eclipse.jgit.util.SimpleLruCache"/>
|
||||||
|
</message_arguments>
|
||||||
|
</filter>
|
||||||
|
</resource>
|
||||||
<resource path="src/org/eclipse/jgit/util/Stats.java" type="org.eclipse.jgit.util.Stats">
|
<resource path="src/org/eclipse/jgit/util/Stats.java" type="org.eclipse.jgit.util.Stats">
|
||||||
<filter id="1109393411">
|
<filter id="1109393411">
|
||||||
<message_arguments>
|
<message_arguments>
|
||||||
|
|
|
@ -390,6 +390,7 @@ invalidPathContainsSeparator=Invalid path (contains separator ''{0}''): {1}
|
||||||
invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0}
|
invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0}
|
||||||
invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0}
|
invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0}
|
||||||
invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1}
|
invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1}
|
||||||
|
invalidPurgeFactor=Invalid purgeFactor {0}, values have to be in range between 0 and 1
|
||||||
invalidRedirectLocation=Invalid redirect location {0} -> {1}
|
invalidRedirectLocation=Invalid redirect location {0} -> {1}
|
||||||
invalidRefAdvertisementLine=Invalid ref advertisement line: ''{1}''
|
invalidRefAdvertisementLine=Invalid ref advertisement line: ''{1}''
|
||||||
invalidReflogRevision=Invalid reflog revision: {0}
|
invalidReflogRevision=Invalid reflog revision: {0}
|
||||||
|
|
|
@ -451,6 +451,7 @@ public static JGitText get() {
|
||||||
/***/ public String invalidPathPeriodAtEndWindows;
|
/***/ public String invalidPathPeriodAtEndWindows;
|
||||||
/***/ public String invalidPathSpaceAtEndWindows;
|
/***/ public String invalidPathSpaceAtEndWindows;
|
||||||
/***/ public String invalidPathReservedOnWindows;
|
/***/ public String invalidPathReservedOnWindows;
|
||||||
|
/***/ public String invalidPurgeFactor;
|
||||||
/***/ public String invalidRedirectLocation;
|
/***/ public String invalidRedirectLocation;
|
||||||
/***/ public String invalidRefAdvertisementLine;
|
/***/ public String invalidRefAdvertisementLine;
|
||||||
/***/ public String invalidReflogRevision;
|
/***/ public String invalidReflogRevision;
|
||||||
|
|
|
@ -230,6 +230,9 @@ public final static class FileStoreAttributes {
|
||||||
|
|
||||||
private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();
|
private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final SimpleLruCache<Path, FileStoreAttributes> attrCacheByPath = new SimpleLruCache<>(
|
||||||
|
100, 0.2f);
|
||||||
|
|
||||||
private static AtomicBoolean background = new AtomicBoolean();
|
private static AtomicBoolean background = new AtomicBoolean();
|
||||||
|
|
||||||
private static Map<FileStore, Lock> locks = new ConcurrentHashMap<>();
|
private static Map<FileStore, Lock> locks = new ConcurrentHashMap<>();
|
||||||
|
@ -245,6 +248,26 @@ private static void setBackground(boolean async) {
|
||||||
private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
|
private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
|
||||||
.ofMillis(10);
|
.ofMillis(10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures size and purge factor of the path-based cache for file
|
||||||
|
* system attributes. Caching of file system attributes avoids recurring
|
||||||
|
* lookup of @{code FileStore} of files which may be expensive on some
|
||||||
|
* platforms.
|
||||||
|
*
|
||||||
|
* @param maxSize
|
||||||
|
* maximum size of the cache, default is 100
|
||||||
|
* @param purgeFactor
|
||||||
|
* when the size of the map reaches maxSize the oldest
|
||||||
|
* entries will be purged to free up some space for new
|
||||||
|
* entries, {@code purgeFactor} is the fraction of
|
||||||
|
* {@code maxSize} to purge when this happens
|
||||||
|
* @since 5.1.9
|
||||||
|
*/
|
||||||
|
public static void configureAttributesPathCache(int maxSize,
|
||||||
|
float purgeFactor) {
|
||||||
|
FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the FileStoreAttributes for the given FileStore
|
* Get the FileStoreAttributes for the given FileStore
|
||||||
*
|
*
|
||||||
|
@ -255,7 +278,13 @@ private static void setBackground(boolean async) {
|
||||||
public static FileStoreAttributes get(Path path) {
|
public static FileStoreAttributes get(Path path) {
|
||||||
path = path.toAbsolutePath();
|
path = path.toAbsolutePath();
|
||||||
Path dir = Files.isDirectory(path) ? path : path.getParent();
|
Path dir = Files.isDirectory(path) ? path : path.getParent();
|
||||||
return getFileStoreAttributes(dir);
|
FileStoreAttributes cached = attrCacheByPath.get(dir);
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
FileStoreAttributes attrs = getFileStoreAttributes(dir);
|
||||||
|
attrCacheByPath.put(dir, attrs);
|
||||||
|
return attrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileStoreAttributes getFileStoreAttributes(Path dir) {
|
private static FileStoreAttributes getFileStoreAttributes(Path dir) {
|
||||||
|
|
|
@ -0,0 +1,253 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019, Marc Strapetz <marc.strapetz@syntevo.com>
|
||||||
|
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.util;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple limited size cache based on ConcurrentHashMap purging entries in LRU
|
||||||
|
* order when reaching size limit
|
||||||
|
*
|
||||||
|
* @param <K>
|
||||||
|
* the type of keys maintained by this cache
|
||||||
|
* @param <V>
|
||||||
|
* the type of mapped values
|
||||||
|
*
|
||||||
|
* @since 5.1.9
|
||||||
|
*/
|
||||||
|
public class SimpleLruCache<K, V> {
|
||||||
|
|
||||||
|
private static class Entry<K, V> {
|
||||||
|
|
||||||
|
private final K key;
|
||||||
|
|
||||||
|
private final V value;
|
||||||
|
|
||||||
|
// pseudo clock timestamp of the last access to this entry
|
||||||
|
private volatile long lastAccessed;
|
||||||
|
|
||||||
|
private long lastAccessedSorting;
|
||||||
|
|
||||||
|
Entry(K key, V value, long lastAccessed) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
this.lastAccessed = lastAccessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copyAccessTime() {
|
||||||
|
lastAccessedSorting = lastAccessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("nls")
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Entry [lastAccessed=" + lastAccessed + ", key=" + key
|
||||||
|
+ ", value=" + value + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Lock lock = new ReentrantLock();
|
||||||
|
|
||||||
|
private Map<K, Entry<K,V>> map = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private volatile int maximumSize;
|
||||||
|
|
||||||
|
private int purgeSize;
|
||||||
|
|
||||||
|
// pseudo clock to implement LRU order of access to entries
|
||||||
|
private volatile long time = 0L;
|
||||||
|
|
||||||
|
private static void checkPurgeFactor(float purgeFactor) {
|
||||||
|
if (purgeFactor <= 0 || purgeFactor >= 1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
MessageFormat.format(JGitText.get().invalidPurgeFactor,
|
||||||
|
Float.valueOf(purgeFactor)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int purgeSize(int maxSize, float purgeFactor) {
|
||||||
|
return (int) ((1 - purgeFactor) * maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new cache
|
||||||
|
*
|
||||||
|
* @param maxSize
|
||||||
|
* maximum size of the cache, to reduce need for synchronization
|
||||||
|
* this is not a hard limit. The real size of the cache could be
|
||||||
|
* slightly above this maximum if multiple threads put new values
|
||||||
|
* concurrently
|
||||||
|
* @param purgeFactor
|
||||||
|
* when the size of the map reaches maxSize the oldest entries
|
||||||
|
* will be purged to free up some space for new entries,
|
||||||
|
* {@code purgeFactor} is the fraction of {@code maxSize} to
|
||||||
|
* purge when this happens
|
||||||
|
*/
|
||||||
|
public SimpleLruCache(int maxSize, float purgeFactor) {
|
||||||
|
checkPurgeFactor(purgeFactor);
|
||||||
|
this.maximumSize = maxSize;
|
||||||
|
this.purgeSize = purgeSize(maxSize, purgeFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value to which the specified key is mapped, or {@code null}
|
||||||
|
* if this map contains no mapping for the key.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* More formally, if this cache contains a mapping from a key {@code k} to a
|
||||||
|
* value {@code v} such that {@code key.equals(k)}, then this method returns
|
||||||
|
* {@code v}; otherwise it returns {@code null}. (There can be at most one
|
||||||
|
* such mapping.)
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* the key
|
||||||
|
*
|
||||||
|
* @throws NullPointerException
|
||||||
|
* if the specified key is null
|
||||||
|
*
|
||||||
|
* @return value mapped for this key, or {@code null} if no value is mapped
|
||||||
|
*/
|
||||||
|
public V get(Object key) {
|
||||||
|
Entry<K, V> entry = map.get(key);
|
||||||
|
if (entry != null) {
|
||||||
|
entry.lastAccessed = ++time;
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the specified key to the specified value in this cache. Neither the
|
||||||
|
* key nor the value can be null.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The value can be retrieved by calling the {@code get} method with a key
|
||||||
|
* that is equal to the original key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* key with which the specified value is to be associated
|
||||||
|
* @param value
|
||||||
|
* value to be associated with the specified key
|
||||||
|
* @return the previous value associated with {@code key}, or {@code null}
|
||||||
|
* if there was no mapping for {@code key}
|
||||||
|
* @throws NullPointerException
|
||||||
|
* if the specified key or value is null
|
||||||
|
*/
|
||||||
|
public V put(@NonNull K key, @NonNull V value) {
|
||||||
|
map.put(key, new Entry<>(key, value, ++time));
|
||||||
|
if (map.size() > maximumSize) {
|
||||||
|
purge();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current size of this cache
|
||||||
|
*
|
||||||
|
* @return the number of key-value mappings in this cache
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconfigures the cache. If {@code maxSize} is reduced some entries will
|
||||||
|
* be purged.
|
||||||
|
*
|
||||||
|
* @param maxSize
|
||||||
|
* maximum size of the cache
|
||||||
|
*
|
||||||
|
* @param purgeFactor
|
||||||
|
* when the size of the map reaches maxSize the oldest entries
|
||||||
|
* will be purged to free up some space for new entries,
|
||||||
|
* {@code purgeFactor} is the fraction of {@code maxSize} to
|
||||||
|
* purge when this happens
|
||||||
|
*/
|
||||||
|
public void configure(int maxSize, float purgeFactor) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
checkPurgeFactor(purgeFactor);
|
||||||
|
this.maximumSize = maxSize;
|
||||||
|
this.purgeSize = purgeSize(maxSize, purgeFactor);
|
||||||
|
if (map.size() >= maximumSize) {
|
||||||
|
purge();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void purge() {
|
||||||
|
// don't try to compete if another thread already has the lock
|
||||||
|
if (lock.tryLock()) {
|
||||||
|
try {
|
||||||
|
List<Entry> entriesToPurge = new ArrayList<>(map.values());
|
||||||
|
// copy access times to avoid other threads interfere with
|
||||||
|
// sorting
|
||||||
|
for (Entry e : entriesToPurge) {
|
||||||
|
e.copyAccessTime();
|
||||||
|
}
|
||||||
|
Collections.sort(entriesToPurge,
|
||||||
|
Comparator.comparingLong(o -> -o.lastAccessedSorting));
|
||||||
|
for (int index = purgeSize; index < entriesToPurge
|
||||||
|
.size(); index++) {
|
||||||
|
map.remove(entriesToPurge.get(index).key);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue