| 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 {
|
|
|