| Index: packages/analyzer/lib/src/context/cache.dart
|
| diff --git a/packages/analyzer/lib/src/context/cache.dart b/packages/analyzer/lib/src/context/cache.dart
|
| index 4535ca4ce1d4cea4079b9eafa45152bce0f267f5..ef780314a2ee2c4f4decd1415f00ebc3fb6e83c3 100644
|
| --- a/packages/analyzer/lib/src/context/cache.dart
|
| +++ b/packages/analyzer/lib/src/context/cache.dart
|
| @@ -7,14 +7,25 @@ library analyzer.src.context.cache;
|
| import 'dart:async';
|
| import 'dart:collection';
|
|
|
| -import 'package:analyzer/src/generated/engine.dart'
|
| - show AnalysisEngine, CacheState, InternalAnalysisContext, RetentionPriority;
|
| -import 'package:analyzer/src/generated/java_engine.dart';
|
| +import 'package:analyzer/exception/exception.dart';
|
| +import 'package:analyzer/file_system/file_system.dart';
|
| +import 'package:analyzer/src/generated/engine.dart';
|
| import 'package:analyzer/src/generated/source.dart';
|
| import 'package:analyzer/src/generated/utilities_collection.dart';
|
| import 'package:analyzer/src/task/model.dart';
|
| import 'package:analyzer/task/model.dart';
|
|
|
| +/**
|
| + * The cache results visiting function type.
|
| + */
|
| +typedef void CacheResultVisitor(AnalysisTarget target, ResultData data);
|
| +
|
| +/**
|
| + * Return `true` if the [result] of the [target] should be flushed.
|
| + */
|
| +typedef bool FlushResultFilter<V>(
|
| + AnalysisTarget target, ResultDescriptor<V> result);
|
| +
|
| /**
|
| * Return `true` if the given [target] is a priority one.
|
| */
|
| @@ -41,20 +52,36 @@ class AnalysisCache {
|
| final ReentrantSynchronousStream<InvalidatedResult> onResultInvalidated =
|
| new ReentrantSynchronousStream<InvalidatedResult>();
|
|
|
| + final List<ReentrantSynchronousStreamSubscription>
|
| + onResultInvalidatedPartitionSubscriptions =
|
| + <ReentrantSynchronousStreamSubscription>[];
|
| +
|
| /**
|
| - * Initialize a newly created cache to have the given [partitions]. The
|
| + * Initialize a newly created cache to have the given [_partitions]. The
|
| * partitions will be searched in the order in which they appear in the array,
|
| * so the most specific partition (usually an [SdkCachePartition]) should be
|
| * first and the most general (usually a [UniversalCachePartition]) last.
|
| */
|
| AnalysisCache(this._partitions) {
|
| for (CachePartition partition in _partitions) {
|
| - partition.onResultInvalidated.listen((InvalidatedResult event) {
|
| + partition.containingCaches.add(this);
|
| + ReentrantSynchronousStreamSubscription<InvalidatedResult> subscription =
|
| + partition.onResultInvalidated.listen((InvalidatedResult event) {
|
| onResultInvalidated.add(event);
|
| });
|
| + onResultInvalidatedPartitionSubscriptions.add(subscription);
|
| }
|
| }
|
|
|
| + /**
|
| + * Return an iterator returning all of the [Source] targets.
|
| + */
|
| + Iterable<Source> get sources {
|
| + return _partitions
|
| + .map((CachePartition partition) => partition.sources)
|
| + .expand((Iterable<Source> sources) => sources);
|
| + }
|
| +
|
| // TODO(brianwilkerson) Implement or delete this.
|
| // /**
|
| // * Return information about each of the partitions in this cache.
|
| @@ -73,12 +100,25 @@ class AnalysisCache {
|
| // }
|
|
|
| /**
|
| - * Return an iterator returning all of the [Source] targets.
|
| + * Free any allocated resources and references.
|
| */
|
| - Iterable<Source> get sources {
|
| - return _partitions
|
| - .map((CachePartition partition) => partition._sources)
|
| - .expand((Iterable<Source> sources) => sources);
|
| + void dispose() {
|
| + for (ReentrantSynchronousStreamSubscription subscription
|
| + in onResultInvalidatedPartitionSubscriptions) {
|
| + subscription.cancel();
|
| + }
|
| + for (CachePartition partition in _partitions) {
|
| + partition.containingCaches.remove(this);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Flush results that satisfy the given [filter].
|
| + */
|
| + void flush(FlushResultFilter filter) {
|
| + for (CachePartition partition in _partitions) {
|
| + partition.flush(filter);
|
| + }
|
| }
|
|
|
| /**
|
| @@ -156,7 +196,8 @@ class AnalysisCache {
|
| * It does not update the cache, if the corresponding [CacheEntry] does not
|
| * exist, then the default value is returned.
|
| */
|
| - Object getValue(AnalysisTarget target, ResultDescriptor result) {
|
| + Object/*=V*/ getValue/*<V>*/(
|
| + AnalysisTarget target, ResultDescriptor/*<V>*/ result) {
|
| CacheEntry entry = get(target);
|
| if (entry == null) {
|
| return result.defaultValue;
|
| @@ -175,7 +216,7 @@ class AnalysisCache {
|
| <Map<AnalysisTarget, CacheEntry>>[];
|
| for (CachePartition partition in _partitions) {
|
| if (context == null || partition.context == context) {
|
| - maps.add(partition.map);
|
| + maps.add(partition.entryMap);
|
| }
|
| }
|
| return new MultipleMapIterator<AnalysisTarget, CacheEntry>(maps);
|
| @@ -224,7 +265,11 @@ class AnalysisCache {
|
| AnalysisEngine.instance.logger
|
| .logInformation('Removed the cache entry for $target.');
|
| }
|
| - return partition.remove(target);
|
| + CacheEntry entry = partition.remove(target);
|
| + if (entry != null) {
|
| + entry.dispose();
|
| + }
|
| + return entry;
|
| }
|
| }
|
| return null;
|
| @@ -255,9 +300,16 @@ class CacheEntry {
|
| static int _EXPLICITLY_ADDED_FLAG = 0;
|
|
|
| /**
|
| - * The next invalidation process identifier.
|
| + * The next visit process identifier.
|
| + */
|
| + static int nextVisitId = 0;
|
| +
|
| + /**
|
| + * A table containing the number of times the value of a result descriptor was
|
| + * recomputed after having been flushed.
|
| */
|
| - static int nextInvalidateId = 0;
|
| + static final Map<ResultDescriptor, int> recomputedCounts =
|
| + new HashMap<ResultDescriptor, int>();
|
|
|
| /**
|
| * The target this entry is about.
|
| @@ -325,12 +377,18 @@ class CacheEntry {
|
| * Notifies the entry that the client is going to stop using it.
|
| */
|
| void dispose() {
|
| - _resultMap.forEach((descriptor, data) {
|
| + _resultMap.forEach((ResultDescriptor descriptor, ResultData data) {
|
| TargetedResult result = new TargetedResult(target, descriptor);
|
| for (TargetedResult dependedOnResult in data.dependedOnResults) {
|
| - ResultData dependedOnData = _partition._getDataFor(dependedOnResult);
|
| - if (dependedOnData != null) {
|
| - dependedOnData.dependentResults.remove(result);
|
| + for (AnalysisCache cache in _partition.containingCaches) {
|
| + CacheEntry entry = cache.get(dependedOnResult.target);
|
| + if (entry != null) {
|
| + ResultData data =
|
| + entry.getResultDataOrNull(dependedOnResult.result);
|
| + if (data != null) {
|
| + data.dependentResults.remove(result);
|
| + }
|
| + }
|
| }
|
| }
|
| });
|
| @@ -347,13 +405,31 @@ class CacheEntry {
|
| }
|
|
|
| /**
|
| - * Look up the [ResultData] of [descriptor], or add a new one if it isn't
|
| - * there.
|
| + * Flush results that satisfy the given [filter].
|
| + */
|
| + void flush(FlushResultFilter filter) {
|
| + _resultMap.forEach((ResultDescriptor result, ResultData data) {
|
| + if (filter(target, result)) {
|
| + data.flush();
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Return the result data associated with the [descriptor], creating one if it
|
| + * isn't there.
|
| */
|
| ResultData getResultData(ResultDescriptor descriptor) {
|
| return _resultMap.putIfAbsent(descriptor, () => new ResultData(descriptor));
|
| }
|
|
|
| + /**
|
| + * Return the result data associated with the [descriptor], or `null` if there
|
| + * is no data currently associated with the descriptor.
|
| + */
|
| + ResultData getResultDataOrNull(ResultDescriptor descriptor) =>
|
| + _resultMap[descriptor];
|
| +
|
| /**
|
| * Return the state of the result represented by the given [descriptor].
|
| */
|
| @@ -369,7 +445,7 @@ class CacheEntry {
|
| * Return the value of the result represented by the given [descriptor], or
|
| * the default value for the result if this entry does not have a valid value.
|
| */
|
| - /*<V>*/ dynamic /*V*/ getValue(ResultDescriptor /*<V>*/ descriptor) {
|
| + dynamic/*=V*/ getValue/*<V>*/(ResultDescriptor/*<V>*/ descriptor) {
|
| ResultData data = _resultMap[descriptor];
|
| if (data == null) {
|
| return descriptor.defaultValue;
|
| @@ -377,7 +453,7 @@ class CacheEntry {
|
| if (_partition != null) {
|
| _partition.resultAccessed(target, descriptor);
|
| }
|
| - return data.value;
|
| + return data.value as Object/*=V*/;
|
| }
|
|
|
| /**
|
| @@ -448,7 +524,12 @@ class CacheEntry {
|
| if (state == CacheState.INVALID) {
|
| ResultData data = _resultMap[descriptor];
|
| if (data != null) {
|
| - _invalidate(nextInvalidateId++, descriptor, delta, 0);
|
| + bool canUseDelta =
|
| + _gatherResultsInvalidatedByDelta(descriptor, delta, 0);
|
| + if (!canUseDelta) {
|
| + delta = null;
|
| + }
|
| + _invalidate(nextVisitId++, descriptor, delta, 0);
|
| }
|
| } else {
|
| ResultData data = getResultData(descriptor);
|
| @@ -467,10 +548,7 @@ 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,
|
| + void setValue/*<V>*/(ResultDescriptor/*<V>*/ descriptor, dynamic/*=V*/ value,
|
| List<TargetedResult> dependedOn) {
|
| // {
|
| // String valueStr = '$value';
|
| @@ -479,7 +557,7 @@ class CacheEntry {
|
| // }
|
| // valueStr = valueStr.replaceAll('\n', '\\n');
|
| // print(
|
| -// 'setValue $descriptor for $target value=$valueStr deps=$dependedOn');
|
| +// 'setValue $descriptor for $target value=$valueStr dependedOn=$dependedOn');
|
| // }
|
| _validateStateChange(descriptor, CacheState.VALID);
|
| TargetedResult thisResult = new TargetedResult(target, descriptor);
|
| @@ -488,21 +566,27 @@ class CacheEntry {
|
| }
|
| ResultData data = getResultData(descriptor);
|
| _setDependedOnResults(data, thisResult, dependedOn);
|
| + if (data.state == CacheState.FLUSHED) {
|
| + int count = recomputedCounts[descriptor] ?? 0;
|
| + recomputedCounts[descriptor] = count + 1;
|
| + }
|
| data.state = CacheState.VALID;
|
| - data.value = value == null ? descriptor.defaultValue : value;
|
| + data.value = value ?? descriptor.defaultValue;
|
| }
|
|
|
| /**
|
| - * Set the value of the result represented by the given [descriptor] to the
|
| - * given [value], keep its dependency, invalidate all the dependent result.
|
| + * If the result represented by the given [descriptor] is valid, set
|
| + * it to the given [value], keep its dependency, and if [invalidateDependent]
|
| + * invalidate all the dependent result.
|
| */
|
| void setValueIncremental(
|
| ResultDescriptor descriptor, dynamic value, bool invalidateDependent) {
|
| ResultData data = getResultData(descriptor);
|
| - data.state = CacheState.VALID;
|
| - data.value = value;
|
| + if (data.state == CacheState.VALID || data.state == CacheState.FLUSHED) {
|
| + data.value = value;
|
| + }
|
| if (invalidateDependent) {
|
| - _invalidateDependentResults(nextInvalidateId++, data, null, 0);
|
| + _invalidateDependentResults(nextVisitId++, data, null, 0);
|
| }
|
| }
|
|
|
| @@ -513,6 +597,38 @@ class CacheEntry {
|
| return buffer.toString();
|
| }
|
|
|
| + /**
|
| + * Visit the given [result] and all results that depend on it, and
|
| + * ask [delta] to gather changes. Return `true` if the [delta] can be used
|
| + * to perform limited invalidation, or `false` if the changes collection
|
| + * process does not stop (should not happen).
|
| + */
|
| + bool _gatherResultsInvalidatedByDelta(
|
| + ResultDescriptor result, Delta delta, int level) {
|
| + if (delta == null) {
|
| + return false;
|
| + }
|
| + if (!delta.shouldGatherChanges) {
|
| + return true;
|
| + }
|
| + for (int i = 0; i < 64; i++) {
|
| + bool hasVisitChanges = false;
|
| + _visitResults(nextVisitId++, result,
|
| + (AnalysisTarget target, ResultData data) {
|
| + bool hasDeltaChanges = delta.gatherChanges(
|
| + _partition.context, target, data.descriptor, data.value);
|
| + if (hasDeltaChanges) {
|
| + hasVisitChanges = true;
|
| + }
|
| + });
|
| + delta.gatherEnd();
|
| + if (!hasVisitChanges) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| /**
|
| * Return the value of the flag with the given [index].
|
| */
|
| @@ -530,49 +646,77 @@ class CacheEntry {
|
| }
|
| // Stop if already validated.
|
| if (delta != null) {
|
| - if (thisData.invalidateId == id) {
|
| + if (thisData.visitId == id) {
|
| return;
|
| }
|
| - thisData.invalidateId = id;
|
| + thisData.visitId = id;
|
| }
|
| // Ask the delta to validate.
|
| DeltaResult deltaResult = null;
|
| if (delta != null) {
|
| - deltaResult = delta.validate(_partition.context, target, descriptor);
|
| + deltaResult = delta.validate(
|
| + _partition.context, target, descriptor, thisData.value);
|
| if (deltaResult == DeltaResult.STOP) {
|
| return;
|
| }
|
| }
|
| +// if (deltaResult != null) {
|
| +// String indent = ' ' * level;
|
| +// String deltaResultName = deltaResult.toString().split('.').last;
|
| +// print('[$id]$indent$deltaResultName $descriptor for $target');
|
| +// }
|
| if (deltaResult == DeltaResult.INVALIDATE_NO_DELTA) {
|
| delta = null;
|
| }
|
| - if (deltaResult == null ||
|
| + if (deltaResult == DeltaResult.INVALIDATE_KEEP_DEPENDENCIES) {
|
| + thisData.value = descriptor.defaultValue;
|
| + thisData.state = CacheState.INVALID;
|
| + } else if (deltaResult == null ||
|
| deltaResult == DeltaResult.INVALIDATE ||
|
| deltaResult == DeltaResult.INVALIDATE_NO_DELTA) {
|
| _resultMap.remove(descriptor);
|
| -// {
|
| + // Stop depending on other results.
|
| + {
|
| + TargetedResult thisResult = new TargetedResult(target, descriptor);
|
| + List<AnalysisCache> caches = _partition.containingCaches;
|
| + int cacheLength = caches.length;
|
| + List<TargetedResult> results = thisData.dependedOnResults;
|
| + int resultLength = results.length;
|
| + for (int i = 0; i < resultLength; i++) {
|
| + TargetedResult dependedOnResult = results[i];
|
| + for (int j = 0; j < cacheLength; j++) {
|
| + AnalysisCache cache = caches[j];
|
| + CacheEntry entry = cache.get(dependedOnResult.target);
|
| + if (entry != null) {
|
| + ResultData data =
|
| + entry.getResultDataOrNull(dependedOnResult.result);
|
| + if (data != null) {
|
| + data.dependentResults.remove(thisResult);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| +// if (deltaResult == null) {
|
| // 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 && deltaResult != DeltaResult.KEEP_CONTINUE) {
|
| - data.dependentResults.remove(thisResult);
|
| - }
|
| - }
|
| // Invalidate results that depend on this result.
|
| _invalidateDependentResults(id, thisData, delta, level + 1);
|
| - // If empty, remove the entry altogether.
|
| - if (_resultMap.isEmpty) {
|
| - _partition._targetMap.remove(target);
|
| + // If empty and not explicitly added, remove the entry altogether.
|
| + if (_resultMap.isEmpty && !explicitlyAdded) {
|
| + CacheEntry entry = _partition.entryMap.remove(target);
|
| + if (entry != null) {
|
| + entry.dispose();
|
| + }
|
| _partition._removeIfSource(target);
|
| }
|
| // Notify controller.
|
| - _partition.onResultInvalidated
|
| - .add(new InvalidatedResult(this, descriptor, thisData.value));
|
| + if (deltaResult != DeltaResult.KEEP_CONTINUE) {
|
| + _partition.onResultInvalidated
|
| + .add(new InvalidatedResult(this, descriptor, thisData.value));
|
| + }
|
| }
|
|
|
| /**
|
| @@ -580,8 +724,10 @@ class CacheEntry {
|
| */
|
| void _invalidateAll() {
|
| List<ResultDescriptor> results = _resultMap.keys.toList();
|
| - for (ResultDescriptor result in results) {
|
| - _invalidate(nextInvalidateId++, result, null, 0);
|
| + int length = results.length;
|
| + for (int i = 0; i < length; i++) {
|
| + ResultDescriptor result = results[i];
|
| + _invalidate(nextVisitId++, result, null, 0);
|
| }
|
| }
|
|
|
| @@ -590,11 +736,20 @@ class CacheEntry {
|
| */
|
| void _invalidateDependentResults(
|
| int id, ResultData thisData, Delta delta, int level) {
|
| + // It is necessary to copy the results to a list to avoid a concurrent
|
| + // modification of the set of dependent results.
|
| + List<AnalysisCache> caches = _partition.containingCaches;
|
| + int cacheLength = caches.length;
|
| 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);
|
| + int resultLength = dependentResults.length;
|
| + for (int i = 0; i < resultLength; i++) {
|
| + TargetedResult dependentResult = dependentResults[i];
|
| + for (int j = 0; j < cacheLength; j++) {
|
| + AnalysisCache cache = caches[j];
|
| + CacheEntry entry = cache.get(dependentResult.target);
|
| + if (entry != null) {
|
| + entry._invalidate(id, dependentResult.result, delta, level);
|
| + }
|
| }
|
| }
|
| }
|
| @@ -604,19 +759,37 @@ class CacheEntry {
|
| */
|
| void _setDependedOnResults(ResultData thisData, TargetedResult thisResult,
|
| List<TargetedResult> dependedOn) {
|
| - thisData.dependedOnResults.forEach((TargetedResult dependedOnResult) {
|
| - ResultData data = _partition._getDataFor(dependedOnResult);
|
| - if (data != null) {
|
| - data.dependentResults.remove(thisResult);
|
| + List<AnalysisCache> caches = _partition.containingCaches;
|
| + int cacheLength = caches.length;
|
| +
|
| + List<TargetedResult> oldResults = thisData.dependedOnResults;
|
| + int oldLength = oldResults.length;
|
| + for (int i = 0; i < oldLength; i++) {
|
| + TargetedResult dependedOnResult = oldResults[i];
|
| + for (int j = 0; j < cacheLength; j++) {
|
| + AnalysisCache cache = caches[j];
|
| + CacheEntry entry = cache.get(dependedOnResult.target);
|
| + if (entry != null) {
|
| + ResultData data = entry.getResultDataOrNull(dependedOnResult.result);
|
| + if (data != null) {
|
| + data.dependentResults.remove(thisResult);
|
| + }
|
| + }
|
| }
|
| - });
|
| + }
|
| thisData.dependedOnResults = dependedOn;
|
| - thisData.dependedOnResults.forEach((TargetedResult dependedOnResult) {
|
| - ResultData data = _partition._getDataFor(dependedOnResult);
|
| - if (data != null) {
|
| - data.dependentResults.add(thisResult);
|
| + int newLength = dependedOn.length;
|
| + for (int i = 0; i < newLength; i++) {
|
| + TargetedResult dependedOnResult = dependedOn[i];
|
| + for (int j = 0; j < cacheLength; j++) {
|
| + AnalysisCache cache = caches[j];
|
| + CacheEntry entry = cache.get(dependedOnResult.target);
|
| + if (entry != null) {
|
| + ResultData data = entry.getResultData(dependedOnResult.result);
|
| + data.dependentResults.add(thisResult);
|
| + }
|
| }
|
| - });
|
| + }
|
| }
|
|
|
| /**
|
| @@ -630,9 +803,16 @@ class CacheEntry {
|
| thisData.state = CacheState.ERROR;
|
| thisData.value = descriptor.defaultValue;
|
| // Propagate the error state.
|
| + List<AnalysisCache> caches = _partition.containingCaches;
|
| + int cacheLength = caches.length;
|
| thisData.dependentResults.forEach((TargetedResult dependentResult) {
|
| - CacheEntry entry = _partition.get(dependentResult.target);
|
| - entry._setErrorState(dependentResult.result, exception);
|
| + for (int i = 0; i < cacheLength; i++) {
|
| + AnalysisCache cache = caches[i];
|
| + CacheEntry entry = cache.get(dependentResult.target);
|
| + if (entry != null) {
|
| + entry._setErrorState(dependentResult.result, exception);
|
| + }
|
| + }
|
| });
|
| }
|
|
|
| @@ -666,6 +846,41 @@ class CacheEntry {
|
| // }
|
| }
|
|
|
| + /**
|
| + * Call [visitor] for the result described by the given [descriptor] and all
|
| + * results that depend on directly or indirectly. Each result is visited
|
| + * only once.
|
| + */
|
| + void _visitResults(
|
| + int id, ResultDescriptor descriptor, CacheResultVisitor visitor) {
|
| + ResultData thisData = _resultMap[descriptor];
|
| + if (thisData == null) {
|
| + return;
|
| + }
|
| + // Stop if already visited.
|
| + if (thisData.visitId == id) {
|
| + return;
|
| + }
|
| + thisData.visitId = id;
|
| + // Visit this result.
|
| + visitor(target, thisData);
|
| + // Visit results that depend on this result.
|
| + List<AnalysisCache> caches = _partition.containingCaches;
|
| + int cacheLength = caches.length;
|
| + List<TargetedResult> dependentResults = thisData.dependentResults.toList();
|
| + int resultLength = dependentResults.length;
|
| + for (int i = 0; i < resultLength; i++) {
|
| + TargetedResult dependentResult = dependentResults[i];
|
| + for (int j = 0; j < cacheLength; j++) {
|
| + AnalysisCache cache = caches[j];
|
| + CacheEntry entry = cache.get(dependentResult.target);
|
| + if (entry != null) {
|
| + entry._visitResults(id, dependentResult.result, visitor);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| /**
|
| * Write a textual representation of this entry to the given [buffer]. The
|
| * result should only be used for debugging purposes.
|
| @@ -725,7 +940,7 @@ class CacheFlushManager<T> {
|
| : policy = policy,
|
| maxActiveSize = policy.maxActiveSize,
|
| maxIdleSize = policy.maxIdleSize,
|
| - maxSize = policy.maxIdleSize;
|
| + maxSize = policy.maxActiveSize;
|
|
|
| /**
|
| * If [currentSize] is already less than [maxSize], returns an empty list.
|
| @@ -824,6 +1039,12 @@ abstract class CachePartition {
|
| */
|
| final InternalAnalysisContext context;
|
|
|
| + /**
|
| + * A list of the caches that contain this partition. This includes the cache
|
| + * associated with the context that owns this partition.
|
| + */
|
| + final List<AnalysisCache> containingCaches = <AnalysisCache>[];
|
| +
|
| /**
|
| * A table mapping caching policies to the cache flush managers.
|
| */
|
| @@ -840,18 +1061,18 @@ abstract class CachePartition {
|
| * A table mapping the targets belonging to this partition to the information
|
| * known about those targets.
|
| */
|
| - HashMap<AnalysisTarget, CacheEntry> _targetMap =
|
| + final HashMap<AnalysisTarget, CacheEntry> entryMap =
|
| new HashMap<AnalysisTarget, CacheEntry>();
|
|
|
| /**
|
| * A set of the [Source] targets.
|
| */
|
| - final HashSet<Source> _sources = new HashSet<Source>();
|
| + final HashSet<Source> sources = new HashSet<Source>();
|
|
|
| /**
|
| * A table mapping full paths to lists of [Source]s with these full paths.
|
| */
|
| - final Map<String, List<Source>> _pathToSources = <String, List<Source>>{};
|
| + final Map<String, List<Source>> pathToSource = <String, List<Source>>{};
|
|
|
| /**
|
| * Initialize a newly created cache partition, belonging to the given
|
| @@ -860,36 +1081,52 @@ abstract class CachePartition {
|
| CachePartition(this.context);
|
|
|
| /**
|
| - * Return a table mapping the targets known to the context to the information
|
| - * known about the target.
|
| - *
|
| - * <b>Note:</b> This method is only visible for use by [AnalysisCache] and
|
| - * should not be used for any other purpose.
|
| + * Specify whether a context that uses this partition is being analyzed.
|
| */
|
| - Map<AnalysisTarget, CacheEntry> get map => _targetMap;
|
| + set isActive(bool active) {
|
| + for (CacheFlushManager manager in _flushManagerMap.values) {
|
| + if (active) {
|
| + manager.madeActive();
|
| + } else {
|
| + List<TargetedResult> resultsToFlush = manager.madeIdle();
|
| + _flushResults(resultsToFlush);
|
| + }
|
| + }
|
| + }
|
|
|
| /**
|
| * Notifies the partition that the client is going to stop using it.
|
| */
|
| void dispose() {
|
| - for (CacheEntry entry in _targetMap.values) {
|
| + for (CacheEntry entry in entryMap.values) {
|
| entry.dispose();
|
| }
|
| - _targetMap.clear();
|
| + entryMap.clear();
|
| + sources.clear();
|
| + pathToSource.clear();
|
| + }
|
| +
|
| + /**
|
| + * Flush results that satisfy the given [filter].
|
| + */
|
| + void flush(FlushResultFilter filter) {
|
| + for (CacheEntry entry in entryMap.values) {
|
| + entry.flush(filter);
|
| + }
|
| }
|
|
|
| /**
|
| * Return the entry associated with the given [target].
|
| */
|
| - CacheEntry get(AnalysisTarget target) => _targetMap[target];
|
| + CacheEntry get(AnalysisTarget target) => entryMap[target];
|
|
|
| /**
|
| * Return [Source]s whose full path is equal to the given [path].
|
| * Maybe empty, but not `null`.
|
| */
|
| List<Source> getSourcesWithFullName(String path) {
|
| - List<Source> sources = _pathToSources[path];
|
| - return sources != null ? sources : Source.EMPTY_LIST;
|
| + List<Source> sources = pathToSource[path];
|
| + return sources ?? Source.EMPTY_LIST;
|
| }
|
|
|
| /**
|
| @@ -902,7 +1139,7 @@ abstract class CachePartition {
|
| * cache entries.
|
| */
|
| MapIterator<AnalysisTarget, CacheEntry> iterator() =>
|
| - new SingleMapIterator<AnalysisTarget, CacheEntry>(_targetMap);
|
| + new SingleMapIterator<AnalysisTarget, CacheEntry>(entryMap);
|
|
|
| /**
|
| * Puts the given [entry] into the partition.
|
| @@ -915,7 +1152,7 @@ abstract class CachePartition {
|
| }
|
| entry._partition = this;
|
| entry.fixExceptionState();
|
| - _targetMap[target] = entry;
|
| + entryMap[target] = entry;
|
| _addIfSource(target);
|
| }
|
|
|
| @@ -928,7 +1165,7 @@ abstract class CachePartition {
|
| for (CacheFlushManager flushManager in _flushManagerMap.values) {
|
| flushManager.targetRemoved(target);
|
| }
|
| - CacheEntry entry = _targetMap.remove(target);
|
| + CacheEntry entry = entryMap.remove(target);
|
| if (entry != null) {
|
| entry._invalidateAll();
|
| }
|
| @@ -953,36 +1190,38 @@ abstract class CachePartition {
|
| CacheFlushManager flushManager = _getFlushManager(result.result);
|
| List<TargetedResult> resultsToFlush =
|
| flushManager.resultStored(result, value);
|
| - for (TargetedResult result in resultsToFlush) {
|
| - CacheEntry entry = get(result.target);
|
| - if (entry != null) {
|
| - ResultData data = entry._resultMap[result.result];
|
| - if (data != null) {
|
| - data.flush();
|
| - }
|
| - }
|
| - }
|
| + _flushResults(resultsToFlush);
|
| }
|
|
|
| /**
|
| * Return the number of targets that are mapped to cache entries.
|
| */
|
| - int size() => _targetMap.length;
|
| + int size() => entryMap.length;
|
|
|
| /**
|
| - * If the given [target] is a [Source], adds it to [_sources].
|
| + * If the given [target] is a [Source], adds it to [sources].
|
| */
|
| void _addIfSource(AnalysisTarget target) {
|
| if (target is Source) {
|
| - _sources.add(target);
|
| + sources.add(target);
|
| String fullName = target.fullName;
|
| - _pathToSources.putIfAbsent(fullName, () => <Source>[]).add(target);
|
| + pathToSource.putIfAbsent(fullName, () => <Source>[]).add(target);
|
| }
|
| }
|
|
|
| - ResultData _getDataFor(TargetedResult result) {
|
| - CacheEntry entry = context.analysisCache.get(result.target);
|
| - return entry != null ? entry._resultMap[result.result] : null;
|
| + /**
|
| + * Flush the given [resultsToFlush].
|
| + */
|
| + void _flushResults(List<TargetedResult> resultsToFlush) {
|
| + for (TargetedResult result in resultsToFlush) {
|
| + CacheEntry entry = get(result.target);
|
| + if (entry != null) {
|
| + ResultData data = entry._resultMap[result.result];
|
| + if (data != null) {
|
| + data.flush();
|
| + }
|
| + }
|
| + }
|
| }
|
|
|
| /**
|
| @@ -990,7 +1229,8 @@ abstract class CachePartition {
|
| */
|
| CacheFlushManager _getFlushManager(ResultDescriptor descriptor) {
|
| ResultCachingPolicy policy = descriptor.cachingPolicy;
|
| - if (identical(policy, DEFAULT_CACHING_POLICY)) {
|
| + if (identical(policy, DEFAULT_CACHING_POLICY) ||
|
| + context.analysisOptions.disableCacheFlushing) {
|
| return UnlimitedCacheFlushManager.INSTANCE;
|
| }
|
| CacheFlushManager manager = _flushManagerMap[policy];
|
| @@ -1002,21 +1242,22 @@ abstract class CachePartition {
|
| }
|
|
|
| bool _isPriorityAnalysisTarget(AnalysisTarget target) {
|
| - return context.priorityTargets.contains(target);
|
| + Source source = target.source;
|
| + return source != null && context.prioritySources.contains(source);
|
| }
|
|
|
| /**
|
| - * If the given [target] is a [Source], remove it from the list of [_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);
|
| + sources.remove(target);
|
| + String path = target.fullName;
|
| + List<Source> pathSources = pathToSource[path];
|
| + if (pathSources != null) {
|
| + pathSources.remove(target);
|
| + if (pathSources.isEmpty) {
|
| + pathToSource.remove(path);
|
| }
|
| }
|
| }
|
| @@ -1031,26 +1272,57 @@ class Delta {
|
|
|
| Delta(this.source);
|
|
|
| + /**
|
| + * Return `true` if this delta needs cache walking to gather additional
|
| + * changes before it can be used to [validate]. In this case [gatherChanges]
|
| + * is invoked for every targeted result in transitive dependencies, and
|
| + * [gatherEnd] is invoked after cache walking is done.
|
| + */
|
| + bool get shouldGatherChanges => false;
|
| +
|
| + /**
|
| + * This method is called during a cache walk, so that the delta can gather
|
| + * additional changes to which are caused by the changes it already knows
|
| + * about. Return `true` if a new change was added, so that one more cache
|
| + * walk will be performed (to include changes that depend on results which we
|
| + * decided to be changed later in the previous cache walk).
|
| + */
|
| + bool gatherChanges(InternalAnalysisContext context, AnalysisTarget target,
|
| + ResultDescriptor descriptor, Object value) {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * The current cache results visit is done.
|
| + */
|
| + void gatherEnd() {}
|
| +
|
| /**
|
| * Check whether this delta affects the result described by the given
|
| - * [descriptor] and [target].
|
| + * [descriptor] and [target]. The current [value] of the result is provided.
|
| */
|
| DeltaResult validate(InternalAnalysisContext context, AnalysisTarget target,
|
| - ResultDescriptor descriptor) {
|
| + ResultDescriptor descriptor, Object value) {
|
| return DeltaResult.INVALIDATE;
|
| }
|
| }
|
|
|
| /**
|
| - * The possible results of validating analysis results againt a [Delta].
|
| + * The possible results of validating analysis results against a [Delta].
|
| */
|
| enum DeltaResult {
|
| /**
|
| * Invalidate this result and continue visiting dependent results
|
| - * with this [Delta].
|
| + * with this [Delta]. Remove the result and all its dependencies.
|
| */
|
| INVALIDATE,
|
|
|
| + /**
|
| + * Invalidate this result and continue visiting dependent results
|
| + * with this [Delta]. Keep the dependencies of this result.
|
| + */
|
| + INVALIDATE_KEEP_DEPENDENCIES,
|
| +
|
| /**
|
| * Invalidate this result and stop using this [Delta], so unconditionally
|
| * invalidate all the dependent results.
|
| @@ -1072,7 +1344,7 @@ enum DeltaResult {
|
| /**
|
| * [InvalidatedResult] describes an invalidated result.
|
| */
|
| -class InvalidatedResult {
|
| +class InvalidatedResult<V> {
|
| /**
|
| * The target in which the result was invalidated.
|
| */
|
| @@ -1081,12 +1353,13 @@ class InvalidatedResult {
|
| /**
|
| * The descriptor of the result which was invalidated.
|
| */
|
| - final ResultDescriptor descriptor;
|
| + final ResultDescriptor<V> descriptor;
|
|
|
| /**
|
| - * The value of the result which was invalidated.
|
| + * The value of the result before it was invalidated, may be the default
|
| + * value if the result was flushed.
|
| */
|
| - final Object value;
|
| + final V value;
|
|
|
| InvalidatedResult(this.entry, this.descriptor, this.value);
|
|
|
| @@ -1094,6 +1367,29 @@ class InvalidatedResult {
|
| String toString() => '$descriptor of ${entry.target}';
|
| }
|
|
|
| +/**
|
| + * A cache partition that contains all of the targets in a single package.
|
| + */
|
| +class PackageCachePartition extends CachePartition {
|
| + /**
|
| + * The root of the directory representing the package.
|
| + */
|
| + final Folder packageRoot;
|
| +
|
| + /**
|
| + * Initialize a newly created cache partition, belonging to the given
|
| + * [context].
|
| + */
|
| + PackageCachePartition(InternalAnalysisContext context, this.packageRoot)
|
| + : super(context);
|
| +
|
| + @override
|
| + bool isResponsibleFor(AnalysisTarget target) {
|
| + Source source = target.source;
|
| + return source != null && packageRoot.contains(source.fullName);
|
| + }
|
| +}
|
| +
|
| /**
|
| * A Stream-like interface, which broadcasts events synchronously.
|
| * If a second event is fired while delivering a first event, then the second
|
| @@ -1118,8 +1414,27 @@ class ReentrantSynchronousStream<T> {
|
| * 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)) {
|
| + ReentrantSynchronousStreamSubscription<T> listen(void listener(T event)) {
|
| listeners.add(listener);
|
| + return new ReentrantSynchronousStreamSubscription<T>(this, listener);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A subscription on events from a [ReentrantSynchronousStream].
|
| + */
|
| +class ReentrantSynchronousStreamSubscription<T> {
|
| + final ReentrantSynchronousStream<T> _stream;
|
| + final Function _listener;
|
| +
|
| + ReentrantSynchronousStreamSubscription(this._stream, this._listener);
|
| +
|
| + /**
|
| + * Cancels this subscription.
|
| + * It will no longer receive events.
|
| + */
|
| + void cancel() {
|
| + _stream.listeners.remove(_listener);
|
| }
|
| }
|
|
|
| @@ -1146,11 +1461,10 @@ 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).
|
| + * The identifier of the most recent visiting process. We use it to visit
|
| + * every result only once.
|
| */
|
| - int invalidateId = -1;
|
| + int visitId = -1;
|
|
|
| /**
|
| * A list of the results on which this result depends.
|
|
|