Issue 26629. Detect cache inconsistency in GetContentTask.

When it is detected, invalidate the outputs and throw an exception to
let Driver know that it should to recover.

R=brianwilkerson@google.com, paulberry@google.com
BUG= https://github.com/dart-lang/sdk/issues/26629

Review URL: https://codereview.chromium.org/2050573003 .
This commit is contained in:
Konstantin Shcheglov 2016-06-09 08:31:04 -07:00
parent d7ec8ca711
commit 979543e3a8
6 changed files with 149 additions and 29 deletions

View file

@ -682,6 +682,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
CacheState state = entry.getState(descriptor);
if (state == CacheState.FLUSHED || state == CacheState.INVALID) {
driver.computeResult(target, descriptor);
entry = getCacheEntry(target);
}
state = entry.getState(descriptor);
if (state == CacheState.ERROR) {
@ -729,7 +730,8 @@ class AnalysisContextImpl implements InternalAnalysisContext {
* to stand in for a real one if one does not exist
* facilitating creation a type provider without dart:async.
*/
LibraryElement createMockAsyncLib(LibraryElement coreLibrary, Source asyncSource) {
LibraryElement createMockAsyncLib(
LibraryElement coreLibrary, Source asyncSource) {
InterfaceType objType = coreLibrary.getType('Object').type;
ClassElement _classElement(String typeName, [List<String> parameterNames]) {
@ -1057,13 +1059,13 @@ class AnalysisContextImpl implements InternalAnalysisContext {
bool changed = newContents != originalContents;
if (newContents != null) {
if (changed) {
entry.modificationTime = _contentCache.getModificationStamp(source);
if (!analysisOptions.incremental ||
!_tryPoorMansIncrementalResolution(source, newContents)) {
// Don't compare with old contents because the cache has already been
// updated, and we know at this point that it changed.
_sourceChanged(source, compareWithOld: false);
}
entry.modificationTime = _contentCache.getModificationStamp(source);
entry.setValue(CONTENT, newContents, TargetedResult.EMPTY_LIST);
} else {
entry.modificationTime = _contentCache.getModificationStamp(source);

View file

@ -867,6 +867,27 @@ final ResultDescriptor<CompilationUnit> RESOLVED_UNIT9 =
new ResultDescriptor<CompilationUnit>('RESOLVED_UNIT9', null,
cachingPolicy: AST_CACHING_POLICY);
/**
* List of all `RESOLVED_UNITx` results.
*/
final List<ResultDescriptor<CompilationUnit>> RESOLVED_UNIT_RESULTS =
<ResultDescriptor<CompilationUnit>>[
RESOLVED_UNIT1,
RESOLVED_UNIT2,
RESOLVED_UNIT3,
RESOLVED_UNIT4,
RESOLVED_UNIT5,
RESOLVED_UNIT6,
RESOLVED_UNIT7,
RESOLVED_UNIT8,
RESOLVED_UNIT9,
RESOLVED_UNIT10,
RESOLVED_UNIT11,
RESOLVED_UNIT12,
RESOLVED_UNIT13,
RESOLVED_UNIT
];
/**
* The errors produced while scanning a compilation unit.
*
@ -5578,7 +5599,7 @@ class ScanDartTask extends SourceBasedAnalysisTask {
static Map<String, TaskInput> buildInputs(AnalysisTarget target) {
if (target is Source) {
return <String, TaskInput>{
CONTENT_INPUT_NAME: CONTENT.of(target),
CONTENT_INPUT_NAME: CONTENT.of(target, flushOnAccess: true),
MODIFICATION_TIME_INPUT: MODIFICATION_TIME.of(target)
};
} else if (target is DartScript) {

View file

@ -101,15 +101,18 @@ class AnalysisDriver {
try {
isTaskRunning = true;
AnalysisTask task;
WorkOrder workOrder = createWorkOrderForResult(target, result);
if (workOrder != null) {
while (workOrder.moveNext()) {
// AnalysisTask previousTask = task;
// String message = workOrder.current.toString();
task = performWorkItem(workOrder.current);
// if (task == null) {
// throw new AnalysisException(message, previousTask.caughtException);
// }
while (true) {
try {
WorkOrder workOrder = createWorkOrderForResult(target, result);
if (workOrder != null) {
while (workOrder.moveNext()) {
task = performWorkItem(workOrder.current);
}
}
break;
} on ModificationTimeMismatchError {
// Cache inconsistency was detected and fixed by invalidating
// corresponding results in cache. Computation must be restarted.
}
}
return task;
@ -248,7 +251,12 @@ class AnalysisDriver {
if (currentWorkOrder == null) {
currentWorkOrder = createNextWorkOrder();
} else if (currentWorkOrder.moveNext()) {
performWorkItem(currentWorkOrder.current);
try {
performWorkItem(currentWorkOrder.current);
} on ModificationTimeMismatchError {
reset();
return true;
}
} else {
currentWorkOrder = createNextWorkOrder();
}

View file

@ -4,6 +4,7 @@
library analyzer.src.task.general;
import 'package:analyzer/src/context/cache.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/task/general.dart';
@ -33,13 +34,36 @@ class GetContentTask extends SourceBasedAnalysisTask {
@override
internalPerform() {
Source source = getRequiredSource();
String content;
int modificationTime;
try {
TimestampedData<String> data = context.getContents(source);
outputs[CONTENT] = data.data;
outputs[MODIFICATION_TIME] = data.modificationTime;
content = data.data;
modificationTime = data.modificationTime;
} catch (exception) {
outputs[CONTENT] = '';
outputs[MODIFICATION_TIME] = -1;
content = '';
modificationTime = -1;
}
_validateModificationTime(source, modificationTime);
outputs[CONTENT] = content;
outputs[MODIFICATION_TIME] = modificationTime;
}
/**
* Validate that the [target] cache entry has the same modification time
* as the given. Otherwise throw a new [ModificationTimeMismatchError] and
* instruct to invalidate targets content.
*/
void _validateModificationTime(Source source, int modificationTime) {
AnalysisContext context = this.context;
if (context is InternalAnalysisContext) {
CacheEntry entry = context.getCacheEntry(target);
if (entry != null && entry.modificationTime != modificationTime) {
entry.modificationTime = modificationTime;
entry.setState(CONTENT, CacheState.INVALID);
entry.setState(MODIFICATION_TIME, CacheState.INVALID);
throw new ModificationTimeMismatchError(source);
}
}
}

View file

@ -335,6 +335,8 @@ abstract class AnalysisTask {
// }
} on AnalysisException {
rethrow;
} on ModificationTimeMismatchError {
rethrow;
} catch (exception, stackTrace) {
throw new AnalysisException(
'Unexpected exception while performing $description',
@ -422,6 +424,18 @@ abstract class MapTaskInput<K, V> implements TaskInput<Map<K, V>> {
BinaryFunction<K, dynamic /*element of V*/, dynamic/*=E*/ > mapper);
}
/**
* Instances of this class are thrown when a task detects that the modification
* time of a cache entry is not the same as the actual modification time. This
* means that any analysis results based on the content of the target cannot be
* used anymore and must be invalidated.
*/
class ModificationTimeMismatchError {
final Source source;
ModificationTimeMismatchError(this.source);
}
/**
* A policy object that can compute sizes of results and provide the maximum
* active and idle sizes that can be kept in the cache.

View file

@ -856,6 +856,57 @@ part of lib;
isTrue);
}
void test_flushResolvedUnit_updateFile_dontNotify() {
String oldCode = '';
String newCode = r'''
import 'dart:async';
''';
String path = '/test.dart';
Source source = resourceProvider.newFile(path, oldCode).createSource();
context.applyChanges(new ChangeSet()..addedSource(source));
context.resolveCompilationUnit2(source, source);
// Flush all results units.
context.analysisCache.flush((target, result) {
if (target.source == source) {
return RESOLVED_UNIT_RESULTS.contains(result);
}
return false;
});
// Update the file, but don't notify the context.
resourceProvider.updateFile(path, newCode);
// Driver must detect that the file was changed and recover.
CompilationUnit unit = context.resolveCompilationUnit2(source, source);
expect(unit, isNotNull);
}
void test_flushResolvedUnit_updateFile_dontNotify2() {
String oldCode = r'''
main() {}
''';
String newCode = r'''
import 'dart:async';
main() {}
''';
String path = '/test.dart';
Source source = resourceProvider.newFile(path, oldCode).createSource();
context.applyChanges(new ChangeSet()..addedSource(source));
context.resolveCompilationUnit2(source, source);
// Flush all results units.
context.analysisCache.flush((target, result) {
if (target.source == source) {
if (target.source == source) {
return RESOLVED_UNIT_RESULTS.contains(result);
}
}
return false;
});
// Update the file, but don't notify the context.
resourceProvider.updateFile(path, newCode);
// Driver must detect that the file was changed and recover.
CompilationUnit unit = context.resolveCompilationUnit2(source, source);
expect(unit, isNotNull);
}
void test_getAnalysisOptions() {
expect(context.analysisOptions, isNotNull);
}
@ -1956,7 +2007,7 @@ library expectedToFindSemicolon
if (AnalysisEngine.instance.limitInvalidationInTaskModel) {
expect(source.readCount, 7);
} else {
expect(source.readCount, 5);
expect(source.readCount, 4);
}
}
@ -2171,6 +2222,17 @@ class ClassTwo {
["dart.core", "dart.async", "dart.math", "libA", "libB"]);
}
// void test_resolveCompilationUnit_sourceChangeDuringResolution() {
// _context = new _AnalysisContext_sourceChangeDuringResolution();
// AnalysisContextFactory.initContextWithCore(_context);
// _sourceFactory = _context.sourceFactory;
// Source source = _addSource("/lib.dart", "library lib;");
// CompilationUnit compilationUnit =
// _context.resolveCompilationUnit2(source, source);
// expect(compilationUnit, isNotNull);
// expect(_context.getLineInfo(source), isNotNull);
// }
void test_resolveCompilationUnit_library() {
Source source = addSource("/lib.dart", "library lib;");
LibraryElement library = context.computeLibraryElement(source);
@ -2187,17 +2249,6 @@ class ClassTwo {
expect(compilationUnit, isNotNull);
}
// void test_resolveCompilationUnit_sourceChangeDuringResolution() {
// _context = new _AnalysisContext_sourceChangeDuringResolution();
// AnalysisContextFactory.initContextWithCore(_context);
// _sourceFactory = _context.sourceFactory;
// Source source = _addSource("/lib.dart", "library lib;");
// CompilationUnit compilationUnit =
// _context.resolveCompilationUnit2(source, source);
// expect(compilationUnit, isNotNull);
// expect(_context.getLineInfo(source), isNotNull);
// }
void test_setAnalysisOptions() {
AnalysisOptionsImpl options = new AnalysisOptionsImpl();
options.cacheSize = 42;