Commit 3845304e by olly Committed by Oliver Woodman

Shard SimpleCache files into 10 sub-directories

Issue: #4253
PiperOrigin-RevId: 232659869
parent 2169b941
...@@ -26,6 +26,7 @@ import java.util.ArrayList; ...@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.NavigableSet; import java.util.NavigableSet;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
...@@ -36,6 +37,14 @@ import java.util.TreeSet; ...@@ -36,6 +37,14 @@ import java.util.TreeSet;
public final class SimpleCache implements Cache { public final class SimpleCache implements Cache {
private static final String TAG = "SimpleCache"; 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 final HashSet<File> lockedCacheDirs = new HashSet<>();
private static boolean cacheFolderLockingDisabled; private static boolean cacheFolderLockingDisabled;
...@@ -44,6 +53,7 @@ public final class SimpleCache implements Cache { ...@@ -44,6 +53,7 @@ public final class SimpleCache implements Cache {
private final CacheEvictor evictor; private final CacheEvictor evictor;
private final CachedContentIndex index; private final CachedContentIndex index;
private final HashMap<String, ArrayList<Listener>> listeners; private final HashMap<String, ArrayList<Listener>> listeners;
private final Random random;
private long totalSpace; private long totalSpace;
private boolean released; private boolean released;
...@@ -128,7 +138,8 @@ public final class SimpleCache implements Cache { ...@@ -128,7 +138,8 @@ public final class SimpleCache implements Cache {
this.cacheDir = cacheDir; this.cacheDir = cacheDir;
this.evictor = evictor; this.evictor = evictor;
this.index = index; this.index = index;
this.listeners = new HashMap<>(); listeners = new HashMap<>();
random = new Random();
// Start cache initialization. // Start cache initialization.
final ConditionVariable conditionVariable = new ConditionVariable(); final ConditionVariable conditionVariable = new ConditionVariable();
...@@ -271,8 +282,13 @@ public final class SimpleCache implements Cache { ...@@ -271,8 +282,13 @@ public final class SimpleCache implements Cache {
removeStaleSpans(); removeStaleSpans();
} }
evictor.onStartFile(this, key, position, length); evictor.onStartFile(this, key, position, length);
return SimpleCacheSpan.getCacheFile( // Randomly distribute files into subdirectories with a uniform distribution.
cacheDir, cachedContent.id, position, System.currentTimeMillis()); 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 @Override
......
...@@ -26,7 +26,9 @@ import java.util.regex.Pattern; ...@@ -26,7 +26,9 @@ import java.util.regex.Pattern;
/** This class stores span metadata in filename. */ /** This class stores span metadata in filename. */
/* package */ final class SimpleCacheSpan extends CacheSpan { /* 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( private static final Pattern CACHE_FILE_PATTERN_V1 = Pattern.compile(
"^(.+)\\.(\\d+)\\.(\\d+)\\.v1\\.exo$", Pattern.DOTALL); "^(.+)\\.(\\d+)\\.(\\d+)\\.v1\\.exo$", Pattern.DOTALL);
private static final Pattern CACHE_FILE_PATTERN_V2 = Pattern.compile( private static final Pattern CACHE_FILE_PATTERN_V2 = Pattern.compile(
......
...@@ -75,7 +75,7 @@ public class SimpleCacheTest { ...@@ -75,7 +75,7 @@ public class SimpleCacheTest {
NavigableSet<CacheSpan> cachedSpans = simpleCache.getCachedSpans(KEY_1); NavigableSet<CacheSpan> cachedSpans = simpleCache.getCachedSpans(KEY_1);
assertThat(cachedSpans.isEmpty()).isTrue(); assertThat(cachedSpans.isEmpty()).isTrue();
assertThat(simpleCache.getCacheSpace()).isEqualTo(0); assertThat(simpleCache.getCacheSpace()).isEqualTo(0);
assertThat(cacheDir.listFiles()).hasLength(0); assertNoCacheFiles(cacheDir);
addCache(simpleCache, KEY_1, 0, 15); addCache(simpleCache, KEY_1, 0, 15);
...@@ -233,7 +233,7 @@ public class SimpleCacheTest { ...@@ -233,7 +233,7 @@ public class SimpleCacheTest {
// Cache should be cleared // Cache should be cleared
assertThat(simpleCache.getKeys()).isEmpty(); assertThat(simpleCache.getKeys()).isEmpty();
assertThat(cacheDir.listFiles()).hasLength(0); assertNoCacheFiles(cacheDir);
} }
@Test @Test
...@@ -252,7 +252,7 @@ public class SimpleCacheTest { ...@@ -252,7 +252,7 @@ public class SimpleCacheTest {
// Cache should be cleared // Cache should be cleared
assertThat(simpleCache.getKeys()).isEmpty(); assertThat(simpleCache.getKeys()).isEmpty();
assertThat(cacheDir.listFiles()).hasLength(0); assertNoCacheFiles(cacheDir);
} }
@Test @Test
...@@ -391,6 +391,20 @@ public class SimpleCacheTest { ...@@ -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) { private static byte[] generateData(String key, int position, int length) {
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
new Random((long) (key.hashCode() ^ position)).nextBytes(bytes); new Random((long) (key.hashCode() ^ position)).nextBytes(bytes);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment