Chromium Code Reviews| 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:collection'; | |
| 8 | |
| 9 import 'package:analyzer/src/generated/ast.dart'; | |
| 10 import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine, | |
| 11 CacheState, InternalAnalysisContext, RetentionPriority; | |
| 12 import 'package:analyzer/src/generated/html.dart'; | |
| 13 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 14 import 'package:analyzer/src/generated/utilities_collection.dart'; | |
| 15 import 'package:analyzer/task/model.dart'; | |
| 16 | |
| 17 /** | |
| 18 * An LRU cache of results produced by analysis. | |
| 19 */ | |
| 20 class AnalysisCache { | |
| 21 /** | |
| 22 * A flag used to control whether trace information should be produced when | |
| 23 * the content of the cache is modified. | |
| 24 */ | |
| 25 static bool _TRACE_CHANGES = false; | |
| 26 | |
| 27 /** | |
| 28 * An array containing the partitions of which this cache is comprised. | |
| 29 */ | |
| 30 final List<CachePartition> _partitions; | |
| 31 | |
| 32 /** | |
| 33 * Initialize a newly created cache to have the given [partitions]. The | |
| 34 * partitions will be searched in the order in which they appear in the array, | |
| 35 * so the most specific partition (usually an [SdkCachePartition]) should be | |
| 36 * first and the most general (usually a [UniversalCachePartition]) last. | |
| 37 */ | |
| 38 AnalysisCache(this._partitions); | |
| 39 | |
| 40 /** | |
| 41 * Return the number of entries in this cache that have an AST associated with | |
| 42 * them. | |
| 43 */ | |
| 44 int get astSize => _partitions[_partitions.length - 1].astSize; | |
|
Paul Berry
2015/03/03 17:21:31
Why not sum .astSize over all partitions?
Brian Wilkerson
2015/03/03 21:21:17
This is kind of a hold-over from an earlier implem
| |
| 45 | |
| 46 // /** | |
|
Paul Berry
2015/03/03 17:21:31
Add a TODO comment here?
Brian Wilkerson
2015/03/03 21:21:16
Done
| |
| 47 // * Return information about each of the partitions in this cache. | |
| 48 // */ | |
| 49 // List<AnalysisContextStatistics_PartitionData> get partitionData { | |
| 50 // int count = _partitions.length; | |
| 51 // List<AnalysisContextStatistics_PartitionData> data = | |
| 52 // new List<AnalysisContextStatistics_PartitionData>(count); | |
| 53 // for (int i = 0; i < count; i++) { | |
| 54 // CachePartition partition = _partitions[i]; | |
| 55 // data[i] = new AnalysisContextStatisticsImpl_PartitionDataImpl( | |
| 56 // partition.astSize, | |
| 57 // partition.map.length); | |
| 58 // } | |
| 59 // return data; | |
| 60 // } | |
| 61 | |
| 62 /** | |
| 63 * Record that the AST associated with the given [target] was just read from | |
| 64 * the cache. | |
| 65 */ | |
| 66 void accessedAst(AnalysisTarget target) { | |
| 67 int count = _partitions.length; | |
| 68 for (int i = 0; i < count; i++) { | |
| 69 if (_partitions[i].contains(target)) { | |
|
Paul Berry
2015/03/03 17:21:31
It seems like we do this loop over partitions in a
Brian Wilkerson
2015/03/03 21:21:16
Sounds reasonable. Left for a later CL.
| |
| 70 _partitions[i].accessedAst(target); | |
| 71 return; | |
| 72 } | |
| 73 } | |
| 74 } | |
| 75 | |
| 76 /** | |
| 77 * Return the entry associated with the given [target]. | |
| 78 */ | |
| 79 CacheEntry get(AnalysisTarget target) { | |
| 80 int count = _partitions.length; | |
| 81 for (int i = 0; i < count; i++) { | |
| 82 if (_partitions[i].contains(target)) { | |
| 83 return _partitions[i].get(target); | |
| 84 } | |
| 85 } | |
| 86 // | |
| 87 // We should never get to this point because the last partition should | |
| 88 // always be a universal partition, except in the case of the SDK context, | |
| 89 // in which case the target should always be part of the SDK. | |
| 90 // | |
| 91 return null; | |
| 92 } | |
| 93 | |
| 94 /** | |
| 95 * Return context that owns the given [target]. | |
|
Paul Berry
2015/03/03 17:21:31
I'm not sure I understand what you mean by "owns".
Brian Wilkerson
2015/03/03 21:21:16
Done
| |
| 96 */ | |
| 97 InternalAnalysisContext getContextFor(AnalysisTarget target) { | |
| 98 int count = _partitions.length; | |
| 99 for (int i = 0; i < count; i++) { | |
| 100 if (_partitions[i].contains(target)) { | |
| 101 return _partitions[i].context; | |
| 102 } | |
| 103 } | |
| 104 // | |
| 105 // We should never get to this point because the last partition should | |
| 106 // always be a universal partition, except in the case of the SDK context, | |
| 107 // in which case the target should always be part of the SDK. | |
| 108 // | |
| 109 AnalysisEngine.instance.logger.logInformation( | |
|
Paul Berry
2015/03/03 17:21:32
I'm concerned that if we just log the error, it wo
Brian Wilkerson
2015/03/03 21:21:16
Saved for a follow-on CL.
| |
| 110 'Could not find context for $target', | |
| 111 new CaughtException(new AnalysisException(), null)); | |
| 112 return null; | |
| 113 } | |
| 114 | |
| 115 /** | |
| 116 * Return an iterator returning all of the map entries mapping targets to | |
| 117 * cache entries. | |
| 118 */ | |
| 119 MapIterator<AnalysisTarget, CacheEntry> iterator() { | |
| 120 int count = _partitions.length; | |
| 121 List<Map<AnalysisTarget, CacheEntry>> maps = new List<Map>(count); | |
| 122 for (int i = 0; i < count; i++) { | |
| 123 maps[i] = _partitions[i].map; | |
| 124 } | |
| 125 return new MultipleMapIterator<AnalysisTarget, CacheEntry>(maps); | |
| 126 } | |
| 127 | |
| 128 /** | |
| 129 * Associate the given [entry] with the given [target]. | |
| 130 */ | |
| 131 void put(AnalysisTarget target, CacheEntry entry) { | |
| 132 entry.fixExceptionState(); | |
| 133 int count = _partitions.length; | |
| 134 for (int i = 0; i < count; i++) { | |
| 135 if (_partitions[i].contains(target)) { | |
| 136 if (_TRACE_CHANGES) { | |
| 137 try { | |
| 138 CacheEntry oldEntry = _partitions[i].get(target); | |
| 139 if (oldEntry == null) { | |
| 140 AnalysisEngine.instance.logger.logInformation( | |
| 141 'Added a cache entry for $target.'); | |
| 142 } else { | |
| 143 AnalysisEngine.instance.logger.logInformation( | |
| 144 'Modified the cache entry for $target.'); | |
| 145 // 'Diff = ${entry.getDiff(oldEntry)}'); | |
| 146 } | |
| 147 } catch (exception) { | |
|
Paul Berry
2015/03/03 17:21:31
Why would an exception ever occur here?
Brian Wilkerson
2015/03/03 21:21:17
My guess is that it was debugging code added to th
| |
| 148 // Ignored | |
| 149 } | |
| 150 } | |
| 151 _partitions[i].put(target, entry); | |
| 152 return; | |
| 153 } | |
| 154 } | |
| 155 } | |
|
Paul Berry
2015/03/03 17:21:32
If we get to the end of the function without findi
Brian Wilkerson
2015/03/03 21:21:16
Added TODO.
| |
| 156 | |
| 157 /** | |
| 158 * Remove all information related to the given [target] from this cache. | |
| 159 */ | |
| 160 void remove(AnalysisTarget target) { | |
| 161 int count = _partitions.length; | |
| 162 for (int i = 0; i < count; i++) { | |
| 163 if (_partitions[i].contains(target)) { | |
| 164 if (_TRACE_CHANGES) { | |
| 165 try { | |
| 166 AnalysisEngine.instance.logger.logInformation( | |
| 167 'Removed the cache entry for $target.'); | |
| 168 } catch (exception) { | |
|
Paul Berry
2015/03/03 17:21:32
Why would an exception ever occur here?
Brian Wilkerson
2015/03/03 21:21:16
Removed
| |
| 169 // Ignored | |
| 170 } | |
| 171 } | |
| 172 _partitions[i].remove(target); | |
| 173 return; | |
| 174 } | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 /** | |
| 179 * Record that the AST associated with the given [target] was just removed | |
| 180 * from the cache. | |
| 181 */ | |
| 182 void removedAst(AnalysisTarget target) { | |
| 183 int count = _partitions.length; | |
| 184 for (int i = 0; i < count; i++) { | |
| 185 if (_partitions[i].contains(target)) { | |
| 186 _partitions[i].removedAst(target); | |
| 187 return; | |
| 188 } | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 /** | |
| 193 * Return the number of targets that are mapped to cache entries. | |
| 194 */ | |
| 195 int size() { | |
| 196 int size = 0; | |
| 197 int count = _partitions.length; | |
| 198 for (int i = 0; i < count; i++) { | |
| 199 size += _partitions[i].size(); | |
| 200 } | |
| 201 return size; | |
| 202 } | |
| 203 | |
| 204 /** | |
| 205 * Record that the AST associated with the given [target] was just stored to | |
| 206 * the cache. | |
| 207 */ | |
| 208 void storedAst(AnalysisTarget target) { | |
| 209 int count = _partitions.length; | |
| 210 for (int i = 0; i < count; i++) { | |
| 211 if (_partitions[i].contains(target)) { | |
| 212 _partitions[i].storedAst(target); | |
| 213 return; | |
| 214 } | |
| 215 } | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 /** | |
| 220 * The information cached by an analysis context about an individual target. | |
| 221 */ | |
| 222 class CacheEntry { | |
| 223 /** | |
| 224 * The index of the flag indicating whether the source was explicitly added to | |
| 225 * the context or whether the source was implicitly added because it was | |
| 226 * referenced by another source. | |
| 227 */ | |
| 228 static int _EXPLICITLY_ADDED_FLAG = 0; | |
| 229 | |
| 230 /** | |
| 231 * The most recent time at which the state of the target matched the state | |
| 232 * represented by this entry. | |
| 233 */ | |
| 234 int modificationTime = 0; | |
| 235 | |
| 236 /** | |
| 237 * The exception that caused one or more values to have a state of | |
| 238 * [CacheState.ERROR]. | |
| 239 */ | |
|
Paul Berry
2015/03/03 17:21:32
I'm assuming that an intended invariant is that wh
Brian Wilkerson
2015/03/03 21:21:16
Yes.
| |
| 240 CaughtException exception; | |
| 241 | |
| 242 /** | |
| 243 * A bit-encoding of boolean flags associated with this entry's target. | |
| 244 */ | |
| 245 int _flags = 0; | |
| 246 | |
| 247 /** | |
| 248 * A table mapping result descriptors to the cached values of those results. | |
| 249 */ | |
| 250 Map<ResultDescriptor, ResultData> _resultMap = | |
| 251 new HashMap<ResultDescriptor, ResultData>(); | |
| 252 | |
| 253 /** | |
| 254 * Return `true` if the source was explicitly added to the context or `false` | |
| 255 * if the source was implicitly added because it was referenced by another | |
| 256 * source. | |
| 257 */ | |
| 258 bool get explicitlyAdded => _getFlag(_EXPLICITLY_ADDED_FLAG); | |
| 259 | |
| 260 /** | |
| 261 * Set whether the source was explicitly added to the context to match the | |
| 262 * [explicitlyAdded] flag. | |
| 263 */ | |
| 264 void set explicitlyAdded(bool explicitlyAdded) { | |
| 265 _setFlag(_EXPLICITLY_ADDED_FLAG, explicitlyAdded); | |
| 266 } | |
| 267 | |
| 268 /** | |
| 269 * Return `true` if this entry contains at least one result whose value is an | |
| 270 * AST structure. | |
| 271 */ | |
| 272 bool get hasAstStructure { | |
| 273 for (ResultData data in _resultMap.values) { | |
| 274 if (data.value is AstNode || data.value is XmlNode) { | |
| 275 return true; | |
| 276 } | |
| 277 } | |
| 278 return false; | |
| 279 } | |
| 280 | |
| 281 /** | |
| 282 * Fix the state of the [exception] to match the current state of the entry. | |
| 283 */ | |
| 284 void fixExceptionState() { | |
| 285 if (hasErrorState()) { | |
| 286 if (exception == null) { | |
| 287 // | |
| 288 // This code should never be reached, but is a fail-safe in case an | |
| 289 // exception is not recorded when it should be. | |
| 290 // | |
|
Paul Berry
2015/03/03 17:21:31
Since this should never happen, should we also add
Brian Wilkerson
2015/03/03 21:21:17
That would potentially let us know that it happene
| |
| 291 String message = 'State set to ERROR without setting an exception'; | |
| 292 exception = new CaughtException(new AnalysisException(message), null); | |
| 293 } | |
| 294 } else { | |
| 295 exception = null; | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 /** | |
| 300 * Mark any AST structures associated with this cache entry as being flushed. | |
| 301 */ | |
| 302 void flushAstStructures() { | |
| 303 _resultMap.forEach((ResultDescriptor descriptor, ResultData data) { | |
| 304 if (data.value is AstNode || data.value is XmlNode) { | |
| 305 _validateStateChange(descriptor, CacheState.FLUSHED); | |
| 306 data.state = CacheState.FLUSHED; | |
| 307 data.value = descriptor.defaultValue; | |
| 308 } | |
| 309 }); | |
|
Paul Berry
2015/03/03 17:21:32
To restore the invariant, we might need to set thi
Brian Wilkerson
2015/03/03 21:21:16
If the state were ERROR, then the value would be n
| |
| 310 } | |
| 311 | |
| 312 /** | |
| 313 * Return the state of the result represented by the given [descriptor]. | |
| 314 */ | |
| 315 CacheState getState(ResultDescriptor descriptor) { | |
| 316 ResultData data = _resultMap[descriptor]; | |
| 317 if (data == null) { | |
| 318 return CacheState.INVALID; | |
| 319 } | |
| 320 return data.state; | |
| 321 } | |
| 322 | |
| 323 /** | |
| 324 * Return the value of the result represented by the given [descriptor], or | |
| 325 * the default value for the result if this entry does not have a valid value. | |
| 326 */ | |
| 327 /*<V>*/ dynamic /*V*/ getValue(ResultDescriptor /*<V>*/ descriptor) { | |
| 328 ResultData data = _resultMap[descriptor]; | |
| 329 if (data == null) { | |
| 330 return descriptor.defaultValue; | |
| 331 } | |
| 332 return data.value; | |
| 333 } | |
| 334 | |
| 335 /** | |
| 336 * Return `true` if the state of any data value is [CacheState.ERROR]. | |
| 337 */ | |
| 338 bool hasErrorState() { | |
| 339 for (ResultData data in _resultMap.values) { | |
| 340 if (data.state == CacheState.ERROR) { | |
| 341 return true; | |
| 342 } | |
| 343 } | |
| 344 return false; | |
| 345 } | |
| 346 | |
| 347 /** | |
| 348 * Invalidate all of the information associated with this entry's target. | |
| 349 */ | |
| 350 void invalidateAllInformation() { | |
| 351 _resultMap.clear(); | |
|
Paul Berry
2015/03/03 17:21:31
To restore the invariant, we should set this.excep
Brian Wilkerson
2015/03/03 21:21:17
Done
| |
| 352 } | |
| 353 | |
| 354 /** | |
| 355 * Set the state of the result represented by the given [descriptor] to the | |
| 356 * given [state]. | |
| 357 */ | |
| 358 void setState(ResultDescriptor descriptor, CacheState state) { | |
| 359 if (state == CacheState.VALID) { | |
| 360 throw new ArgumentError('use setValue() to set the state to VALID'); | |
| 361 } | |
| 362 _validateStateChange(descriptor, state); | |
| 363 if (state == CacheState.INVALID) { | |
| 364 _resultMap.remove(descriptor); | |
| 365 } else { | |
| 366 ResultData data = | |
| 367 _resultMap.putIfAbsent(descriptor, () => new ResultData(descriptor)); | |
| 368 data.state = state; | |
| 369 if (state != CacheState.IN_PROCESS) { | |
| 370 // | |
| 371 // If the state is in-process, we can leave the current value in the | |
| 372 // cache for any 'get' methods to access. | |
| 373 // | |
| 374 data.value = descriptor.defaultValue; | |
| 375 } | |
| 376 } | |
| 377 } | |
|
Paul Berry
2015/03/03 17:21:31
I'm surprised this function doesn't set this.excep
Brian Wilkerson
2015/03/03 21:21:17
Then the client is expected to set the exception a
| |
| 378 | |
| 379 /** | |
| 380 * Set the value of the result represented by the given [descriptor] to the | |
| 381 * given [value]. | |
| 382 */ | |
| 383 /*<V>*/ void setValue(ResultDescriptor /*<V>*/ descriptor, dynamic /*V*/ | |
| 384 value) { | |
| 385 _validateStateChange(descriptor, CacheState.VALID); | |
| 386 ResultData data = | |
| 387 _resultMap.putIfAbsent(descriptor, () => new ResultData(descriptor)); | |
| 388 data.state = CacheState.VALID; | |
| 389 data.value = value == null ? descriptor.defaultValue : value; | |
| 390 } | |
|
Paul Berry
2015/03/03 17:21:31
Similar question about this.exception for this fun
Brian Wilkerson
2015/03/03 21:21:17
The current assumption is that values won't be cre
| |
| 391 | |
| 392 @override | |
| 393 String toString() { | |
| 394 StringBuffer buffer = new StringBuffer(); | |
| 395 _writeOn(buffer); | |
| 396 return buffer.toString(); | |
| 397 } | |
| 398 | |
| 399 /** | |
| 400 * Return the value of the flag with the given [index]. | |
| 401 */ | |
| 402 bool _getFlag(int index) => BooleanArray.get(_flags, index); | |
| 403 | |
| 404 /** | |
| 405 * Set the value of the flag with the given [index] to the given [value]. | |
| 406 */ | |
| 407 void _setFlag(int index, bool value) { | |
| 408 _flags = BooleanArray.set(_flags, index, value); | |
| 409 } | |
| 410 | |
| 411 /** | |
| 412 * If the state of the value described by the given [descriptor] is changing | |
| 413 * from ERROR to anything else, capture the information. This is an attempt to | |
| 414 * discover the underlying cause of a long-standing bug. | |
| 415 */ | |
| 416 void _validateStateChange(ResultDescriptor descriptor, CacheState newState) { | |
| 417 // TODO(brianwilkerson) Decide whether we still want to capture this data. | |
| 418 // if (descriptor != CONTENT) { | |
| 419 // return; | |
| 420 // } | |
| 421 // ResultData data = resultMap[CONTENT]; | |
| 422 // if (data != null && data.state == CacheState.ERROR) { | |
| 423 // String message = | |
| 424 // 'contentState changing from ${data.state} to $newState'; | |
| 425 // InstrumentationBuilder builder = | |
| 426 // Instrumentation.builder2('CacheEntry-validateStateChange'); | |
| 427 // builder.data3('message', message); | |
| 428 // //builder.data('source', source.getFullName()); | |
| 429 // builder.record(new CaughtException(new AnalysisException(message), null) ); | |
| 430 // builder.log(); | |
| 431 // } | |
| 432 } | |
| 433 | |
| 434 /** | |
| 435 * Write a textual representation of this entry to the given [buffer]. The | |
| 436 * result should only be used for debugging purposes. | |
| 437 */ | |
| 438 void _writeOn(StringBuffer buffer) { | |
| 439 buffer.write('time = '); | |
| 440 buffer.write(modificationTime); | |
| 441 List<ResultDescriptor> results = _resultMap.keys.toList(); | |
| 442 results.sort( | |
| 443 (ResultDescriptor first, ResultDescriptor second) => | |
| 444 first.toString().compareTo(second.toString())); | |
| 445 for (ResultDescriptor result in results) { | |
| 446 ResultData data = _resultMap[result]; | |
| 447 buffer.write('; '); | |
| 448 buffer.write(result.toString()); | |
| 449 buffer.write(' = '); | |
| 450 buffer.write(data..state); | |
| 451 } | |
| 452 } | |
| 453 } | |
| 454 | |
| 455 /** | |
| 456 * A single partition in an LRU cache of information related to analysis. | |
| 457 */ | |
| 458 abstract class CachePartition { | |
| 459 /** | |
| 460 * The context that owns this partition. Multiple contexts can reference a | |
| 461 * partition, but only one context can own it. | |
| 462 */ | |
| 463 final InternalAnalysisContext context; | |
| 464 | |
| 465 /** | |
| 466 * The maximum number of sources for which AST structures should be kept in | |
| 467 * the cache. | |
| 468 */ | |
| 469 int _maxCacheSize = 0; | |
| 470 | |
| 471 /** | |
| 472 * The policy used to determine which results to remove from the cache. | |
| 473 */ | |
| 474 final CacheRetentionPolicy _retentionPolicy; | |
| 475 | |
| 476 /** | |
| 477 * A table mapping the targets belonging to this partition to the information | |
| 478 * known about those targets. | |
| 479 */ | |
| 480 HashMap<AnalysisTarget, CacheEntry> _targetMap = | |
| 481 new HashMap<AnalysisTarget, CacheEntry>(); | |
| 482 | |
| 483 /** | |
| 484 * A list containing the most recently accessed targets with the most recently | |
| 485 * used at the end of the list. When more targets are added than the maximum | |
| 486 * allowed then the least recently used target will be removed and will have | |
| 487 * it's cached AST structure flushed. | |
| 488 */ | |
| 489 List<AnalysisTarget> _recentlyUsed = <AnalysisTarget>[]; | |
|
Paul Berry
2015/03/03 17:21:32
How big is this list expected to get in typical us
Brian Wilkerson
2015/03/03 21:21:16
It's a valid concern, but we should discuss off-li
| |
| 490 | |
| 491 /** | |
| 492 * Initialize a newly created cache partition, belonging to the given | |
| 493 * [context]. The partition will maintain at most [_maxCacheSize] AST | |
| 494 * structures in the cache, using the [_retentionPolicy] to determine which | |
| 495 * AST structures to flush. | |
| 496 */ | |
| 497 CachePartition(this.context, this._maxCacheSize, this._retentionPolicy); | |
| 498 | |
| 499 /** | |
| 500 * Return the number of entries in this partition that have an AST associated | |
| 501 * with them. | |
| 502 */ | |
| 503 int get astSize { | |
| 504 int astSize = 0; | |
| 505 int count = _recentlyUsed.length; | |
| 506 for (int i = 0; i < count; i++) { | |
| 507 AnalysisTarget target = _recentlyUsed[i]; | |
| 508 CacheEntry entry = _targetMap[target]; | |
| 509 if (entry.hasAstStructure) { | |
| 510 astSize++; | |
| 511 } | |
| 512 } | |
| 513 return astSize; | |
| 514 } | |
| 515 | |
| 516 /** | |
| 517 * Return a table mapping the targets known to the context to the information | |
| 518 * known about the target. | |
| 519 * | |
| 520 * <b>Note:</b> This method is only visible for use by [AnalysisCache] and | |
|
Paul Berry
2015/03/03 17:21:32
Since AnalysisCache is in the same library, how ab
Brian Wilkerson
2015/03/03 21:21:16
A hold-over from Java. It's currently also being u
| |
| 521 * should not be used for any other purpose. | |
| 522 */ | |
| 523 Map<AnalysisTarget, CacheEntry> get map => _targetMap; | |
| 524 | |
| 525 /** | |
| 526 * Return the maximum size of the cache. | |
| 527 */ | |
| 528 int get maxCacheSize => _maxCacheSize; | |
| 529 | |
| 530 /** | |
| 531 * Set the maximum size of the cache to the given [size]. | |
| 532 */ | |
| 533 void set maxCacheSize(int size) { | |
| 534 _maxCacheSize = size; | |
| 535 while (_recentlyUsed.length > _maxCacheSize) { | |
| 536 if (!_flushAstFromCache()) { | |
| 537 break; | |
| 538 } | |
| 539 } | |
| 540 } | |
| 541 | |
| 542 /** | |
| 543 * Record that the AST associated with the given [target] was just read from | |
| 544 * the cache. | |
| 545 */ | |
| 546 void accessedAst(AnalysisTarget target) { | |
| 547 if (_recentlyUsed.remove(target)) { | |
| 548 _recentlyUsed.add(target); | |
| 549 return; | |
| 550 } | |
| 551 while (_recentlyUsed.length >= _maxCacheSize) { | |
| 552 if (!_flushAstFromCache()) { | |
| 553 break; | |
|
Paul Berry
2015/03/03 17:21:32
Here's a case where I'm concerned about the invari
Brian Wilkerson
2015/03/03 21:21:15
Part of a larger discussion.
| |
| 554 } | |
| 555 } | |
| 556 _recentlyUsed.add(target); | |
| 557 } | |
| 558 | |
| 559 /** | |
| 560 * Return `true` if the given [target] is contained in this partition. | |
| 561 */ | |
| 562 bool contains(AnalysisTarget target); | |
|
Paul Berry
2015/03/03 17:21:32
For a long time I was confused by code calling con
Brian Wilkerson
2015/03/03 21:21:16
Yes, in a future CL.
| |
| 563 | |
| 564 /** | |
| 565 * Return the entry associated with the given [target]. | |
| 566 */ | |
| 567 CacheEntry get(AnalysisTarget target) => _targetMap[target]; | |
| 568 | |
| 569 /** | |
| 570 * Return an iterator returning all of the map entries mapping targets to | |
| 571 * cache entries. | |
| 572 */ | |
| 573 MapIterator<AnalysisTarget, CacheEntry> iterator() => | |
| 574 new SingleMapIterator<AnalysisTarget, CacheEntry>(_targetMap); | |
| 575 | |
| 576 /** | |
| 577 * Associate the given [entry] with the given [target]. | |
| 578 */ | |
| 579 void put(AnalysisTarget target, CacheEntry entry) { | |
| 580 entry.fixExceptionState(); | |
| 581 _targetMap[target] = entry; | |
| 582 } | |
| 583 | |
| 584 /** | |
| 585 * Remove all information related to the given [target] from this cache. | |
| 586 */ | |
| 587 void remove(AnalysisTarget target) { | |
| 588 _recentlyUsed.remove(target); | |
| 589 _targetMap.remove(target); | |
| 590 } | |
| 591 | |
| 592 /** | |
| 593 * Record that the AST associated with the given [target] was just removed | |
| 594 * from the cache. | |
| 595 */ | |
| 596 void removedAst(AnalysisTarget target) { | |
| 597 _recentlyUsed.remove(target); | |
| 598 } | |
| 599 | |
| 600 /** | |
| 601 * Return the number of targets that are mapped to cache entries. | |
| 602 */ | |
| 603 int size() => _targetMap.length; | |
| 604 | |
| 605 /** | |
| 606 * Record that the AST associated with the given [target] was just stored to | |
| 607 * the cache. | |
| 608 */ | |
| 609 void storedAst(AnalysisTarget target) { | |
| 610 if (_recentlyUsed.contains(target)) { | |
| 611 return; | |
| 612 } | |
| 613 while (_recentlyUsed.length >= _maxCacheSize) { | |
| 614 if (!_flushAstFromCache()) { | |
| 615 break; | |
| 616 } | |
| 617 } | |
| 618 _recentlyUsed.add(target); | |
| 619 } | |
| 620 | |
| 621 /** | |
| 622 * Attempt to flush one AST structure from the cache. Return `true` if a | |
| 623 * structure was flushed. | |
| 624 */ | |
| 625 bool _flushAstFromCache() { | |
| 626 AnalysisTarget removedTarget = _removeAstToFlush(); | |
| 627 if (removedTarget == null) { | |
| 628 return false; | |
| 629 } | |
| 630 CacheEntry entry = _targetMap[removedTarget]; | |
| 631 entry.flushAstStructures(); | |
| 632 return true; | |
| 633 } | |
| 634 | |
| 635 /** | |
| 636 * Remove and return one target from the list of recently used targets whose | |
| 637 * AST structure can be flushed from the cache. The target that will be | |
| 638 * returned will be the target that has been unreferenced for the longest | |
| 639 * period of time but that is not a priority for analysis. | |
| 640 */ | |
|
Paul Berry
2015/03/03 17:21:30
The doc comments should also explain when this met
Brian Wilkerson
2015/03/03 21:21:17
Done
| |
| 641 AnalysisTarget _removeAstToFlush() { | |
| 642 int targetToRemove = -1; | |
| 643 for (int i = 0; i < _recentlyUsed.length; i++) { | |
| 644 AnalysisTarget target = _recentlyUsed[i]; | |
| 645 RetentionPriority priority = | |
| 646 _retentionPolicy.getAstPriority(target, _targetMap[target]); | |
| 647 if (priority == RetentionPriority.LOW) { | |
| 648 return _recentlyUsed.removeAt(i); | |
| 649 } else if (priority == RetentionPriority.MEDIUM && targetToRemove < 0) { | |
| 650 targetToRemove = i; | |
| 651 } | |
| 652 } | |
| 653 if (targetToRemove < 0) { | |
| 654 // This happens if the retention policy returns a priority of HIGH for all | |
| 655 // of the targets that have been recently used. This is the case, for | |
| 656 // example, when the list of priority sources is bigger than the current | |
| 657 // cache size. | |
| 658 return null; | |
| 659 } | |
| 660 return _recentlyUsed.removeAt(targetToRemove); | |
| 661 } | |
| 662 } | |
| 663 | |
| 664 /** | |
| 665 * A policy objecy that determines how important it is for data to be retained | |
| 666 * in the analysis cache. | |
| 667 */ | |
| 668 abstract class CacheRetentionPolicy { | |
| 669 /** | |
| 670 * Return the priority of retaining the AST structure for the given [target] | |
| 671 * in the given [entry]. | |
| 672 */ | |
| 673 // TODO(brianwilkerson) Find a more general mechanism, probably based on task | |
| 674 // descriptors, to determine which data is still needed for analysis and which | |
| 675 // can be removed from the cache. Ideally we could (a) remove the need for | |
| 676 // this class and (b) be able to flush all result data (not just AST's). | |
| 677 RetentionPriority getAstPriority(AnalysisTarget target, CacheEntry entry); | |
| 678 } | |
| 679 | |
| 680 /** | |
| 681 * A retention policy that will keep AST's in the cache if there is analysis | |
| 682 * information that needs to be computed for a source, where the computation is | |
| 683 * dependent on having the AST. | |
| 684 */ | |
| 685 class DefaultRetentionPolicy implements CacheRetentionPolicy { | |
| 686 /** | |
| 687 * An instance of this class that can be shared. | |
| 688 */ | |
| 689 static const DefaultRetentionPolicy POLICY = const DefaultRetentionPolicy(); | |
| 690 | |
| 691 /** | |
| 692 * Initialize a newly created instance of this class. | |
| 693 */ | |
| 694 const DefaultRetentionPolicy(); | |
| 695 | |
| 696 // /** | |
| 697 // * Return `true` if there is analysis information in the given entry that ne eds to be | |
| 698 // * computed, where the computation is dependent on having the AST. | |
| 699 // * | |
| 700 // * @param dartEntry the entry being tested | |
| 701 // * @return `true` if there is analysis information that needs to be computed from the AST | |
| 702 // */ | |
| 703 // bool astIsNeeded(DartEntry dartEntry) => | |
| 704 // dartEntry.hasInvalidData(DartEntry.HINTS) || | |
| 705 // dartEntry.hasInvalidData(DartEntry.LINTS) || | |
| 706 // dartEntry.hasInvalidData(DartEntry.VERIFICATION_ERRORS) || | |
| 707 // dartEntry.hasInvalidData(DartEntry.RESOLUTION_ERRORS); | |
| 708 | |
| 709 @override | |
| 710 RetentionPriority getAstPriority(AnalysisTarget target, CacheEntry entry) { | |
| 711 // if (sourceEntry is DartEntry) { | |
|
Paul Berry
2015/03/03 17:21:31
Add a TODO comment here?
Brian Wilkerson
2015/03/03 21:21:17
Done
| |
| 712 // DartEntry dartEntry = sourceEntry; | |
| 713 // if (astIsNeeded(dartEntry)) { | |
| 714 // return RetentionPriority.MEDIUM; | |
| 715 // } | |
| 716 // } | |
| 717 // return RetentionPriority.LOW; | |
| 718 return RetentionPriority.MEDIUM; | |
| 719 } | |
| 720 } | |
| 721 | |
| 722 /** | |
| 723 * The data about a single analysis result that is stored in a [CacheEntry]. | |
| 724 */ | |
| 725 class ResultData { | |
|
Paul Berry
2015/03/03 17:21:31
Would it be beneficial to make this a generic clas
Brian Wilkerson
2015/03/03 21:21:16
Possibly. I'll look into it for a follow-on CL.
| |
| 726 /** | |
| 727 * The state of the cached value. | |
| 728 */ | |
| 729 CacheState state; | |
| 730 | |
| 731 /** | |
| 732 * The value being cached, or the default value for the result if there is no | |
| 733 * value (for example, when the [state] is [CacheState.INVALID]. | |
| 734 */ | |
| 735 Object value; | |
| 736 | |
| 737 /** | |
| 738 * Initialize a newly created result holder to represent the value of data | |
| 739 * described by the given [descriptor]. | |
| 740 */ | |
| 741 ResultData(ResultDescriptor descriptor) { | |
| 742 state = CacheState.INVALID; | |
| 743 value = descriptor.defaultValue; | |
| 744 } | |
| 745 } | |
| 746 | |
| 747 /** | |
| 748 * A cache partition that contains all of the targets in the SDK. | |
| 749 */ | |
| 750 class SdkCachePartition extends CachePartition { | |
| 751 /** | |
| 752 * Initialize a newly created cache partition, belonging to the given | |
| 753 * [context]. The partition will maintain at most [maxCacheSize] AST | |
| 754 * structures in the cache. | |
| 755 */ | |
| 756 SdkCachePartition(InternalAnalysisContext context, int maxCacheSize) | |
| 757 : super(context, maxCacheSize, DefaultRetentionPolicy.POLICY); | |
| 758 | |
| 759 @override | |
| 760 bool contains(AnalysisTarget target) => target.source.isInSystemLibrary; | |
| 761 } | |
| 762 | |
| 763 /** | |
| 764 * A cache partition that contains all targets not contained in other partitions . | |
| 765 */ | |
| 766 class UniversalCachePartition extends CachePartition { | |
| 767 /** | |
| 768 * Initialize a newly created cache partition, belonging to the given | |
| 769 * [context]. The partition will maintain at most [maxCacheSize] AST | |
| 770 * structures in the cache, using the [retentionPolicy] to determine which | |
| 771 * AST structures to flush. | |
| 772 */ | |
| 773 UniversalCachePartition(InternalAnalysisContext context, int maxCacheSize, | |
| 774 CacheRetentionPolicy retentionPolicy) | |
| 775 : super(context, maxCacheSize, retentionPolicy); | |
| 776 | |
| 777 @override | |
| 778 bool contains(AnalysisTarget target) => true; | |
| 779 } | |
| OLD | NEW |