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}, []);
   }