Use the first two key characters for sharding in FileByteStore.
R=brianwilkerson@google.com
Bug: https://github.com/dart-lang/sdk/issues/28383
Change-Id: I177e9bedef79627d7b21e565684004054cef1e54
Reviewed-on: https://dart-review.googlesource.com/c/87521
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_byte_store.dart b/pkg/analyzer/lib/src/dart/analysis/file_byte_store.dart
index 7c1a7fe..1aaa47d 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_byte_store.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_byte_store.dart
@@ -110,7 +110,8 @@
List<File> files = <File>[];
Map<File, FileStat> fileStatMap = {};
int currentSizeBytes = 0;
- List<FileSystemEntity> resources = new Directory(cachePath).listSync();
+ List<FileSystemEntity> resources =
+ new Directory(cachePath).listSync(recursive: true);
for (FileSystemEntity resource in resources) {
if (resource is File) {
try {
@@ -148,6 +149,7 @@
*/
class FileByteStore implements ByteStore {
static final FileByteStoreValidator _validator = new FileByteStoreValidator();
+ static final _dotCodeUnit = '.'.codeUnitAt(0);
final String _cachePath;
final String _tempSuffix;
@@ -164,17 +166,18 @@
@override
List<int> get(String key) {
+ if (!_canShard(key)) return null;
+
List<int> bytes = _writeInProgress[key];
if (bytes != null) {
return bytes;
}
try {
- final File file = _getFileForKey(key);
- if (!file.existsSync()) {
- return null;
- }
- return _validator.getData(file.readAsBytesSync());
+ var shardPath = _getShardPath(key);
+ var path = join(shardPath, key);
+ var bytes = new File(path).readAsBytesSync();
+ return _validator.getData(bytes);
} catch (_) {
// ignore exceptions
return null;
@@ -183,15 +186,22 @@
@override
void put(String key, List<int> bytes) {
+ if (!_canShard(key)) return;
+
_writeInProgress[key] = bytes;
final List<int> wrappedBytes = _validator.wrapData(bytes);
// We don't wait for the write and rename to complete.
_pool.execute(() {
- final File tempFile = _getFileForKey('$key$_tempSuffix');
+ var tempPath = join(_cachePath, '$key$_tempSuffix');
+ var tempFile = new File(tempPath);
return tempFile.writeAsBytes(wrappedBytes).then((_) {
- return tempFile.rename(join(_cachePath, key));
+ var shardPath = _getShardPath(key);
+ return Directory(shardPath).create(recursive: true).then((_) {
+ var path = join(shardPath, key);
+ return tempFile.rename(path);
+ });
}).catchError((_) {
// ignore exceptions
}).whenComplete(() {
@@ -202,7 +212,16 @@
});
}
- File _getFileForKey(String key) => new File(join(_cachePath, key));
+ String _getShardPath(String key) {
+ var shardName = key.substring(0, 2);
+ return join(_cachePath, shardName);
+ }
+
+ static bool _canShard(String key) {
+ return key.length > 2 &&
+ key.codeUnitAt(0) != _dotCodeUnit &&
+ key.codeUnitAt(1) != _dotCodeUnit;
+ }
}
/**
diff --git a/pkg/analyzer/lib/src/dart/analysis/protected_file_byte_store.dart b/pkg/analyzer/lib/src/dart/analysis/protected_file_byte_store.dart
deleted file mode 100644
index b5ec4e1..0000000
--- a/pkg/analyzer/lib/src/dart/analysis/protected_file_byte_store.dart
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:meta/meta.dart';
-import 'package:path/path.dart';
-
-import 'byte_store.dart';
-import 'cache.dart';
-import 'file_byte_store.dart';
-
-/// The function that returns current time in milliseconds.
-typedef int GetCurrentTime();
-
-/// [ByteStore] that stores values as files, allows to mark some of keys as
-/// temporary protected, and supports periodical [flush] of unprotected keys.
-///
-/// The set of protected keys is stored in a file, which is locked during
-/// updating to prevent races across multiple processes.
-class ProtectedFileByteStore implements ByteStore {
- @visibleForTesting
- static const PROTECTED_FILE_NAME = '.temporary_protected_keys';
-
- final String _cachePath;
- final Duration _protectionDuration;
- final GetCurrentTime _getCurrentTimeFunction;
-
- final FileByteStore _fileByteStore;
- final Cache<String, List<int>> _cache;
-
- /// Create a new instance of the [ProtectedFileByteStore].
- ///
- /// The [protectionDuration] specifies how long temporary protected keys
- /// stay protected.
- ProtectedFileByteStore(this._cachePath,
- {Duration protectionDuration,
- GetCurrentTime getCurrentTime,
- int cacheSizeBytes: 128 * 1024 * 1024})
- : _protectionDuration = protectionDuration,
- _getCurrentTimeFunction = getCurrentTime ?? _getCurrentTimeDefault,
- _fileByteStore = new FileByteStore(_cachePath),
- _cache = new Cache(cacheSizeBytes, (bytes) => bytes.length);
-
- /// Remove all not protected keys.
- void flush() {
- var protectedKeysText = _keysReadTextLocked();
- var protectedKeys = new ProtectedKeys.decode(protectedKeysText);
- List<FileSystemEntity> files = new Directory(_cachePath).listSync();
- for (var file in files) {
- if (file is File) {
- String key = basename(file.path);
- if (key == PROTECTED_FILE_NAME) {
- continue;
- }
- if (protectedKeys.containsKey(key)) {
- continue;
- }
- try {
- file.deleteSync();
- } catch (e) {}
- }
- }
- }
-
- @override
- List<int> get(String key) {
- return _cache.get(key, () => _fileByteStore.get(key));
- }
-
- @override
- void put(String key, List<int> bytes) {
- if (key == PROTECTED_FILE_NAME) {
- throw new ArgumentError('The key $key is reserved.');
- }
- _fileByteStore.put(key, bytes);
- _cache.put(key, bytes);
- }
-
- /// The [add] keys are added to the set of temporary protected keys, and
- /// their age is reset to zero.
- ///
- /// The [remove] keys are removed from the set of temporary protected keys,
- /// and become subjects of LRU cached eviction.
- void updateProtectedKeys(
- {List<String> add: const <String>[],
- List<String> remove: const <String>[]}) {
- _withProtectedKeysLockSync(_cachePath, (ProtectedKeys protectedKeys) {
- var now = _getCurrentTimeFunction();
-
- if (_protectionDuration != null) {
- var maxAge = _protectionDuration.inMilliseconds;
- protectedKeys.removeOlderThan(maxAge, now);
- }
-
- for (var addedKey in add) {
- protectedKeys.add(addedKey, now);
- }
-
- for (var removedKey in remove) {
- protectedKeys.remove(removedKey);
- }
- });
- }
-
- /// Read the protected keys, but don't keep the lock.
- ///
- /// We do this before performing any long running operation that
- /// just read, and where it is important to keep system unlocked.
- String _keysReadTextLocked() {
- File keysFile = new File(join(_cachePath, PROTECTED_FILE_NAME));
- RandomAccessFile keysLock = keysFile.openSync(mode: FileMode.append);
- keysLock.lockSync(FileLock.blockingExclusive);
- try {
- return _keysReadText(keysLock);
- } finally {
- keysLock.unlockSync();
- keysLock.closeSync();
- }
- }
-
- /// The default implementation of [GetCurrentTime].
- static int _getCurrentTimeDefault() {
- return new DateTime.now().millisecondsSinceEpoch;
- }
-
- static ProtectedKeys _keysRead(RandomAccessFile file) {
- String text = _keysReadText(file);
- return new ProtectedKeys.decode(text);
- }
-
- static String _keysReadText(RandomAccessFile file) {
- file.setPositionSync(0);
- List<int> bytes = file.readSync(file.lengthSync());
- return utf8.decode(bytes);
- }
-
- static void _keysWrite(RandomAccessFile file, ProtectedKeys keys) {
- String text = keys.encode();
- file.setPositionSync(0);
- file.writeStringSync(text);
- file.truncateSync(file.positionSync());
- }
-
- /// Perform [f] over the locked keys file, decoded into [ProtectedKeys].
- static void _withProtectedKeysLockSync(
- String cachePath, void f(ProtectedKeys keys)) {
- String path = join(cachePath, PROTECTED_FILE_NAME);
- RandomAccessFile file = new File(path).openSync(mode: FileMode.append);
- file.lockSync(FileLock.blockingExclusive);
- try {
- ProtectedKeys keys = _keysRead(file);
- f(keys);
- _keysWrite(file, keys);
- } finally {
- file.unlockSync();
- file.closeSync();
- }
- }
-}
-
-/// Container with protected keys.
-@visibleForTesting
-class ProtectedKeys {
- /// The map from a key in [ByteStore] to the time in milliseconds when the
- /// key was marked as temporary protected.
- final Map<String, int> map;
-
- ProtectedKeys(this.map);
-
- factory ProtectedKeys.decode(String text) {
- var map = <String, int>{};
- try {
- List<String> lines = text.split('\n').toList();
- if (lines.length % 2 == 0) {
- for (int i = 0; i < lines.length; i += 2) {
- String key = lines[i];
- String startMillisecondsStr = lines[i + 1];
- int startMilliseconds = int.parse(startMillisecondsStr);
- map[key] = startMilliseconds;
- }
- }
- } catch (e) {}
- return new ProtectedKeys(map);
- }
-
- /// Add the given [key] with the current time.
- void add(String key, int time) {
- map[key] = time;
- }
-
- bool containsKey(String key) => map.containsKey(key);
-
- String encode() {
- var buffer = new StringBuffer();
- map.forEach((key, start) {
- buffer.writeln(key);
- buffer.writeln(start);
- });
- return buffer.toString().trim();
- }
-
- void remove(String key) {
- map.remove(key);
- }
-
- /// If the time is [now] milliseconds, remove all keys that are older than
- /// the given [maxAge] is milliseconds.
- void removeOlderThan(int maxAge, int now) {
- var keysToRemove = <String>[];
- for (var key in map.keys) {
- if (now - map[key] > maxAge) {
- keysToRemove.add(key);
- }
- }
- keysToRemove.forEach(map.remove);
- }
-}
diff --git a/pkg/analyzer/test/src/dart/analysis/protected_file_byte_store_test.dart b/pkg/analyzer/test/src/dart/analysis/protected_file_byte_store_test.dart
deleted file mode 100644
index 05bcad5..0000000
--- a/pkg/analyzer/test/src/dart/analysis/protected_file_byte_store_test.dart
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'dart:async';
-import 'dart:io' as io;
-
-import 'package:analyzer/src/dart/analysis/protected_file_byte_store.dart';
-import 'package:path/path.dart' as pathos;
-import 'package:test/test.dart';
-import 'package:test_reflective_loader/test_reflective_loader.dart';
-
-main() {
- defineReflectiveSuite(() {
- defineReflectiveTests(ProtectedKeysTest);
- defineReflectiveTests(ProtectedFileByteStoreTest);
- });
-}
-
-List<int> _b(int length) {
- return new List<int>.filled(length, 0);
-}
-
-@reflectiveTest
-class ProtectedFileByteStoreTest {
- static const PADDING = 4;
-
- io.Directory cacheDirectory;
- String cachePath;
- ProtectedFileByteStore store;
-
- int time = 0;
-
- String get protectedKeysText {
- String path =
- pathos.join(cachePath, ProtectedFileByteStore.PROTECTED_FILE_NAME);
- return new io.File(path).readAsStringSync();
- }
-
- void setUp() {
- io.Directory systemTemp = io.Directory.systemTemp;
- cacheDirectory = systemTemp.createTempSync('ProtectedFileByteStoreTest');
- cachePath = cacheDirectory.absolute.path;
- _createStore();
- }
-
- void tearDown() {
- try {
- cacheDirectory.deleteSync(recursive: true);
- } on io.FileSystemException {}
- }
-
- test_flush() async {
- store.put('a', _b(1));
- store.put('b', _b(2));
- store.put('c', _b(3));
- store.put('d', _b(4));
-
- store.updateProtectedKeys(add: ['b', 'd']);
-
- // Add a delay to give the store time to write to disk.
- await new Future.delayed(const Duration(milliseconds: 200));
-
- // Flush, only protected 'b' and 'd' survive.
- store.flush();
- store.flush();
- _assertCacheContent({'b': 2, 'd': 4}, ['a', 'c']);
-
- // Remove 'b' and flush.
- // Only 'd' survives.
- store.updateProtectedKeys(remove: ['b']);
- store.flush();
- _assertCacheContent({'d': 4}, ['b']);
- }
-
- test_put() async {
- store.put('a', _b(65));
- store.put('b', _b(63));
- store.put('c', _b(1));
-
- // We can access all results.
- expect(store.get('a'), hasLength(65));
- expect(store.get('b'), hasLength(63));
- expect(store.get('c'), hasLength(1));
-
- // Add a delay to give the store time to write to disk.
- await new Future.delayed(const Duration(milliseconds: 200));
-
- _assertCacheContent({'a': 65, 'b': 63, 'c': 1}, []);
- }
-
- test_put_reservedKey() {
- expect(() {
- store.put(ProtectedFileByteStore.PROTECTED_FILE_NAME, <int>[]);
- }, throwsArgumentError);
- }
-
- test_updateProtectedKeys_add() {
- store.updateProtectedKeys(add: ['a', 'b']);
- _assertKeys({'a': 0, 'b': 0});
-
- time++;
- store.updateProtectedKeys(add: ['c']);
- _assertKeys({'a': 0, 'b': 0, 'c': 1});
- }
-
- test_updateProtectedKeys_add_hasSame() {
- store.updateProtectedKeys(add: ['a', 'b', 'c']);
- _assertKeys({'a': 0, 'b': 0, 'c': 0});
-
- time++;
- store.updateProtectedKeys(add: ['b', 'd']);
- _assertKeys({'a': 0, 'b': 1, 'c': 0, 'd': 1});
- }
-
- test_updateProtectedKeys_add_removeTooOld() {
- store.updateProtectedKeys(add: ['a', 'b']);
- _assertKeys({'a': 0, 'b': 0});
-
- // Move time to 10 ms, both 'a' and 'b' are still alive.
- time = 10;
- store.updateProtectedKeys(add: ['c']);
- _assertKeys({'a': 0, 'b': 0, 'c': 10});
-
- // Move time to 11 ms, now 'a' and 'b' are too old and removed.
- time = 11;
- store.updateProtectedKeys(add: ['d']);
- _assertKeys({'c': 10, 'd': 11});
- }
-
- test_updateProtectedKeys_add_removeTooOld_nullDuration() {
- _createStore(protectionDuration: null);
-
- store.updateProtectedKeys(add: ['a', 'b']);
- _assertKeys({'a': 0, 'b': 0});
-
- // Move time far into the future, both 'a' and 'b' are still alive.
- time = 1 << 30;
- store.updateProtectedKeys(add: ['c']);
- _assertKeys({'a': 0, 'b': 0, 'c': time});
- }
-
- test_updateProtectedKeys_addRemove() {
- store.updateProtectedKeys(add: ['a', 'b', 'c']);
- _assertKeys({'a': 0, 'b': 0, 'c': 0});
-
- time++;
- store.updateProtectedKeys(add: ['d'], remove: ['b']);
- _assertKeys({'a': 0, 'c': 0, 'd': 1});
- }
-
- test_updateProtectedKeys_addRemove_same() {
- store.updateProtectedKeys(add: ['a', 'b', 'c']);
- _assertKeys({'a': 0, 'b': 0, 'c': 0});
-
- time++;
- store.updateProtectedKeys(add: ['b'], remove: ['b']);
- _assertKeys({'a': 0, 'c': 0});
- }
-
- test_updateProtectedKeys_remove() {
- store.updateProtectedKeys(add: ['a', 'b', 'c']);
- _assertKeys({'a': 0, 'b': 0, 'c': 0});
-
- time++;
- store.updateProtectedKeys(remove: ['b']);
- _assertKeys({'a': 0, 'c': 0});
- }
-
- void _assertCacheContent(Map<String, int> includes, List<String> excludes) {
- Map<String, int> keyToLength = {};
- for (var file in cacheDirectory.listSync()) {
- String key = pathos.basename(file.path);
- if (file is io.File) {
- keyToLength[key] = file.lengthSync();
- }
- }
- includes.forEach((expectedKey, expectedLength) {
- expect(keyToLength, contains(expectedKey));
- expect(keyToLength, containsPair(expectedKey, expectedLength + PADDING));
- });
- for (var excludedKey in excludes) {
- expect(keyToLength.keys, isNot(contains(excludedKey)));
- }
- }
-
- void _assertKeys(Map<String, int> expected) {
- var path =
- pathos.join(cachePath, ProtectedFileByteStore.PROTECTED_FILE_NAME);
- var text = new io.File(path).readAsStringSync();
- var keys = new ProtectedKeys.decode(text);
- expect(keys.map.keys, expected.keys);
- expected.forEach((key, start) {
- expect(keys.map, containsPair(key, start));
- });
- }
-
- void _createStore(
- {Duration protectionDuration: const Duration(milliseconds: 10)}) {
- store = new ProtectedFileByteStore(cachePath,
- protectionDuration: protectionDuration,
- cacheSizeBytes: 256,
- getCurrentTime: _getTime);
- }
-
- int _getTime() => time;
-}
-
-@reflectiveTest
-class ProtectedKeysTest {
- test_decode() {
- var keys = new ProtectedKeys({'/a/b/c': 10, '/a/d/e': 123});
-
- String text = keys.encode();
- expect(text, r'''
-/a/b/c
-10
-/a/d/e
-123''');
-
- keys = _decode(text);
- expect(keys.map['/a/b/c'], 10);
- expect(keys.map['/a/d/e'], 123);
- }
-
- test_decode_empty() {
- var keys = _decode('');
- expect(keys.map, isEmpty);
- }
-
- test_decode_error_notEvenNumberOfLines() {
- var keys = _decode('a');
- expect(keys.map, isEmpty);
- }
-
- test_decode_error_startIsEmpty() {
- var keys = _decode('a\n');
- expect(keys.map, isEmpty);
- }
-
- test_decode_error_startIsNotInt() {
- var keys = _decode('a\n1.23');
- expect(keys.map, isEmpty);
- }
-
- test_decode_error_startIsNotNumber() {
- var keys = _decode('a\nb');
- expect(keys.map, isEmpty);
- }
-
- test_removeOlderThan() {
- var keys = new ProtectedKeys({'a': 1, 'b': 2, 'c': 3});
- _assertKeys(keys, {'a': 1, 'b': 2, 'c': 3});
-
- keys.removeOlderThan(5, 7);
- _assertKeys(keys, {'b': 2, 'c': 3});
- }
-
- void _assertKeys(ProtectedKeys keys, Map<String, int> expected) {
- expect(keys.map.keys, expected.keys);
- expected.forEach((key, start) {
- expect(keys.map, containsPair(key, start));
- });
- }
-
- ProtectedKeys _decode(String text) {
- return new ProtectedKeys.decode(text);
- }
-}
diff --git a/pkg/analyzer/test/src/dart/analysis/test_all.dart b/pkg/analyzer/test/src/dart/analysis/test_all.dart
index 8cda60d..77d968e 100644
--- a/pkg/analyzer/test/src/dart/analysis/test_all.dart
+++ b/pkg/analyzer/test/src/dart/analysis/test_all.dart
@@ -21,7 +21,6 @@
import 'fletcher16_test.dart' as fletcher16_test;
import 'index_test.dart' as index;
import 'mutex_test.dart' as mutex;
-import 'protected_file_byte_store_test.dart' as protected_file_byte_store_test;
import 'referenced_names_test.dart' as referenced_names;
import 'search_test.dart' as search;
import 'session_helper_test.dart' as session_helper;
@@ -47,7 +46,6 @@
fletcher16_test.main();
index.main();
mutex.main();
- protected_file_byte_store_test.main();
referenced_names.main();
search.main();
session.main();