| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library analyzer.src.task.driver; | 5 library analyzer.src.task.driver; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'package:analyzer/exception/exception.dart'; |
| 10 import 'package:analyzer/src/context/cache.dart'; | 11 import 'package:analyzer/src/context/cache.dart'; |
| 11 import 'package:analyzer/src/generated/engine.dart' | 12 import 'package:analyzer/src/generated/engine.dart'; |
| 12 hide AnalysisTask, AnalysisContextImpl, WorkManager; | |
| 13 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 14 import 'package:analyzer/src/generated/resolver.dart'; | 13 import 'package:analyzer/src/generated/resolver.dart'; |
| 15 import 'package:analyzer/src/generated/utilities_general.dart'; | 14 import 'package:analyzer/src/generated/utilities_general.dart'; |
| 16 import 'package:analyzer/src/task/inputs.dart'; | 15 import 'package:analyzer/src/task/inputs.dart'; |
| 17 import 'package:analyzer/src/task/manager.dart'; | 16 import 'package:analyzer/src/task/manager.dart'; |
| 18 import 'package:analyzer/task/model.dart'; | 17 import 'package:analyzer/task/model.dart'; |
| 19 | 18 |
| 20 final PerformanceTag workOrderMoveNextPerfTag = | 19 final PerformanceTag analysisDriverProcessOutputs = |
| 20 new PerformanceTag('AnalysisDriver.processOutputs'); |
| 21 |
| 22 final PerformanceTag workOrderMoveNextPerformanceTag = |
| 21 new PerformanceTag('WorkOrder.moveNext'); | 23 new PerformanceTag('WorkOrder.moveNext'); |
| 22 | 24 |
| 23 /** | 25 /** |
| 24 * An object that is used to cause analysis to be performed until all of the | 26 * An object that is used to cause analysis to be performed until all of the |
| 25 * required analysis information has been computed. | 27 * required analysis information has been computed. |
| 26 */ | 28 */ |
| 27 class AnalysisDriver { | 29 class AnalysisDriver { |
| 28 /** | 30 /** |
| 29 * The task manager used to figure out how to compute analysis results. | 31 * The task manager used to figure out how to compute analysis results. |
| 30 */ | 32 */ |
| 31 final TaskManager taskManager; | 33 final TaskManager taskManager; |
| 32 | 34 |
| 33 /** | 35 /** |
| 34 * The list of [WorkManager] used to figure out which analysis results to | 36 * The list of [WorkManager] used to figure out which analysis results to |
| 35 * compute. | 37 * compute. |
| 36 */ | 38 */ |
| 37 final List<WorkManager> workManagers; | 39 final List<WorkManager> workManagers; |
| 38 | 40 |
| 39 /** | 41 /** |
| 40 * The context in which analysis is to be performed. | 42 * The context in which analysis is to be performed. |
| 41 */ | 43 */ |
| 42 final InternalAnalysisContext context; | 44 final InternalAnalysisContext context; |
| 43 | 45 |
| 44 /** | 46 /** |
| 45 * The map of [ComputedResult] controllers. | 47 * The map of [ResultChangedEvent] controllers. |
| 46 */ | 48 */ |
| 47 final Map<ResultDescriptor, | 49 final Map<ResultDescriptor, StreamController<ResultChangedEvent>> |
| 48 StreamController<ComputedResult>> resultComputedControllers = | 50 resultComputedControllers = |
| 49 <ResultDescriptor, StreamController<ComputedResult>>{}; | 51 <ResultDescriptor, StreamController<ResultChangedEvent>>{}; |
| 50 | 52 |
| 51 /** | 53 /** |
| 52 * The work order that was previously computed but that has not yet been | 54 * The work order that was previously computed but that has not yet been |
| 53 * completed. | 55 * completed. |
| 54 */ | 56 */ |
| 55 WorkOrder currentWorkOrder; | 57 WorkOrder currentWorkOrder; |
| 56 | 58 |
| 57 /** | 59 /** |
| 58 * Indicates whether any tasks are currently being performed (or building | 60 * Indicates whether any tasks are currently being performed (or building |
| 59 * their inputs). In debug builds, we use this to ensure that tasks don't | 61 * their inputs). In debug builds, we use this to ensure that tasks don't |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 92 | 94 |
| 93 /** | 95 /** |
| 94 * Perform work until the given [result] has been computed for the given | 96 * Perform work until the given [result] has been computed for the given |
| 95 * [target]. Return the last [AnalysisTask] that was performed. | 97 * [target]. Return the last [AnalysisTask] that was performed. |
| 96 */ | 98 */ |
| 97 AnalysisTask computeResult(AnalysisTarget target, ResultDescriptor result) { | 99 AnalysisTask computeResult(AnalysisTarget target, ResultDescriptor result) { |
| 98 assert(!isTaskRunning); | 100 assert(!isTaskRunning); |
| 99 try { | 101 try { |
| 100 isTaskRunning = true; | 102 isTaskRunning = true; |
| 101 AnalysisTask task; | 103 AnalysisTask task; |
| 102 WorkOrder workOrder = createWorkOrderForResult(target, result); | 104 while (true) { |
| 103 if (workOrder != null) { | 105 try { |
| 104 while (workOrder.moveNext()) { | 106 WorkOrder workOrder = createWorkOrderForResult(target, result); |
| 105 // AnalysisTask previousTask = task; | 107 if (workOrder != null) { |
| 106 // String message = workOrder.current.toString(); | 108 while (workOrder.moveNext()) { |
| 107 task = performWorkItem(workOrder.current); | 109 task = performWorkItem(workOrder.current); |
| 108 // if (task == null) { | 110 } |
| 109 // throw new AnalysisException(message, previousTask.caughtException)
; | 111 } |
| 110 // } | 112 break; |
| 113 } on ModificationTimeMismatchError { |
| 114 // Cache inconsistency was detected and fixed by invalidating |
| 115 // corresponding results in cache. Computation must be restarted. |
| 111 } | 116 } |
| 112 } | 117 } |
| 113 return task; | 118 return task; |
| 114 } finally { | 119 } finally { |
| 115 isTaskRunning = false; | 120 isTaskRunning = false; |
| 116 } | 121 } |
| 117 } | 122 } |
| 118 | 123 |
| 119 /** | 124 /** |
| 120 * Return the work order describing the work that should be getting worked on, | 125 * Return the work order describing the work that should be getting worked on, |
| (...skipping 24 matching lines...) Expand all Loading... |
| 145 if (workOrder != null) { | 150 if (workOrder != null) { |
| 146 return workOrder; | 151 return workOrder; |
| 147 } | 152 } |
| 148 } | 153 } |
| 149 } | 154 } |
| 150 } | 155 } |
| 151 | 156 |
| 152 /** | 157 /** |
| 153 * Create a work order that will produce the given [result] for the given | 158 * Create a work order that will produce the given [result] for the given |
| 154 * [target]. Return the work order that was created, or `null` if the result | 159 * [target]. Return the work order that was created, or `null` if the result |
| 155 * has already been computed. | 160 * has either already been computed or cannot be computed. |
| 156 */ | 161 */ |
| 157 WorkOrder createWorkOrderForResult( | 162 WorkOrder createWorkOrderForResult( |
| 158 AnalysisTarget target, ResultDescriptor result) { | 163 AnalysisTarget target, ResultDescriptor result) { |
| 159 CacheEntry entry = context.getCacheEntry(target); | 164 CacheEntry entry = context.getCacheEntry(target); |
| 160 CacheState state = entry.getState(result); | 165 CacheState state = entry.getState(result); |
| 161 if (state == CacheState.VALID || | 166 if (state == CacheState.VALID || |
| 162 state == CacheState.ERROR || | 167 state == CacheState.ERROR || |
| 163 state == CacheState.IN_PROCESS) { | 168 state == CacheState.IN_PROCESS) { |
| 164 return null; | 169 return null; |
| 165 } | 170 } |
| 171 if (context.aboutToComputeResult(entry, result)) { |
| 172 return null; |
| 173 } |
| 166 TaskDescriptor taskDescriptor = taskManager.findTask(target, result); | 174 TaskDescriptor taskDescriptor = taskManager.findTask(target, result); |
| 175 if (taskDescriptor == null) { |
| 176 return null; |
| 177 } |
| 167 try { | 178 try { |
| 168 WorkItem workItem = new WorkItem(context, target, taskDescriptor, result); | 179 WorkItem workItem = |
| 180 new WorkItem(context, target, taskDescriptor, result, 0, null); |
| 169 return new WorkOrder(taskManager, workItem); | 181 return new WorkOrder(taskManager, workItem); |
| 170 } catch (exception, stackTrace) { | 182 } catch (exception, stackTrace) { |
| 171 throw new AnalysisException( | 183 throw new AnalysisException( |
| 172 'Could not create work order (target = $target; taskDescriptor = $task
Descriptor; result = $result)', | 184 'Could not create work order (target = $target; taskDescriptor = $task
Descriptor; result = $result)', |
| 173 new CaughtException(exception, stackTrace)); | 185 new CaughtException(exception, stackTrace)); |
| 174 } | 186 } |
| 175 } | 187 } |
| 176 | 188 |
| 177 /** | 189 /** |
| 178 * Create a work order that will produce the required analysis results for | 190 * Create a work order that will produce the required analysis results for |
| (...skipping 16 matching lines...) Expand all Loading... |
| 195 } | 207 } |
| 196 } | 208 } |
| 197 } | 209 } |
| 198 return null; | 210 return null; |
| 199 } | 211 } |
| 200 | 212 |
| 201 /** | 213 /** |
| 202 * Return the stream that is notified when a new value for the given | 214 * Return the stream that is notified when a new value for the given |
| 203 * [descriptor] is computed. | 215 * [descriptor] is computed. |
| 204 */ | 216 */ |
| 205 Stream<ComputedResult> onResultComputed(ResultDescriptor descriptor) { | 217 Stream<ResultChangedEvent> onResultComputed(ResultDescriptor descriptor) { |
| 206 return resultComputedControllers | 218 return resultComputedControllers.putIfAbsent(descriptor, () { |
| 207 .putIfAbsent(descriptor, | 219 return new StreamController<ResultChangedEvent>.broadcast(sync: true); |
| 208 () => new StreamController<ComputedResult>.broadcast(sync: true)) | 220 }).stream; |
| 209 .stream; | |
| 210 } | 221 } |
| 211 | 222 |
| 212 /** | 223 /** |
| 213 * Perform the next analysis task, and return `true` if there is more work to | 224 * Perform the next analysis task, and return `true` if there is more work to |
| 214 * be done in order to compute all of the required analysis information. | 225 * be done in order to compute all of the required analysis information. |
| 215 */ | 226 */ |
| 216 bool performAnalysisTask() { | 227 bool performAnalysisTask() { |
| 217 // | 228 // |
| 218 // TODO(brianwilkerson) This implementaiton does not allow us to prioritize | 229 // TODO(brianwilkerson) This implementaiton does not allow us to prioritize |
| 219 // work across contexts. What we need is a way for an external client to ask | 230 // work across contexts. What we need is a way for an external client to ask |
| (...skipping 13 matching lines...) Expand all Loading... |
| 233 // the client choosing a context and telling it do to some work, the client | 244 // the client choosing a context and telling it do to some work, the client |
| 234 // simply says "do some work", and the engine chooses the best thing to do | 245 // simply says "do some work", and the engine chooses the best thing to do |
| 235 // next regardless of what context it's in. | 246 // next regardless of what context it's in. |
| 236 // | 247 // |
| 237 assert(!isTaskRunning); | 248 assert(!isTaskRunning); |
| 238 try { | 249 try { |
| 239 isTaskRunning = true; | 250 isTaskRunning = true; |
| 240 if (currentWorkOrder == null) { | 251 if (currentWorkOrder == null) { |
| 241 currentWorkOrder = createNextWorkOrder(); | 252 currentWorkOrder = createNextWorkOrder(); |
| 242 } else if (currentWorkOrder.moveNext()) { | 253 } else if (currentWorkOrder.moveNext()) { |
| 243 performWorkItem(currentWorkOrder.current); | 254 try { |
| 255 performWorkItem(currentWorkOrder.current); |
| 256 } on ModificationTimeMismatchError { |
| 257 reset(); |
| 258 return true; |
| 259 } |
| 244 } else { | 260 } else { |
| 245 currentWorkOrder = createNextWorkOrder(); | 261 currentWorkOrder = createNextWorkOrder(); |
| 246 } | 262 } |
| 247 return currentWorkOrder != null; | 263 return currentWorkOrder != null; |
| 248 } finally { | 264 } finally { |
| 249 isTaskRunning = false; | 265 isTaskRunning = false; |
| 250 } | 266 } |
| 251 } | 267 } |
| 252 | 268 |
| 253 /** | 269 /** |
| 254 * Perform the given work item. | 270 * Perform the given work item. |
| 255 * Return the performed [AnalysisTask]. | 271 * Return the performed [AnalysisTask]. |
| 256 */ | 272 */ |
| 257 AnalysisTask performWorkItem(WorkItem item) { | 273 AnalysisTask performWorkItem(WorkItem item) { |
| 258 if (item.exception != null) { | 274 if (item.exception != null) { |
| 259 // Mark all of the results that the task would have computed as being in | 275 // Mark all of the results that the task would have computed as being in |
| 260 // ERROR with the exception recorded on the work item. | 276 // ERROR with the exception recorded on the work item. |
| 261 CacheEntry targetEntry = context.getCacheEntry(item.target); | 277 CacheEntry targetEntry = context.getCacheEntry(item.target); |
| 262 targetEntry.setErrorState(item.exception, item.descriptor.results); | 278 targetEntry.setErrorState(item.exception, item.descriptor.results); |
| 263 return null; | 279 return null; |
| 264 } | 280 } |
| 265 // Otherwise, perform the task. | 281 // Otherwise, perform the task. |
| 266 AnalysisTask task = item.buildTask(); | 282 AnalysisTask task = item.buildTask(); |
| 267 _onTaskStartedController.add(task); | 283 _onTaskStartedController.add(task); |
| 268 task.perform(); | 284 task.perform(); |
| 269 AnalysisTarget target = task.target; | 285 analysisDriverProcessOutputs.makeCurrentWhile(() { |
| 270 CacheEntry entry = context.getCacheEntry(target); | 286 AnalysisTarget target = task.target; |
| 271 if (task.caughtException == null) { | 287 CacheEntry entry = context.getCacheEntry(target); |
| 272 List<TargetedResult> dependedOn = item.inputTargetedResults.toList(); | 288 if (task.caughtException == null) { |
| 273 Map<ResultDescriptor, dynamic> outputs = task.outputs; | 289 List<TargetedResult> dependedOn = |
| 274 for (ResultDescriptor result in task.descriptor.results) { | 290 context.analysisOptions.trackCacheDependencies |
| 275 // TODO(brianwilkerson) We could check here that a value was produced | 291 ? item.inputTargetedResults.toList() |
| 276 // and throw an exception if not (unless we want to allow null values). | 292 : const <TargetedResult>[]; |
| 277 entry.setValue(result, outputs[result], dependedOn); | 293 Map<ResultDescriptor, dynamic> outputs = task.outputs; |
| 294 List<ResultDescriptor> results = task.descriptor.results; |
| 295 int resultLength = results.length; |
| 296 for (int i = 0; i < resultLength; i++) { |
| 297 ResultDescriptor result = results[i]; |
| 298 // TODO(brianwilkerson) We could check here that a value was produced |
| 299 // and throw an exception if not (unless we want to allow null values)
. |
| 300 entry.setValue(result, outputs[result], dependedOn); |
| 301 } |
| 302 outputs.forEach((ResultDescriptor descriptor, value) { |
| 303 StreamController<ResultChangedEvent> controller = |
| 304 resultComputedControllers[descriptor]; |
| 305 if (controller != null) { |
| 306 ResultChangedEvent event = new ResultChangedEvent( |
| 307 context, target, descriptor, value, true); |
| 308 controller.add(event); |
| 309 } |
| 310 }); |
| 311 for (WorkManager manager in workManagers) { |
| 312 manager.resultsComputed(target, outputs); |
| 313 } |
| 314 } else { |
| 315 entry.setErrorState(task.caughtException, item.descriptor.results); |
| 278 } | 316 } |
| 279 outputs.forEach((ResultDescriptor descriptor, value) { | 317 }); |
| 280 StreamController<ComputedResult> controller = | |
| 281 resultComputedControllers[descriptor]; | |
| 282 if (controller != null) { | |
| 283 ComputedResult event = | |
| 284 new ComputedResult(context, descriptor, target, value); | |
| 285 controller.add(event); | |
| 286 } | |
| 287 }); | |
| 288 for (WorkManager manager in workManagers) { | |
| 289 manager.resultsComputed(target, outputs); | |
| 290 } | |
| 291 } else { | |
| 292 entry.setErrorState(task.caughtException, item.descriptor.results); | |
| 293 } | |
| 294 _onTaskCompletedController.add(task); | 318 _onTaskCompletedController.add(task); |
| 295 return task; | 319 return task; |
| 296 } | 320 } |
| 297 | 321 |
| 298 /** | 322 /** |
| 299 * Reset the state of the driver in response to a change in the state of one | 323 * Reset the state of the driver in response to a change in the state of one |
| 300 * or more analysis targets. This will cause any analysis that was currently | 324 * or more analysis targets. This will cause any analysis that was currently |
| 301 * in process to be stopped and for analysis to resume based on the new state. | 325 * in process to be stopped and for analysis to resume based on the new state. |
| 302 */ | 326 */ |
| 303 void reset() { | 327 void reset() { |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 382 | 406 |
| 383 /** | 407 /** |
| 384 * Determine the next strongly connected component in the graph, and return | 408 * Determine the next strongly connected component in the graph, and return |
| 385 * it. The client is expected to evaluate this component before calling | 409 * it. The client is expected to evaluate this component before calling |
| 386 * [getNextStronglyConnectedComponent] again. | 410 * [getNextStronglyConnectedComponent] again. |
| 387 */ | 411 */ |
| 388 StronglyConnectedComponent<Node> getNextStronglyConnectedComponent() { | 412 StronglyConnectedComponent<Node> getNextStronglyConnectedComponent() { |
| 389 while (_currentIndices.isNotEmpty) { | 413 while (_currentIndices.isNotEmpty) { |
| 390 Node nextUnevaluatedInput = getNextInput(_path[_currentIndices.last], | 414 Node nextUnevaluatedInput = getNextInput(_path[_currentIndices.last], |
| 391 _provisionalDependencies[_currentIndices.last]); | 415 _provisionalDependencies[_currentIndices.last]); |
| 416 // If the assertion below fails, it indicates that [getNextInput] did not |
| 417 // skip an input that we asked it to skip. |
| 392 assert(!_provisionalDependencies[_currentIndices.last] | 418 assert(!_provisionalDependencies[_currentIndices.last] |
| 393 .contains(nextUnevaluatedInput)); | 419 .contains(nextUnevaluatedInput)); |
| 394 if (nextUnevaluatedInput != null) { | 420 if (nextUnevaluatedInput != null) { |
| 395 // TODO(paulberry): the call to _path.indexOf makes the algorithm | 421 // TODO(paulberry): the call to _path.indexOf makes the algorithm |
| 396 // O(n^2) in the depth of the dependency graph. If this becomes a | 422 // O(n^2) in the depth of the dependency graph. If this becomes a |
| 397 // problem, consider maintaining a map from node to index. | 423 // problem, consider maintaining a map from node to index. |
| 398 int previousIndex = _path.indexOf(nextUnevaluatedInput); | 424 int previousIndex = _path.indexOf(nextUnevaluatedInput); |
| 399 if (previousIndex != -1) { | 425 if (previousIndex != -1) { |
| 400 // Update contractedPath to indicate that all nodes in the path | 426 // Update contractedPath to indicate that all nodes in the path |
| 401 // between previousIndex and currentIndex are part of the same | 427 // between previousIndex and currentIndex are part of the same |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 466 CacheEntry getCacheEntry(AnalysisTarget target); | 492 CacheEntry getCacheEntry(AnalysisTarget target); |
| 467 } | 493 } |
| 468 | 494 |
| 469 /** | 495 /** |
| 470 * An exception indicating that an attempt was made to perform a task on a | 496 * An exception indicating that an attempt was made to perform a task on a |
| 471 * target while gathering the inputs to perform the same task for the same | 497 * target while gathering the inputs to perform the same task for the same |
| 472 * target. | 498 * target. |
| 473 */ | 499 */ |
| 474 class InfiniteTaskLoopException extends AnalysisException { | 500 class InfiniteTaskLoopException extends AnalysisException { |
| 475 /** | 501 /** |
| 502 * A concrete cyclic path of [TargetedResults] within the [dependencyCycle], |
| 503 * `null` if no such path exists. All nodes in the path are in the |
| 504 * dependencyCycle, but the path is not guaranteed to cover the |
| 505 * entire cycle. |
| 506 */ |
| 507 final List<TargetedResult> cyclicPath; |
| 508 |
| 509 /** |
| 476 * If a dependency cycle was found while computing the inputs for the task, | 510 * If a dependency cycle was found while computing the inputs for the task, |
| 477 * the set of [WorkItem]s contained in the cycle (if there are overlapping | 511 * the set of [WorkItem]s contained in the cycle (if there are overlapping |
| 478 * cycles, this is the set of all [WorkItem]s in the entire strongly | 512 * cycles, this is the set of all [WorkItem]s in the entire strongly |
| 479 * connected component). Otherwise, `null`. | 513 * connected component). Otherwise, `null`. |
| 480 */ | 514 */ |
| 481 final List<WorkItem> dependencyCycle; | 515 final List<WorkItem> dependencyCycle; |
| 482 | 516 |
| 483 /** | 517 /** |
| 484 * Initialize a newly created exception to represent a failed attempt to | 518 * Initialize a newly created exception to represent a failed attempt to |
| 485 * perform the given [task] due to the given [dependencyCycle]. | 519 * perform the given [task] due to the given [dependencyCycle]. |
| 486 */ | 520 */ |
| 487 InfiniteTaskLoopException(AnalysisTask task, this.dependencyCycle) | 521 InfiniteTaskLoopException(AnalysisTask task, List<WorkItem> dependencyCycle, |
| 488 : super( | 522 [List<TargetedResult> cyclicPath]) |
| 489 'Infinite loop while performing task ${task.descriptor.name} for ${t
ask.target}'); | 523 : this.dependencyCycle = dependencyCycle, |
| 524 this.cyclicPath = cyclicPath, |
| 525 super(_composeMessage(task, dependencyCycle, cyclicPath)); |
| 526 |
| 527 /** |
| 528 * Compose an error message based on the data we have available. |
| 529 */ |
| 530 static String _composeMessage(AnalysisTask task, |
| 531 List<WorkItem> dependencyCycle, List<TargetedResult> cyclicPath) { |
| 532 StringBuffer buffer = new StringBuffer(); |
| 533 buffer.write('Infinite loop while performing task '); |
| 534 buffer.write(task.descriptor.name); |
| 535 buffer.write(' for '); |
| 536 buffer.writeln(task.target); |
| 537 buffer.writeln(' Dependency Cycle:'); |
| 538 for (WorkItem item in dependencyCycle) { |
| 539 buffer.write(' '); |
| 540 buffer.writeln(item); |
| 541 } |
| 542 if (cyclicPath != null) { |
| 543 buffer.writeln(' Cyclic Path:'); |
| 544 for (TargetedResult result in cyclicPath) { |
| 545 buffer.write(' '); |
| 546 buffer.writeln(result); |
| 547 } |
| 548 } |
| 549 return buffer.toString(); |
| 550 } |
| 490 } | 551 } |
| 491 | 552 |
| 492 /** | 553 /** |
| 493 * Object used by CycleAwareDependencyWalker to report a single strongly | 554 * Object used by [CycleAwareDependencyWalker] to report a single strongly |
| 494 * connected component of nodes. | 555 * connected component of nodes. |
| 495 */ | 556 */ |
| 496 class StronglyConnectedComponent<Node> { | 557 class StronglyConnectedComponent<Node> { |
| 497 /** | 558 /** |
| 498 * The nodes contained in the strongly connected component. | 559 * The nodes contained in the strongly connected component. |
| 499 */ | 560 */ |
| 500 final List<Node> nodes; | 561 final List<Node> nodes; |
| 501 | 562 |
| 502 /** | 563 /** |
| 503 * Indicates whether the strongly component contains any cycles. Note that | 564 * Indicates whether the strongly component contains any cycles. Note that |
| 504 * if [nodes] has multiple elements, this will always be `true`. However, if | 565 * if [nodes] has multiple elements, this will always be `true`. However, if |
| 505 * [nodes] has exactly one element, this may be either `true` or `false` | 566 * [nodes] has exactly one element, this may be either `true` or `false` |
| 506 * depending on whether the node has a dependency on itself. | 567 * depending on whether the node has a dependency on itself. |
| 507 */ | 568 */ |
| 508 final bool containsCycle; | 569 final bool containsCycle; |
| 509 | 570 |
| 510 StronglyConnectedComponent(this.nodes, this.containsCycle); | 571 StronglyConnectedComponent(this.nodes, this.containsCycle); |
| 511 } | 572 } |
| 512 | 573 |
| 513 /** | 574 /** |
| 514 * A description of a single anaysis task that can be performed to advance | 575 * A description of a single analysis task that can be performed to advance |
| 515 * analysis. | 576 * analysis. |
| 516 */ | 577 */ |
| 517 class WorkItem { | 578 class WorkItem { |
| 518 /** | 579 /** |
| 519 * The context in which the task will be performed. | 580 * The context in which the task will be performed. |
| 520 */ | 581 */ |
| 521 final InternalAnalysisContext context; | 582 final InternalAnalysisContext context; |
| 522 | 583 |
| 523 /** | 584 /** |
| 524 * The target for which a task is to be performed. | 585 * The target for which a task is to be performed. |
| 525 */ | 586 */ |
| 526 final AnalysisTarget target; | 587 final AnalysisTarget target; |
| 527 | 588 |
| 528 /** | 589 /** |
| 529 * A description of the task to be performed. | 590 * A description of the task to be performed. |
| 530 */ | 591 */ |
| 531 final TaskDescriptor descriptor; | 592 final TaskDescriptor descriptor; |
| 532 | 593 |
| 533 /** | 594 /** |
| 534 * The [ResultDescriptor] which was led to this work item being spawned. | 595 * The [ResultDescriptor] which was led to this work item being spawned. |
| 535 */ | 596 */ |
| 536 final ResultDescriptor spawningResult; | 597 final ResultDescriptor spawningResult; |
| 537 | 598 |
| 538 /** | 599 /** |
| 600 * The level of this item in its [WorkOrder]. |
| 601 */ |
| 602 final int level; |
| 603 |
| 604 /** |
| 605 * The work order that this item is part of, may be `null`. |
| 606 */ |
| 607 WorkOrder workOrder; |
| 608 |
| 609 /** |
| 539 * An iterator used to iterate over the descriptors of the inputs to the task, | 610 * An iterator used to iterate over the descriptors of the inputs to the task, |
| 540 * or `null` if all of the inputs have been collected and the task can be | 611 * or `null` if all of the inputs have been collected and the task can be |
| 541 * created. | 612 * created. |
| 542 */ | 613 */ |
| 543 TaskInputBuilder builder; | 614 TopLevelTaskInputBuilder builder; |
| 544 | 615 |
| 545 /** | 616 /** |
| 546 * The [TargetedResult]s outputs of this task depends on. | 617 * The [TargetedResult]s outputs of this task depends on. |
| 547 */ | 618 */ |
| 548 final HashSet<TargetedResult> inputTargetedResults = | 619 final HashSet<TargetedResult> inputTargetedResults = |
| 549 new HashSet<TargetedResult>(); | 620 new HashSet<TargetedResult>(); |
| 550 | 621 |
| 551 /** | 622 /** |
| 552 * The inputs to the task that have been computed. | 623 * The inputs to the task that have been computed. |
| 553 */ | 624 */ |
| 554 Map<String, dynamic> inputs; | 625 Map<String, dynamic> inputs = const <String, dynamic>{}; |
| 555 | 626 |
| 556 /** | 627 /** |
| 557 * The exception that was found while trying to populate the inputs. If this | 628 * The exception that was found while trying to populate the inputs. If this |
| 558 * field is non-`null`, then the task cannot be performed and all of the | 629 * field is non-`null`, then the task cannot be performed and all of the |
| 559 * results that this task would have computed need to be marked as being in | 630 * results that this task would have computed need to be marked as being in |
| 560 * ERROR with this exception. | 631 * ERROR with this exception. |
| 561 */ | 632 */ |
| 562 CaughtException exception = null; | 633 CaughtException exception = null; |
| 563 | 634 |
| 564 /** | 635 /** |
| 565 * If a dependency cycle was found while computing the inputs for the task, | 636 * If a dependency cycle was found while computing the inputs for the task, |
| 566 * the set of [WorkItem]s contained in the cycle (if there are overlapping | 637 * the set of [WorkItem]s contained in the cycle (if there are overlapping |
| 567 * cycles, this is the set of all [WorkItem]s in the entire strongly | 638 * cycles, this is the set of all [WorkItem]s in the entire strongly |
| 568 * connected component). Otherwise, `null`. | 639 * connected component). Otherwise, `null`. |
| 569 */ | 640 */ |
| 570 List<WorkItem> dependencyCycle; | 641 List<WorkItem> dependencyCycle; |
| 571 | 642 |
| 572 /** | 643 /** |
| 573 * Initialize a newly created work item to compute the inputs for the task | 644 * Initialize a newly created work item to compute the inputs for the task |
| 574 * described by the given descriptor. | 645 * described by the given descriptor. |
| 575 */ | 646 */ |
| 576 WorkItem(this.context, this.target, this.descriptor, this.spawningResult) { | 647 WorkItem(this.context, this.target, this.descriptor, this.spawningResult, |
| 648 this.level, this.workOrder) { |
| 577 AnalysisTarget actualTarget = | 649 AnalysisTarget actualTarget = |
| 578 identical(target, AnalysisContextTarget.request) | 650 identical(target, AnalysisContextTarget.request) |
| 579 ? new AnalysisContextTarget(context) | 651 ? new AnalysisContextTarget(context) |
| 580 : target; | 652 : target; |
| 653 // print('${'\t' * level}$spawningResult of $actualTarget'); |
| 581 Map<String, TaskInput> inputDescriptors = | 654 Map<String, TaskInput> inputDescriptors = |
| 582 descriptor.createTaskInputs(actualTarget); | 655 descriptor.createTaskInputs(actualTarget); |
| 583 builder = new TopLevelTaskInputBuilder(inputDescriptors); | 656 builder = new TopLevelTaskInputBuilder(inputDescriptors); |
| 584 if (!builder.moveNext()) { | 657 if (!builder.moveNext()) { |
| 585 builder = null; | 658 builder = null; |
| 586 } | 659 } |
| 587 inputs = new HashMap<String, dynamic>(); | |
| 588 } | 660 } |
| 589 | 661 |
| 590 @override | 662 @override |
| 591 int get hashCode => | 663 int get hashCode => |
| 592 JenkinsSmiHash.hash2(descriptor.hashCode, target.hashCode); | 664 JenkinsSmiHash.hash2(descriptor.hashCode, target.hashCode); |
| 593 | 665 |
| 594 @override | 666 @override |
| 595 bool operator ==(other) { | 667 bool operator ==(other) { |
| 596 if (other is WorkItem) { | 668 if (other is WorkItem) { |
| 597 return this.descriptor == other.descriptor && this.target == other.target; | 669 return this.descriptor == other.descriptor && this.target == other.target; |
| (...skipping 27 matching lines...) Expand all Loading... |
| 625 * returned if some of the inputs cannot be computed and the task cannot be | 697 * returned if some of the inputs cannot be computed and the task cannot be |
| 626 * performed. Callers can differentiate between these cases by checking the | 698 * performed. Callers can differentiate between these cases by checking the |
| 627 * [exception] field. If the field is `null`, then the task can be performed; | 699 * [exception] field. If the field is `null`, then the task can be performed; |
| 628 * if the field is non-`null` then the task cannot be performed and all of the | 700 * if the field is non-`null` then the task cannot be performed and all of the |
| 629 * tasks' results should be marked as being in ERROR. | 701 * tasks' results should be marked as being in ERROR. |
| 630 */ | 702 */ |
| 631 WorkItem gatherInputs(TaskManager taskManager, List<WorkItem> skipInputs) { | 703 WorkItem gatherInputs(TaskManager taskManager, List<WorkItem> skipInputs) { |
| 632 while (builder != null) { | 704 while (builder != null) { |
| 633 AnalysisTarget inputTarget = builder.currentTarget; | 705 AnalysisTarget inputTarget = builder.currentTarget; |
| 634 ResultDescriptor inputResult = builder.currentResult; | 706 ResultDescriptor inputResult = builder.currentResult; |
| 707 |
| 708 // TODO(scheglov) record information to debug |
| 709 // https://github.com/dart-lang/sdk/issues/24939 |
| 710 if (inputTarget == null || inputResult == null) { |
| 711 try { |
| 712 String message = |
| 713 'Invalid input descriptor ($inputTarget, $inputResult) for $this'; |
| 714 if (workOrder != null) { |
| 715 message += '\nPath:\n' + workOrder.workItems.join('|\n'); |
| 716 } |
| 717 throw new AnalysisException(message); |
| 718 } catch (exception, stackTrace) { |
| 719 this.exception = new CaughtException(exception, stackTrace); |
| 720 AnalysisEngine.instance.logger |
| 721 .logError('Task failed: $this', this.exception); |
| 722 } |
| 723 return null; |
| 724 } |
| 725 |
| 635 inputTargetedResults.add(new TargetedResult(inputTarget, inputResult)); | 726 inputTargetedResults.add(new TargetedResult(inputTarget, inputResult)); |
| 636 CacheEntry inputEntry = context.getCacheEntry(inputTarget); | 727 CacheEntry inputEntry = context.getCacheEntry(inputTarget); |
| 637 CacheState inputState = inputEntry.getState(inputResult); | 728 CacheState inputState = inputEntry.getState(inputResult); |
| 638 if (skipInputs.any((WorkItem item) => | 729 if (inputState == CacheState.ERROR) { |
| 639 item.target == inputTarget && item.spawningResult == inputResult)) { | |
| 640 // This input is being skipped due to a circular dependency. Tell the | |
| 641 // builder that it's not available so we can move on to other inputs. | |
| 642 builder.currentValueNotAvailable(); | |
| 643 } else if (inputState == CacheState.ERROR) { | |
| 644 exception = inputEntry.exception; | 730 exception = inputEntry.exception; |
| 645 return null; | 731 return null; |
| 646 } else if (inputState == CacheState.IN_PROCESS) { | 732 } else if (inputState == CacheState.IN_PROCESS) { |
| 647 // | 733 // |
| 648 // TODO(brianwilkerson) Implement this case. | 734 // TODO(brianwilkerson) Implement this case. |
| 649 // | 735 // |
| 650 // One possibility would be to return a WorkItem that would perform a | 736 // One possibility would be to return a WorkItem that would perform a |
| 651 // no-op task in order to cause us to come back to this work item on the | 737 // no-op task in order to cause us to come back to this work item on the |
| 652 // next iteration. It would be more efficient, in general, to push this | 738 // next iteration. It would be more efficient, in general, to push this |
| 653 // input onto a waiting list and proceed to the next input so that work | 739 // input onto a waiting list and proceed to the next input so that work |
| 654 // could proceed, but given that the only result that can currently be | 740 // could proceed, but given that the only result that can currently be |
| 655 // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort | 741 // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort |
| 656 // to implement the general solution at this point. | 742 // to implement the general solution at this point. |
| 657 // | 743 // |
| 658 throw new UnimplementedError(); | 744 throw new UnimplementedError(); |
| 659 } else if (inputState != CacheState.VALID) { | 745 } else if (inputState != CacheState.VALID) { |
| 660 try { | 746 if (context.aboutToComputeResult(inputEntry, inputResult)) { |
| 661 TaskDescriptor descriptor = | 747 inputState = CacheState.VALID; |
| 662 taskManager.findTask(inputTarget, inputResult); | 748 builder.currentValue = inputEntry.getValue(inputResult); |
| 663 return new WorkItem(context, inputTarget, descriptor, inputResult); | 749 } else { |
| 664 } on AnalysisException catch (exception, stackTrace) { | 750 try { |
| 665 this.exception = new CaughtException(exception, stackTrace); | 751 TaskDescriptor descriptor = |
| 666 return null; | 752 taskManager.findTask(inputTarget, inputResult); |
| 753 if (descriptor == null) { |
| 754 throw new AnalysisException( |
| 755 'Cannot find task to build $inputResult for $inputTarget'); |
| 756 } |
| 757 if (skipInputs.any((WorkItem item) => |
| 758 item.target == inputTarget && item.descriptor == descriptor)) { |
| 759 // This input is being skipped due to a circular dependency. Tell |
| 760 // the builder that it's not available so we can move on to other |
| 761 // inputs. |
| 762 builder.currentValueNotAvailable(); |
| 763 } else { |
| 764 return new WorkItem(context, inputTarget, descriptor, inputResult, |
| 765 level + 1, workOrder); |
| 766 } |
| 767 } on AnalysisException catch (exception, stackTrace) { |
| 768 this.exception = new CaughtException(exception, stackTrace); |
| 769 return null; |
| 770 } catch (exception, stackTrace) { |
| 771 this.exception = new CaughtException(exception, stackTrace); |
| 772 throw new AnalysisException( |
| 773 'Cannot create work order to build $inputResult for $inputTarget
', |
| 774 this.exception); |
| 775 } |
| 667 } | 776 } |
| 668 } else { | 777 } else { |
| 669 builder.currentValue = inputEntry.getValue(inputResult); | 778 builder.currentValue = inputEntry.getValue(inputResult); |
| 670 if (builder.flushOnAccess) { | 779 if (builder.flushOnAccess) { |
| 671 inputEntry.setState(inputResult, CacheState.FLUSHED); | 780 inputEntry.setState(inputResult, CacheState.FLUSHED); |
| 672 } | 781 } |
| 673 } | 782 } |
| 674 if (!builder.moveNext()) { | 783 if (!builder.moveNext()) { |
| 675 inputs = builder.inputValue; | 784 inputs = builder.inputValue; |
| 676 builder = null; | 785 builder = null; |
| (...skipping 24 matching lines...) Expand all Loading... |
| 701 * | 810 * |
| 702 * Null if the [_dependencyWalker] hasn't been used yet. | 811 * Null if the [_dependencyWalker] hasn't been used yet. |
| 703 */ | 812 */ |
| 704 List<WorkItem> currentItems; | 813 List<WorkItem> currentItems; |
| 705 | 814 |
| 706 /** | 815 /** |
| 707 * Initialize a newly created work order to compute the result described by | 816 * Initialize a newly created work order to compute the result described by |
| 708 * the given work item. | 817 * the given work item. |
| 709 */ | 818 */ |
| 710 WorkOrder(TaskManager taskManager, WorkItem item) | 819 WorkOrder(TaskManager taskManager, WorkItem item) |
| 711 : _dependencyWalker = new _WorkOrderDependencyWalker(taskManager, item); | 820 : _dependencyWalker = new _WorkOrderDependencyWalker(taskManager, item) { |
| 821 item.workOrder = this; |
| 822 } |
| 712 | 823 |
| 713 @override | 824 @override |
| 714 WorkItem get current { | 825 WorkItem get current { |
| 715 if (currentItems == null) { | 826 if (currentItems == null) { |
| 716 return null; | 827 return null; |
| 717 } else { | 828 } else { |
| 718 return currentItems.last; | 829 return currentItems.last; |
| 719 } | 830 } |
| 720 } | 831 } |
| 721 | 832 |
| 833 List<WorkItem> get workItems => _dependencyWalker._path; |
| 834 |
| 722 @override | 835 @override |
| 723 bool moveNext() { | 836 bool moveNext() { |
| 724 return workOrderMoveNextPerfTag.makeCurrentWhile(() { | 837 return workOrderMoveNextPerformanceTag.makeCurrentWhile(() { |
| 725 if (currentItems != null && currentItems.length > 1) { | 838 if (currentItems != null && currentItems.length > 1) { |
| 726 // Yield more items. | 839 // Yield more items. |
| 727 currentItems.removeLast(); | 840 currentItems.removeLast(); |
| 728 return true; | 841 return true; |
| 729 } else { | 842 } else { |
| 730 // Get a new strongly connected component. | 843 // Get a new strongly connected component. |
| 731 StronglyConnectedComponent<WorkItem> nextStronglyConnectedComponent = | 844 StronglyConnectedComponent<WorkItem> nextStronglyConnectedComponent = |
| 732 _dependencyWalker.getNextStronglyConnectedComponent(); | 845 _dependencyWalker.getNextStronglyConnectedComponent(); |
| 733 if (nextStronglyConnectedComponent == null) { | 846 if (nextStronglyConnectedComponent == null) { |
| 734 currentItems = null; | 847 currentItems = null; |
| 735 return false; | 848 return false; |
| 736 } | 849 } |
| 737 currentItems = nextStronglyConnectedComponent.nodes; | 850 currentItems = nextStronglyConnectedComponent.nodes; |
| 738 if (nextStronglyConnectedComponent.containsCycle) { | 851 if (nextStronglyConnectedComponent.containsCycle) { |
| 739 // A cycle has been found. | 852 // A cycle has been found. |
| 740 for (WorkItem item in currentItems) { | 853 for (WorkItem item in currentItems) { |
| 741 item.dependencyCycle = currentItems.toList(); | 854 item.dependencyCycle = currentItems.toList(); |
| 742 } | 855 } |
| 743 } else { | 856 } else { |
| 744 assert(currentItems.length == 1); | 857 assert(currentItems.length == 1); |
| 745 } | 858 } |
| 746 return true; | 859 return true; |
| 747 } | 860 } |
| 748 }); | 861 }); |
| 749 } | 862 } |
| 750 } | 863 } |
| 751 | 864 |
| 752 /** | 865 /** |
| 753 * Specilaization of [CycleAwareDependencyWalker] for use by [WorkOrder]. | 866 * Specialization of [CycleAwareDependencyWalker] for use by [WorkOrder]. |
| 754 */ | 867 */ |
| 755 class _WorkOrderDependencyWalker extends CycleAwareDependencyWalker<WorkItem> { | 868 class _WorkOrderDependencyWalker extends CycleAwareDependencyWalker<WorkItem> { |
| 756 /** | 869 /** |
| 757 * The task manager used to build work items. | 870 * The task manager used to build work items. |
| 758 */ | 871 */ |
| 759 final TaskManager taskManager; | 872 final TaskManager taskManager; |
| 760 | 873 |
| 761 _WorkOrderDependencyWalker(this.taskManager, WorkItem startingNode) | 874 _WorkOrderDependencyWalker(this.taskManager, WorkItem startingNode) |
| 762 : super(startingNode); | 875 : super(startingNode); |
| 763 | 876 |
| 764 @override | 877 @override |
| 765 WorkItem getNextInput(WorkItem node, List<WorkItem> skipInputs) { | 878 WorkItem getNextInput(WorkItem node, List<WorkItem> skipInputs) { |
| 766 return node.gatherInputs(taskManager, skipInputs); | 879 return node.gatherInputs(taskManager, skipInputs); |
| 767 } | 880 } |
| 768 } | 881 } |
| OLD | NEW |