| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 import 'dart:async'; | |
| 6 import 'dart:io'; | |
| 7 import 'dart:isolate'; | |
| 8 | |
| 9 import 'package:front_end/src/incremental/byte_store.dart'; | |
| 10 import 'package:path/path.dart'; | |
| 11 | |
| 12 /** | |
| 13 * The request that is sent from the main isolate to the clean-up isolate. | |
| 14 */ | |
| 15 class CacheCleanUpRequest { | |
| 16 final String cachePath; | |
| 17 final int maxSizeBytes; | |
| 18 final SendPort replyTo; | |
| 19 | |
| 20 CacheCleanUpRequest(this.cachePath, this.maxSizeBytes, this.replyTo); | |
| 21 } | |
| 22 | |
| 23 /** | |
| 24 * [ByteStore] that stores values as files and performs cache eviction. | |
| 25 * | |
| 26 * Only the process that manages the cache, e.g. Analysis Server, should use | |
| 27 * this class. Other processes, e.g. Analysis Server plugins, should use | |
| 28 * [FileByteStore] instead and let the main process to perform eviction. | |
| 29 */ | |
| 30 class EvictingFileByteStore implements ByteStore { | |
| 31 static bool _cleanUpSendPortShouldBePrepared = true; | |
| 32 static SendPort _cleanUpSendPort; | |
| 33 | |
| 34 final String _cachePath; | |
| 35 final int _maxSizeBytes; | |
| 36 final FileByteStore _fileByteStore; | |
| 37 | |
| 38 int _bytesWrittenSinceCleanup = 0; | |
| 39 bool _evictionIsolateIsRunning = false; | |
| 40 | |
| 41 EvictingFileByteStore(this._cachePath, this._maxSizeBytes) | |
| 42 : _fileByteStore = new FileByteStore(_cachePath) { | |
| 43 _requestCacheCleanUp(); | |
| 44 } | |
| 45 | |
| 46 @override | |
| 47 List<int> get(String key) { | |
| 48 return _fileByteStore.get(key); | |
| 49 } | |
| 50 | |
| 51 @override | |
| 52 void put(String key, List<int> bytes) { | |
| 53 _fileByteStore.put(key, bytes); | |
| 54 // Update the current size. | |
| 55 _bytesWrittenSinceCleanup += bytes.length; | |
| 56 if (_bytesWrittenSinceCleanup > _maxSizeBytes ~/ 8) { | |
| 57 _requestCacheCleanUp(); | |
| 58 } | |
| 59 } | |
| 60 | |
| 61 /** | |
| 62 * If the cache clean up process has not been requested yet, request it. | |
| 63 */ | |
| 64 Future<Null> _requestCacheCleanUp() async { | |
| 65 if (_cleanUpSendPortShouldBePrepared) { | |
| 66 _cleanUpSendPortShouldBePrepared = false; | |
| 67 ReceivePort response = new ReceivePort(); | |
| 68 await Isolate.spawn(_cacheCleanUpFunction, response.sendPort); | |
| 69 _cleanUpSendPort = await response.first as SendPort; | |
| 70 } else { | |
| 71 while (_cleanUpSendPort == null) { | |
| 72 await new Future.delayed(new Duration(milliseconds: 100), () {}); | |
| 73 } | |
| 74 } | |
| 75 | |
| 76 if (!_evictionIsolateIsRunning) { | |
| 77 _evictionIsolateIsRunning = true; | |
| 78 try { | |
| 79 ReceivePort response = new ReceivePort(); | |
| 80 _cleanUpSendPort.send(new CacheCleanUpRequest( | |
| 81 _cachePath, _maxSizeBytes, response.sendPort)); | |
| 82 await response.first; | |
| 83 } finally { | |
| 84 _evictionIsolateIsRunning = false; | |
| 85 _bytesWrittenSinceCleanup = 0; | |
| 86 } | |
| 87 } | |
| 88 } | |
| 89 | |
| 90 /** | |
| 91 * This function is started in a new isolate, receives cache folder clean up | |
| 92 * requests and evicts older files from the folder. | |
| 93 */ | |
| 94 static void _cacheCleanUpFunction(SendPort initialReplyTo) { | |
| 95 ReceivePort port = new ReceivePort(); | |
| 96 initialReplyTo.send(port.sendPort); | |
| 97 port.listen((request) async { | |
| 98 if (request is CacheCleanUpRequest) { | |
| 99 await _cleanUpFolder(request.cachePath, request.maxSizeBytes); | |
| 100 // Let the client know that we're done. | |
| 101 request.replyTo.send(true); | |
| 102 } | |
| 103 }); | |
| 104 } | |
| 105 | |
| 106 static Future<Null> _cleanUpFolder(String cachePath, int maxSizeBytes) async { | |
| 107 // Prepare the list of files and their statistics. | |
| 108 List<File> files = <File>[]; | |
| 109 Map<File, FileStat> fileStatMap = {}; | |
| 110 int currentSizeBytes = 0; | |
| 111 List<FileSystemEntity> resources = new Directory(cachePath).listSync(); | |
| 112 for (FileSystemEntity resource in resources) { | |
| 113 if (resource is File) { | |
| 114 try { | |
| 115 FileStat fileStat = await resource.stat(); | |
| 116 files.add(resource); | |
| 117 fileStatMap[resource] = fileStat; | |
| 118 currentSizeBytes += fileStat.size; | |
| 119 } catch (_) {} | |
| 120 } | |
| 121 } | |
| 122 files.sort((a, b) { | |
| 123 return fileStatMap[a].accessed.millisecondsSinceEpoch - | |
| 124 fileStatMap[b].accessed.millisecondsSinceEpoch; | |
| 125 }); | |
| 126 | |
| 127 // Delete files until the current size is less than the max. | |
| 128 for (File file in files) { | |
| 129 if (currentSizeBytes < maxSizeBytes) { | |
| 130 break; | |
| 131 } | |
| 132 try { | |
| 133 await file.delete(); | |
| 134 } catch (_) {} | |
| 135 currentSizeBytes -= fileStatMap[file].size; | |
| 136 } | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 /** | |
| 141 * [ByteStore] that stores values as files. | |
| 142 */ | |
| 143 class FileByteStore implements ByteStore { | |
| 144 final String _cachePath; | |
| 145 final String _tempName = 'temp_$pid'; | |
| 146 | |
| 147 FileByteStore(this._cachePath); | |
| 148 | |
| 149 @override | |
| 150 List<int> get(String key) { | |
| 151 try { | |
| 152 return _getFileForKey(key).readAsBytesSync(); | |
| 153 } catch (_) { | |
| 154 return null; | |
| 155 } | |
| 156 } | |
| 157 | |
| 158 @override | |
| 159 void put(String key, List<int> bytes) { | |
| 160 try { | |
| 161 File tempFile = _getFileForKey(_tempName); | |
| 162 tempFile.writeAsBytesSync(bytes); | |
| 163 File file = _getFileForKey(key); | |
| 164 tempFile.renameSync(file.path); | |
| 165 } catch (_) {} | |
| 166 } | |
| 167 | |
| 168 File _getFileForKey(String key) { | |
| 169 return new File(join(_cachePath, key)); | |
| 170 } | |
| 171 } | |
| OLD | NEW |