Chromium Code Reviews| Index: pkg/analyzer/lib/src/dart/analysis/file_byte_store.dart |
| 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 49e668fe08f769ff796c77547e3b546830684fa9..02031ed7d0da4aa7003ade9e8ffc768d65af1123 100644 |
| --- a/pkg/analyzer/lib/src/dart/analysis/file_byte_store.dart |
| +++ b/pkg/analyzer/lib/src/dart/analysis/file_byte_store.dart |
| @@ -2,24 +2,35 @@ |
| // 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 'package:analyzer/file_system/file_system.dart'; |
| +import 'dart:async'; |
| +import 'dart:io'; |
| +import 'dart:isolate'; |
| + |
| import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
| +import 'package:path/path.dart'; |
| /** |
| - * [ByteStore] that stores values as [File]s. |
| - * |
| - * TODO(scheglov) Add some eviction policies. |
| + * [ByteStore] that stores values as files. |
| */ |
| class FileByteStore implements ByteStore { |
| - final Folder folder; |
| + static bool _evictSendPortShouldBePrepared = true; |
| + static SendPort _evictSendPort; |
| + |
| + final String _cachePath; |
| + final String _tempName = 'temp_${pid}'; |
| + final int _maxSizeBytes; |
| - FileByteStore(this.folder); |
| + int _currentSizeBytes = 0; |
|
Paul Berry
2016/11/13 15:18:17
Rename this to something like "_bytesWrittenSinceC
scheglov
2016/11/14 16:51:47
Done.
|
| + bool _evictionIsolateIsRunning = false; |
| + |
| + FileByteStore(this._cachePath, this._maxSizeBytes) { |
| + _requestCacheCleanUp(); |
| + } |
| @override |
| List<int> get(String key) { |
| try { |
| - File file = folder.getChildAssumingFile(key); |
| - return file.readAsBytesSync(); |
| + return _getFileForKey(key).readAsBytesSync(); |
| } catch (_) { |
| return null; |
| } |
| @@ -28,8 +39,104 @@ class FileByteStore implements ByteStore { |
| @override |
| void put(String key, List<int> bytes) { |
| try { |
| - File file = folder.getChildAssumingFile(key); |
| - file.writeAsBytesSync(bytes); |
| + File tempFile = _getFileForKey(_tempName); |
| + tempFile.writeAsBytesSync(bytes); |
| + File file = _getFileForKey(key); |
| + tempFile.renameSync(file.path); |
| + // Update the current size. |
| + _currentSizeBytes += bytes.length; |
| + if (_currentSizeBytes > _maxSizeBytes ~/ 8) { |
| + _requestCacheCleanUp(); |
| + } |
| } catch (_) {} |
| } |
| + |
| + File _getFileForKey(String key) { |
| + return new File(join(_cachePath, key)); |
| + } |
| + |
| + /** |
| + * If the cache clean up process has not been requested yet, request it. |
| + */ |
| + Future<Null> _requestCacheCleanUp() async { |
| + if (_evictSendPortShouldBePrepared) { |
| + _evictSendPortShouldBePrepared = false; |
| + ReceivePort response = new ReceivePort(); |
| + await Isolate.spawn(_cacheCleanUpFunction, response.sendPort); |
| + _evictSendPort = await response.first as SendPort; |
| + } else { |
| + while (_evictSendPort == null) { |
| + await new Future.delayed(new Duration(milliseconds: 100), () {}); |
| + } |
| + } |
| + |
| + if (!_evictionIsolateIsRunning) { |
| + _evictionIsolateIsRunning = true; |
| + try { |
| + ReceivePort response = new ReceivePort(); |
| + _evictSendPort.send([_cachePath, _maxSizeBytes, response.sendPort]); |
| + await response.first; |
| + } finally { |
| + _evictionIsolateIsRunning = false; |
| + _currentSizeBytes = 0; |
| + } |
| + } |
| + } |
| + |
| + /** |
| + * This function is stated in a new isolate, receives cache clean up requests |
|
Paul Berry
2016/11/13 15:18:17
s/stated/started/
scheglov
2016/11/14 16:51:47
Done.
|
| + * and |
| + */ |
| + static void _cacheCleanUpFunction(SendPort initialReplyTo) { |
| + ReceivePort port = new ReceivePort(); |
| + initialReplyTo.send(port.sendPort); |
| + port.listen((args) async { |
| + if (args is List && |
| + args.length == 3 && |
| + args[0] is String && |
| + args[1] is int && |
| + args[2] is SendPort) { |
|
Paul Berry
2016/11/13 15:18:17
It seems weird to send a list of objects to the is
scheglov
2016/11/14 16:51:47
Unfortunately this does not work.
SendPort.send s
Paul Berry
2016/11/14 21:19:28
+rmacnak, is this due to a bug in the VM?
|
| + String cachePath = args[0] as String; |
| + int maxSizeBytes = args[1] as int; |
| + await _evictFromFolder(cachePath, maxSizeBytes); |
| + // Let that client know that we're done. |
| + SendPort replyTo = args[2] as SendPort; |
| + replyTo.send(true); |
| + } |
| + }); |
| + } |
| + |
| + static Future<Null> _evictFromFolder( |
| + String cachePath, int maxSizeBytes) async { |
| + // Prepare the list of files and their statistics. |
| + List<File> files = <File>[]; |
| + Map<File, FileStat> fileStatMap = {}; |
| + int currentSizeBytes = 0; |
| + List<FileSystemEntity> resources = new Directory(cachePath).listSync(); |
| + for (FileSystemEntity resource in resources) { |
| + if (resource is File) { |
| + try { |
| + FileStat fileStat = await resource.stat(); |
| + files.add(resource); |
| + fileStatMap[resource] = fileStat; |
| + currentSizeBytes += fileStat.size; |
| + } catch (_) {} |
| + } |
| + } |
| + files.sort((a, b) { |
| + return fileStatMap[a].accessed.millisecondsSinceEpoch - |
| + fileStatMap[b].accessed.millisecondsSinceEpoch; |
| + }); |
| + |
| + // Delete files until the current size is less than the max. |
| + for (File file in files) { |
| + if (currentSizeBytes < maxSizeBytes) { |
| + break; |
| + } |
| + try { |
| + await file.delete(); |
| + } catch (_) {} |
| + currentSizeBytes -= fileStatMap[file].size; |
| + } |
| + } |
| } |