OLD | NEW |
---|---|
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, 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 import 'dart:async'; | 5 import 'dart:async'; |
6 import 'dart:collection'; | 6 import 'dart:collection'; |
7 import 'dart:typed_data'; | 7 import 'dart:typed_data'; |
8 | 8 |
9 import 'package:analyzer/dart/ast/ast.dart'; | 9 import 'package:analyzer/dart/ast/ast.dart'; |
10 import 'package:analyzer/error/error.dart'; | 10 import 'package:analyzer/error/error.dart'; |
11 import 'package:analyzer/error/listener.dart'; | 11 import 'package:analyzer/error/listener.dart'; |
12 import 'package:analyzer/file_system/file_system.dart'; | 12 import 'package:analyzer/file_system/file_system.dart'; |
13 import 'package:analyzer/src/context/context.dart'; | 13 import 'package:analyzer/src/context/context.dart'; |
14 import 'package:analyzer/src/dart/analysis/byte_store.dart'; | 14 import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
15 import 'package:analyzer/src/dart/analysis/file_state.dart'; | 15 import 'package:analyzer/src/dart/analysis/file_state.dart'; |
16 import 'package:analyzer/src/generated/engine.dart' | 16 import 'package:analyzer/src/generated/engine.dart' |
17 show AnalysisContext, AnalysisEngine, AnalysisOptions, ChangeSet; | 17 show AnalysisContext, AnalysisEngine, AnalysisOptions, ChangeSet; |
18 import 'package:analyzer/src/generated/source.dart'; | 18 import 'package:analyzer/src/generated/source.dart'; |
19 import 'package:analyzer/src/summary/api_signature.dart'; | 19 import 'package:analyzer/src/summary/api_signature.dart'; |
20 import 'package:analyzer/src/summary/format.dart'; | 20 import 'package:analyzer/src/summary/format.dart'; |
21 import 'package:analyzer/src/summary/idl.dart'; | 21 import 'package:analyzer/src/summary/idl.dart'; |
22 import 'package:analyzer/src/summary/link.dart'; | 22 import 'package:analyzer/src/summary/link.dart'; |
23 import 'package:analyzer/src/summary/package_bundle_reader.dart'; | 23 import 'package:analyzer/src/summary/package_bundle_reader.dart'; |
24 import 'package:analyzer/src/summary/summarize_elements.dart'; | 24 import 'package:analyzer/src/summary/summarize_elements.dart'; |
25 import 'package:meta/meta.dart'; | |
25 | 26 |
26 /** | 27 /** |
27 * This class computes [AnalysisResult]s for Dart files. | 28 * This class computes [AnalysisResult]s for Dart files. |
28 * | 29 * |
29 * Let the set of "explicitly analyzed files" denote the set of paths that have | 30 * Let the set of "explicitly analyzed files" denote the set of paths that have |
30 * been passed to [addFile] but not subsequently passed to [removeFile]. Let | 31 * been passed to [addFile] but not subsequently passed to [removeFile]. Let |
31 * the "current analysis results" denote the map from the set of explicitly | 32 * the "current analysis results" denote the map from the set of explicitly |
32 * analyzed files to the most recent [AnalysisResult] delivered to [results] | 33 * analyzed files to the most recent [AnalysisResult] delivered to [results] |
33 * for each file. Let the "current file state" represent a map from file path | 34 * for each file. Let the "current file state" represent a map from file path |
34 * to the file contents most recently read from that file, or fetched from the | 35 * to the file contents most recently read from that file, or fetched from the |
(...skipping 26 matching lines...) Expand all Loading... | |
61 * TODO(scheglov) Clean up the list of implicitly analyzed files. | 62 * TODO(scheglov) Clean up the list of implicitly analyzed files. |
62 * | 63 * |
63 * TODO(scheglov) Handle not existing 'dart:x' URIs (while user is typing). | 64 * TODO(scheglov) Handle not existing 'dart:x' URIs (while user is typing). |
64 */ | 65 */ |
65 class AnalysisDriver { | 66 class AnalysisDriver { |
66 /** | 67 /** |
67 * The version of data format, should be incremented on every format change. | 68 * The version of data format, should be incremented on every format change. |
68 */ | 69 */ |
69 static const int DATA_VERSION = 1; | 70 static const int DATA_VERSION = 1; |
70 | 71 |
72 /** | |
73 * The name of the driver, e.g. the name of the folder. | |
74 */ | |
71 String name; | 75 String name; |
76 | |
77 /** | |
78 * The scheduler that schedules analysis work in this, and possible other | |
Paul Berry
2016/11/08 22:43:26
s/possible/possibly/
scheglov
2016/11/09 01:54:50
Done.
| |
79 * analysis drivers. | |
80 */ | |
81 final AnalysisDriverScheduler _scheduler; | |
82 | |
83 /** | |
84 * The logger to write performed operations and performance to. | |
85 */ | |
72 final PerformanceLog _logger; | 86 final PerformanceLog _logger; |
73 | 87 |
74 /** | 88 /** |
75 * The resource provider for working with files. | 89 * The resource provider for working with files. |
76 */ | 90 */ |
77 final ResourceProvider _resourceProvider; | 91 final ResourceProvider _resourceProvider; |
78 | 92 |
79 /** | 93 /** |
80 * The byte storage to get and put serialized data. | 94 * The byte storage to get and put serialized data. |
81 * | 95 * |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
142 * The set of files that are currently scheduled for analysis. | 156 * The set of files that are currently scheduled for analysis. |
143 */ | 157 */ |
144 final _filesToAnalyze = new LinkedHashSet<String>(); | 158 final _filesToAnalyze = new LinkedHashSet<String>(); |
145 | 159 |
146 /** | 160 /** |
147 * Mapping from library URIs to the dependency signature of the library. | 161 * Mapping from library URIs to the dependency signature of the library. |
148 */ | 162 */ |
149 final _dependencySignatureMap = <Uri, String>{}; | 163 final _dependencySignatureMap = <Uri, String>{}; |
150 | 164 |
151 /** | 165 /** |
152 * The monitor that is signalled when there is work to do. | 166 * The controller for the [results] stream. |
153 */ | 167 */ |
154 final _Monitor _hasWork = new _Monitor(); | 168 final _resultController = new StreamController<AnalysisResult>(); |
155 | 169 |
156 /** | 170 /** |
157 * The controller for the [status] stream. | 171 * The controller for the [status] stream. |
158 */ | 172 */ |
159 final _statusController = new StreamController<AnalysisStatus>(); | 173 final _statusController = new StreamController<AnalysisStatus>(); |
160 | 174 |
161 /** | 175 /** |
162 * The last status sent to the [status] stream. | 176 * The last status sent to the [status] stream. |
163 */ | 177 */ |
164 AnalysisStatus _currentStatus = AnalysisStatus.IDLE; | 178 AnalysisStatus _currentStatus = AnalysisStatus.IDLE; |
165 | 179 |
166 /** | 180 /** |
167 * Create a new instance of [AnalysisDriver]. | 181 * Create a new instance of [AnalysisDriver]. |
168 * | 182 * |
169 * The given [SourceFactory] is cloned to ensure that it does not contain a | 183 * The given [SourceFactory] is cloned to ensure that it does not contain a |
170 * reference to a [AnalysisContext] in which it could have been used. | 184 * reference to a [AnalysisContext] in which it could have been used. |
171 */ | 185 */ |
172 AnalysisDriver(this._logger, this._resourceProvider, this._byteStore, | 186 AnalysisDriver( |
173 this._contentOverlay, SourceFactory sourceFactory, this._analysisOptions) | 187 this._scheduler, |
188 this._logger, | |
189 this._resourceProvider, | |
190 this._byteStore, | |
191 this._contentOverlay, | |
192 SourceFactory sourceFactory, | |
193 this._analysisOptions) | |
174 : _sourceFactory = sourceFactory.clone() { | 194 : _sourceFactory = sourceFactory.clone() { |
175 _fillSalt(); | 195 _fillSalt(); |
176 _sdkBundle = sourceFactory.dartSdk.getLinkedBundle(); | 196 _sdkBundle = sourceFactory.dartSdk.getLinkedBundle(); |
177 _fsState = new FileSystemState(_logger, _byteStore, _contentOverlay, | 197 _fsState = new FileSystemState(_logger, _byteStore, _contentOverlay, |
178 _resourceProvider, _sourceFactory, _analysisOptions, _salt); | 198 _resourceProvider, _sourceFactory, _analysisOptions, _salt); |
199 _scheduler._add(this); | |
179 } | 200 } |
180 | 201 |
181 /** | 202 /** |
182 * Set the list of files that the driver should try to analyze sooner. | 203 * Set the list of files that the driver should try to analyze sooner. |
183 * | 204 * |
184 * Every path in the list must be absolute and normalized. | 205 * Every path in the list must be absolute and normalized. |
185 * | 206 * |
186 * The driver will produce the results through the [results] stream. The | 207 * The driver will produce the results through the [results] stream. The |
187 * exact order in which results are produced is not defined, neither | 208 * exact order in which results are produced is not defined, neither |
188 * between priority files, nor between priority and non-priority files. | 209 * between priority files, nor between priority and non-priority files. |
189 */ | 210 */ |
190 void set priorityFiles(List<String> priorityPaths) { | 211 void set priorityFiles(List<String> priorityPaths) { |
191 _priorityFiles.clear(); | 212 _priorityFiles.clear(); |
192 _priorityFiles.addAll(priorityPaths); | 213 _priorityFiles.addAll(priorityPaths); |
193 _transitionToAnalyzing(); | 214 _transitionToAnalyzing(); |
194 _hasWork.notify(); | 215 _scheduler._notify(this); |
195 } | 216 } |
196 | 217 |
197 /** | 218 /** |
198 * Return the [Stream] that produces [AnalysisResult]s for added files. | 219 * Return the [Stream] that produces [AnalysisResult]s for added files. |
199 * | 220 * |
200 * Analysis starts when the client starts listening to the stream, and stops | 221 * Note that the stream supports only one single subscriber. |
201 * when the client cancels the subscription. Note that the stream supports | |
202 * only one single subscriber. | |
203 * | 222 * |
204 * When the client starts listening, the analysis state transitions to | 223 * Analysis starts when the [AnalysisDriverScheduler] is started and the |
205 * "analyzing" and an analysis result is produced for every added file prior | 224 * driver is added to it. The analysis state transitions to "analyzing" and |
206 * to the next time the analysis state transitions to "idle". | 225 * an analysis result is produced for every added file prior to the next time |
226 * the analysis state transitions to "idle". | |
207 * | 227 * |
208 * At least one analysis result is produced for every file passed to | 228 * At least one analysis result is produced for every file passed to |
209 * [addFile] or [changeFile] prior to the next time the analysis state | 229 * [addFile] or [changeFile] prior to the next time the analysis state |
210 * transitions to "idle", unless the file is later removed from analysis | 230 * transitions to "idle", unless the file is later removed from analysis |
211 * using [removeFile]. Analysis results for other files are produced only if | 231 * using [removeFile]. Analysis results for other files are produced only if |
212 * the changes affect analysis results of other files. | 232 * the changes affect analysis results of other files. |
213 * | 233 * |
214 * More than one result might be produced for the same file, even if the | 234 * More than one result might be produced for the same file, even if the |
215 * client does not change the state of the files. | 235 * client does not change the state of the files. |
216 * | 236 * |
217 * Results might be produced even for files that have never been added | 237 * Results might be produced even for files that have never been added |
218 * using [addFile], for example when [getResult] was called for a file. | 238 * using [addFile], for example when [getResult] was called for a file. |
219 */ | 239 */ |
220 Stream<AnalysisResult> get results async* { | 240 Stream<AnalysisResult> get results => _resultController.stream; |
221 try { | |
222 PerformanceLogSection analysisSection = null; | |
223 while (true) { | |
224 // Pump the event queue to allow IO and other asynchronous data | |
225 // processing while analysis is active. For example Analysis Server | |
226 // needs to be able to process `updateContent` or `setPriorityFiles` | |
227 // requests while background analysis is in progress. | |
228 // | |
229 // The number of pumpings is arbitrary, might be changed if we see that | |
230 // analysis or other data processing tasks are starving. Ideally we | |
231 // would need to be able to set priority of (continuous) asynchronous | |
232 // tasks. | |
233 await _pumpEventQueue(128); | |
234 | |
235 await _hasWork.signal; | |
236 | |
237 if (analysisSection == null) { | |
238 analysisSection = _logger.enter('Analyzing'); | |
239 } | |
240 | |
241 // Verify all changed files one at a time. | |
242 if (_changedFiles.isNotEmpty) { | |
243 String path = _removeFirst(_changedFiles); | |
244 _verifyApiSignature(path); | |
245 // Repeat the processing loop. | |
246 _hasWork.notify(); | |
247 continue; | |
248 } | |
249 | |
250 // Analyze a requested file. | |
251 if (_requestedFiles.isNotEmpty) { | |
252 String path = _requestedFiles.keys.first; | |
253 AnalysisResult result = _computeAnalysisResult(path, withUnit: true); | |
254 // Notify the completers. | |
255 _requestedFiles.remove(path).forEach((completer) { | |
256 completer.complete(result); | |
257 }); | |
258 // Remove from to be analyzed and produce it now. | |
259 _filesToAnalyze.remove(path); | |
260 yield result; | |
261 // Repeat the processing loop. | |
262 _hasWork.notify(); | |
263 continue; | |
264 } | |
265 | |
266 // Analyze a priority file. | |
267 if (_priorityFiles.isNotEmpty) { | |
268 bool analyzed = false; | |
269 for (String path in _priorityFiles) { | |
270 if (_filesToAnalyze.remove(path)) { | |
271 analyzed = true; | |
272 AnalysisResult result = | |
273 _computeAnalysisResult(path, withUnit: true); | |
274 yield result; | |
275 break; | |
276 } | |
277 } | |
278 // Repeat the processing loop. | |
279 if (analyzed) { | |
280 _hasWork.notify(); | |
281 continue; | |
282 } | |
283 } | |
284 | |
285 // Analyze a general file. | |
286 if (_filesToAnalyze.isNotEmpty) { | |
287 String path = _removeFirst(_filesToAnalyze); | |
288 AnalysisResult result = _computeAnalysisResult(path, withUnit: false); | |
289 yield result; | |
290 // Repeat the processing loop. | |
291 _hasWork.notify(); | |
292 continue; | |
293 } | |
294 | |
295 // There is nothing to do. | |
296 analysisSection.exit(); | |
297 analysisSection = null; | |
298 _transitionToIdle(); | |
299 } | |
300 } finally { | |
301 print('The stream was cancelled.'); | |
302 } | |
303 } | |
304 | 241 |
305 /** | 242 /** |
306 * Return the stream that produces [AnalysisStatus] events. | 243 * Return the stream that produces [AnalysisStatus] events. |
307 */ | 244 */ |
308 Stream<AnalysisStatus> get status => _statusController.stream; | 245 Stream<AnalysisStatus> get status => _statusController.stream; |
309 | 246 |
310 /** | 247 /** |
248 * Return the priority of work that the driver needs to perform. | |
249 */ | |
250 AnalysisDriverPriority get _workPriority { | |
251 if (_requestedFiles.isNotEmpty) { | |
252 return AnalysisDriverPriority.interactive; | |
253 } | |
254 if (_priorityFiles.isNotEmpty) { | |
255 for (String path in _priorityFiles) { | |
256 if (_filesToAnalyze.contains(path)) { | |
257 return AnalysisDriverPriority.priority; | |
258 } | |
259 } | |
260 } | |
261 if (_filesToAnalyze.isNotEmpty) { | |
262 return AnalysisDriverPriority.general; | |
263 } | |
264 if (_changedFiles.isNotEmpty) { | |
265 return AnalysisDriverPriority.general; | |
266 } | |
267 _transitionToIdle(); | |
268 return AnalysisDriverPriority.nothing; | |
269 } | |
270 | |
271 /** | |
311 * Add the file with the given [path] to the set of files to analyze. | 272 * Add the file with the given [path] to the set of files to analyze. |
312 * | 273 * |
313 * The [path] must be absolute and normalized. | 274 * The [path] must be absolute and normalized. |
314 * | 275 * |
315 * The results of analysis are eventually produced by the [results] stream. | 276 * The results of analysis are eventually produced by the [results] stream. |
316 */ | 277 */ |
317 void addFile(String path) { | 278 void addFile(String path) { |
318 if (AnalysisEngine.isDartFileName(path)) { | 279 if (AnalysisEngine.isDartFileName(path)) { |
319 _explicitFiles.add(path); | 280 _explicitFiles.add(path); |
320 _filesToAnalyze.add(path); | 281 _filesToAnalyze.add(path); |
321 } | 282 } |
322 _transitionToAnalyzing(); | 283 _transitionToAnalyzing(); |
323 _hasWork.notify(); | 284 _scheduler._notify(this); |
324 } | 285 } |
325 | 286 |
326 /** | 287 /** |
327 * The file with the given [path] might have changed - updated, added or | 288 * The file with the given [path] might have changed - updated, added or |
328 * removed. Or not, we don't know. Or it might have, but then changed back. | 289 * removed. Or not, we don't know. Or it might have, but then changed back. |
329 * | 290 * |
330 * The [path] must be absolute and normalized. | 291 * The [path] must be absolute and normalized. |
331 * | 292 * |
332 * The [path] can be any file - explicitly or implicitly analyzed, or neither. | 293 * The [path] can be any file - explicitly or implicitly analyzed, or neither. |
333 * | 294 * |
334 * Causes the analysis state to transition to "analyzing" (if it is not in | 295 * Causes the analysis state to transition to "analyzing" (if it is not in |
335 * that state already). Schedules the file contents for [path] to be read | 296 * that state already). Schedules the file contents for [path] to be read |
336 * into the current file state prior to the next time the analysis state | 297 * into the current file state prior to the next time the analysis state |
337 * transitions to "idle". | 298 * transitions to "idle". |
338 * | 299 * |
339 * Invocation of this method will not prevent a [Future] returned from | 300 * Invocation of this method will not prevent a [Future] returned from |
340 * [getResult] from completing with a result, but the result is not | 301 * [getResult] from completing with a result, but the result is not |
341 * guaranteed to be consistent with the new current file state after this | 302 * guaranteed to be consistent with the new current file state after this |
342 * [changeFile] invocation. | 303 * [changeFile] invocation. |
343 */ | 304 */ |
344 void changeFile(String path) { | 305 void changeFile(String path) { |
345 if (AnalysisEngine.isDartFileName(path)) { | 306 if (AnalysisEngine.isDartFileName(path)) { |
346 _changedFiles.add(path); | 307 _changedFiles.add(path); |
347 if (_explicitFiles.contains(path)) { | 308 if (_explicitFiles.contains(path)) { |
348 _filesToAnalyze.add(path); | 309 _filesToAnalyze.add(path); |
349 } | 310 } |
350 } | 311 } |
351 _transitionToAnalyzing(); | 312 _transitionToAnalyzing(); |
352 _hasWork.notify(); | 313 _scheduler._notify(this); |
353 } | 314 } |
354 | 315 |
355 /** | 316 /** |
317 * Notify the driver that the client is going to stop using it. | |
318 */ | |
319 void dispose() { | |
320 _scheduler._remove(this); | |
321 } | |
322 | |
323 /** | |
356 * Return the [Future] that completes with a [AnalysisResult] for the file | 324 * Return the [Future] that completes with a [AnalysisResult] for the file |
357 * with the given [path]. | 325 * with the given [path]. |
358 * | 326 * |
359 * The [path] must be absolute and normalized. | 327 * The [path] must be absolute and normalized. |
360 * | 328 * |
361 * The [path] can be any file - explicitly or implicitly analyzed, or neither. | 329 * The [path] can be any file - explicitly or implicitly analyzed, or neither. |
362 * | 330 * |
363 * Causes the analysis state to transition to "analyzing" (if it is not in | 331 * Causes the analysis state to transition to "analyzing" (if it is not in |
364 * that state already), the driver will read the file and produce the analysis | 332 * that state already), the driver will read the file and produce the analysis |
365 * result for it, which is consistent with the current file state (including | 333 * result for it, which is consistent with the current file state (including |
366 * the new state of the file), prior to the next time the analysis state | 334 * the new state of the file), prior to the next time the analysis state |
367 * transitions to "idle". | 335 * transitions to "idle". |
368 */ | 336 */ |
369 Future<AnalysisResult> getResult(String path) { | 337 Future<AnalysisResult> getResult(String path) { |
370 var completer = new Completer<AnalysisResult>(); | 338 var completer = new Completer<AnalysisResult>(); |
371 _requestedFiles | 339 _requestedFiles |
372 .putIfAbsent(path, () => <Completer<AnalysisResult>>[]) | 340 .putIfAbsent(path, () => <Completer<AnalysisResult>>[]) |
373 .add(completer); | 341 .add(completer); |
374 _transitionToAnalyzing(); | 342 _transitionToAnalyzing(); |
375 _hasWork.notify(); | 343 _scheduler._notify(this); |
376 return completer.future; | 344 return completer.future; |
377 } | 345 } |
378 | 346 |
379 /** | 347 /** |
380 * Return `true` if the file with the given [path] was explicitly added | 348 * Return `true` if the file with the given [path] was explicitly added |
381 * to analysis using [addFile]. | 349 * to analysis using [addFile]. |
382 */ | 350 */ |
383 bool isAddedFile(String path) { | 351 bool isAddedFile(String path) { |
384 return _explicitFiles.contains(path); | 352 return _explicitFiles.contains(path); |
385 } | 353 } |
(...skipping 282 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
668 ApiSignature signature = new ApiSignature(); | 636 ApiSignature signature = new ApiSignature(); |
669 signature.addUint32List(_salt); | 637 signature.addUint32List(_salt); |
670 signature.addString(dependencyHash); | 638 signature.addString(dependencyHash); |
671 signature.addString(file.contentHash); | 639 signature.addString(file.contentHash); |
672 return '${signature.toHex()}.resolved'; | 640 return '${signature.toHex()}.resolved'; |
673 } | 641 } |
674 return null; | 642 return null; |
675 } | 643 } |
676 | 644 |
677 /** | 645 /** |
646 * Perform a single chunk of work and produce [results]. | |
647 */ | |
648 Future<Null> _performWork() async { | |
649 // Verify all changed files one at a time. | |
650 if (_changedFiles.isNotEmpty) { | |
651 String path = _removeFirst(_changedFiles); | |
652 _verifyApiSignature(path); | |
653 return; | |
654 } | |
655 | |
656 // Analyze a requested file. | |
657 if (_requestedFiles.isNotEmpty) { | |
658 String path = _requestedFiles.keys.first; | |
659 AnalysisResult result = _computeAnalysisResult(path, withUnit: true); | |
660 // Notify the completers. | |
661 _requestedFiles.remove(path).forEach((completer) { | |
662 completer.complete(result); | |
663 }); | |
664 // Remove from to be analyzed and produce it now. | |
665 _filesToAnalyze.remove(path); | |
666 _resultController.add(result); | |
667 return; | |
668 } | |
669 | |
670 // Analyze a priority file. | |
671 if (_priorityFiles.isNotEmpty) { | |
672 for (String path in _priorityFiles) { | |
673 if (_filesToAnalyze.remove(path)) { | |
674 AnalysisResult result = _computeAnalysisResult(path, withUnit: true); | |
675 _resultController.add(result); | |
676 return; | |
677 } | |
678 } | |
679 } | |
680 | |
681 // Analyze a general file. | |
682 if (_filesToAnalyze.isNotEmpty) { | |
683 String path = _removeFirst(_filesToAnalyze); | |
684 AnalysisResult result = _computeAnalysisResult(path, withUnit: false); | |
685 _resultController.add(result); | |
686 return; | |
687 } | |
688 } | |
689 | |
690 /** | |
678 * Send a notifications to the [status] stream that the driver started | 691 * Send a notifications to the [status] stream that the driver started |
679 * analyzing. | 692 * analyzing. |
680 */ | 693 */ |
681 void _transitionToAnalyzing() { | 694 void _transitionToAnalyzing() { |
682 if (_currentStatus != AnalysisStatus.ANALYZING) { | 695 if (_currentStatus != AnalysisStatus.ANALYZING) { |
683 _currentStatus = AnalysisStatus.ANALYZING; | 696 _currentStatus = AnalysisStatus.ANALYZING; |
684 _statusController.add(AnalysisStatus.ANALYZING); | 697 _statusController.add(AnalysisStatus.ANALYZING); |
685 } | 698 } |
686 } | 699 } |
687 | 700 |
(...skipping 26 matching lines...) Expand all Loading... | |
714 if (anyApiChanged) { | 727 if (anyApiChanged) { |
715 _logger.writeln('API signatures mismatch found for $path'); | 728 _logger.writeln('API signatures mismatch found for $path'); |
716 _dependencySignatureMap.clear(); | 729 _dependencySignatureMap.clear(); |
717 _filesToAnalyze.addAll(_explicitFiles); | 730 _filesToAnalyze.addAll(_explicitFiles); |
718 } | 731 } |
719 return files[0]; | 732 return files[0]; |
720 }); | 733 }); |
721 } | 734 } |
722 | 735 |
723 /** | 736 /** |
737 * Remove and return the first item in the given [set]. | |
738 */ | |
739 static Object/*=T*/ _removeFirst/*<T>*/(LinkedHashSet<Object/*=T*/ > set) { | |
740 Object/*=T*/ element = set.first; | |
741 set.remove(element); | |
742 return element; | |
743 } | |
744 } | |
745 | |
746 /** | |
747 * Priorities of [AnalysisDriver] work. The closer a priority to the beginning | |
748 * of the list, the earlier the corresponding [AnalysisDriver] should be asked | |
749 * to perform work. | |
750 */ | |
751 @visibleForTesting | |
752 enum AnalysisDriverPriority { interactive, priority, general, nothing } | |
753 | |
754 /** | |
755 * Instances of this class schedule work in multiple [AnalysisDriver]s so that | |
756 * work with the lowest priority is performed first. | |
757 */ | |
758 class AnalysisDriverScheduler { | |
759 final PerformanceLog _logger; | |
760 final List<AnalysisDriver> _drivers = []; | |
761 final _Monitor _hasWork = new _Monitor(); | |
762 | |
763 bool _started = false; | |
764 | |
765 AnalysisDriverScheduler(this._logger); | |
766 | |
767 /** | |
768 * Start the scheduler, so that any [AnalysisDriver] created before or | |
769 * after will be asked to perform work. | |
770 */ | |
771 void start() { | |
772 if (_started) { | |
773 throw new StateError('The scheduler has already been started.'); | |
774 } | |
775 _started = true; | |
776 _run(); | |
777 } | |
778 | |
779 /** | |
780 * Add the given [driver] and schedule it to perform its work. | |
781 */ | |
782 void _add(AnalysisDriver driver) { | |
783 _drivers.add(driver); | |
784 _hasWork.notify(); | |
785 } | |
786 | |
787 /** | |
788 * Notify that there is a change to the [driver], it it might need to | |
789 * perform some work. | |
790 */ | |
791 void _notify(AnalysisDriver driver) { | |
792 _hasWork.notify(); | |
793 } | |
794 | |
795 /** | |
796 * Remove the given [driver] from the scheduler, so that it will not be | |
797 * asked to perform any new work. | |
798 */ | |
799 void _remove(AnalysisDriver driver) { | |
800 _drivers.remove(driver); | |
801 _hasWork.notify(); | |
802 } | |
803 | |
804 /** | |
805 * Run infinitely analysis cycle, selecting the drivers with the lowest | |
806 * priority first. | |
807 */ | |
808 Future<Null> _run() async { | |
809 PerformanceLogSection analysisSection; | |
810 while (true) { | |
811 // Pump the event queue to allow IO and other asynchronous data | |
812 // processing while analysis is active. For example Analysis Server | |
813 // needs to be able to process `updateContent` or `setPriorityFiles` | |
814 // requests while background analysis is in progress. | |
815 // | |
816 // The number of pumpings is arbitrary, might be changed if we see that | |
817 // analysis or other data processing tasks are starving. Ideally we | |
818 // would need to be able to set priority of (continuous) asynchronous | |
819 // tasks. | |
820 await _pumpEventQueue(128); | |
821 | |
822 await _hasWork.signal; | |
823 | |
824 if (analysisSection == null) { | |
825 analysisSection = _logger.enter('Analyzing'); | |
826 } | |
827 | |
828 // Find the driver with the most priority work. | |
Paul Berry
2016/11/08 22:43:26
s/most/highest/
scheglov
2016/11/09 01:54:50
Done.
| |
829 AnalysisDriver bestDriver; | |
830 AnalysisDriverPriority bestDriverPriority; | |
831 for (AnalysisDriver driver in _drivers) { | |
832 AnalysisDriverPriority priority = driver._workPriority; | |
833 if (bestDriverPriority == null || | |
834 priority.index < bestDriverPriority.index) { | |
835 bestDriver = driver; | |
836 bestDriverPriority = priority; | |
837 } | |
838 } | |
839 | |
840 // Continue to sleeping if no work to do. | |
841 if (bestDriverPriority == null || | |
842 bestDriverPriority == AnalysisDriverPriority.nothing) { | |
843 analysisSection.exit(); | |
844 analysisSection = null; | |
845 continue; | |
846 } | |
847 | |
848 // Ask the driver to perform a chunk of work. | |
849 await bestDriver._performWork(); | |
850 | |
851 // Schedule one more cycle. | |
852 _hasWork.notify(); | |
853 } | |
854 } | |
855 | |
856 /** | |
724 * Returns a [Future] that completes after performing [times] pumpings of | 857 * Returns a [Future] that completes after performing [times] pumpings of |
725 * the event queue. | 858 * the event queue. |
726 */ | 859 */ |
727 static Future _pumpEventQueue(int times) { | 860 static Future _pumpEventQueue(int times) { |
728 if (times == 0) { | 861 if (times == 0) { |
729 return new Future.value(); | 862 return new Future.value(); |
730 } | 863 } |
731 return new Future.delayed(Duration.ZERO, () => _pumpEventQueue(times - 1)); | 864 return new Future.delayed(Duration.ZERO, () => _pumpEventQueue(times - 1)); |
732 } | 865 } |
733 | |
734 /** | |
735 * Remove and return the first item in the given [set]. | |
736 */ | |
737 static Object/*=T*/ _removeFirst/*<T>*/(LinkedHashSet<Object/*=T*/ > set) { | |
738 Object/*=T*/ element = set.first; | |
739 set.remove(element); | |
740 return element; | |
741 } | |
742 } | 866 } |
743 | 867 |
744 /** | 868 /** |
745 * The result of analyzing of a single file. | 869 * The result of analyzing of a single file. |
746 * | 870 * |
747 * These results are self-consistent, i.e. [content], [contentHash], the | 871 * These results are self-consistent, i.e. [content], [contentHash], the |
748 * resolved [unit] correspond to each other. All referenced elements, even | 872 * resolved [unit] correspond to each other. All referenced elements, even |
749 * external ones, are also self-consistent. But none of the results is | 873 * external ones, are also self-consistent. But none of the results is |
750 * guaranteed to be consistent with the state of the files. | 874 * guaranteed to be consistent with the state of the files. |
751 * | 875 * |
(...skipping 270 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1022 /** | 1146 /** |
1023 * Complete the [signal] future if it is not completed yet. It is safe to | 1147 * Complete the [signal] future if it is not completed yet. It is safe to |
1024 * call this method multiple times, but the [signal] will complete only once. | 1148 * call this method multiple times, but the [signal] will complete only once. |
1025 */ | 1149 */ |
1026 void notify() { | 1150 void notify() { |
1027 if (!_completer.isCompleted) { | 1151 if (!_completer.isCompleted) { |
1028 _completer.complete(null); | 1152 _completer.complete(null); |
1029 } | 1153 } |
1030 } | 1154 } |
1031 } | 1155 } |
OLD | NEW |