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

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: address comments and discussions 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 bool dependencyChanged = false;
111 modificationStamps.forEach((String path, int modificationStamp) {
112 Resource res = resourceProvider.getResource(path);
113 if (res is File) {
114 if (!res.exists ||
115 res.createSource().modificationStamp != modificationStamp) {
116 dependencyChanged = true;
117 }
118 } else {
119 dependencyChanged = true;
120 }
121 });
122 if (!dependencyChanged) {
123 return parsePackageMap(entry[pubListResultKey], folder);
124 }
125 }
126 }
127 int runCount = 0;
128 PackageMapInfo info;
129 while (true) {
130 // Capture the current time so that we can tell if an input file
131 // has changed while running pub list. This is done
132 // by writing to a file rather than getting millisecondsSinceEpoch
133 // because file modification time has different granularity
134 // on diferent systems.
135 int startStamp;
136 try {
137 startStamp = _writeFile(_touchFile, 'touch');
138 } catch (exception, stackTrace) {
139 AnalysisEngine.instance.logger.logInformation(
140 'Exception writing $_touchFile\n$exception\n$stackTrace');
141 startStamp = new DateTime.now().millisecondsSinceEpoch;
142 }
143 // computePackageMap calls parsePackageMap which caches the result
144 info = super.computePackageMap(folder);
145 ++runCount;
146 if (!_haveDependenciesChangedSince(info, startStamp)) {
147 // If no dependencies have changed while running pub then finished
148 break;
149 }
150 if (runCount == 4) {
151 // Don't run forever
152 AnalysisEngine.instance.logger.logInformation(
153 'pub list called $runCount times: $folder');
154 break;
155 }
156 }
157 _writeCache();
158 return info;
159 }
160
161 @override
162 PackageMapInfo parsePackageMap(Map obj, Folder folder) {
163 PackageMapInfo info = super.parsePackageMap(obj, folder);
164 Map<String, int> modificationStamps = new Map<String, int>();
165 for (String path in info.dependencies) {
166 Resource res = resourceProvider.getResource(path);
167 if (res is File && res.exists) {
168 modificationStamps[path] = res.createSource().modificationStamp;
169 }
170 }
171 // Assumes entry has been initialized by computePackageMap
172 _cache[folder.path] = <String, Map>{
173 pubListResultKey: obj,
174 modificationStampsKey: modificationStamps
175 };
176 return info;
177 }
178
179 /**
180 * Determine if any of the dependencies have changed since the given time.
181 */
182 bool _haveDependenciesChangedSince(PackageMapInfo info, int startStamp) {
183 for (String path in info.dependencies) {
184 Resource res = resourceProvider.getResource(path);
185 if (res is File) {
186 int modStamp = res.createSource().modificationStamp;
187 if (modStamp != null && modStamp >= startStamp) {
188 return true;
189 }
190 }
191 }
192 return false;
193 }
194
195 /**
196 * Read the cache from disk if it has not been read before.
197 */
198 void _readCache() {
199 // TODO(danrubel) This implementation assumes that
200 // two separate processes are not accessing the cache file at the same time
201 Source source = cacheFile.createSource();
202 if (source.exists() &&
203 (_cache == null || _cacheModificationTime != source.modificationStamp)) {
204 try {
205 TimestampedData<String> data = source.contents;
206 Map map = JSON.decode(data.data);
207 if (map[cacheVersionKey] == cacheVersion) {
208 _cache = map[cacheKey];
209 _cacheModificationTime = data.modificationTime;
210 }
211 } catch (exception, stackTrace) {
212 AnalysisEngine.instance.logger.logInformation(
213 'Exception reading $cacheFile\n$exception\n$stackTrace');
214 }
215 }
216 if (_cache == null) {
217 _cache = new Map<String, Map>();
218 }
219 }
220
221 /**
222 * Write the cache to disk.
223 */
224 void _writeCache() {
225 try {
226 _cacheModificationTime = _writeFile(cacheFile, JSON.encode({
227 cacheVersionKey: cacheVersion,
228 cacheKey: _cache
229 }));
230 } catch (exception, stackTrace) {
231 AnalysisEngine.instance.logger.logInformation(
232 'Exception writing $cacheFile\n$exception\n$stackTrace');
233 }
234 }
235
236 /**
237 * Update the given file with the specified content.
238 */
239 int _writeFileDefault(File cacheFile, String content) {
240 // TODO(danrubel) This implementation assumes that
241 // two separate processes are not accessing the cache file at the same time
242 io.File file = new io.File(cacheFile.path);
243 if (!file.parent.existsSync()) {
244 file.parent.createSync(recursive: true);
245 }
246 file.writeAsStringSync(content, flush: true);
247 return file.lastModifiedSync().millisecondsSinceEpoch;
248 }
249 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698