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

Unified 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, 3 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 side-by-side diff with in-line comments
Download patch
« 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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..964e7dec57871bac973570ad62e4869d9b13ec1f
--- /dev/null
+++ b/pkg/analysis_server/lib/src/source/caching_pub_package_map_provider.dart
@@ -0,0 +1,260 @@
+// 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:core';
+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/dart/sdk/sdk.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/source.dart';
+
+/**
+ * The function used to write the cache file.
+ * Returns the modification stamp 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.
+ *
+ * TODO(paulberry): before this class is used again, it should be ported over
+ * to extend OptimizingPubPackageMapProvider instead of PubPackageMapProvider.
+ */
+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
+ * 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, FolderBasedDartSdk sdk,
+ [RunPubList runPubList, this._writeFile])
+ : super(resourceProvider, sdk, runPubList) {
+ if (_writeFile == null) {
+ _writeFile = _writeFileDefault;
+ }
+ }
+
+ File get cacheFile => _cacheDir.getChild('cache');
+ Folder get _cacheDir => resourceProvider.getStateLocation('.pub-list');
+ File get _touchFile => _cacheDir.getChild('touch');
+
+ @override
+ PackageMapInfo computePackageMap(Folder folder) {
+ //
+ // Return error if folder does not exist, but don't remove previously
+ // cached result because folder may be only temporarily inaccessible
+ //
+ 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] as Map<String, int>;
+ if (modificationStamps != null) {
+ //
+ // Check to see if any dependencies have changed
+ // before returning cached result
+ //
+ if (!_haveDependenciesChanged(modificationStamps)) {
+ return parsePackageMap(entry[pubListResultKey], folder);
+ }
+ }
+ }
+ int runCount = 0;
+ PackageMapInfo info;
+ while (true) {
+ // Capture the current time so that we can tell if an input file
+ // has changed while running pub list. This is done
+ // by writing to a file rather than getting millisecondsSinceEpoch
+ // because file modification time has different granularity
+ // on diferent systems.
+ int startStamp;
+ try {
+ startStamp = _writeFile(_touchFile, 'touch');
+ } catch (exception, stackTrace) {
+ AnalysisEngine.instance.logger.logInformation(
+ 'Exception writing $_touchFile\n$exception\n$stackTrace');
+ startStamp = new DateTime.now().millisecondsSinceEpoch;
+ }
+ // computePackageMap calls parsePackageMap which caches the result
+ info = super.computePackageMap(folder);
+ ++runCount;
+ if (!_haveDependenciesChangedSince(info, startStamp)) {
+ // If no dependencies have changed while running pub then finished
+ break;
+ }
+ if (runCount == 4) {
+ // Don't run forever
+ AnalysisEngine.instance.logger
+ .logInformation('pub list called $runCount times: $folder');
+ break;
+ }
+ }
+ _writeCache();
+ return info;
+ }
+
+ @override
+ PackageMapInfo parsePackageMap(Map obj, Folder folder) {
+ PackageMapInfo info = super.parsePackageMap(obj, folder);
+ Map<String, int> modificationStamps = new Map<String, int>();
+ 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
+ _cache[folder.path] = <String, Map>{
+ pubListResultKey: obj,
+ modificationStampsKey: modificationStamps
+ };
+ return info;
+ }
+
+ /**
+ * Determine if any of the dependencies have changed.
+ */
+ bool _haveDependenciesChanged(Map<String, int> modificationStamps) {
+ for (String path in modificationStamps.keys) {
+ Resource res = resourceProvider.getResource(path);
+ if (res is File) {
+ if (!res.exists ||
+ res.createSource().modificationStamp != modificationStamps[path]) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine if any of the dependencies have changed since the given time.
+ */
+ bool _haveDependenciesChangedSince(PackageMapInfo info, int startStamp) {
+ for (String path in info.dependencies) {
+ Resource res = resourceProvider.getResource(path);
+ if (res is File) {
+ int modStamp = res.createSource().modificationStamp;
+ if (modStamp != null && modStamp >= startStamp) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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)) {
+ try {
+ TimestampedData<String> data = source.contents;
+ Map map = JSON.decode(data.data);
+ if (map[cacheVersionKey] == cacheVersion) {
+ _cache = map[cacheKey] as Map<String, Map>;
+ _cacheModificationTime = data.modificationTime;
+ }
+ } catch (exception, stackTrace) {
+ AnalysisEngine.instance.logger.logInformation(
+ 'Exception reading $cacheFile\n$exception\n$stackTrace');
+ }
+ }
+ if (_cache == null) {
+ _cache = new Map<String, Map>();
+ }
+ }
+
+ /**
+ * Write the cache to disk.
+ */
+ void _writeCache() {
+ try {
+ _cacheModificationTime = _writeFile(cacheFile,
+ JSON.encode({cacheVersionKey: cacheVersion, cacheKey: _cache}));
+ } catch (exception, stackTrace) {
+ AnalysisEngine.instance.logger.logInformation(
+ 'Exception writing $cacheFile\n$exception\n$stackTrace');
+ }
+ }
+
+ /**
+ * 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;
+ }
+}
« 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