Chromium Code Reviews| Index: pkg/analysis_server/lib/src/source/caching_pub_package_map_provider.dart |
| diff --git a/pkg/analysis_server/lib/src/source/caching_pub_package_map_provider.dart b/pkg/analysis_server/lib/src/source/caching_pub_package_map_provider.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a8289e412532d2b49ab51b5ef178a4418c317c52 |
| --- /dev/null |
| +++ b/pkg/analysis_server/lib/src/source/caching_pub_package_map_provider.dart |
| @@ -0,0 +1,194 @@ |
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +library source.caching_pub_package_map_provider; |
| + |
| +import 'dart:convert'; |
| +import 'dart:io' as io; |
| + |
| +import 'package:analyzer/file_system/file_system.dart'; |
| +import 'package:analyzer/source/package_map_provider.dart'; |
| +import 'package:analyzer/source/pub_package_map_provider.dart'; |
| +import 'package:analyzer/src/generated/engine.dart'; |
| +import 'package:analyzer/src/generated/sdk_io.dart'; |
| +import 'package:analyzer/src/generated/source.dart'; |
| + |
| +/** |
| + * The function used to write the cache file. |
| + * Returns the modification timestamp for the newly written file. |
| + */ |
| +typedef int WriteFile(File file, String content); |
| + |
| +/** |
| + * [PubPackageMapProvider] extension which caches pub list results. |
| + * These results are cached in memory and in a single place on disk that is |
| + * shared cross session and between different simultaneous sessions. |
| + */ |
| +class CachingPubPackageMapProvider extends PubPackageMapProvider { |
| + static const cacheKey = 'pub_list_cache'; |
| + static const cacheVersion = 1; |
| + static const cacheVersionKey = 'pub_list_cache_version'; |
| + static const pubListResultKey = 'pub_list_result'; |
| + static const modificationStampsKey = 'modification_stamps'; |
| + |
| + /** |
| + * A cache of folder path to pub list information as shown below |
| + * or `null` if the cache has not yet been initialized. |
| + * |
| + * { |
| + * "path/to/folder": { |
| + * "pub_list_result": { |
| + * "packages": { |
| + * "foo": "path/to/foo", |
| + * "bar": ["path/to/bar1", "path/to/bar2"], |
| + * "myapp": "path/to/myapp", // self link is included |
| + * }, |
| + * "input_files": [ |
| + * "path/to/myapp/pubspec.lock" |
| + * ] |
| + * }, |
| + * "modification_stamps": { |
| + * "path/to/myapp/pubspec.lock": 1424305309 |
| + * } |
| + * } |
| + * "path/to/another/folder": { |
| + * ... |
| + * } |
| + * ... |
| + * } |
| + */ |
| + Map<String, Map> _cache; |
| + |
| + /** |
| + * The modification time of the cache file when it was last read |
|
Paul Berry
2015/02/20 22:00:25
s/read/accessed/g
We update the modification time
danrubel
2015/02/21 03:09:30
Done.
|
| + * or `null` if it has not yet been read. |
| + */ |
| + int _cacheModificationTime; |
| + |
| + /** |
| + * The function used to write the cache file. |
| + */ |
| + WriteFile _writeFile; |
| + |
| + /** |
| + * Construct a new instance. |
| + * [RunPubList] and [WriteFile] implementations may be injected for testing |
| + */ |
| + CachingPubPackageMapProvider(ResourceProvider resourceProvider, |
| + DirectoryBasedDartSdk sdk, [RunPubList runPubList, this._writeFile]) |
| + : super(resourceProvider, sdk, runPubList) { |
| + if (_writeFile == null) { |
| + _writeFile = _writeFileDefault; |
| + } |
| + } |
| + |
| + File get _cacheFile => |
| + resourceProvider.getStateLocation('.pub-list').getChild('cache'); |
| + |
| + @override |
| + PackageMapInfo computePackageMap(Folder folder) { |
| + if (!folder.exists) { |
| + return computePackageMapError(folder); |
| + } |
| + // Ensure cache is up to date |
| + _readCache(); |
| + // Check for cached entry |
| + Map entry = _cache[folder.path]; |
| + if (entry != null) { |
| + Map<String, int> modificationStamps = entry[modificationStampsKey]; |
| + if (modificationStamps != null) { |
| + // |
| + // Check to see if any dependencies have changed |
| + // before returning cached result |
| + // |
| + bool dependencyChanged = false; |
| + modificationStamps.forEach((String path, int modificationStamp) { |
|
Paul Berry
2015/02/20 22:00:26
I'd recommend changing this to an ordinary "for" l
danrubel
2015/02/21 03:09:30
That would require a hash lookup on each iteration
Paul Berry
2015/02/23 18:54:58
Since calling res.exists and res.createSource().mo
danrubel
2015/02/24 02:29:45
Good point. Done.
|
| + Resource res = resourceProvider.getResource(path); |
| + if (res is File) { |
| + if (!res.exists || |
| + res.createSource().modificationStamp != modificationStamp) { |
| + dependencyChanged = true; |
| + } |
| + } else { |
| + dependencyChanged = true; |
| + } |
| + }); |
| + if (!dependencyChanged) { |
| + return parsePackageMap(entry[pubListResultKey], folder); |
| + } |
| + } |
| + } |
| + // Create a new entry if one does not already exist |
|
Paul Berry
2015/02/20 22:00:25
It's risky to do this here, since it leaves _cache
danrubel
2015/02/21 03:09:30
Done.
|
| + if (entry == null) { |
| + entry = new Map<String, Map>(); |
| + _cache[folder.path] = entry; |
| + } |
| + // computePackageMap calls parsePackageMap which caches the result |
| + PackageMapInfo info = super.computePackageMap(folder); |
| + _writeCache(); |
| + return info; |
| + } |
| + |
| + @override |
| + PackageMapInfo parsePackageMap(Map obj, Folder folder) { |
| + PackageMapInfo info = super.parsePackageMap(obj, folder); |
| + Map<String, int> modificationStamps = new Map<String, int>(); |
|
Paul Berry
2015/02/20 22:00:26
Nit: I believe the standard dart idiom for creati
Brian Wilkerson
2015/02/20 22:05:36
True, but what we really want, for efficiency's sa
|
| + for (String path in info.dependencies) { |
| + Resource res = resourceProvider.getResource(path); |
| + if (res is File && res.exists) { |
| + modificationStamps[path] = res.createSource().modificationStamp; |
| + } |
| + } |
| + // Assumes entry has been initialized by computePackageMap |
| + Map entry = _cache[folder.path]; |
|
Paul Berry
2015/02/20 22:00:25
These three lines can be replaced with a single at
danrubel
2015/02/21 03:09:30
Great suggestion. Done.
|
| + entry[pubListResultKey] = obj; |
| + entry[modificationStampsKey] = modificationStamps; |
| + return info; |
| + } |
| + |
| + /** |
| + * Read the cache from disk if it has not been read before. |
| + */ |
| + void _readCache() { |
| + // TODO(danrubel) This implementation assumes that |
| + // two separate processes are not accessing the cache file at the same time |
| + Source source = _cacheFile.createSource(); |
| + if (source.exists() && |
| + (_cache == null || _cacheModificationTime != source.modificationStamp)) { |
| + TimestampedData<String> data = source.contents; |
| + Map map = JSON.decode(data.data); |
| + if (map[cacheVersionKey] == cacheVersion) { |
| + _cache = map[cacheKey]; |
| + _cacheModificationTime = data.modificationTime; |
| + } |
| + } |
| + if (_cache == null) { |
| + _cache = new Map<String, Map>(); |
| + } |
| + } |
| + |
| + /** |
| + * Write the cache to disk. |
| + */ |
| + void _writeCache() { |
| + _cacheModificationTime = _writeFile(_cacheFile, JSON.encode({ |
| + cacheVersionKey: cacheVersion, |
| + cacheKey: _cache |
| + })); |
| + } |
| + |
| + /** |
| + * Update the given file with the specified content. |
| + */ |
| + int _writeFileDefault(File cacheFile, String content) { |
| + // TODO(danrubel) This implementation assumes that |
| + // two separate processes are not accessing the cache file at the same time |
| + io.File file = new io.File(cacheFile.path); |
| + if (!file.parent.existsSync()) { |
| + file.parent.createSync(recursive: true); |
| + } |
| + file.writeAsStringSync(content, flush: true); |
| + return file.lastModifiedSync().millisecondsSinceEpoch; |
| + } |
| +} |