OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library analyzer.src.context.cache; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 9 |
| 10 import 'package:analyzer/src/generated/engine.dart' |
| 11 show AnalysisEngine, CacheState, InternalAnalysisContext, RetentionPriority; |
| 12 import 'package:analyzer/src/generated/java_engine.dart'; |
| 13 import 'package:analyzer/src/generated/source.dart'; |
| 14 import 'package:analyzer/src/generated/utilities_collection.dart'; |
| 15 import 'package:analyzer/src/task/model.dart'; |
| 16 import 'package:analyzer/task/model.dart'; |
| 17 |
| 18 /** |
| 19 * Return `true` if the given [target] is a priority one. |
| 20 */ |
| 21 typedef bool IsPriorityAnalysisTarget(AnalysisTarget target); |
| 22 |
| 23 /** |
| 24 * An LRU cache of results produced by analysis. |
| 25 */ |
| 26 class AnalysisCache { |
| 27 /** |
| 28 * A flag used to control whether trace information should be produced when |
| 29 * the content of the cache is modified. |
| 30 */ |
| 31 static bool _TRACE_CHANGES = false; |
| 32 |
| 33 /** |
| 34 * An array containing the partitions of which this cache is comprised. |
| 35 */ |
| 36 final List<CachePartition> _partitions; |
| 37 |
| 38 /** |
| 39 * The [StreamController] reporting [InvalidatedResult]s. |
| 40 */ |
| 41 final StreamController<InvalidatedResult> _onResultInvalidated = |
| 42 new StreamController<InvalidatedResult>.broadcast(sync: true); |
| 43 |
| 44 /** |
| 45 * Initialize a newly created cache to have the given [partitions]. The |
| 46 * partitions will be searched in the order in which they appear in the array, |
| 47 * so the most specific partition (usually an [SdkCachePartition]) should be |
| 48 * first and the most general (usually a [UniversalCachePartition]) last. |
| 49 */ |
| 50 AnalysisCache(this._partitions) { |
| 51 for (CachePartition partition in _partitions) { |
| 52 partition.onResultInvalidated.listen((InvalidatedResult event) { |
| 53 _onResultInvalidated.add(event); |
| 54 }); |
| 55 } |
| 56 } |
| 57 |
| 58 /** |
| 59 * Return the stream that is notified when a value is invalidated. |
| 60 */ |
| 61 Stream<InvalidatedResult> get onResultInvalidated => |
| 62 _onResultInvalidated.stream; |
| 63 |
| 64 // TODO(brianwilkerson) Implement or delete this. |
| 65 // /** |
| 66 // * Return information about each of the partitions in this cache. |
| 67 // */ |
| 68 // List<AnalysisContextStatistics_PartitionData> get partitionData { |
| 69 // int count = _partitions.length; |
| 70 // List<AnalysisContextStatistics_PartitionData> data = |
| 71 // new List<AnalysisContextStatistics_PartitionData>(count); |
| 72 // for (int i = 0; i < count; i++) { |
| 73 // CachePartition partition = _partitions[i]; |
| 74 // data[i] = new AnalysisContextStatisticsImpl_PartitionDataImpl( |
| 75 // partition.astSize, |
| 76 // partition.map.length); |
| 77 // } |
| 78 // return data; |
| 79 // } |
| 80 |
| 81 /** |
| 82 * Return an iterator returning all of the [Source] targets. |
| 83 */ |
| 84 Iterable<Source> get sources { |
| 85 return _partitions |
| 86 .map((CachePartition partition) => partition._sources) |
| 87 .expand((Iterable<Source> sources) => sources); |
| 88 } |
| 89 |
| 90 /** |
| 91 * Return the entry associated with the given [target]. |
| 92 */ |
| 93 CacheEntry get(AnalysisTarget target) { |
| 94 int count = _partitions.length; |
| 95 for (int i = 0; i < count; i++) { |
| 96 CachePartition partition = _partitions[i]; |
| 97 if (partition.isResponsibleFor(target)) { |
| 98 return partition.get(target); |
| 99 } |
| 100 } |
| 101 // |
| 102 // We should never get to this point because the last partition should |
| 103 // always be a universal partition, except in the case of the SDK context, |
| 104 // in which case the target should always be part of the SDK. |
| 105 // |
| 106 return null; |
| 107 } |
| 108 |
| 109 /** |
| 110 * Return the context to which the given [target] was explicitly added. |
| 111 */ |
| 112 InternalAnalysisContext getContextFor(AnalysisTarget target) { |
| 113 int count = _partitions.length; |
| 114 for (int i = 0; i < count; i++) { |
| 115 CachePartition partition = _partitions[i]; |
| 116 if (partition.isResponsibleFor(target)) { |
| 117 return partition.context; |
| 118 } |
| 119 } |
| 120 // |
| 121 // We should never get to this point because the last partition should |
| 122 // always be a universal partition, except in the case of the SDK context, |
| 123 // in which case the target should always be part of the SDK. |
| 124 // |
| 125 // TODO(brianwilkerson) Throw an exception here. |
| 126 AnalysisEngine.instance.logger.logInformation( |
| 127 'Could not find context for $target', |
| 128 new CaughtException(new AnalysisException(), null)); |
| 129 return null; |
| 130 } |
| 131 |
| 132 /** |
| 133 * Return [Source]s whose full path is equal to the given [path]. |
| 134 * Maybe empty, but not `null`. |
| 135 */ |
| 136 List<Source> getSourcesWithFullName(String path) { |
| 137 List<Source> sources = <Source>[]; |
| 138 for (CachePartition partition in _partitions) { |
| 139 List<Source> partitionSources = partition.getSourcesWithFullName(path); |
| 140 sources.addAll(partitionSources); |
| 141 } |
| 142 return sources; |
| 143 } |
| 144 |
| 145 /** |
| 146 * Return the state of the given [result] for the given [target]. |
| 147 * |
| 148 * It does not update the cache, if the corresponding [CacheEntry] does not |
| 149 * exist, then [CacheState.INVALID] is returned. |
| 150 */ |
| 151 CacheState getState(AnalysisTarget target, ResultDescriptor result) { |
| 152 CacheEntry entry = get(target); |
| 153 if (entry == null) { |
| 154 return CacheState.INVALID; |
| 155 } |
| 156 return entry.getState(result); |
| 157 } |
| 158 |
| 159 /** |
| 160 * Return the value of the given [result] for the given [target]. |
| 161 * |
| 162 * It does not update the cache, if the corresponding [CacheEntry] does not |
| 163 * exist, then the default value is returned. |
| 164 */ |
| 165 Object getValue(AnalysisTarget target, ResultDescriptor result) { |
| 166 CacheEntry entry = get(target); |
| 167 if (entry == null) { |
| 168 return result.defaultValue; |
| 169 } |
| 170 return entry.getValue(result); |
| 171 } |
| 172 |
| 173 /** |
| 174 * Return an iterator returning all of the map entries mapping targets to |
| 175 * cache entries. |
| 176 */ |
| 177 MapIterator<AnalysisTarget, CacheEntry> iterator() { |
| 178 int count = _partitions.length; |
| 179 List<Map<AnalysisTarget, CacheEntry>> maps = |
| 180 new List<Map<AnalysisTarget, CacheEntry>>(count); |
| 181 for (int i = 0; i < count; i++) { |
| 182 maps[i] = _partitions[i].map; |
| 183 } |
| 184 return new MultipleMapIterator<AnalysisTarget, CacheEntry>(maps); |
| 185 } |
| 186 |
| 187 /** |
| 188 * Puts the given [entry] into the cache. |
| 189 */ |
| 190 void put(CacheEntry entry) { |
| 191 AnalysisTarget target = entry.target; |
| 192 entry.fixExceptionState(); |
| 193 int count = _partitions.length; |
| 194 for (int i = 0; i < count; i++) { |
| 195 CachePartition partition = _partitions[i]; |
| 196 if (partition.isResponsibleFor(target)) { |
| 197 if (_TRACE_CHANGES) { |
| 198 CacheEntry oldEntry = partition.get(target); |
| 199 if (oldEntry == null) { |
| 200 AnalysisEngine.instance.logger |
| 201 .logInformation('Added a cache entry for $target.'); |
| 202 } else { |
| 203 AnalysisEngine.instance.logger |
| 204 .logInformation('Modified the cache entry for $target.'); |
| 205 // 'Diff = ${entry.getDiff(oldEntry)}'); |
| 206 } |
| 207 } |
| 208 partition.put(entry); |
| 209 return; |
| 210 } |
| 211 } |
| 212 // TODO(brianwilkerson) Handle the case where no partition was found, |
| 213 // possibly by throwing an exception. |
| 214 } |
| 215 |
| 216 /** |
| 217 * Remove all information related to the given [target] from this cache. |
| 218 * Return the entry associated with the target, or `null` if there was cache |
| 219 * entry for the target. |
| 220 */ |
| 221 CacheEntry remove(AnalysisTarget target) { |
| 222 int count = _partitions.length; |
| 223 for (int i = 0; i < count; i++) { |
| 224 CachePartition partition = _partitions[i]; |
| 225 if (partition.isResponsibleFor(target)) { |
| 226 if (_TRACE_CHANGES) { |
| 227 AnalysisEngine.instance.logger |
| 228 .logInformation('Removed the cache entry for $target.'); |
| 229 } |
| 230 return partition.remove(target); |
| 231 } |
| 232 } |
| 233 return null; |
| 234 } |
| 235 |
| 236 /** |
| 237 * Return the number of targets that are mapped to cache entries. |
| 238 */ |
| 239 int size() { |
| 240 int size = 0; |
| 241 int count = _partitions.length; |
| 242 for (int i = 0; i < count; i++) { |
| 243 size += _partitions[i].size(); |
| 244 } |
| 245 return size; |
| 246 } |
| 247 } |
| 248 |
| 249 /** |
| 250 * The information cached by an analysis context about an individual target. |
| 251 */ |
| 252 class CacheEntry { |
| 253 /** |
| 254 * The index of the flag indicating whether the source was explicitly added to |
| 255 * the context or whether the source was implicitly added because it was |
| 256 * referenced by another source. |
| 257 */ |
| 258 static int _EXPLICITLY_ADDED_FLAG = 0; |
| 259 |
| 260 /** |
| 261 * The target this entry is about. |
| 262 */ |
| 263 final AnalysisTarget target; |
| 264 |
| 265 /** |
| 266 * The partition that is responsible for this entry. |
| 267 */ |
| 268 CachePartition _partition; |
| 269 |
| 270 /** |
| 271 * The most recent time at which the state of the target matched the state |
| 272 * represented by this entry, `-1` if the target does not exist. |
| 273 */ |
| 274 int modificationTime = -1; |
| 275 |
| 276 /** |
| 277 * The exception that caused one or more values to have a state of |
| 278 * [CacheState.ERROR]. |
| 279 */ |
| 280 CaughtException _exception; |
| 281 |
| 282 /** |
| 283 * A bit-encoding of boolean flags associated with this entry's target. |
| 284 */ |
| 285 int _flags = 0; |
| 286 |
| 287 /** |
| 288 * A table mapping result descriptors to the cached values of those results. |
| 289 */ |
| 290 Map<ResultDescriptor, ResultData> _resultMap = |
| 291 new HashMap<ResultDescriptor, ResultData>(); |
| 292 |
| 293 CacheEntry(this.target); |
| 294 |
| 295 /** |
| 296 * The exception that caused one or more values to have a state of |
| 297 * [CacheState.ERROR]. |
| 298 */ |
| 299 CaughtException get exception => _exception; |
| 300 |
| 301 /** |
| 302 * Return `true` if the source was explicitly added to the context or `false` |
| 303 * if the source was implicitly added because it was referenced by another |
| 304 * source. |
| 305 */ |
| 306 bool get explicitlyAdded => _getFlag(_EXPLICITLY_ADDED_FLAG); |
| 307 |
| 308 /** |
| 309 * Set whether the source was explicitly added to the context to match the |
| 310 * [explicitlyAdded] flag. |
| 311 */ |
| 312 void set explicitlyAdded(bool explicitlyAdded) { |
| 313 _setFlag(_EXPLICITLY_ADDED_FLAG, explicitlyAdded); |
| 314 } |
| 315 |
| 316 /** |
| 317 * Return a list of result descriptors for results whose state is not |
| 318 * [CacheState.INVALID]. |
| 319 */ |
| 320 List<ResultDescriptor> get nonInvalidResults => _resultMap.keys.toList(); |
| 321 |
| 322 /** |
| 323 * Notifies the entry that the client is going to stop using it. |
| 324 */ |
| 325 void dispose() { |
| 326 _resultMap.forEach((descriptor, data) { |
| 327 TargetedResult result = new TargetedResult(target, descriptor); |
| 328 for (TargetedResult dependedOnResult in data.dependedOnResults) { |
| 329 ResultData dependedOnData = _partition._getDataFor(dependedOnResult); |
| 330 if (dependedOnData != null) { |
| 331 dependedOnData.dependentResults.remove(result); |
| 332 } |
| 333 } |
| 334 }); |
| 335 _resultMap.clear(); |
| 336 } |
| 337 |
| 338 /** |
| 339 * Fix the state of the [exception] to match the current state of the entry. |
| 340 */ |
| 341 void fixExceptionState() { |
| 342 if (!hasErrorState()) { |
| 343 _exception = null; |
| 344 } |
| 345 } |
| 346 |
| 347 /** |
| 348 * Look up the [ResultData] of [descriptor], or add a new one if it isn't |
| 349 * there. |
| 350 */ |
| 351 ResultData getResultData(ResultDescriptor descriptor) { |
| 352 return _resultMap.putIfAbsent(descriptor, () => new ResultData(descriptor)); |
| 353 } |
| 354 |
| 355 /** |
| 356 * Return the state of the result represented by the given [descriptor]. |
| 357 */ |
| 358 CacheState getState(ResultDescriptor descriptor) { |
| 359 ResultData data = _resultMap[descriptor]; |
| 360 if (data == null) { |
| 361 return CacheState.INVALID; |
| 362 } |
| 363 return data.state; |
| 364 } |
| 365 |
| 366 /** |
| 367 * Return the value of the result represented by the given [descriptor], or |
| 368 * the default value for the result if this entry does not have a valid value. |
| 369 */ |
| 370 /*<V>*/ dynamic /*V*/ getValue(ResultDescriptor /*<V>*/ descriptor) { |
| 371 ResultData data = _resultMap[descriptor]; |
| 372 if (data == null) { |
| 373 return descriptor.defaultValue; |
| 374 } |
| 375 if (_partition != null) { |
| 376 _partition.resultAccessed(target, descriptor); |
| 377 } |
| 378 return data.value; |
| 379 } |
| 380 |
| 381 /** |
| 382 * Return `true` if the state of any data value is [CacheState.ERROR]. |
| 383 */ |
| 384 bool hasErrorState() { |
| 385 for (ResultData data in _resultMap.values) { |
| 386 if (data.state == CacheState.ERROR) { |
| 387 return true; |
| 388 } |
| 389 } |
| 390 return false; |
| 391 } |
| 392 |
| 393 /** |
| 394 * Invalidate all of the information associated with this entry's target. |
| 395 */ |
| 396 void invalidateAllInformation() { |
| 397 _resultMap.clear(); |
| 398 _exception = null; |
| 399 } |
| 400 |
| 401 /** |
| 402 * Return `true` if the state of the result represented by the given |
| 403 * [descriptor] is [CacheState.INVALID]. |
| 404 */ |
| 405 bool isInvalid(ResultDescriptor descriptor) => |
| 406 getState(descriptor) == CacheState.INVALID; |
| 407 |
| 408 /** |
| 409 * Return `true` if the state of the result represented by the given |
| 410 * [descriptor] is [CacheState.VALID]. |
| 411 */ |
| 412 bool isValid(ResultDescriptor descriptor) => |
| 413 getState(descriptor) == CacheState.VALID; |
| 414 |
| 415 /** |
| 416 * For each of the given [descriptors], set their states to |
| 417 * [CacheState.ERROR], their values to the corresponding default values, and |
| 418 * remember the [exception] that caused this state. |
| 419 */ |
| 420 void setErrorState( |
| 421 CaughtException exception, List<ResultDescriptor> descriptors) { |
| 422 if (descriptors == null || descriptors.isEmpty) { |
| 423 throw new ArgumentError('at least one descriptor is expected'); |
| 424 } |
| 425 if (exception == null) { |
| 426 throw new ArgumentError('an exception is expected'); |
| 427 } |
| 428 this._exception = exception; |
| 429 for (ResultDescriptor descriptor in descriptors) { |
| 430 _setErrorState(descriptor, exception); |
| 431 } |
| 432 } |
| 433 |
| 434 /** |
| 435 * Set the state of the result represented by the given [descriptor] to the |
| 436 * given [state]. |
| 437 */ |
| 438 void setState(ResultDescriptor descriptor, CacheState state, {Delta delta}) { |
| 439 if (state == CacheState.ERROR) { |
| 440 throw new ArgumentError('use setErrorState() to set the state to ERROR'); |
| 441 } |
| 442 if (state == CacheState.VALID) { |
| 443 throw new ArgumentError('use setValue() to set the state to VALID'); |
| 444 } |
| 445 _validateStateChange(descriptor, state); |
| 446 if (state == CacheState.INVALID) { |
| 447 ResultData data = _resultMap[descriptor]; |
| 448 if (data != null) { |
| 449 _invalidate(descriptor, delta); |
| 450 } |
| 451 } else { |
| 452 ResultData data = getResultData(descriptor); |
| 453 data.state = state; |
| 454 if (state != CacheState.IN_PROCESS) { |
| 455 // |
| 456 // If the state is in-process, we can leave the current value in the |
| 457 // cache for any 'get' methods to access. |
| 458 // |
| 459 data.value = descriptor.defaultValue; |
| 460 } |
| 461 } |
| 462 } |
| 463 |
| 464 /** |
| 465 * Set the value of the result represented by the given [descriptor] to the |
| 466 * given [value]. |
| 467 */ |
| 468 /*<V>*/ void setValue(ResultDescriptor /*<V>*/ descriptor, dynamic /*V*/ |
| 469 value, List<TargetedResult> dependedOn) { |
| 470 // { |
| 471 // String valueStr = '$value'; |
| 472 // if (valueStr.length > 20) { |
| 473 // valueStr = valueStr.substring(0, 20) + '...'; |
| 474 // } |
| 475 // valueStr = valueStr.replaceAll('\n', '\\n'); |
| 476 // print( |
| 477 // 'setValue $descriptor for $target value=$valueStr deps=$dependedOn')
; |
| 478 // } |
| 479 _validateStateChange(descriptor, CacheState.VALID); |
| 480 TargetedResult thisResult = new TargetedResult(target, descriptor); |
| 481 if (_partition != null) { |
| 482 _partition.resultStored(thisResult, value); |
| 483 } |
| 484 ResultData data = getResultData(descriptor); |
| 485 _setDependedOnResults(data, thisResult, dependedOn); |
| 486 data.state = CacheState.VALID; |
| 487 data.value = value == null ? descriptor.defaultValue : value; |
| 488 } |
| 489 |
| 490 /** |
| 491 * Set the value of the result represented by the given [descriptor] to the |
| 492 * given [value], keep its dependency, invalidate all the dependent result. |
| 493 */ |
| 494 void setValueIncremental(ResultDescriptor descriptor, dynamic value) { |
| 495 ResultData data = getResultData(descriptor); |
| 496 List<TargetedResult> dependedOn = data.dependedOnResults; |
| 497 _invalidate(descriptor, null); |
| 498 setValue(descriptor, value, dependedOn); |
| 499 } |
| 500 |
| 501 @override |
| 502 String toString() { |
| 503 StringBuffer buffer = new StringBuffer(); |
| 504 _writeOn(buffer); |
| 505 return buffer.toString(); |
| 506 } |
| 507 |
| 508 /** |
| 509 * Return the value of the flag with the given [index]. |
| 510 */ |
| 511 bool _getFlag(int index) => BooleanArray.get(_flags, index); |
| 512 |
| 513 /** |
| 514 * Invalidate the result represented by the given [descriptor] and propagate |
| 515 * invalidation to other results that depend on it. |
| 516 */ |
| 517 void _invalidate(ResultDescriptor descriptor, Delta delta) { |
| 518 DeltaResult deltaResult = null; |
| 519 if (delta != null) { |
| 520 deltaResult = delta.validate(_partition.context, target, descriptor); |
| 521 if (deltaResult == DeltaResult.STOP) { |
| 522 // print('not-invalidate $descriptor for $target'); |
| 523 return; |
| 524 } |
| 525 } |
| 526 // print('invalidate $descriptor for $target'); |
| 527 ResultData thisData; |
| 528 if (deltaResult == null || deltaResult == DeltaResult.INVALIDATE) { |
| 529 thisData = _resultMap.remove(descriptor); |
| 530 } |
| 531 if (deltaResult == DeltaResult.KEEP_CONTINUE) { |
| 532 thisData = _resultMap[descriptor]; |
| 533 } |
| 534 if (thisData == null) { |
| 535 return; |
| 536 } |
| 537 // Stop depending on other results. |
| 538 TargetedResult thisResult = new TargetedResult(target, descriptor); |
| 539 for (TargetedResult dependedOnResult in thisData.dependedOnResults) { |
| 540 ResultData data = _partition._getDataFor(dependedOnResult); |
| 541 if (data != null) { |
| 542 data.dependentResults.remove(thisResult); |
| 543 } |
| 544 } |
| 545 // Invalidate results that depend on this result. |
| 546 List<TargetedResult> dependentResults = thisData.dependentResults.toList(); |
| 547 for (TargetedResult dependentResult in dependentResults) { |
| 548 CacheEntry entry = _partition.get(dependentResult.target); |
| 549 if (entry != null) { |
| 550 entry._invalidate(dependentResult.result, delta); |
| 551 } |
| 552 } |
| 553 // If empty, remove the entry altogether. |
| 554 if (_resultMap.isEmpty) { |
| 555 _partition._targetMap.remove(target); |
| 556 _partition._removeIfSource(target); |
| 557 } |
| 558 // Notify controller. |
| 559 _partition._onResultInvalidated |
| 560 .add(new InvalidatedResult(this, descriptor)); |
| 561 } |
| 562 |
| 563 /** |
| 564 * Invalidates all the results of this entry, with propagation. |
| 565 */ |
| 566 void _invalidateAll() { |
| 567 List<ResultDescriptor> results = _resultMap.keys.toList(); |
| 568 for (ResultDescriptor result in results) { |
| 569 _invalidate(result, null); |
| 570 } |
| 571 } |
| 572 |
| 573 /** |
| 574 * Set the [dependedOn] on which this result depends. |
| 575 */ |
| 576 void _setDependedOnResults(ResultData thisData, TargetedResult thisResult, |
| 577 List<TargetedResult> dependedOn) { |
| 578 thisData.dependedOnResults.forEach((TargetedResult dependedOnResult) { |
| 579 ResultData data = _partition._getDataFor(dependedOnResult); |
| 580 if (data != null) { |
| 581 data.dependentResults.remove(thisResult); |
| 582 } |
| 583 }); |
| 584 thisData.dependedOnResults = dependedOn; |
| 585 thisData.dependedOnResults.forEach((TargetedResult dependedOnResult) { |
| 586 ResultData data = _partition._getDataFor(dependedOnResult); |
| 587 if (data != null) { |
| 588 data.dependentResults.add(thisResult); |
| 589 } |
| 590 }); |
| 591 } |
| 592 |
| 593 /** |
| 594 * Set states of the given and dependent results to [CacheState.ERROR] and |
| 595 * their values to the corresponding default values |
| 596 */ |
| 597 void _setErrorState(ResultDescriptor descriptor, CaughtException exception) { |
| 598 ResultData thisData = getResultData(descriptor); |
| 599 // Set the error state. |
| 600 _exception = exception; |
| 601 thisData.state = CacheState.ERROR; |
| 602 thisData.value = descriptor.defaultValue; |
| 603 // Propagate the error state. |
| 604 thisData.dependentResults.forEach((TargetedResult dependentResult) { |
| 605 CacheEntry entry = _partition.get(dependentResult.target); |
| 606 entry._setErrorState(dependentResult.result, exception); |
| 607 }); |
| 608 } |
| 609 |
| 610 /** |
| 611 * Set the value of the flag with the given [index] to the given [value]. |
| 612 */ |
| 613 void _setFlag(int index, bool value) { |
| 614 _flags = BooleanArray.set(_flags, index, value); |
| 615 } |
| 616 |
| 617 /** |
| 618 * If the state of the value described by the given [descriptor] is changing |
| 619 * from ERROR to anything else, capture the information. This is an attempt to |
| 620 * discover the underlying cause of a long-standing bug. |
| 621 */ |
| 622 void _validateStateChange(ResultDescriptor descriptor, CacheState newState) { |
| 623 // TODO(brianwilkerson) Decide whether we still want to capture this data. |
| 624 // if (descriptor != CONTENT) { |
| 625 // return; |
| 626 // } |
| 627 // ResultData data = resultMap[CONTENT]; |
| 628 // if (data != null && data.state == CacheState.ERROR) { |
| 629 // String message = |
| 630 // 'contentState changing from ${data.state} to $newState'; |
| 631 // InstrumentationBuilder builder = |
| 632 // Instrumentation.builder2('CacheEntry-validateStateChange'); |
| 633 // builder.data3('message', message); |
| 634 // //builder.data('source', source.getFullName()); |
| 635 // builder.record(new CaughtException(new AnalysisException(message), null)
); |
| 636 // builder.log(); |
| 637 // } |
| 638 } |
| 639 |
| 640 /** |
| 641 * Write a textual representation of this entry to the given [buffer]. The |
| 642 * result should only be used for debugging purposes. |
| 643 */ |
| 644 void _writeOn(StringBuffer buffer) { |
| 645 buffer.write('time = '); |
| 646 buffer.write(modificationTime); |
| 647 List<ResultDescriptor> results = _resultMap.keys.toList(); |
| 648 results.sort((ResultDescriptor first, ResultDescriptor second) => |
| 649 first.toString().compareTo(second.toString())); |
| 650 for (ResultDescriptor result in results) { |
| 651 ResultData data = _resultMap[result]; |
| 652 buffer.write('; '); |
| 653 buffer.write(result.toString()); |
| 654 buffer.write(' = '); |
| 655 buffer.write(data.state); |
| 656 } |
| 657 } |
| 658 } |
| 659 |
| 660 /** |
| 661 * An object that controls flushing of analysis results from the cache. |
| 662 */ |
| 663 class CacheFlushManager<T> { |
| 664 final IsPriorityAnalysisTarget isPriorityAnalysisTarget; |
| 665 final ResultCachingPolicy<T> policy; |
| 666 final int maxActiveSize; |
| 667 final int maxIdleSize; |
| 668 |
| 669 /** |
| 670 * A map of the stored [TargetedResult] to their sizes. |
| 671 */ |
| 672 final HashMap<TargetedResult, int> resultSizeMap = |
| 673 new HashMap<TargetedResult, int>(); |
| 674 |
| 675 /** |
| 676 * A linked set containing the most recently accessed results with the most |
| 677 * recently used at the end of the list. When more results are added than the |
| 678 * maximum size allowed then the least recently used results will be flushed |
| 679 * from the cache. |
| 680 */ |
| 681 final LinkedHashSet<TargetedResult> recentlyUsed = |
| 682 new LinkedHashSet<TargetedResult>(); |
| 683 |
| 684 /** |
| 685 * The current size of stored results. |
| 686 */ |
| 687 int currentSize = 0; |
| 688 |
| 689 /** |
| 690 * The current maximum cache size. |
| 691 */ |
| 692 int maxSize; |
| 693 |
| 694 CacheFlushManager( |
| 695 ResultCachingPolicy<T> policy, this.isPriorityAnalysisTarget) |
| 696 : policy = policy, |
| 697 maxActiveSize = policy.maxActiveSize, |
| 698 maxIdleSize = policy.maxIdleSize, |
| 699 maxSize = policy.maxIdleSize; |
| 700 |
| 701 /** |
| 702 * If [currentSize] is already less than [maxSize], returns an empty list. |
| 703 * Otherwise returns [TargetedResult]s to flush from the cache to make |
| 704 * [currentSize] less or equal to [maxSize]. |
| 705 * |
| 706 * Results for priority files are never flushed, so this method might leave |
| 707 * [currentSize] greater than [maxSize]. |
| 708 */ |
| 709 List<TargetedResult> flushToSize() { |
| 710 // If still under the cap, done. |
| 711 if (currentSize <= maxSize) { |
| 712 return TargetedResult.EMPTY_LIST; |
| 713 } |
| 714 // Flush results until we are under the cap. |
| 715 List<TargetedResult> resultsToFlush = <TargetedResult>[]; |
| 716 for (TargetedResult result in recentlyUsed) { |
| 717 if (isPriorityAnalysisTarget(result.target)) { |
| 718 continue; |
| 719 } |
| 720 resultsToFlush.add(result); |
| 721 int size = resultSizeMap.remove(result); |
| 722 assert(size != null); |
| 723 currentSize -= size; |
| 724 if (currentSize <= maxSize) { |
| 725 break; |
| 726 } |
| 727 } |
| 728 recentlyUsed.removeAll(resultsToFlush); |
| 729 return resultsToFlush; |
| 730 } |
| 731 |
| 732 /** |
| 733 * Notifies this manager that the corresponding analysis context is active. |
| 734 */ |
| 735 void madeActive() { |
| 736 maxSize = maxActiveSize; |
| 737 } |
| 738 |
| 739 /** |
| 740 * Notifies this manager that the corresponding analysis context is idle. |
| 741 * Returns [TargetedResult]s that should be flushed from the cache. |
| 742 */ |
| 743 List<TargetedResult> madeIdle() { |
| 744 maxSize = maxIdleSize; |
| 745 return flushToSize(); |
| 746 } |
| 747 |
| 748 /** |
| 749 * Records that the given [result] was just read from the cache. |
| 750 */ |
| 751 void resultAccessed(TargetedResult result) { |
| 752 if (recentlyUsed.remove(result)) { |
| 753 recentlyUsed.add(result); |
| 754 } |
| 755 } |
| 756 |
| 757 /** |
| 758 * Records that the given [newResult] and [newValue] were stored to the cache. |
| 759 * Returns [TargetedResult]s that should be flushed from the cache. |
| 760 */ |
| 761 List<TargetedResult> resultStored(TargetedResult newResult, T newValue) { |
| 762 if (!recentlyUsed.remove(newResult)) { |
| 763 int size = policy.measure(newValue); |
| 764 resultSizeMap[newResult] = size; |
| 765 currentSize += size; |
| 766 } |
| 767 recentlyUsed.add(newResult); |
| 768 return flushToSize(); |
| 769 } |
| 770 |
| 771 /** |
| 772 * Records that the given [target] was just removed from to the cache. |
| 773 */ |
| 774 void targetRemoved(AnalysisTarget target) { |
| 775 List<TargetedResult> resultsToRemove = <TargetedResult>[]; |
| 776 for (TargetedResult result in recentlyUsed) { |
| 777 if (result.target == target) { |
| 778 resultsToRemove.add(result); |
| 779 int size = resultSizeMap.remove(result); |
| 780 assert(size != null); |
| 781 currentSize -= size; |
| 782 } |
| 783 } |
| 784 recentlyUsed.removeAll(resultsToRemove); |
| 785 } |
| 786 } |
| 787 |
| 788 /** |
| 789 * A single partition in an LRU cache of information related to analysis. |
| 790 */ |
| 791 abstract class CachePartition { |
| 792 /** |
| 793 * The context that owns this partition. Multiple contexts can reference a |
| 794 * partition, but only one context can own it. |
| 795 */ |
| 796 final InternalAnalysisContext context; |
| 797 |
| 798 /** |
| 799 * A table mapping caching policies to the cache flush managers. |
| 800 */ |
| 801 final HashMap<ResultCachingPolicy, CacheFlushManager> _flushManagerMap = |
| 802 new HashMap<ResultCachingPolicy, CacheFlushManager>(); |
| 803 |
| 804 /** |
| 805 * The [StreamController] reporting [InvalidatedResult]s. |
| 806 */ |
| 807 final StreamController<InvalidatedResult> _onResultInvalidated = |
| 808 new StreamController<InvalidatedResult>.broadcast(sync: true); |
| 809 |
| 810 /** |
| 811 * A table mapping the targets belonging to this partition to the information |
| 812 * known about those targets. |
| 813 */ |
| 814 HashMap<AnalysisTarget, CacheEntry> _targetMap = |
| 815 new HashMap<AnalysisTarget, CacheEntry>(); |
| 816 |
| 817 /** |
| 818 * A set of the [Source] targets. |
| 819 */ |
| 820 final HashSet<Source> _sources = new HashSet<Source>(); |
| 821 |
| 822 /** |
| 823 * A table mapping full paths to lists of [Source]s with these full paths. |
| 824 */ |
| 825 final Map<String, List<Source>> _pathToSources = <String, List<Source>>{}; |
| 826 |
| 827 /** |
| 828 * Initialize a newly created cache partition, belonging to the given |
| 829 * [context]. |
| 830 */ |
| 831 CachePartition(this.context); |
| 832 |
| 833 /** |
| 834 * Return a table mapping the targets known to the context to the information |
| 835 * known about the target. |
| 836 * |
| 837 * <b>Note:</b> This method is only visible for use by [AnalysisCache] and |
| 838 * should not be used for any other purpose. |
| 839 */ |
| 840 Map<AnalysisTarget, CacheEntry> get map => _targetMap; |
| 841 |
| 842 /** |
| 843 * Return the stream that is notified when a value is invalidated. |
| 844 */ |
| 845 Stream<InvalidatedResult> get onResultInvalidated => |
| 846 _onResultInvalidated.stream; |
| 847 |
| 848 /** |
| 849 * Notifies the partition that the client is going to stop using it. |
| 850 */ |
| 851 void dispose() { |
| 852 for (CacheEntry entry in _targetMap.values) { |
| 853 entry.dispose(); |
| 854 } |
| 855 _targetMap.clear(); |
| 856 } |
| 857 |
| 858 /** |
| 859 * Return the entry associated with the given [target]. |
| 860 */ |
| 861 CacheEntry get(AnalysisTarget target) => _targetMap[target]; |
| 862 |
| 863 /** |
| 864 * Return [Source]s whose full path is equal to the given [path]. |
| 865 * Maybe empty, but not `null`. |
| 866 */ |
| 867 List<Source> getSourcesWithFullName(String path) { |
| 868 List<Source> sources = _pathToSources[path]; |
| 869 return sources != null ? sources : Source.EMPTY_LIST; |
| 870 } |
| 871 |
| 872 /** |
| 873 * Return `true` if this partition is responsible for the given [target]. |
| 874 */ |
| 875 bool isResponsibleFor(AnalysisTarget target); |
| 876 |
| 877 /** |
| 878 * Return an iterator returning all of the map entries mapping targets to |
| 879 * cache entries. |
| 880 */ |
| 881 MapIterator<AnalysisTarget, CacheEntry> iterator() => |
| 882 new SingleMapIterator<AnalysisTarget, CacheEntry>(_targetMap); |
| 883 |
| 884 /** |
| 885 * Puts the given [entry] into the partition. |
| 886 */ |
| 887 void put(CacheEntry entry) { |
| 888 AnalysisTarget target = entry.target; |
| 889 if (entry._partition != null) { |
| 890 throw new StateError( |
| 891 'The entry for $target is already in ${entry._partition}'); |
| 892 } |
| 893 entry._partition = this; |
| 894 entry.fixExceptionState(); |
| 895 _targetMap[target] = entry; |
| 896 _addIfSource(target); |
| 897 } |
| 898 |
| 899 /** |
| 900 * Remove all information related to the given [target] from this partition. |
| 901 * Return the entry associated with the target, or `null` if there was cache |
| 902 * entry for the target. |
| 903 */ |
| 904 CacheEntry remove(AnalysisTarget target) { |
| 905 for (CacheFlushManager flushManager in _flushManagerMap.values) { |
| 906 flushManager.targetRemoved(target); |
| 907 } |
| 908 CacheEntry entry = _targetMap.remove(target); |
| 909 if (entry != null) { |
| 910 entry._invalidateAll(); |
| 911 } |
| 912 _removeIfSource(target); |
| 913 return entry; |
| 914 } |
| 915 |
| 916 /** |
| 917 * Records that a value of the result described by the given [descriptor] |
| 918 * for the given [target] was just read from the cache. |
| 919 */ |
| 920 void resultAccessed(AnalysisTarget target, ResultDescriptor descriptor) { |
| 921 CacheFlushManager flushManager = _getFlushManager(descriptor); |
| 922 TargetedResult result = new TargetedResult(target, descriptor); |
| 923 flushManager.resultAccessed(result); |
| 924 } |
| 925 |
| 926 /** |
| 927 * Records that the given [result] was just stored into the cache. |
| 928 */ |
| 929 void resultStored(TargetedResult result, Object value) { |
| 930 CacheFlushManager flushManager = _getFlushManager(result.result); |
| 931 List<TargetedResult> resultsToFlush = |
| 932 flushManager.resultStored(result, value); |
| 933 for (TargetedResult result in resultsToFlush) { |
| 934 CacheEntry entry = get(result.target); |
| 935 if (entry != null) { |
| 936 ResultData data = entry._resultMap[result.result]; |
| 937 if (data != null) { |
| 938 data.flush(); |
| 939 } |
| 940 } |
| 941 } |
| 942 } |
| 943 |
| 944 /** |
| 945 * Return the number of targets that are mapped to cache entries. |
| 946 */ |
| 947 int size() => _targetMap.length; |
| 948 |
| 949 /** |
| 950 * If the given [target] is a [Source], adds it to [_sources]. |
| 951 */ |
| 952 void _addIfSource(AnalysisTarget target) { |
| 953 if (target is Source) { |
| 954 _sources.add(target); |
| 955 { |
| 956 String fullName = target.fullName; |
| 957 _pathToSources.putIfAbsent(fullName, () => <Source>[]).add(target); |
| 958 } |
| 959 } |
| 960 } |
| 961 |
| 962 ResultData _getDataFor(TargetedResult result) { |
| 963 CacheEntry entry = context.analysisCache.get(result.target); |
| 964 return entry != null ? entry._resultMap[result.result] : null; |
| 965 } |
| 966 |
| 967 /** |
| 968 * Return the [CacheFlushManager] for the given [descriptor], not `null`. |
| 969 */ |
| 970 CacheFlushManager _getFlushManager(ResultDescriptor descriptor) { |
| 971 ResultCachingPolicy policy = descriptor.cachingPolicy; |
| 972 if (identical(policy, DEFAULT_CACHING_POLICY)) { |
| 973 return UnlimitedCacheFlushManager.INSTANCE; |
| 974 } |
| 975 CacheFlushManager manager = _flushManagerMap[policy]; |
| 976 if (manager == null) { |
| 977 manager = new CacheFlushManager(policy, _isPriorityAnalysisTarget); |
| 978 _flushManagerMap[policy] = manager; |
| 979 } |
| 980 return manager; |
| 981 } |
| 982 |
| 983 bool _isPriorityAnalysisTarget(AnalysisTarget target) { |
| 984 return context.priorityTargets.contains(target); |
| 985 } |
| 986 |
| 987 /** |
| 988 * If the given [target] is a [Source], remove it from the list of [_sources]. |
| 989 */ |
| 990 void _removeIfSource(AnalysisTarget target) { |
| 991 if (target is Source) { |
| 992 _sources.remove(target); |
| 993 String fullName = target.fullName; |
| 994 List<Source> sources = _pathToSources[fullName]; |
| 995 if (sources != null) { |
| 996 sources.remove(target); |
| 997 if (sources.isEmpty) { |
| 998 _pathToSources.remove(fullName); |
| 999 } |
| 1000 } |
| 1001 } |
| 1002 } |
| 1003 } |
| 1004 |
| 1005 /** |
| 1006 * The description for a change. |
| 1007 */ |
| 1008 class Delta { |
| 1009 final Source source; |
| 1010 |
| 1011 Delta(this.source); |
| 1012 |
| 1013 /** |
| 1014 * Check whether this delta affects the result described by the given |
| 1015 * [descriptor] and [target]. |
| 1016 */ |
| 1017 DeltaResult validate(InternalAnalysisContext context, AnalysisTarget target, |
| 1018 ResultDescriptor descriptor) { |
| 1019 return DeltaResult.INVALIDATE; |
| 1020 } |
| 1021 } |
| 1022 |
| 1023 /** |
| 1024 * The possible results of validating analysis results againt a [Delta]. |
| 1025 */ |
| 1026 enum DeltaResult { INVALIDATE, KEEP_CONTINUE, STOP } |
| 1027 |
| 1028 /** |
| 1029 * [InvalidatedResult] describes an invalidated result. |
| 1030 */ |
| 1031 class InvalidatedResult { |
| 1032 /** |
| 1033 * The target in which the result was invalidated. |
| 1034 */ |
| 1035 final CacheEntry entry; |
| 1036 |
| 1037 /** |
| 1038 * The descriptor of the result which was invalidated. |
| 1039 */ |
| 1040 final ResultDescriptor descriptor; |
| 1041 |
| 1042 InvalidatedResult(this.entry, this.descriptor); |
| 1043 |
| 1044 @override |
| 1045 String toString() => '$descriptor of ${entry.target}'; |
| 1046 } |
| 1047 |
| 1048 /** |
| 1049 * The data about a single analysis result that is stored in a [CacheEntry]. |
| 1050 */ |
| 1051 // TODO(brianwilkerson) Consider making this a generic class so that the value |
| 1052 // can be typed. |
| 1053 class ResultData { |
| 1054 /** |
| 1055 * The [ResultDescriptor] this result is for. |
| 1056 */ |
| 1057 final ResultDescriptor descriptor; |
| 1058 |
| 1059 /** |
| 1060 * The state of the cached value. |
| 1061 */ |
| 1062 CacheState state; |
| 1063 |
| 1064 /** |
| 1065 * The value being cached, or the default value for the result if there is no |
| 1066 * value (for example, when the [state] is [CacheState.INVALID]). |
| 1067 */ |
| 1068 Object value; |
| 1069 |
| 1070 /** |
| 1071 * A list of the results on which this result depends. |
| 1072 */ |
| 1073 List<TargetedResult> dependedOnResults = <TargetedResult>[]; |
| 1074 |
| 1075 /** |
| 1076 * A list of the results that depend on this result. |
| 1077 */ |
| 1078 Set<TargetedResult> dependentResults = new Set<TargetedResult>(); |
| 1079 |
| 1080 /** |
| 1081 * Initialize a newly created result holder to represent the value of data |
| 1082 * described by the given [descriptor]. |
| 1083 */ |
| 1084 ResultData(this.descriptor) { |
| 1085 state = CacheState.INVALID; |
| 1086 value = descriptor.defaultValue; |
| 1087 } |
| 1088 |
| 1089 /** |
| 1090 * Flush this value. |
| 1091 */ |
| 1092 void flush() { |
| 1093 state = CacheState.FLUSHED; |
| 1094 value = descriptor.defaultValue; |
| 1095 } |
| 1096 } |
| 1097 |
| 1098 /** |
| 1099 * A cache partition that contains all of the targets in the SDK. |
| 1100 */ |
| 1101 class SdkCachePartition extends CachePartition { |
| 1102 /** |
| 1103 * Initialize a newly created cache partition, belonging to the given |
| 1104 * [context]. |
| 1105 */ |
| 1106 SdkCachePartition(InternalAnalysisContext context) : super(context); |
| 1107 |
| 1108 @override |
| 1109 bool isResponsibleFor(AnalysisTarget target) { |
| 1110 if (target is AnalysisContextTarget) { |
| 1111 return true; |
| 1112 } |
| 1113 Source source = target.source; |
| 1114 return source != null && source.isInSystemLibrary; |
| 1115 } |
| 1116 } |
| 1117 |
| 1118 /** |
| 1119 * A cache partition that contains all targets not contained in other partitions
. |
| 1120 */ |
| 1121 class UniversalCachePartition extends CachePartition { |
| 1122 /** |
| 1123 * Initialize a newly created cache partition, belonging to the given |
| 1124 * [context]. |
| 1125 */ |
| 1126 UniversalCachePartition(InternalAnalysisContext context) : super(context); |
| 1127 |
| 1128 @override |
| 1129 bool isResponsibleFor(AnalysisTarget target) => true; |
| 1130 } |
| 1131 |
| 1132 /** |
| 1133 * [CacheFlushManager] that does nothing, results are never flushed. |
| 1134 */ |
| 1135 class UnlimitedCacheFlushManager extends CacheFlushManager { |
| 1136 static final CacheFlushManager INSTANCE = new UnlimitedCacheFlushManager(); |
| 1137 |
| 1138 UnlimitedCacheFlushManager() : super(DEFAULT_CACHING_POLICY, (_) => false); |
| 1139 |
| 1140 @override |
| 1141 void resultAccessed(TargetedResult result) {} |
| 1142 |
| 1143 @override |
| 1144 List<TargetedResult> resultStored(TargetedResult newResult, newValue) { |
| 1145 return TargetedResult.EMPTY_LIST; |
| 1146 } |
| 1147 |
| 1148 @override |
| 1149 void targetRemoved(AnalysisTarget target) {} |
| 1150 } |
OLD | NEW |