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.task.driver; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 | |
| 10 import 'package:analyzer/src/context/cache.dart'; | |
| 11 import 'package:analyzer/src/generated/engine.dart' hide AnalysisTask; | |
| 12 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 13 import 'package:analyzer/src/task/inputs.dart'; | |
| 14 import 'package:analyzer/src/task/manager.dart'; | |
| 15 import 'package:analyzer/task/model.dart'; | |
| 16 | |
| 17 /** | |
| 18 * An object that is used to cause analysis to be performed until all of the | |
| 19 * required analysis information has been computed. | |
| 20 */ | |
| 21 class AnalysisDriver { | |
| 22 /** | |
| 23 * The task manager used to figure out how to compute analysis results. | |
| 24 */ | |
| 25 final TaskManager taskManager; | |
| 26 | |
| 27 /** | |
| 28 * The context in which analysis is to be performed. | |
| 29 */ | |
| 30 final ExtendedAnalysisContext context; | |
| 31 | |
| 32 /** | |
| 33 * The work order that was previously computed but that has not yet been | |
| 34 * completed. | |
| 35 */ | |
| 36 WorkOrder currentWorkOrder; | |
| 37 | |
| 38 /** | |
| 39 * The controller that is notified when a task is started. | |
| 40 */ | |
| 41 StreamController<AnalysisTask> _onTaskStartedController; | |
| 42 | |
| 43 /** | |
| 44 * The controller that is notified when a task is complete. | |
| 45 */ | |
| 46 StreamController<AnalysisTask> _onTaskCompletedController; | |
| 47 | |
| 48 /** | |
| 49 * Initialize a newly created driver to use the tasks know to the given | |
| 50 * [taskManager] to perform analysis in the given [context]. | |
| 51 */ | |
| 52 AnalysisDriver(this.taskManager, this.context) { | |
| 53 _onTaskStartedController = new StreamController.broadcast(); | |
| 54 _onTaskCompletedController = new StreamController.broadcast(); | |
| 55 } | |
| 56 | |
| 57 /** | |
| 58 * The stream that is notified when a task is complete. | |
| 59 */ | |
| 60 Stream<AnalysisTask> get onTaskCompleted => _onTaskCompletedController.stream; | |
| 61 | |
| 62 /** | |
| 63 * The stream that is notified when a task is started. | |
| 64 */ | |
| 65 Stream<AnalysisTask> get onTaskStarted => _onTaskStartedController.stream; | |
| 66 | |
| 67 /** | |
| 68 * Perform work until the given [result] has been computed for the given | |
| 69 * [target]. | |
| 70 */ | |
| 71 void computeResult(AnalysisTarget target, ResultDescriptor result) { | |
| 72 WorkOrder workOrder = createWorkOrderForResult(target, result); | |
| 73 if (workOrder != null) { | |
| 74 while (workOrder.moveNext()) { | |
| 75 performWorkItem(workOrder.current); | |
| 76 } | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 /** | |
| 81 * Return the work order describing the work that should be getting worked on, | |
| 82 * or `null` if there is currently no work to be done. | |
| 83 */ | |
| 84 WorkOrder createNextWorkOrder() { | |
| 85 // | |
| 86 // TODO(brianwilkerson) This is an inefficient implementation. We need to | |
| 87 // port over the concept of the WorkManager to manage the list of sources | |
| 88 // for which some work needs to be performed so that we do not waste time | |
| 89 // repeatedly looking at the same completed sources to see whether there is | |
| 90 // work that needs to be done. | |
|
Paul Berry
2015/03/03 17:21:32
An easy (but slightly hacky) way to achieve most o
Brian Wilkerson
2015/03/03 21:21:17
True. I think WorkManager is less "hacky" and shou
| |
| 91 // | |
| 92 for (AnalysisTarget target in context.priorityTargets) { | |
|
Paul Berry
2015/03/03 17:21:32
I'm surprised to see only two levels of priority h
Brian Wilkerson
2015/03/03 21:21:17
That was probably done after I copied this code. I
| |
| 93 WorkOrder workOrder = createWorkOrderForTarget(target, true); | |
| 94 if (workOrder != null) { | |
| 95 return workOrder; | |
| 96 } | |
| 97 } | |
| 98 for (AnalysisTarget target in context.explicitTargets) { | |
| 99 WorkOrder workOrder = createWorkOrderForTarget(target, false); | |
| 100 if (workOrder != null) { | |
| 101 return workOrder; | |
| 102 } | |
| 103 } | |
| 104 return null; | |
| 105 } | |
| 106 | |
| 107 /** | |
| 108 * Create a work order that will produce the given [result] for the given | |
| 109 * [target]. Return the work order that was created, or `null` if the result | |
| 110 * has already been computed. | |
| 111 */ | |
| 112 WorkOrder createWorkOrderForResult(AnalysisTarget target, | |
| 113 ResultDescriptor result) { | |
| 114 CacheEntry entry = context.getCacheEntry(target); | |
| 115 CacheState state = entry.getState(result); | |
| 116 if (state == CacheState.VALID || | |
| 117 state == CacheState.ERROR || | |
| 118 state == CacheState.IN_PROCESS) { | |
| 119 return null; | |
| 120 } | |
| 121 return new WorkOrder( | |
| 122 taskManager, | |
| 123 new WorkItem(context, target, taskManager.findTask(target, result))); | |
| 124 } | |
| 125 | |
| 126 /** | |
| 127 * Create a work order that will produce the required analysis results for | |
| 128 * the given [target]. If [isPriority] is true, then the target is a priority | |
| 129 * target. Return the work order that was created, or `null` if there is no | |
| 130 * further work that needs to be done for the given target. | |
| 131 */ | |
| 132 WorkOrder createWorkOrderForTarget(AnalysisTarget target, bool isPriority) { | |
| 133 for (ResultDescriptor result in taskManager.generalResults) { | |
| 134 WorkOrder workOrder = createWorkOrderForResult(target, result); | |
| 135 if (workOrder != null) { | |
| 136 return workOrder; | |
| 137 } | |
| 138 } | |
| 139 if (isPriority) { | |
| 140 for (ResultDescriptor result in taskManager.priorityResults) { | |
| 141 WorkOrder workOrder = createWorkOrderForResult(target, result); | |
| 142 if (workOrder != null) { | |
| 143 return workOrder; | |
| 144 } | |
| 145 } | |
| 146 } | |
| 147 return null; | |
| 148 } | |
| 149 | |
| 150 /** | |
| 151 * Perform the next analysis task, and return `true` if there is more work to | |
| 152 * be done in order to compute all of the required analysis information. | |
| 153 */ | |
| 154 bool performAnalysisTask() { | |
| 155 // | |
| 156 // TODO(brianwilkerson) This implementaiton does not allow us to prioritize | |
| 157 // work across contexts. What we need is a way for an external client to ask | |
| 158 // to have all priority files analyzed for each context, then ask for normal | |
| 159 // files to be analyzed. There are a couple of ways to do this. | |
| 160 // | |
| 161 // First, we could add a "bool priorityOnly" parameter to this method and | |
| 162 // return null here when it is true. | |
| 163 // | |
| 164 // Second, we could add a concept of a priority order and (externally) run | |
| 165 // through the priorities from highest to lowest. That would be a nice | |
| 166 // generalization of the previous idea, but it isn't clear that we need the | |
| 167 // generality. | |
| 168 // | |
|
Paul Berry
2015/03/03 17:21:32
A third approach, which I personally favor, would
Brian Wilkerson
2015/03/03 21:21:18
Added to the TODO.
| |
| 169 if (currentWorkOrder == null) { | |
| 170 currentWorkOrder = createNextWorkOrder(); | |
| 171 } else if (currentWorkOrder.moveNext()) { | |
| 172 performWorkItem(currentWorkOrder.current); | |
| 173 } else { | |
| 174 currentWorkOrder = createNextWorkOrder(); | |
| 175 } | |
| 176 return currentWorkOrder != null; | |
| 177 } | |
| 178 | |
| 179 /** | |
| 180 * Perform the given work item. | |
| 181 */ | |
| 182 void performWorkItem(WorkItem item) { | |
| 183 if (item.exception != null) { | |
| 184 // Mark all of the results that the task would have computed as being in | |
| 185 // ERROR with the exception recorded on the work item. | |
| 186 CacheEntry targetEntry = context.getCacheEntry(item.target); | |
| 187 targetEntry.exception = item.exception; | |
| 188 for (ResultDescriptor result in item.descriptor.results) { | |
| 189 targetEntry.setState(result, CacheState.ERROR); | |
| 190 } | |
| 191 return; | |
| 192 } | |
| 193 // Otherwise, perform the task. | |
| 194 AnalysisTask task = item.buildTask(); | |
| 195 _onTaskStartedController.add(task); | |
| 196 task.perform(); | |
| 197 CacheEntry entry = context.getCacheEntry(task.target); | |
| 198 if (task.caughtException == null) { | |
| 199 Map<ResultDescriptor, dynamic> outputs = task.outputs; | |
| 200 for (ResultDescriptor result in task.descriptor.results) { | |
| 201 // TODO(brianwilkerson) We could check here that a value was produced | |
| 202 // and throw an exception if not (unless we want to allow null values). | |
| 203 entry.setValue(result, outputs[result]); | |
| 204 } | |
| 205 } else { | |
| 206 entry.exception = task.caughtException; | |
| 207 for (ResultDescriptor result in task.descriptor.results) { | |
| 208 entry.setState(result, CacheState.ERROR); | |
| 209 } | |
| 210 } | |
| 211 _onTaskCompletedController.add(task); | |
| 212 } | |
| 213 | |
| 214 /** | |
| 215 * Reset the state of the driver in response to a change in the state of one | |
| 216 * or more analysis targets. This will cause any analysis that was currently | |
| 217 * in process to be stopped and for analysis to resume based on the new state. | |
| 218 */ | |
| 219 void reset() { | |
| 220 currentWorkOrder = null; | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 /** | |
| 225 * A place to define the behaviors that need to be added to | |
| 226 * [InternalAnalysisContext]. | |
| 227 */ | |
| 228 abstract class ExtendedAnalysisContext implements InternalAnalysisContext { | |
| 229 List<AnalysisTarget> get explicitTargets; | |
| 230 List<AnalysisTarget> get priorityTargets; | |
| 231 CacheEntry getCacheEntry(AnalysisTarget target); | |
| 232 } | |
| 233 | |
| 234 /** | |
| 235 * A description of a single anaysis task that can be performed to advance | |
| 236 * analysis. | |
| 237 */ | |
| 238 class WorkItem { | |
| 239 /** | |
| 240 * The context in which the task will be performed. | |
| 241 */ | |
| 242 final ExtendedAnalysisContext context; | |
| 243 | |
| 244 /** | |
| 245 * The target for which a task is to be performed. | |
| 246 */ | |
| 247 final AnalysisTarget target; | |
| 248 | |
| 249 /** | |
| 250 * A description of the task to be performed. | |
| 251 */ | |
| 252 final TaskDescriptor descriptor; | |
| 253 | |
| 254 /** | |
| 255 * An iterator used to iterate over the descriptors of the inputs to the task, | |
| 256 * or `null` if all of the inputs have been collected and the task can be | |
| 257 * created. | |
| 258 */ | |
| 259 TaskInputBuilder builder; | |
| 260 | |
| 261 /** | |
| 262 * The inputs to the task that have been computed. | |
| 263 */ | |
| 264 Map<String, dynamic> inputs; | |
| 265 | |
| 266 /** | |
| 267 * The exception that was found while trying to populate the inputs. If this | |
| 268 * field is non-`null`, then the task cannot be performed and all of the | |
| 269 * results that this task would have computed need to be marked as being in | |
| 270 * ERROR with this exception. | |
| 271 */ | |
| 272 CaughtException exception = null; | |
| 273 | |
| 274 /** | |
| 275 * Initialize a newly created work item to compute the inputs for the task | |
| 276 * described by the given descriptor. | |
| 277 */ | |
| 278 WorkItem(this.context, this.target, this.descriptor) { | |
| 279 Map<String, TaskInput> inputDescriptors = | |
| 280 descriptor.createTaskInputs(target); | |
| 281 builder = new TopLevelTaskInputBuilder(inputDescriptors); | |
| 282 if (!builder.moveNext()) { | |
| 283 builder = null; | |
| 284 } | |
| 285 inputs = new HashMap<String, dynamic>(); | |
| 286 } | |
| 287 | |
| 288 /** | |
| 289 * Build the task represented by this work item. | |
| 290 */ | |
| 291 AnalysisTask buildTask() { | |
| 292 if (builder != null) { | |
| 293 throw new StateError("some inputs have not been computed"); | |
| 294 } | |
| 295 return descriptor.createTask(context, target, inputs); | |
| 296 } | |
| 297 | |
| 298 /** | |
| 299 * Gather all of the inputs needed to perform the task. | |
| 300 * | |
| 301 * If at least one of the inputs have not yet been computed, return a work | |
| 302 * item that can be used to generate that input to indicate that the caller | |
| 303 * should perform the returned item's task before returning to gathering | |
| 304 * inputs for this item's task. | |
| 305 * | |
| 306 * If all of the inputs have been gathered, return `null` to indicate that the | |
| 307 * client should build and perform the task. A value of `null` will also be | |
| 308 * returned if some of the inputs cannot be computed and the task cannot be | |
| 309 * performed. Callers can differentiate between these cases by checking the | |
| 310 * [exception] field. If the field is `null`, then the task can be performed; | |
| 311 * if the field is non-`null` then the task cannot be performed and all of the | |
| 312 * tasks' results should be marked as being in ERROR. | |
| 313 */ | |
| 314 WorkItem gatherInputs(TaskManager taskManager) { | |
| 315 while (builder != null) { | |
| 316 // | |
| 317 // TODO(brianwilkerson) Capture information about which inputs were used | |
| 318 // to compute the results. This information can later be used to compute | |
| 319 // which results depend on a given result, and hence which results need to | |
| 320 // be invalidated when one result is invalidated. | |
| 321 // | |
| 322 AnalysisTarget inputTarget = builder.currentTarget; | |
| 323 ResultDescriptor inputResult = builder.currentResult; | |
| 324 CacheEntry inputEntry = context.getCacheEntry(inputTarget); | |
| 325 CacheState inputState = inputEntry.getState(inputResult); | |
| 326 if (inputState == CacheState.ERROR) { | |
| 327 exception = inputEntry.exception; | |
| 328 return null; | |
| 329 } else if (inputState == CacheState.IN_PROCESS) { | |
| 330 // | |
| 331 // TODO(brianwilkerson) Implement this case. | |
| 332 // | |
| 333 // One possibility would be to return a WorkItem that would perform a | |
| 334 // no-op task in order to cause us to come back to this work item on the | |
| 335 // next iteration. It would be more efficient, in general, to push this | |
| 336 // input onto a waiting list and proceed to the next input so that work | |
| 337 // could proceed, but given that the only result that can currently be | |
| 338 // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort | |
| 339 // to implement the general solution at this point. | |
| 340 // | |
| 341 } else if (inputState != CacheState.VALID) { | |
| 342 try { | |
| 343 TaskDescriptor descriptor = | |
| 344 taskManager.findTask(inputTarget, inputResult); | |
| 345 return new WorkItem(context, inputTarget, descriptor); | |
| 346 } on AnalysisException catch (exception, stackTrace) { | |
| 347 this.exception = new CaughtException(exception, stackTrace); | |
| 348 return null; | |
| 349 } | |
| 350 } | |
| 351 builder.currentValue = inputEntry.getValue(inputResult); | |
| 352 if (!builder.moveNext()) { | |
| 353 inputs = builder.inputValue; | |
| 354 builder = null; | |
| 355 } | |
| 356 } | |
| 357 return null; | |
| 358 } | |
| 359 } | |
| 360 | |
| 361 /** | |
| 362 * A description of the work to be done to compute a desired analysis result. | |
|
Paul Berry
2015/03/03 17:21:32
It would me nice to have a comment explaining that
Brian Wilkerson
2015/03/03 21:21:18
Done
| |
| 363 */ | |
| 364 class WorkOrder implements Iterator<WorkItem> { | |
| 365 /** | |
| 366 * The task manager used to build work items. | |
| 367 */ | |
| 368 final TaskManager taskManager; | |
| 369 | |
| 370 /** | |
| 371 * A list containing the work items that are being prepared for being worked. | |
| 372 */ | |
| 373 final List<WorkItem> pendingItems = <WorkItem>[]; | |
| 374 | |
| 375 /** | |
| 376 * The current work item. | |
| 377 */ | |
| 378 WorkItem currentItem; | |
| 379 | |
| 380 /** | |
| 381 * Initialize a newly created work order to compute the result described by | |
| 382 * the given work item. | |
| 383 */ | |
| 384 WorkOrder(this.taskManager, WorkItem item) { | |
| 385 pendingItems.add(item); | |
| 386 } | |
| 387 | |
| 388 @override | |
| 389 WorkItem get current { | |
| 390 WorkItem item = currentItem; | |
| 391 currentItem = null; | |
|
Paul Berry
2015/03/03 17:21:32
This seems dangerous because it violates the usual
Brian Wilkerson
2015/03/03 21:21:18
Done
| |
| 392 return item; | |
| 393 } | |
| 394 | |
| 395 @override | |
| 396 bool moveNext() { | |
| 397 if (currentItem == null) { | |
| 398 if (pendingItems.isEmpty) { | |
| 399 return false; | |
| 400 } | |
| 401 currentItem = pendingItems.removeLast(); | |
| 402 } | |
| 403 WorkItem childItem = currentItem.gatherInputs(taskManager); | |
| 404 while (childItem != null) { | |
| 405 pendingItems.add(currentItem); | |
| 406 currentItem = childItem; | |
| 407 childItem = currentItem.gatherInputs(taskManager); | |
| 408 } | |
| 409 return true; | |
| 410 } | |
| 411 } | |
| OLD | NEW |