Write data to the driver cache asynchronously.
This is perhaps a 3.6% performance improvement for the analysis server.
Change-Id: Ic820177dded1fe230e25ee19b979de8008a9b76b
Reviewed-on: https://dart-review.googlesource.com/65032
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/front_end/lib/src/byte_store/file_byte_store.dart b/pkg/front_end/lib/src/byte_store/file_byte_store.dart
index 9a9bc29..00d3630 100644
--- a/pkg/front_end/lib/src/byte_store/file_byte_store.dart
+++ b/pkg/front_end/lib/src/byte_store/file_byte_store.dart
@@ -46,9 +46,7 @@
}
@override
- List<int> get(String key) {
- return _fileByteStore.get(key);
- }
+ List<int> get(String key) => _fileByteStore.get(key);
@override
void put(String key, List<int> bytes) {
@@ -97,16 +95,16 @@
SendPort initialReplyTo = message;
ReceivePort port = new ReceivePort();
initialReplyTo.send(port.sendPort);
- port.listen((request) async {
+ port.listen((request) {
if (request is CacheCleanUpRequest) {
- await _cleanUpFolder(request.cachePath, request.maxSizeBytes);
+ _cleanUpFolder(request.cachePath, request.maxSizeBytes);
// Let the client know that we're done.
request.replyTo.send(true);
}
});
}
- static Future<Null> _cleanUpFolder(String cachePath, int maxSizeBytes) async {
+ static void _cleanUpFolder(String cachePath, int maxSizeBytes) {
// Prepare the list of files and their statistics.
List<File> files = <File>[];
Map<File, FileStat> fileStatMap = {};
@@ -115,10 +113,14 @@
for (FileSystemEntity resource in resources) {
if (resource is File) {
try {
- FileStat fileStat = await resource.stat();
- files.add(resource);
- fileStatMap[resource] = fileStat;
- currentSizeBytes += fileStat.size;
+ final FileStat fileStat = resource.statSync();
+ // Make sure that the file was not deleted out from under us (a return
+ // value of FileSystemEntityType.notFound).
+ if (fileStat.type == FileSystemEntityType.file) {
+ files.add(resource);
+ fileStatMap[resource] = fileStat;
+ currentSizeBytes += fileStat.size;
+ }
} catch (_) {}
}
}
@@ -133,7 +135,7 @@
break;
}
try {
- await file.delete();
+ file.deleteSync();
} catch (_) {}
currentSizeBytes -= fileStatMap[file].size;
}
@@ -144,51 +146,71 @@
* [ByteStore] that stores values as files.
*/
class FileByteStore implements ByteStore {
+ static final FileByteStoreValidator _validator = new FileByteStoreValidator();
+
final String _cachePath;
- final String _tempName;
- final FileByteStoreValidator _validator = new FileByteStoreValidator();
+ final String _tempSuffix;
+ final Map<String, List<int>> _writeInProgress = {};
+ final FuturePool _pool = new FuturePool(20);
/**
* If the same cache path is used from more than one isolate of the same
* process, then a unique [tempNameSuffix] must be provided for each isolate.
*/
FileByteStore(this._cachePath, {String tempNameSuffix: ''})
- : _tempName = 'temp_${pid}_${tempNameSuffix}';
+ : _tempSuffix =
+ '-temp-${pid}${tempNameSuffix.isEmpty ? '' : '-$tempNameSuffix'}';
@override
List<int> get(String key) {
+ List<int> bytes = _writeInProgress[key];
+ if (bytes != null) {
+ return bytes;
+ }
+
try {
- File file = _getFileForKey(key);
- List<int> rawBytes = file.readAsBytesSync();
- return _validator.getData(rawBytes);
+ final File file = _getFileForKey(key);
+ if (!file.existsSync()) {
+ return null;
+ }
+ return _validator.getData(file.readAsBytesSync());
} catch (_) {
+ // ignore exceptions
return null;
}
}
@override
void put(String key, List<int> bytes) {
- try {
- bytes = _validator.wrapData(bytes);
- File tempFile = _getFileForKey(_tempName);
- tempFile.writeAsBytesSync(bytes);
- File file = _getFileForKey(key);
- tempFile.renameSync(file.path);
- } catch (_) {}
+ _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');
+ return tempFile.writeAsBytes(wrappedBytes).then((_) {
+ return tempFile.rename(join(_cachePath, key));
+ }).catchError((_) {
+ // ignore exceptions
+ }).whenComplete(() {
+ if (_writeInProgress[key] == bytes) {
+ _writeInProgress.remove(key);
+ }
+ });
+ });
}
- File _getFileForKey(String key) {
- return new File(join(_cachePath, key));
- }
+ File _getFileForKey(String key) => new File(join(_cachePath, key));
}
/**
- * Generally speaking, we cannot guarantee that any data written into a
- * file will stay the same - there is always a chance of a hardware problem,
- * file system problem, truncated data, etc.
+ * Generally speaking, we cannot guarantee that any data written into a file
+ * will stay the same - there is always a chance of a hardware problem, file
+ * system problem, truncated data, etc.
*
- * So, we need to embed some validation into data itself.
- * This class append the version and the checksum to data.
+ * So, we need to embed some validation into data itself. This class append the
+ * version and the checksum to data.
*/
class FileByteStoreValidator {
static const List<int> _VERSION = const [0x01, 0x00, 0x00, 0x00];
@@ -253,3 +275,30 @@
return bytes;
}
}
+
+class FuturePool {
+ int _available;
+ List waiting = [];
+
+ FuturePool(this._available);
+
+ void execute(Future Function() fn) {
+ if (_available > 0) {
+ _run(fn);
+ } else {
+ waiting.add(fn);
+ }
+ }
+
+ void _run(Future Function() fn) {
+ _available--;
+
+ fn().whenComplete(() {
+ _available++;
+
+ if (waiting.isNotEmpty) {
+ _run(waiting.removeAt(0));
+ }
+ });
+ }
+}
diff --git a/pkg/front_end/lib/src/fasta/scanner/token.dart b/pkg/front_end/lib/src/fasta/scanner/token.dart
index cea026d..bd837c1 100644
--- a/pkg/front_end/lib/src/fasta/scanner/token.dart
+++ b/pkg/front_end/lib/src/fasta/scanner/token.dart
@@ -235,7 +235,7 @@
int get length;
/**
- * If this substring is based on a String, the [boolValue] indicates wheter
+ * If this substring is based on a String, the [boolValue] indicates whether
* the resulting substring should be canonicalized.
*
* For substrings based on a byte array, the [boolValue] is true if the
diff --git a/pkg/front_end/test/src/byte_store/protected_file_byte_store_test.dart b/pkg/front_end/test/src/byte_store/protected_file_byte_store_test.dart
index d117bf4..e1fc175 100644
--- a/pkg/front_end/test/src/byte_store/protected_file_byte_store_test.dart
+++ b/pkg/front_end/test/src/byte_store/protected_file_byte_store_test.dart
@@ -2,6 +2,7 @@
// 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:front_end/src/byte_store/protected_file_byte_store.dart';
@@ -49,7 +50,7 @@
} on io.FileSystemException {}
}
- test_flush() {
+ test_flush() async {
store.put('a', _b(1));
store.put('b', _b(2));
store.put('c', _b(3));
@@ -57,6 +58,9 @@
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();
@@ -69,7 +73,7 @@
_assertCacheContent({'d': 4}, ['b']);
}
- test_put() {
+ test_put() async {
store.put('a', _b(65));
store.put('b', _b(63));
store.put('c', _b(1));
@@ -78,6 +82,10 @@
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}, []);
}