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

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

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

Powered by Google App Engine
This is Rietveld 408576698