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

Unified Diff: pkg/analyzer/lib/src/generated/engine.dart

Issue 800723002: Allow completions to wait for analysis without requiring a busy wait loop. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years 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
Index: pkg/analyzer/lib/src/generated/engine.dart
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index 4b27d6e9f1e75318e23b2528254191010eefe216..103af2c83b796e1588eace057ee8c6e3da5e22a8 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -33,6 +33,17 @@ import 'utilities_collection.dart';
import 'utilities_general.dart';
/**
+ * Type of callback functions used by PendingFuture. Functions of this type
+ * should perform a computation based on the data in [sourceEntry] and return
+ * it. If the computation can't be performed yet because more analysis is
+ * needed, null should be returned.
+ *
+ * The function may also throw an exception, in which case the corresponding
+ * future will be completed with failure.
+ */
+typedef T PendingFutureComputer<T>(SourceEntry sourceEntry);
+
+/**
* Instances of the class `AnalysisCache` implement an LRU cache of information related to
* analysis.
*/
@@ -681,6 +692,20 @@ abstract class AnalysisContext {
LibraryElement getLibraryElement(Source source);
/**
+ * Return a future which will be completed with the element model for the
+ * given source once that element model is up to date.
+ *
+ * If the element model for the source can't be computed for some reason, the
+ * future will be completed with an error. One possible error is
+ * AnalysisNotScheduledError, which means that the element model can't be
+ * computed because the given source file is not scheduled to be analyzed.
+ * (This could happen, for example, because the source file is not known to
+ * the context, or because it's a part file for which there is no
+ * corresponding library).
+ */
+ Future<LibraryElement> getLibraryElementFuture(Source source);
+
+ /**
* Return the line information for the given source, or `null` if the line information is
* not known. The line information is used to map offsets from the beginning of the source to line
* and column pairs.
@@ -732,6 +757,20 @@ abstract class AnalysisContext {
Source librarySource);
/**
+ * Return a future which will be completed with the fully resolved AST for a
+ * single compilation unit within the given library, once that AST is up to
+ * date.
+ *
+ * If the resolved AST can't be computed for some reason, the future will be
+ * completed with an error. One possible error is AnalysisNotScheduledError,
+ * which means that the resolved AST can't be computed because the given
+ * source file is not scheduled to be analyzed within the context of the
+ * given library.
+ */
+ Future<CompilationUnit> getResolvedCompilationUnitFuture(Source source,
danrubel 2014/12/12 16:06:15 How do I cancel waiting for this when I realize th
Paul Berry 2014/12/12 17:36:38 As discussed over VC, I'll make a follow-up CL tha
+ LibraryElement library);
scheglov 2014/12/12 03:41:56 It would be slightly easier for clients to get lib
Brian Wilkerson 2014/12/12 15:13:12 In some cases, yes, in some cases no. And it's mor
scheglov 2014/12/12 15:25:09 It's much easier and cheaper for the client to go
Brian Wilkerson 2014/12/12 15:31:42 Right. I'd forgotten that.
Paul Berry 2014/12/12 17:36:38 Good point. Also, in this case, the client alread
+
+ /**
* Return a fully resolved HTML unit, or `null` if the resolved unit is not already
* computed.
*
@@ -950,6 +989,21 @@ class AnalysisContextImpl implements InternalAnalysisContext {
List<Source> _priorityOrder = Source.EMPTY_ARRAY;
/**
+ * A map from all sources for which there are futures pending to a list of
+ * the corresponding PendingFuture objects. These sources will be analyzed
+ * in the same way as priority sources, except with higher priority.
+ *
+ * TODO(paulberry): since the size of this map is not constrained (as it is
+ * for _priorityOrder), we run the risk of creating an analysis loop if
+ * re-caching one AST structure causes the AST structure for another source
+ * with pending futures to be flushed. However, this is unlikely to happen
+ * in practice since sources are removed from this hash set as soon as their
+ * futures have completed.
+ */
+ HashMap<Source, List<PendingFuture>> _pendingFutureSources =
+ new HashMap<Source, List<PendingFuture>>();
+
+ /**
* An array containing sources whose AST structure is needed in order to resolve the next library
* to be resolved.
*/
@@ -1159,6 +1213,50 @@ class AnalysisContextImpl implements InternalAnalysisContext {
return task;
}
//
+ // Look for a source that needs to be analyzed because it has futures
+ // pending.
+ //
+ if (_pendingFutureSources.isNotEmpty) {
+ List<Source> sourcesToRemove = <Source>[];
+ AnalysisTask task;
+ for (Source source in _pendingFutureSources.keys) {
+ SourceEntry sourceEntry = _cache.get(source);
+ List<PendingFuture> pendingFutures = _pendingFutureSources[source];
+ for (int i = 0; i < pendingFutures.length; ) {
+ if (pendingFutures[i].evaluate(sourceEntry)) {
Brian Wilkerson 2014/12/12 15:13:12 Could the evaluation of the pending future ever ca
Paul Berry 2014/12/12 17:36:38 You are right in general that it's not safe to mod
+ pendingFutures.removeAt(i);
+ } else {
+ i++;
+ }
+ }
+ if (pendingFutures.isEmpty) {
+ sourcesToRemove.add(source);
+ continue;
+ }
+ AnalysisContextImpl_TaskData taskData =
+ _getNextAnalysisTaskForSource(source, sourceEntry, true, hintsEnabled);
+ task = taskData.task;
+ if (task != null) {
+ break;
+ } else if (taskData.isBlocked) {
+ hasBlockedTask = true;
+ } else {
+ // There is no more work to do for this task, so forcibly complete
+ // all its pending futures.
+ for (PendingFuture pendingFuture in pendingFutures) {
+ pendingFuture.forciblyComplete();
+ }
+ sourcesToRemove.add(source);
+ }
+ }
+ for (Source source in sourcesToRemove) {
+ _pendingFutureSources.remove(source);
Brian Wilkerson 2014/12/12 15:13:12 Could the evaluation of a pending future (on line
Paul Berry 2014/12/12 17:36:38 No, for the same reasons stated above.
+ }
+ if (task != null) {
+ return task;
+ }
+ }
+ //
// Look for a priority source that needs to be analyzed.
//
int priorityCount = _priorityOrder.length;
@@ -1907,6 +2005,19 @@ class AnalysisContextImpl implements InternalAnalysisContext {
}
@override
+ Future<LibraryElement> getLibraryElementFuture(Source source) {
+ return _getFuture(source, (SourceEntry sourceEntry) {
+ if (sourceEntry is DartEntry) {
+ if (sourceEntry.hasErrorState()) {
Brian Wilkerson 2014/12/12 15:13:12 I think that this should be checking explicitly fo
Paul Berry 2014/12/12 17:36:38 Fair enough. This code is gone now as a result of
+ throw sourceEntry.exception;
+ }
+ return sourceEntry.getValue(DartEntry.ELEMENT);
+ }
+ throw new AnalysisNotScheduledError();
+ });
+ }
+
+ @override
LineInfo getLineInfo(Source source) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(source);
if (sourceEntry != null) {
@@ -1986,6 +2097,23 @@ class AnalysisContextImpl implements InternalAnalysisContext {
}
@override
+ Future<CompilationUnit> getResolvedCompilationUnitFuture(Source unitSource,
+ LibraryElement library) {
+ Source librarySource = library.source;
+ return _getFuture(unitSource, (SourceEntry sourceEntry) {
+ if (sourceEntry is DartEntry) {
+ if (sourceEntry.hasErrorState()) {
Brian Wilkerson 2014/12/12 15:13:12 Ditto
Paul Berry 2014/12/12 17:36:38 Done.
+ throw sourceEntry.exception;
+ }
+ return sourceEntry.getValueInLibrary(
+ DartEntry.RESOLVED_UNIT,
+ librarySource);
+ }
+ throw new AnalysisNotScheduledError();
+ });
+ }
+
+ @override
ht.HtmlUnit getResolvedHtmlUnit(Source htmlSource) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(htmlSource);
if (sourceEntry is HtmlEntry) {
@@ -3519,6 +3647,33 @@ class AnalysisContextImpl implements InternalAnalysisContext {
}
/**
+ * Return a future that will be completed with the result of calling
+ * [computeValue]. If [computeValue] returns non-null, the future will be
+ * completed immediately with the resulting value. If it returns null, then
+ * it will be re-executed in the future, after the next time the cached
+ * information for [source] has changed. If [computeValue] throws an
+ * exception, the future will fail with that exception.
+ *
+ * If the [computeValue] still returns null after there is no further
+ * analysis to be done for [source], then the future will be completed with
+ * the error AnalysisNotScheduledError.
+ */
+ Future /*<T>*/ _getFuture(Source source, /*T*/
+ computeValue(SourceEntry sourceEntry)) {
+ SourceEntry sourceEntry = getReadableSourceEntryOrNull(source);
+ if (sourceEntry == null) {
+ return new Future.error(new AnalysisNotScheduledError());
+ }
+ PendingFuture pendingFuture = new PendingFuture(computeValue);
+ if (!pendingFuture.evaluate(sourceEntry)) {
+ _pendingFutureSources.putIfAbsent(
+ source,
+ () => <PendingFuture>[]).add(pendingFuture);
+ }
+ return pendingFuture.future;
+ }
+
+ /**
* Given a source for an HTML file, return the data represented by the given descriptor that is
* associated with that source, or the given default value if the source is not an HTML file. This
* method assumes that the data can be produced by parsing the source if it is not already cached.
@@ -6278,6 +6433,16 @@ abstract class AnalysisListener {
}
/**
+ * Futures returned by [AnalysisContext] for pending analysis results will
+ * complete with this error if it is determined that analysis results will
+ * never become available (e.g. because the requested source is not subject to
+ * analysis, or because the requested source is a part file which is not a part
+ * of any known library).
+ */
+class AnalysisNotScheduledError implements Exception {
+}
+
+/**
* The interface `AnalysisOptions` defines the behavior of objects that provide access to a
* set of analysis options used to control the behavior of an analysis context.
*/
@@ -9486,6 +9651,7 @@ class DataDescriptor<E> {
String toString() => _name;
}
+
/**
* Instances of the class `DefaultRetentionPolicy` implement a retention policy that will keep
* AST's in the cache if there is analysis information that needs to be computed for a source, where
@@ -9521,7 +9687,6 @@ class DefaultRetentionPolicy implements CacheRetentionPolicy {
}
}
-
/**
* Recursively visits [HtmlUnit] and every embedded [Expression].
*/
@@ -11492,6 +11657,62 @@ class PartitionManager {
}
/**
+ * Representation of a pending computation which is based on the results of
+ * analysis that may or may not have been completed.
+ */
+class PendingFuture<T> {
+ /**
+ * The function which implements the computation.
+ */
+ final PendingFutureComputer<T> _computeValue;
+
+ /**
+ * The completer that should be completed once the computation has succeeded.
+ */
+ final Completer<T> _completer = new Completer<T>();
+
+ PendingFuture(this._computeValue);
+
+ /**
+ * Retrieve the future which will be completed when this object is
+ * successfully evaluated.
+ */
+ Future<T> get future => _completer.future;
+
+ /**
+ * Execute [_computeValue], passing it the given [sourceEntry], and complete
+ * the pending future if it's appropriate to do so. If the pending future is
+ * completed by this call, true is returned; otherwise false is returned.
+ *
+ * Once this function has returned true, it should not be called again.
+ */
+ bool evaluate(SourceEntry sourceEntry) {
+ assert(!_completer.isCompleted);
+ try {
+ T result = _computeValue(sourceEntry);
+ if (result == null) {
+ return false;
+ } else {
+ _completer.complete(result);
+ return true;
+ }
+ } catch (exception, stackTrace) {
+ _completer.completeError(exception, stackTrace);
+ return true;
+ }
+ }
+
+ /**
+ * No further analysis updates are expected which affect this future, so
+ * complete it with an AnalysisNotScheduledError in order to avoid
+ * deadlocking the client.
+ */
+ void forciblyComplete() {
+ _completer.completeError(new AnalysisNotScheduledError());
Brian Wilkerson 2014/12/12 15:13:12 Should this be try { throw new AnalysisNotSched
Paul Berry 2014/12/12 17:36:38 Good point. Done.
+ }
+}
+
+/**
* Container with global [AnalysisContext] performance statistics.
*/
class PerformanceStatistics {

Powered by Google App Engine
This is Rietveld 408576698