| 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/src/context/cache.dart'; | 10 import 'package:analyzer/src/context/cache.dart'; |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 142 AnalysisTarget target, ResultDescriptor result) { | 142 AnalysisTarget target, ResultDescriptor result) { |
| 143 CacheEntry entry = context.getCacheEntry(target); | 143 CacheEntry entry = context.getCacheEntry(target); |
| 144 CacheState state = entry.getState(result); | 144 CacheState state = entry.getState(result); |
| 145 if (state == CacheState.VALID || | 145 if (state == CacheState.VALID || |
| 146 state == CacheState.ERROR || | 146 state == CacheState.ERROR || |
| 147 state == CacheState.IN_PROCESS) { | 147 state == CacheState.IN_PROCESS) { |
| 148 return null; | 148 return null; |
| 149 } | 149 } |
| 150 try { | 150 try { |
| 151 TaskDescriptor taskDescriptor = taskManager.findTask(target, result); | 151 TaskDescriptor taskDescriptor = taskManager.findTask(target, result); |
| 152 WorkItem workItem = new WorkItem(context, target, taskDescriptor); | 152 WorkItem workItem = new WorkItem(context, target, taskDescriptor, result); |
| 153 return new WorkOrder(taskManager, workItem); | 153 return new WorkOrder(taskManager, workItem); |
| 154 } catch (exception, stackTrace) { | 154 } catch (exception, stackTrace) { |
| 155 throw new AnalysisException( | 155 throw new AnalysisException( |
| 156 'Could not create work order (target = $target; result = $result)', | 156 'Could not create work order (target = $target; result = $result)', |
| 157 new CaughtException(exception, stackTrace)); | 157 new CaughtException(exception, stackTrace)); |
| 158 } | 158 } |
| 159 } | 159 } |
| 160 | 160 |
| 161 /** | 161 /** |
| 162 * Create a work order that will produce the required analysis results for | 162 * Create a work order that will produce the required analysis results for |
| (...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 345 | 345 |
| 346 /** | 346 /** |
| 347 * Determine the next strongly connected component in the graph, and return | 347 * Determine the next strongly connected component in the graph, and return |
| 348 * it. The client is expected to evaluate this component before calling | 348 * it. The client is expected to evaluate this component before calling |
| 349 * [getNextStronglyConnectedComponent] again. | 349 * [getNextStronglyConnectedComponent] again. |
| 350 */ | 350 */ |
| 351 List<Node> getNextStronglyConnectedComponent() { | 351 List<Node> getNextStronglyConnectedComponent() { |
| 352 while (_currentIndices.isNotEmpty) { | 352 while (_currentIndices.isNotEmpty) { |
| 353 Node nextUnevaluatedInput = getNextInput(_path[_currentIndices.last], | 353 Node nextUnevaluatedInput = getNextInput(_path[_currentIndices.last], |
| 354 _provisionalDependencies[_currentIndices.last]); | 354 _provisionalDependencies[_currentIndices.last]); |
| 355 assert(!_provisionalDependencies[_currentIndices.last] |
| 356 .contains(nextUnevaluatedInput)); |
| 355 if (nextUnevaluatedInput != null) { | 357 if (nextUnevaluatedInput != null) { |
| 356 // TODO(paulberry): the call to _path.indexOf makes the algorithm | 358 // TODO(paulberry): the call to _path.indexOf makes the algorithm |
| 357 // O(n^2) in the depth of the dependency graph. If this becomes a | 359 // O(n^2) in the depth of the dependency graph. If this becomes a |
| 358 // problem, consider maintaining a map from node to index. | 360 // problem, consider maintaining a map from node to index. |
| 359 int previousIndex = _path.indexOf(nextUnevaluatedInput); | 361 int previousIndex = _path.indexOf(nextUnevaluatedInput); |
| 360 if (previousIndex != -1) { | 362 if (previousIndex != -1) { |
| 361 // Update contractedPath to indicate that all nodes in the path | 363 // Update contractedPath to indicate that all nodes in the path |
| 362 // between previousIndex and currentIndex are part of the same | 364 // between previousIndex and currentIndex are part of the same |
| 363 // strongly connected component. | 365 // strongly connected component. |
| 364 while (_contractedPath.last > previousIndex) { | 366 while (_contractedPath.last > previousIndex) { |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 421 CacheEntry getCacheEntry(AnalysisTarget target); | 423 CacheEntry getCacheEntry(AnalysisTarget target); |
| 422 } | 424 } |
| 423 | 425 |
| 424 /** | 426 /** |
| 425 * An exception indicating that an attempt was made to perform a task on a | 427 * An exception indicating that an attempt was made to perform a task on a |
| 426 * target while gathering the inputs to perform the same task for the same | 428 * target while gathering the inputs to perform the same task for the same |
| 427 * target. | 429 * target. |
| 428 */ | 430 */ |
| 429 class InfiniteTaskLoopException extends AnalysisException { | 431 class InfiniteTaskLoopException extends AnalysisException { |
| 430 /** | 432 /** |
| 431 * Initialize a newly created exception to represent an attempt to perform | 433 * If a dependency cycle was found while computing the inputs for the task, |
| 432 * the task for the target represented by the given [item]. | 434 * the set of [WorkItem]s contained in the cycle (if there are overlapping |
| 435 * cycles, this is the set of all [WorkItem]s in the entire strongly |
| 436 * connected component). Otherwise, `null`. |
| 433 */ | 437 */ |
| 434 InfiniteTaskLoopException(WorkItem item) : super( | 438 final List<WorkItem> dependencyCycle; |
| 435 'Infinite loop while performing task ${item.descriptor.name} for ${ite
m.target}'); | 439 |
| 440 /** |
| 441 * Initialize a newly created exception to represent a failed attempt to |
| 442 * perform the given [task] due to the given [dependencyCycle]. |
| 443 */ |
| 444 InfiniteTaskLoopException(AnalysisTask task, this.dependencyCycle) : super( |
| 445 'Infinite loop while performing task ${task.descriptor.name} for ${tas
k.target}'); |
| 436 } | 446 } |
| 437 | 447 |
| 438 /** | 448 /** |
| 439 * A description of a single anaysis task that can be performed to advance | 449 * A description of a single anaysis task that can be performed to advance |
| 440 * analysis. | 450 * analysis. |
| 441 */ | 451 */ |
| 442 class WorkItem { | 452 class WorkItem { |
| 443 /** | 453 /** |
| 444 * The context in which the task will be performed. | 454 * The context in which the task will be performed. |
| 445 */ | 455 */ |
| 446 final InternalAnalysisContext context; | 456 final InternalAnalysisContext context; |
| 447 | 457 |
| 448 /** | 458 /** |
| 449 * The target for which a task is to be performed. | 459 * The target for which a task is to be performed. |
| 450 */ | 460 */ |
| 451 final AnalysisTarget target; | 461 final AnalysisTarget target; |
| 452 | 462 |
| 453 /** | 463 /** |
| 454 * A description of the task to be performed. | 464 * A description of the task to be performed. |
| 455 */ | 465 */ |
| 456 final TaskDescriptor descriptor; | 466 final TaskDescriptor descriptor; |
| 457 | 467 |
| 458 /** | 468 /** |
| 469 * The [ResultDescriptor] which was led to this work item being spawned. |
| 470 */ |
| 471 final ResultDescriptor spawningResult; |
| 472 |
| 473 /** |
| 459 * An iterator used to iterate over the descriptors of the inputs to the task, | 474 * An iterator used to iterate over the descriptors of the inputs to the task, |
| 460 * or `null` if all of the inputs have been collected and the task can be | 475 * or `null` if all of the inputs have been collected and the task can be |
| 461 * created. | 476 * created. |
| 462 */ | 477 */ |
| 463 TaskInputBuilder builder; | 478 TaskInputBuilder builder; |
| 464 | 479 |
| 465 /** | 480 /** |
| 466 * The [TargetedResult]s outputs of this task depends on. | 481 * The [TargetedResult]s outputs of this task depends on. |
| 467 */ | 482 */ |
| 468 final HashSet<TargetedResult> inputTargetedResults = | 483 final HashSet<TargetedResult> inputTargetedResults = |
| 469 new HashSet<TargetedResult>(); | 484 new HashSet<TargetedResult>(); |
| 470 | 485 |
| 471 /** | 486 /** |
| 472 * The inputs to the task that have been computed. | 487 * The inputs to the task that have been computed. |
| 473 */ | 488 */ |
| 474 Map<String, dynamic> inputs; | 489 Map<String, dynamic> inputs; |
| 475 | 490 |
| 476 /** | 491 /** |
| 477 * The exception that was found while trying to populate the inputs. If this | 492 * The exception that was found while trying to populate the inputs. If this |
| 478 * field is non-`null`, then the task cannot be performed and all of the | 493 * field is non-`null`, then the task cannot be performed and all of the |
| 479 * results that this task would have computed need to be marked as being in | 494 * results that this task would have computed need to be marked as being in |
| 480 * ERROR with this exception. | 495 * ERROR with this exception. |
| 481 */ | 496 */ |
| 482 CaughtException exception = null; | 497 CaughtException exception = null; |
| 483 | 498 |
| 484 /** | 499 /** |
| 500 * If a dependency cycle was found while computing the inputs for the task, |
| 501 * the set of [WorkItem]s contained in the cycle (if there are overlapping |
| 502 * cycles, this is the set of all [WorkItem]s in the entire strongly |
| 503 * connected component). Otherwise, `null`. |
| 504 */ |
| 505 List<WorkItem> dependencyCycle; |
| 506 |
| 507 /** |
| 485 * Initialize a newly created work item to compute the inputs for the task | 508 * Initialize a newly created work item to compute the inputs for the task |
| 486 * described by the given descriptor. | 509 * described by the given descriptor. |
| 487 */ | 510 */ |
| 488 WorkItem(this.context, this.target, this.descriptor) { | 511 WorkItem(this.context, this.target, this.descriptor, this.spawningResult) { |
| 489 AnalysisTarget actualTarget = identical( | 512 AnalysisTarget actualTarget = identical( |
| 490 target, AnalysisContextTarget.request) | 513 target, AnalysisContextTarget.request) |
| 491 ? new AnalysisContextTarget(context) | 514 ? new AnalysisContextTarget(context) |
| 492 : target; | 515 : target; |
| 493 Map<String, TaskInput> inputDescriptors = | 516 Map<String, TaskInput> inputDescriptors = |
| 494 descriptor.createTaskInputs(actualTarget); | 517 descriptor.createTaskInputs(actualTarget); |
| 495 builder = new TopLevelTaskInputBuilder(inputDescriptors); | 518 builder = new TopLevelTaskInputBuilder(inputDescriptors); |
| 496 if (!builder.moveNext()) { | 519 if (!builder.moveNext()) { |
| 497 builder = null; | 520 builder = null; |
| 498 } | 521 } |
| (...skipping 13 matching lines...) Expand all Loading... |
| 512 } | 535 } |
| 513 } | 536 } |
| 514 | 537 |
| 515 /** | 538 /** |
| 516 * Build the task represented by this work item. | 539 * Build the task represented by this work item. |
| 517 */ | 540 */ |
| 518 AnalysisTask buildTask() { | 541 AnalysisTask buildTask() { |
| 519 if (builder != null) { | 542 if (builder != null) { |
| 520 throw new StateError("some inputs have not been computed"); | 543 throw new StateError("some inputs have not been computed"); |
| 521 } | 544 } |
| 522 return descriptor.createTask(context, target, inputs); | 545 AnalysisTask task = descriptor.createTask(context, target, inputs); |
| 546 task.dependencyCycle = dependencyCycle; |
| 547 return task; |
| 523 } | 548 } |
| 524 | 549 |
| 525 /** | 550 /** |
| 526 * Gather all of the inputs needed to perform the task. | 551 * Gather all of the inputs needed to perform the task. |
| 527 * | 552 * |
| 528 * If at least one of the inputs have not yet been computed, return a work | 553 * If at least one of the inputs have not yet been computed, return a work |
| 529 * item that can be used to generate that input to indicate that the caller | 554 * item that can be used to generate that input to indicate that the caller |
| 530 * should perform the returned item's task before returning to gathering | 555 * should perform the returned item's task before returning to gathering |
| 531 * inputs for this item's task. | 556 * inputs for this item's task. |
| 532 * | 557 * |
| 533 * If all of the inputs have been gathered, return `null` to indicate that the | 558 * If all of the inputs have been gathered, return `null` to indicate that the |
| 534 * client should build and perform the task. A value of `null` will also be | 559 * client should build and perform the task. A value of `null` will also be |
| 535 * returned if some of the inputs cannot be computed and the task cannot be | 560 * returned if some of the inputs cannot be computed and the task cannot be |
| 536 * performed. Callers can differentiate between these cases by checking the | 561 * performed. Callers can differentiate between these cases by checking the |
| 537 * [exception] field. If the field is `null`, then the task can be performed; | 562 * [exception] field. If the field is `null`, then the task can be performed; |
| 538 * if the field is non-`null` then the task cannot be performed and all of the | 563 * if the field is non-`null` then the task cannot be performed and all of the |
| 539 * tasks' results should be marked as being in ERROR. | 564 * tasks' results should be marked as being in ERROR. |
| 540 */ | 565 */ |
| 541 WorkItem gatherInputs(TaskManager taskManager) { | 566 WorkItem gatherInputs(TaskManager taskManager, List<WorkItem> skipInputs) { |
| 542 while (builder != null) { | 567 while (builder != null) { |
| 543 AnalysisTarget inputTarget = builder.currentTarget; | 568 AnalysisTarget inputTarget = builder.currentTarget; |
| 544 ResultDescriptor inputResult = builder.currentResult; | 569 ResultDescriptor inputResult = builder.currentResult; |
| 545 inputTargetedResults.add(new TargetedResult(inputTarget, inputResult)); | 570 inputTargetedResults.add(new TargetedResult(inputTarget, inputResult)); |
| 546 CacheEntry inputEntry = context.getCacheEntry(inputTarget); | 571 CacheEntry inputEntry = context.getCacheEntry(inputTarget); |
| 547 CacheState inputState = inputEntry.getState(inputResult); | 572 CacheState inputState = inputEntry.getState(inputResult); |
| 548 if (inputState == CacheState.ERROR) { | 573 if (skipInputs.any((WorkItem item) => |
| 574 item.target == inputTarget && item.spawningResult == inputResult)) { |
| 575 // This input is being skipped due to a circular dependency. Tell the |
| 576 // builder that it's not available so we can move on to other inputs. |
| 577 builder.currentValueNotAvailable(); |
| 578 } else if (inputState == CacheState.ERROR) { |
| 549 exception = inputEntry.exception; | 579 exception = inputEntry.exception; |
| 550 return null; | 580 return null; |
| 551 } else if (inputState == CacheState.IN_PROCESS) { | 581 } else if (inputState == CacheState.IN_PROCESS) { |
| 552 // | 582 // |
| 553 // TODO(brianwilkerson) Implement this case. | 583 // TODO(brianwilkerson) Implement this case. |
| 554 // | 584 // |
| 555 // One possibility would be to return a WorkItem that would perform a | 585 // One possibility would be to return a WorkItem that would perform a |
| 556 // no-op task in order to cause us to come back to this work item on the | 586 // no-op task in order to cause us to come back to this work item on the |
| 557 // next iteration. It would be more efficient, in general, to push this | 587 // next iteration. It would be more efficient, in general, to push this |
| 558 // input onto a waiting list and proceed to the next input so that work | 588 // input onto a waiting list and proceed to the next input so that work |
| 559 // could proceed, but given that the only result that can currently be | 589 // could proceed, but given that the only result that can currently be |
| 560 // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort | 590 // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort |
| 561 // to implement the general solution at this point. | 591 // to implement the general solution at this point. |
| 562 // | 592 // |
| 593 throw new UnimplementedError(); |
| 563 } else if (inputState != CacheState.VALID) { | 594 } else if (inputState != CacheState.VALID) { |
| 564 try { | 595 try { |
| 565 TaskDescriptor descriptor = | 596 TaskDescriptor descriptor = |
| 566 taskManager.findTask(inputTarget, inputResult); | 597 taskManager.findTask(inputTarget, inputResult); |
| 567 return new WorkItem(context, inputTarget, descriptor); | 598 return new WorkItem(context, inputTarget, descriptor, inputResult); |
| 568 } on AnalysisException catch (exception, stackTrace) { | 599 } on AnalysisException catch (exception, stackTrace) { |
| 569 this.exception = new CaughtException(exception, stackTrace); | 600 this.exception = new CaughtException(exception, stackTrace); |
| 570 return null; | 601 return null; |
| 571 } | 602 } |
| 603 } else { |
| 604 builder.currentValue = inputEntry.getValue(inputResult); |
| 572 } | 605 } |
| 573 builder.currentValue = inputEntry.getValue(inputResult); | |
| 574 if (!builder.moveNext()) { | 606 if (!builder.moveNext()) { |
| 575 inputs = builder.inputValue; | 607 inputs = builder.inputValue; |
| 576 builder = null; | 608 builder = null; |
| 577 } | 609 } |
| 578 } | 610 } |
| 579 return null; | 611 return null; |
| 580 } | 612 } |
| 581 | 613 |
| 582 @override | 614 @override |
| 583 String toString() => 'Run $descriptor on $target'; | 615 String toString() => 'Run $descriptor on $target'; |
| 584 } | 616 } |
| 585 | 617 |
| 586 /** | 618 /** |
| 587 * The priorities of work orders returned by [WorkManager]s. | |
| 588 */ | |
| 589 enum WorkOrderPriority { | |
| 590 /** | |
| 591 * Responding to an user's action. | |
| 592 */ | |
| 593 INTERACTIVE, | |
| 594 | |
| 595 /** | |
| 596 * Computing information for priority sources. | |
| 597 */ | |
| 598 PRIORITY, | |
| 599 | |
| 600 /** | |
| 601 * A work should be done, but without any special urgency. | |
| 602 */ | |
| 603 NORMAL, | |
| 604 | |
| 605 /** | |
| 606 * Nothing to do. | |
| 607 */ | |
| 608 NONE | |
| 609 } | |
| 610 | |
| 611 /** | |
| 612 * [AnalysisDriver] uses [WorkManager]s to select results to compute. | 619 * [AnalysisDriver] uses [WorkManager]s to select results to compute. |
| 613 * | 620 * |
| 614 * They know specific of the targets and results they care about, | 621 * They know specific of the targets and results they care about, |
| 615 * so they can request analysis results in optimal order. | 622 * so they can request analysis results in optimal order. |
| 616 */ | 623 */ |
| 617 abstract class WorkManager { | 624 abstract class WorkManager { |
| 618 /** | 625 /** |
| 619 * Notifies the managers that the given set of priority [targets] was set. | 626 * Notifies the managers that the given set of priority [targets] was set. |
| 620 */ | 627 */ |
| 621 void applyPriorityTargets(List<AnalysisTarget> targets); | 628 void applyPriorityTargets(List<AnalysisTarget> targets); |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 688 return true; | 695 return true; |
| 689 } else { | 696 } else { |
| 690 // Get a new strongly connected component. | 697 // Get a new strongly connected component. |
| 691 currentItems = _dependencyWalker.getNextStronglyConnectedComponent(); | 698 currentItems = _dependencyWalker.getNextStronglyConnectedComponent(); |
| 692 if (currentItems == null) { | 699 if (currentItems == null) { |
| 693 return false; | 700 return false; |
| 694 } | 701 } |
| 695 if (currentItems.length > 1) { | 702 if (currentItems.length > 1) { |
| 696 // A cycle has been found. | 703 // A cycle has been found. |
| 697 for (WorkItem item in currentItems) { | 704 for (WorkItem item in currentItems) { |
| 698 try { | 705 item.dependencyCycle = currentItems.toList(); |
| 699 throw new InfiniteTaskLoopException(item); | |
| 700 } on InfiniteTaskLoopException catch (exception, stackTrace) { | |
| 701 item.exception = new CaughtException(exception, stackTrace); | |
| 702 } | |
| 703 } | 706 } |
| 704 } else { | 707 } else { |
| 705 assert(currentItems.length == 1); | 708 assert(currentItems.length == 1); |
| 706 } | 709 } |
| 707 return true; | 710 return true; |
| 708 } | 711 } |
| 709 } | 712 } |
| 710 } | 713 } |
| 711 | 714 |
| 712 /** | 715 /** |
| 716 * The priorities of work orders returned by [WorkManager]s. |
| 717 */ |
| 718 enum WorkOrderPriority { |
| 719 /** |
| 720 * Responding to an user's action. |
| 721 */ |
| 722 INTERACTIVE, |
| 723 |
| 724 /** |
| 725 * Computing information for priority sources. |
| 726 */ |
| 727 PRIORITY, |
| 728 |
| 729 /** |
| 730 * A work should be done, but without any special urgency. |
| 731 */ |
| 732 NORMAL, |
| 733 |
| 734 /** |
| 735 * Nothing to do. |
| 736 */ |
| 737 NONE |
| 738 } |
| 739 |
| 740 /** |
| 713 * Specilaization of [CycleAwareDependencyWalker] for use by [WorkOrder]. | 741 * Specilaization of [CycleAwareDependencyWalker] for use by [WorkOrder]. |
| 714 */ | 742 */ |
| 715 class _WorkOrderDependencyWalker extends CycleAwareDependencyWalker<WorkItem> { | 743 class _WorkOrderDependencyWalker extends CycleAwareDependencyWalker<WorkItem> { |
| 716 /** | 744 /** |
| 717 * The task manager used to build work items. | 745 * The task manager used to build work items. |
| 718 */ | 746 */ |
| 719 final TaskManager taskManager; | 747 final TaskManager taskManager; |
| 720 | 748 |
| 721 _WorkOrderDependencyWalker(this.taskManager, WorkItem startingNode) | 749 _WorkOrderDependencyWalker(this.taskManager, WorkItem startingNode) |
| 722 : super(startingNode); | 750 : super(startingNode); |
| 723 | 751 |
| 724 @override | 752 @override |
| 725 WorkItem getNextInput(WorkItem node, List<WorkItem> skipInputs) { | 753 WorkItem getNextInput(WorkItem node, List<WorkItem> skipInputs) { |
| 726 if (skipInputs.isNotEmpty) { | 754 return node.gatherInputs(taskManager, skipInputs); |
| 727 // TODO(paulberry): this is a hack. We assume that an analysis loop has | |
| 728 // been found, so we don't try to compute anything else. | |
| 729 return null; | |
| 730 } | |
| 731 return node.gatherInputs(taskManager); | |
| 732 } | 755 } |
| 733 } | 756 } |
| OLD | NEW |