Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(146)

Side by Side Diff: pkg/analysis_server/lib/src/source/caching_pub_package_map_provider.dart

Issue 941883002: cache pub list results (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merge Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 library source.caching_pub_package_map_provider;
6
7 import 'dart:convert';
8 import 'dart:io' as io;
9
10 import 'package:analyzer/file_system/file_system.dart';
11 import 'package:analyzer/source/package_map_provider.dart';
12 import 'package:analyzer/source/pub_package_map_provider.dart';
13 import 'package:analyzer/src/generated/engine.dart';
14 import 'package:analyzer/src/generated/sdk_io.dart';
15 import 'package:analyzer/src/generated/source.dart';
16
17 /**
18 * The function used to write the cache file.
19 * Returns the modification stamp for the newly written file.
20 */
21 typedef int WriteFile(File file, String content);
22
23 /**
24 * [PubPackageMapProvider] extension which caches pub list results.
25 * These results are cached in memory and in a single place on disk that is
26 * shared cross session and between different simultaneous sessions.
27 */
28 class CachingPubPackageMapProvider extends PubPackageMapProvider {
29 static const cacheKey = 'pub_list_cache';
30 static const cacheVersion = 1;
31 static const cacheVersionKey = 'pub_list_cache_version';
32 static const pubListResultKey = 'pub_list_result';
33 static const modificationStampsKey = 'modification_stamps';
34
35 /**
36 * A cache of folder path to pub list information as shown below
37 * or `null` if the cache has not yet been initialized.
38 *
39 * {
40 * "path/to/folder": {
41 * "pub_list_result": {
42 * "packages": {
43 * "foo": "path/to/foo",
44 * "bar": ["path/to/bar1", "path/to/bar2"],
45 * "myapp": "path/to/myapp", // self link is included
46 * },
47 * "input_files": [
48 * "path/to/myapp/pubspec.lock"
49 * ]
50 * },
51 * "modification_stamps": {
52 * "path/to/myapp/pubspec.lock": 1424305309
53 * }
54 * }
55 * "path/to/another/folder": {
56 * ...
57 * }
58 * ...
59 * }
60 */
61 Map<String, Map> _cache;
62
63 /**
64 * The modification time of the cache file
65 * or `null` if it has not yet been read.
66 */
67 int _cacheModificationTime;
68
69 /**
70 * The function used to write the cache file.
71 */
72 WriteFile _writeFile;
73
74 /**
75 * Construct a new instance.
76 * [RunPubList] and [WriteFile] implementations may be injected for testing
77 */
78 CachingPubPackageMapProvider(ResourceProvider resourceProvider,
79 DirectoryBasedDartSdk sdk, [RunPubList runPubList, this._writeFile])
80 : super(resourceProvider, sdk, runPubList) {
81 if (_writeFile == null) {
82 _writeFile = _writeFileDefault;
83 }
84 }
85
86 File get cacheFile => _cacheDir.getChild('cache');
87 Folder get _cacheDir => resourceProvider.getStateLocation('.pub-list');
88 File get _touchFile => _cacheDir.getChild('touch');
89
90 @override
91 PackageMapInfo computePackageMap(Folder folder) {
92 //
93 // Return error if folder does not exist, but don't remove previously
94 // cached result because folder may be only temporarily inaccessible
95 //
96 if (!folder.exists) {
97 return computePackageMapError(folder);
98 }
99 // Ensure cache is up to date
100 _readCache();
101 // Check for cached entry
102 Map entry = _cache[folder.path];
103 if (entry != null) {
104 Map<String, int> modificationStamps = entry[modificationStampsKey];
105 if (modificationStamps != null) {
106 //
107 // Check to see if any dependencies have changed
108 // before returning cached result
109 //
110 if (!_haveDependenciesChanged(modificationStamps)) {
111 return parsePackageMap(entry[pubListResultKey], folder);
112 }
113 }
114 }
115 int runCount = 0;
116 PackageMapInfo info;
117 while (true) {
118 // Capture the current time so that we can tell if an input file
119 // has changed while running pub list. This is done
120 // by writing to a file rather than getting millisecondsSinceEpoch
121 // because file modification time has different granularity
122 // on diferent systems.
123 int startStamp;
124 try {
125 startStamp = _writeFile(_touchFile, 'touch');
126 } catch (exception, stackTrace) {
127 AnalysisEngine.instance.logger.logInformation(
128 'Exception writing $_touchFile\n$exception\n$stackTrace');
129 startStamp = new DateTime.now().millisecondsSinceEpoch;
130 }
131 // computePackageMap calls parsePackageMap which caches the result
132 info = super.computePackageMap(folder);
133 ++runCount;
134 if (!_haveDependenciesChangedSince(info, startStamp)) {
135 // If no dependencies have changed while running pub then finished
136 break;
137 }
138 if (runCount == 4) {
139 // Don't run forever
140 AnalysisEngine.instance.logger.logInformation(
141 'pub list called $runCount times: $folder');
142 break;
143 }
144 }
145 _writeCache();
146 return info;
147 }
148
149 @override
150 PackageMapInfo parsePackageMap(Map obj, Folder folder) {
151 PackageMapInfo info = super.parsePackageMap(obj, folder);
152 Map<String, int> modificationStamps = new Map<String, int>();
153 for (String path in info.dependencies) {
154 Resource res = resourceProvider.getResource(path);
155 if (res is File && res.exists) {
156 modificationStamps[path] = res.createSource().modificationStamp;
157 }
158 }
159 // Assumes entry has been initialized by computePackageMap
160 _cache[folder.path] = <String, Map>{
161 pubListResultKey: obj,
162 modificationStampsKey: modificationStamps
163 };
164 return info;
165 }
166
167 /**
168 * Determine if any of the dependencies have changed.
169 */
170 bool _haveDependenciesChanged(Map<String, int> modificationStamps) {
171 for (String path in modificationStamps.keys) {
172 Resource res = resourceProvider.getResource(path);
173 if (res is File) {
174 if (!res.exists ||
175 res.createSource().modificationStamp != modificationStamps[path]) {
176 return true;
177 }
178 } else {
179 return true;
180 }
181 }
182 return false;
183 }
184
185 /**
186 * Determine if any of the dependencies have changed since the given time.
187 */
188 bool _haveDependenciesChangedSince(PackageMapInfo info, int startStamp) {
189 for (String path in info.dependencies) {
190 Resource res = resourceProvider.getResource(path);
191 if (res is File) {
192 int modStamp = res.createSource().modificationStamp;
193 if (modStamp != null && modStamp >= startStamp) {
194 return true;
195 }
196 }
197 }
198 return false;
199 }
200
201 /**
202 * Read the cache from disk if it has not been read before.
203 */
204 void _readCache() {
205 // TODO(danrubel) This implementation assumes that
206 // two separate processes are not accessing the cache file at the same time
207 Source source = cacheFile.createSource();
208 if (source.exists() &&
209 (_cache == null || _cacheModificationTime != source.modificationStamp)) {
210 try {
211 TimestampedData<String> data = source.contents;
212 Map map = JSON.decode(data.data);
213 if (map[cacheVersionKey] == cacheVersion) {
214 _cache = map[cacheKey];
215 _cacheModificationTime = data.modificationTime;
216 }
217 } catch (exception, stackTrace) {
218 AnalysisEngine.instance.logger.logInformation(
219 'Exception reading $cacheFile\n$exception\n$stackTrace');
220 }
221 }
222 if (_cache == null) {
223 _cache = new Map<String, Map>();
224 }
225 }
226
227 /**
228 * Write the cache to disk.
229 */
230 void _writeCache() {
231 try {
232 _cacheModificationTime = _writeFile(cacheFile, JSON.encode({
233 cacheVersionKey: cacheVersion,
234 cacheKey: _cache
235 }));
236 } catch (exception, stackTrace) {
237 AnalysisEngine.instance.logger.logInformation(
238 'Exception writing $cacheFile\n$exception\n$stackTrace');
239 }
240 }
241
242 /**
243 * Update the given file with the specified content.
244 */
245 int _writeFileDefault(File cacheFile, String content) {
246 // TODO(danrubel) This implementation assumes that
247 // two separate processes are not accessing the cache file at the same time
248 io.File file = new io.File(cacheFile.path);
249 if (!file.parent.existsSync()) {
250 file.parent.createSync(recursive: true);
251 }
252 file.writeAsStringSync(content, flush: true);
253 return file.lastModifiedSync().millisecondsSinceEpoch;
254 }
255 }
OLDNEW
« no previous file with comments | « pkg/analysis_server/lib/src/socket_server.dart ('k') | pkg/analysis_server/test/context_manager_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698