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 |