Shard SimpleCache files into 10 sub-directories

Issue: #4253
PiperOrigin-RevId: 232659869
This commit is contained in:
olly 2019-02-06 13:45:55 +00:00 committed by Oliver Woodman
parent 2169b9417f
commit 3845304e58
3 changed files with 39 additions and 7 deletions

View file

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.NavigableSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
@ -36,6 +37,14 @@ import java.util.TreeSet;
public final class SimpleCache implements Cache {
private static final String TAG = "SimpleCache";
/**
* Cache files are distributed between a number of subdirectories. This helps to avoid poor
* performance in cases where the performance of the underlying file system (e.g. FAT32) scales
* badly with the number of files per directory. See
* https://github.com/google/ExoPlayer/issues/4253.
*/
private static final int SUBDIRECTORY_COUNT = 10;
private static final HashSet<File> lockedCacheDirs = new HashSet<>();
private static boolean cacheFolderLockingDisabled;
@ -44,6 +53,7 @@ public final class SimpleCache implements Cache {
private final CacheEvictor evictor;
private final CachedContentIndex index;
private final HashMap<String, ArrayList<Listener>> listeners;
private final Random random;
private long totalSpace;
private boolean released;
@ -128,7 +138,8 @@ public final class SimpleCache implements Cache {
this.cacheDir = cacheDir;
this.evictor = evictor;
this.index = index;
this.listeners = new HashMap<>();
listeners = new HashMap<>();
random = new Random();
// Start cache initialization.
final ConditionVariable conditionVariable = new ConditionVariable();
@ -271,8 +282,13 @@ public final class SimpleCache implements Cache {
removeStaleSpans();
}
evictor.onStartFile(this, key, position, length);
return SimpleCacheSpan.getCacheFile(
cacheDir, cachedContent.id, position, System.currentTimeMillis());
// Randomly distribute files into subdirectories with a uniform distribution.
File fileDir = new File(cacheDir, Integer.toString(random.nextInt(SUBDIRECTORY_COUNT)));
if (!fileDir.exists()) {
fileDir.mkdir();
}
long lastAccessTimestamp = System.currentTimeMillis();
return SimpleCacheSpan.getCacheFile(fileDir, cachedContent.id, position, lastAccessTimestamp);
}
@Override

View file

@ -26,7 +26,9 @@ import java.util.regex.Pattern;
/** This class stores span metadata in filename. */
/* package */ final class SimpleCacheSpan extends CacheSpan {
private static final String SUFFIX = ".v3.exo";
/* package */ static final String COMMON_SUFFIX = ".exo";
private static final String SUFFIX = ".v3" + COMMON_SUFFIX;
private static final Pattern CACHE_FILE_PATTERN_V1 = Pattern.compile(
"^(.+)\\.(\\d+)\\.(\\d+)\\.v1\\.exo$", Pattern.DOTALL);
private static final Pattern CACHE_FILE_PATTERN_V2 = Pattern.compile(

View file

@ -75,7 +75,7 @@ public class SimpleCacheTest {
NavigableSet<CacheSpan> cachedSpans = simpleCache.getCachedSpans(KEY_1);
assertThat(cachedSpans.isEmpty()).isTrue();
assertThat(simpleCache.getCacheSpace()).isEqualTo(0);
assertThat(cacheDir.listFiles()).hasLength(0);
assertNoCacheFiles(cacheDir);
addCache(simpleCache, KEY_1, 0, 15);
@ -233,7 +233,7 @@ public class SimpleCacheTest {
// Cache should be cleared
assertThat(simpleCache.getKeys()).isEmpty();
assertThat(cacheDir.listFiles()).hasLength(0);
assertNoCacheFiles(cacheDir);
}
@Test
@ -252,7 +252,7 @@ public class SimpleCacheTest {
// Cache should be cleared
assertThat(simpleCache.getKeys()).isEmpty();
assertThat(cacheDir.listFiles()).hasLength(0);
assertNoCacheFiles(cacheDir);
}
@Test
@ -391,6 +391,20 @@ public class SimpleCacheTest {
}
}
private static void assertNoCacheFiles(File dir) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
assertNoCacheFiles(file);
} else {
assertThat(file.getName().endsWith(SimpleCacheSpan.COMMON_SUFFIX)).isFalse();
}
}
}
private static byte[] generateData(String key, int position, int length) {
byte[] bytes = new byte[length];
new Random((long) (key.hashCode() ^ position)).nextBytes(bytes);