Index: packages/analyzer/lib/src/context/cache.dart |
diff --git a/analyzer/lib/src/context/cache.dart b/packages/analyzer/lib/src/context/cache.dart |
similarity index 86% |
rename from analyzer/lib/src/context/cache.dart |
rename to packages/analyzer/lib/src/context/cache.dart |
index 0d67817f80ab56763b9737666b361f1640b8b793..4535ca4ce1d4cea4079b9eafa45152bce0f267f5 100644 |
--- a/analyzer/lib/src/context/cache.dart |
+++ b/packages/analyzer/lib/src/context/cache.dart |
@@ -12,7 +12,6 @@ import 'package:analyzer/src/generated/engine.dart' |
import 'package:analyzer/src/generated/java_engine.dart'; |
import 'package:analyzer/src/generated/source.dart'; |
import 'package:analyzer/src/generated/utilities_collection.dart'; |
-import 'package:analyzer/src/generated/utilities_general.dart'; |
import 'package:analyzer/src/task/model.dart'; |
import 'package:analyzer/task/model.dart'; |
@@ -39,8 +38,8 @@ class AnalysisCache { |
/** |
* The [StreamController] reporting [InvalidatedResult]s. |
*/ |
- final StreamController<InvalidatedResult> _onResultInvalidated = |
- new StreamController<InvalidatedResult>.broadcast(sync: true); |
+ final ReentrantSynchronousStream<InvalidatedResult> onResultInvalidated = |
+ new ReentrantSynchronousStream<InvalidatedResult>(); |
/** |
* Initialize a newly created cache to have the given [partitions]. The |
@@ -51,17 +50,11 @@ class AnalysisCache { |
AnalysisCache(this._partitions) { |
for (CachePartition partition in _partitions) { |
partition.onResultInvalidated.listen((InvalidatedResult event) { |
- _onResultInvalidated.add(event); |
+ onResultInvalidated.add(event); |
}); |
} |
} |
- /** |
- * Return the stream that is notified when a value is invalidated. |
- */ |
- Stream<InvalidatedResult> get onResultInvalidated => |
- _onResultInvalidated.stream; |
- |
// TODO(brianwilkerson) Implement or delete this. |
// /** |
// * Return information about each of the partitions in this cache. |
@@ -173,14 +166,17 @@ class AnalysisCache { |
/** |
* Return an iterator returning all of the map entries mapping targets to |
- * cache entries. |
+ * cache entries. If the [context] is not `null`, then only entries that are |
+ * owned by the given context will be returned. |
*/ |
- MapIterator<AnalysisTarget, CacheEntry> iterator() { |
- int count = _partitions.length; |
+ MapIterator<AnalysisTarget, CacheEntry> iterator( |
+ {InternalAnalysisContext context: null}) { |
List<Map<AnalysisTarget, CacheEntry>> maps = |
- new List<Map<AnalysisTarget, CacheEntry>>(count); |
- for (int i = 0; i < count; i++) { |
- maps[i] = _partitions[i].map; |
+ <Map<AnalysisTarget, CacheEntry>>[]; |
+ for (CachePartition partition in _partitions) { |
+ if (context == null || partition.context == context) { |
+ maps.add(partition.map); |
+ } |
} |
return new MultipleMapIterator<AnalysisTarget, CacheEntry>(maps); |
} |
@@ -216,8 +212,10 @@ class AnalysisCache { |
/** |
* Remove all information related to the given [target] from this cache. |
+ * Return the entry associated with the target, or `null` if there was cache |
+ * entry for the target. |
*/ |
- void remove(AnalysisTarget target) { |
+ CacheEntry remove(AnalysisTarget target) { |
int count = _partitions.length; |
for (int i = 0; i < count; i++) { |
CachePartition partition = _partitions[i]; |
@@ -226,10 +224,10 @@ class AnalysisCache { |
AnalysisEngine.instance.logger |
.logInformation('Removed the cache entry for $target.'); |
} |
- partition.remove(target); |
- return; |
+ return partition.remove(target); |
} |
} |
+ return null; |
} |
/** |
@@ -257,6 +255,11 @@ class CacheEntry { |
static int _EXPLICITLY_ADDED_FLAG = 0; |
/** |
+ * The next invalidation process identifier. |
+ */ |
+ static int nextInvalidateId = 0; |
+ |
+ /** |
* The target this entry is about. |
*/ |
final AnalysisTarget target; |
@@ -445,7 +448,7 @@ class CacheEntry { |
if (state == CacheState.INVALID) { |
ResultData data = _resultMap[descriptor]; |
if (data != null) { |
- _invalidate(descriptor, delta); |
+ _invalidate(nextInvalidateId++, descriptor, delta, 0); |
} |
} else { |
ResultData data = getResultData(descriptor); |
@@ -464,8 +467,11 @@ class CacheEntry { |
* Set the value of the result represented by the given [descriptor] to the |
* given [value]. |
*/ |
- /*<V>*/ void setValue(ResultDescriptor /*<V>*/ descriptor, dynamic /*V*/ |
- value, List<TargetedResult> dependedOn) { |
+ /*<V>*/ void setValue( |
+ ResultDescriptor /*<V>*/ descriptor, |
+ dynamic /*V*/ |
+ value, |
+ List<TargetedResult> dependedOn) { |
// { |
// String valueStr = '$value'; |
// if (valueStr.length > 20) { |
@@ -490,11 +496,14 @@ class CacheEntry { |
* Set the value of the result represented by the given [descriptor] to the |
* given [value], keep its dependency, invalidate all the dependent result. |
*/ |
- void setValueIncremental(ResultDescriptor descriptor, dynamic value) { |
+ void setValueIncremental( |
+ ResultDescriptor descriptor, dynamic value, bool invalidateDependent) { |
ResultData data = getResultData(descriptor); |
- List<TargetedResult> dependedOn = data.dependedOnResults; |
- _invalidate(descriptor, null); |
- setValue(descriptor, value, dependedOn); |
+ data.state = CacheState.VALID; |
+ data.value = value; |
+ if (invalidateDependent) { |
+ _invalidateDependentResults(nextInvalidateId++, data, null, 0); |
+ } |
} |
@override |
@@ -513,50 +522,57 @@ class CacheEntry { |
* Invalidate the result represented by the given [descriptor] and propagate |
* invalidation to other results that depend on it. |
*/ |
- void _invalidate(ResultDescriptor descriptor, Delta delta) { |
+ void _invalidate( |
+ int id, ResultDescriptor descriptor, Delta delta, int level) { |
+ ResultData thisData = _resultMap[descriptor]; |
+ if (thisData == null) { |
+ return; |
+ } |
+ // Stop if already validated. |
+ if (delta != null) { |
+ if (thisData.invalidateId == id) { |
+ return; |
+ } |
+ thisData.invalidateId = id; |
+ } |
+ // Ask the delta to validate. |
DeltaResult deltaResult = null; |
if (delta != null) { |
deltaResult = delta.validate(_partition.context, target, descriptor); |
if (deltaResult == DeltaResult.STOP) { |
-// print('not-invalidate $descriptor for $target'); |
return; |
} |
} |
-// print('invalidate $descriptor for $target'); |
- ResultData thisData; |
- if (deltaResult == null || deltaResult == DeltaResult.INVALIDATE) { |
- thisData = _resultMap.remove(descriptor); |
- } |
- if (deltaResult == DeltaResult.KEEP_CONTINUE) { |
- thisData = _resultMap[descriptor]; |
+ if (deltaResult == DeltaResult.INVALIDATE_NO_DELTA) { |
+ delta = null; |
} |
- if (thisData == null) { |
- return; |
+ if (deltaResult == null || |
+ deltaResult == DeltaResult.INVALIDATE || |
+ deltaResult == DeltaResult.INVALIDATE_NO_DELTA) { |
+ _resultMap.remove(descriptor); |
+// { |
+// String indent = ' ' * level; |
+// print('[$id]$indent invalidate $descriptor for $target'); |
+// } |
} |
// Stop depending on other results. |
TargetedResult thisResult = new TargetedResult(target, descriptor); |
for (TargetedResult dependedOnResult in thisData.dependedOnResults) { |
ResultData data = _partition._getDataFor(dependedOnResult); |
- if (data != null) { |
+ if (data != null && deltaResult != DeltaResult.KEEP_CONTINUE) { |
data.dependentResults.remove(thisResult); |
} |
} |
// Invalidate results that depend on this result. |
- List<TargetedResult> dependentResults = thisData.dependentResults.toList(); |
- for (TargetedResult dependentResult in dependentResults) { |
- CacheEntry entry = _partition.get(dependentResult.target); |
- if (entry != null) { |
- entry._invalidate(dependentResult.result, delta); |
- } |
- } |
+ _invalidateDependentResults(id, thisData, delta, level + 1); |
// If empty, remove the entry altogether. |
if (_resultMap.isEmpty) { |
_partition._targetMap.remove(target); |
_partition._removeIfSource(target); |
} |
// Notify controller. |
- _partition._onResultInvalidated |
- .add(new InvalidatedResult(this, descriptor)); |
+ _partition.onResultInvalidated |
+ .add(new InvalidatedResult(this, descriptor, thisData.value)); |
} |
/** |
@@ -565,7 +581,21 @@ class CacheEntry { |
void _invalidateAll() { |
List<ResultDescriptor> results = _resultMap.keys.toList(); |
for (ResultDescriptor result in results) { |
- _invalidate(result, null); |
+ _invalidate(nextInvalidateId++, result, null, 0); |
+ } |
+ } |
+ |
+ /** |
+ * Invalidate results that depend on [thisData]. |
+ */ |
+ void _invalidateDependentResults( |
+ int id, ResultData thisData, Delta delta, int level) { |
+ List<TargetedResult> dependentResults = thisData.dependentResults.toList(); |
+ for (TargetedResult dependentResult in dependentResults) { |
+ CacheEntry entry = _partition.get(dependentResult.target); |
+ if (entry != null) { |
+ entry._invalidate(id, dependentResult.result, delta, level); |
+ } |
} |
} |
@@ -803,8 +833,8 @@ abstract class CachePartition { |
/** |
* The [StreamController] reporting [InvalidatedResult]s. |
*/ |
- final StreamController<InvalidatedResult> _onResultInvalidated = |
- new StreamController<InvalidatedResult>.broadcast(sync: true); |
+ final ReentrantSynchronousStream<InvalidatedResult> onResultInvalidated = |
+ new ReentrantSynchronousStream<InvalidatedResult>(); |
/** |
* A table mapping the targets belonging to this partition to the information |
@@ -839,12 +869,6 @@ abstract class CachePartition { |
Map<AnalysisTarget, CacheEntry> get map => _targetMap; |
/** |
- * Return the stream that is notified when a value is invalidated. |
- */ |
- Stream<InvalidatedResult> get onResultInvalidated => |
- _onResultInvalidated.stream; |
- |
- /** |
* Notifies the partition that the client is going to stop using it. |
*/ |
void dispose() { |
@@ -896,9 +920,11 @@ abstract class CachePartition { |
} |
/** |
- * Remove all information related to the given [target] from this cache. |
+ * Remove all information related to the given [target] from this partition. |
+ * Return the entry associated with the target, or `null` if there was cache |
+ * entry for the target. |
*/ |
- void remove(AnalysisTarget target) { |
+ CacheEntry remove(AnalysisTarget target) { |
for (CacheFlushManager flushManager in _flushManagerMap.values) { |
flushManager.targetRemoved(target); |
} |
@@ -907,6 +933,7 @@ abstract class CachePartition { |
entry._invalidateAll(); |
} |
_removeIfSource(target); |
+ return entry; |
} |
/** |
@@ -948,10 +975,8 @@ abstract class CachePartition { |
void _addIfSource(AnalysisTarget target) { |
if (target is Source) { |
_sources.add(target); |
- { |
- String fullName = target.fullName; |
- _pathToSources.putIfAbsent(fullName, () => <Source>[]).add(target); |
- } |
+ String fullName = target.fullName; |
+ _pathToSources.putIfAbsent(fullName, () => <Source>[]).add(target); |
} |
} |
@@ -981,19 +1006,17 @@ abstract class CachePartition { |
} |
/** |
- * If the given [target] is a [Source], removes it from [_sources]. |
+ * If the given [target] is a [Source], remove it from the list of [_sources]. |
*/ |
void _removeIfSource(AnalysisTarget target) { |
if (target is Source) { |
_sources.remove(target); |
- { |
- String fullName = target.fullName; |
- List<Source> sources = _pathToSources[fullName]; |
- if (sources != null) { |
- sources.remove(target); |
- if (sources.isEmpty) { |
- _pathToSources.remove(fullName); |
- } |
+ String fullName = target.fullName; |
+ List<Source> sources = _pathToSources[fullName]; |
+ if (sources != null) { |
+ sources.remove(target); |
+ if (sources.isEmpty) { |
+ _pathToSources.remove(fullName); |
} |
} |
} |
@@ -1021,7 +1044,30 @@ class Delta { |
/** |
* The possible results of validating analysis results againt a [Delta]. |
*/ |
-enum DeltaResult { INVALIDATE, KEEP_CONTINUE, STOP } |
+enum DeltaResult { |
+ /** |
+ * Invalidate this result and continue visiting dependent results |
+ * with this [Delta]. |
+ */ |
+ INVALIDATE, |
+ |
+ /** |
+ * Invalidate this result and stop using this [Delta], so unconditionally |
+ * invalidate all the dependent results. |
+ */ |
+ INVALIDATE_NO_DELTA, |
+ |
+ /** |
+ * Keep this result and continue validating dependent results |
+ * with this [Delta]. |
+ */ |
+ KEEP_CONTINUE, |
+ |
+ /** |
+ * Keep this result and stop visiting results that depend on this one. |
+ */ |
+ STOP |
+} |
/** |
* [InvalidatedResult] describes an invalidated result. |
@@ -1037,13 +1083,47 @@ class InvalidatedResult { |
*/ |
final ResultDescriptor descriptor; |
- InvalidatedResult(this.entry, this.descriptor); |
+ /** |
+ * The value of the result which was invalidated. |
+ */ |
+ final Object value; |
+ |
+ InvalidatedResult(this.entry, this.descriptor, this.value); |
@override |
String toString() => '$descriptor of ${entry.target}'; |
} |
/** |
+ * A Stream-like interface, which broadcasts events synchronously. |
+ * If a second event is fired while delivering a first event, then the second |
+ * event will be delivered first, and then delivering of the first will be |
+ * continued. |
+ */ |
+class ReentrantSynchronousStream<T> { |
+ final List<Function> listeners = <Function>[]; |
+ |
+ /** |
+ * Send the given [event] to the stream. |
+ */ |
+ void add(T event) { |
+ List<Function> listeners = this.listeners.toList(); |
+ for (Function listener in listeners) { |
+ listener(event); |
+ } |
+ } |
+ |
+ /** |
+ * Listen for the events in this stream. |
+ * Note that if the [listener] fires a new event, then the [listener] will be |
+ * invoked again before returning from the [add] invocation. |
+ */ |
+ void listen(void listener(T event)) { |
+ listeners.add(listener); |
+ } |
+} |
+ |
+/** |
* The data about a single analysis result that is stored in a [CacheEntry]. |
*/ |
// TODO(brianwilkerson) Consider making this a generic class so that the value |
@@ -1066,6 +1146,13 @@ class ResultData { |
Object value; |
/** |
+ * The identifier of the invalidation process that most recently checked |
+ * this value. If it is the same as the current invalidation identifier, |
+ * then there is no reason to check it (and its subtree again). |
+ */ |
+ int invalidateId = -1; |
+ |
+ /** |
* A list of the results on which this result depends. |
*/ |
List<TargetedResult> dependedOnResults = <TargetedResult>[]; |
@@ -1114,46 +1201,6 @@ class SdkCachePartition extends CachePartition { |
} |
/** |
- * A specification of a specific result computed for a specific target. |
- */ |
-class TargetedResult { |
- /** |
- * An empty list of results. |
- */ |
- static final List<TargetedResult> EMPTY_LIST = const <TargetedResult>[]; |
- |
- /** |
- * The target with which the result is associated. |
- */ |
- final AnalysisTarget target; |
- |
- /** |
- * The result associated with the target. |
- */ |
- final ResultDescriptor result; |
- |
- /** |
- * Initialize a new targeted result. |
- */ |
- TargetedResult(this.target, this.result); |
- |
- @override |
- int get hashCode { |
- return JenkinsSmiHash.combine(target.hashCode, result.hashCode); |
- } |
- |
- @override |
- bool operator ==(other) { |
- return other is TargetedResult && |
- other.target == target && |
- other.result == result; |
- } |
- |
- @override |
- String toString() => '$result for $target'; |
-} |
- |
-/** |
* A cache partition that contains all targets not contained in other partitions. |
*/ |
class UniversalCachePartition extends CachePartition { |