Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, 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:io' show Platform; | 7 import 'dart:io' show Platform; |
| 8 | 8 |
| 9 import 'package:analysis_server/src/plugin/notification_manager.dart'; | 9 import 'package:analysis_server/src/plugin/notification_manager.dart'; |
| 10 import 'package:analyzer/context/context_root.dart' as analyzer; | 10 import 'package:analyzer/context/context_root.dart' as analyzer; |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 228 currentSession.sendRequest(params); | 228 currentSession.sendRequest(params); |
| 229 } | 229 } |
| 230 } | 230 } |
| 231 } | 231 } |
| 232 | 232 |
| 233 /** | 233 /** |
| 234 * An object used to manage the currently running plugins. | 234 * An object used to manage the currently running plugins. |
| 235 */ | 235 */ |
| 236 class PluginManager { | 236 class PluginManager { |
| 237 /** | 237 /** |
| 238 * A table, keyed by both a plugin and a request method, to a list of the | |
| 239 * times that it took the plugin to return a response to requests with the | |
| 240 * method. | |
| 241 */ | |
| 242 static Map<PluginInfo, Map<String, List<int>>> pluginResponseTimes = | |
| 243 <PluginInfo, Map<String, List<int>>>{}; | |
| 244 | |
| 245 /** | |
| 238 * The resource provider used to access the file system. | 246 * The resource provider used to access the file system. |
| 239 */ | 247 */ |
| 240 final ResourceProvider resourceProvider; | 248 final ResourceProvider resourceProvider; |
| 241 | 249 |
| 242 /** | 250 /** |
| 243 * The absolute path of the directory containing the on-disk byte store, or | 251 * The absolute path of the directory containing the on-disk byte store, or |
| 244 * `null` if there is no on-disk store. | 252 * `null` if there is no on-disk store. |
| 245 */ | 253 */ |
| 246 final String byteStorePath; | 254 final String byteStorePath; |
| 247 | 255 |
| (...skipping 312 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 560 return computePaths(executionFolder, runPub: true); | 568 return computePaths(executionFolder, runPub: true); |
| 561 } | 569 } |
| 562 | 570 |
| 563 /** | 571 /** |
| 564 * Return a hex-encoded MD5 signature of the given file [path]. | 572 * Return a hex-encoded MD5 signature of the given file [path]. |
| 565 */ | 573 */ |
| 566 String _uniqueDirectoryName(String path) { | 574 String _uniqueDirectoryName(String path) { |
| 567 List<int> bytes = md5.convert(path.codeUnits).bytes; | 575 List<int> bytes = md5.convert(path.codeUnits).bytes; |
| 568 return hex.encode(bytes); | 576 return hex.encode(bytes); |
| 569 } | 577 } |
| 578 | |
| 579 /** | |
| 580 * Record the fact that the given [plugin] responded to a request with the | |
| 581 * given [method] in the given [time]. | |
| 582 */ | |
| 583 static void recordResponseTime(PluginInfo plugin, String method, int time) { | |
| 584 pluginResponseTimes | |
| 585 .putIfAbsent(plugin, () => <String, List<int>>{}) | |
| 586 .putIfAbsent(method, () => <int>[]) | |
| 587 .add(time); | |
| 588 } | |
| 570 } | 589 } |
| 571 | 590 |
| 572 /** | 591 /** |
| 573 * Information about the execution a single plugin. | 592 * Information about the execution a single plugin. |
| 574 */ | 593 */ |
| 575 @visibleForTesting | 594 @visibleForTesting |
| 576 class PluginSession { | 595 class PluginSession { |
| 577 /** | 596 /** |
| 597 * The maximum number of milliseconds that server should wait for a response | |
| 598 * from a plugin before deciding that the plugin is hung. | |
| 599 */ | |
| 600 static const Duration MAXIMUM_RESPONSE_TIME = const Duration(minutes: 2); | |
| 601 | |
| 602 /** | |
| 603 * The length of time to wait after sending a 'plugin.shutdown' request before | |
| 604 * a failure to terminate will cause the isolate to be killed. | |
| 605 */ | |
| 606 static const Duration WAIT_FOR_SHUTDOWN_DURATION = | |
| 607 const Duration(seconds: 10); | |
| 608 | |
| 609 /** | |
| 578 * The information about the plugin being executed. | 610 * The information about the plugin being executed. |
| 579 */ | 611 */ |
| 580 final PluginInfo info; | 612 final PluginInfo info; |
| 581 | 613 |
| 582 /** | 614 /** |
| 583 * The completer used to signal when the plugin has stopped. | 615 * The completer used to signal when the plugin has stopped. |
| 584 */ | 616 */ |
| 585 Completer<Null> pluginStoppedCompleter = new Completer<Null>(); | 617 Completer<Null> pluginStoppedCompleter = new Completer<Null>(); |
| 586 | 618 |
| 587 /** | 619 /** |
| 588 * The channel used to communicate with the plugin. | 620 * The channel used to communicate with the plugin. |
| 589 */ | 621 */ |
| 590 ServerCommunicationChannel channel; | 622 ServerCommunicationChannel channel; |
| 591 | 623 |
| 592 /** | 624 /** |
| 593 * The index of the next request to be sent to the plugin. | 625 * The index of the next request to be sent to the plugin. |
| 594 */ | 626 */ |
| 595 int requestId = 0; | 627 int requestId = 0; |
| 596 | 628 |
| 597 /** | 629 /** |
| 598 * A table mapping the id's of requests to the functions used to handle the | 630 * A table mapping the id's of requests to the functions used to handle the |
| 599 * response to those requests. | 631 * response to those requests. |
| 600 */ | 632 */ |
| 601 Map<String, Completer<Response>> pendingRequests = | 633 Map<String, _PendingRequest> pendingRequests = <String, _PendingRequest>{}; |
| 602 <String, Completer<Response>>{}; | |
| 603 | 634 |
| 604 /** | 635 /** |
| 605 * A boolean indicating whether the plugin is compatible with the version of | 636 * A boolean indicating whether the plugin is compatible with the version of |
| 606 * the plugin API being used by this server. | 637 * the plugin API being used by this server. |
| 607 */ | 638 */ |
| 608 bool isCompatible = true; | 639 bool isCompatible = true; |
| 609 | 640 |
| 610 /** | 641 /** |
| 611 * The contact information to include when reporting problems related to the | 642 * The contact information to include when reporting problems related to the |
| 612 * plugin. | 643 * plugin. |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 682 // print(' $message'); | 713 // print(' $message'); |
| 683 // print(' ${new StackTrace.fromString(stackTrace)}'); | 714 // print(' ${new StackTrace.fromString(stackTrace)}'); |
| 684 // pluginStoppedCompleter.completeError(message, new StackTrace.fromString(st ackTrace)); | 715 // pluginStoppedCompleter.completeError(message, new StackTrace.fromString(st ackTrace)); |
| 685 } | 716 } |
| 686 | 717 |
| 687 /** | 718 /** |
| 688 * Handle a [response] from the plugin by completing the future that was | 719 * Handle a [response] from the plugin by completing the future that was |
| 689 * created when the request was sent. | 720 * created when the request was sent. |
| 690 */ | 721 */ |
| 691 void handleResponse(Response response) { | 722 void handleResponse(Response response) { |
| 692 Completer<Response> completer = pendingRequests.remove(response.id); | 723 _PendingRequest requestData = pendingRequests.remove(response.id); |
| 724 int responseTime = new DateTime.now().millisecondsSinceEpoch; | |
| 725 int duration = responseTime - requestData.requestTime; | |
| 726 PluginManager.recordResponseTime(info, requestData.method, duration); | |
| 727 Completer<Response> completer = requestData.completer; | |
| 693 if (completer != null) { | 728 if (completer != null) { |
| 694 completer.complete(response); | 729 completer.complete(response); |
| 695 } | 730 } |
| 696 } | 731 } |
| 697 | 732 |
| 698 /** | 733 /** |
| 734 * Return `true` if there are any requests that have not been responded to | |
| 735 * within the maximum allowed amount of time. | |
| 736 */ | |
| 737 bool isNonResponsive() { | |
| 738 // TODO(brianwilkerson) Figure out when to invoke this method in order to | |
| 739 // identify non-responsive plugins and kill them. | |
| 740 int cutOffTime = new DateTime.now().millisecondsSinceEpoch - | |
| 741 MAXIMUM_RESPONSE_TIME.inMilliseconds; | |
| 742 for (var requestData in pendingRequests.values) { | |
| 743 if (requestData.requestTime < cutOffTime) { | |
| 744 return true; | |
| 745 } | |
| 746 } | |
| 747 return false; | |
| 748 } | |
| 749 | |
| 750 /** | |
| 699 * Send a request, based on the given [parameters]. Return a future that will | 751 * Send a request, based on the given [parameters]. Return a future that will |
| 700 * complete when a response is received. | 752 * complete when a response is received. |
| 701 */ | 753 */ |
| 702 Future<Response> sendRequest(RequestParams parameters) { | 754 Future<Response> sendRequest(RequestParams parameters) { |
| 703 if (channel == null) { | 755 if (channel == null) { |
| 704 throw new StateError( | 756 throw new StateError( |
| 705 'Cannot send a request to a plugin that has stopped.'); | 757 'Cannot send a request to a plugin that has stopped.'); |
| 706 } | 758 } |
| 707 String id = nextRequestId; | 759 String id = nextRequestId; |
| 708 Completer<Response> completer = new Completer(); | 760 Completer<Response> completer = new Completer(); |
| 709 pendingRequests[id] = completer; | 761 int requestTime = new DateTime.now().millisecondsSinceEpoch; |
| 710 channel.sendRequest(parameters.toRequest(id)); | 762 Request request = parameters.toRequest(id); |
| 763 pendingRequests[id] = | |
| 764 new _PendingRequest(request.method, requestTime, completer); | |
| 765 channel.sendRequest(request); | |
| 711 return completer.future; | 766 return completer.future; |
| 712 } | 767 } |
| 713 | 768 |
| 714 /** | 769 /** |
| 715 * Start a new isolate that is running this plugin. The plugin will be sent | 770 * Start a new isolate that is running this plugin. The plugin will be sent |
| 716 * the given [byteStorePath]. Return `true` if the plugin is compatible and | 771 * the given [byteStorePath]. Return `true` if the plugin is compatible and |
| 717 * running. | 772 * running. |
| 718 */ | 773 */ |
| 719 Future<bool> start(String byteStorePath, String sdkPath) async { | 774 Future<bool> start(String byteStorePath, String sdkPath) async { |
| 720 if (channel != null) { | 775 if (channel != null) { |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 750 return true; | 805 return true; |
| 751 } | 806 } |
| 752 | 807 |
| 753 /** | 808 /** |
| 754 * Request that the plugin shutdown. | 809 * Request that the plugin shutdown. |
| 755 */ | 810 */ |
| 756 Future<Null> stop() { | 811 Future<Null> stop() { |
| 757 if (channel == null) { | 812 if (channel == null) { |
| 758 throw new StateError('Cannot stop a plugin that is not running.'); | 813 throw new StateError('Cannot stop a plugin that is not running.'); |
| 759 } | 814 } |
| 760 // TODO(brianwilkerson) Ensure that the isolate is killed if it does not | |
| 761 // terminate normally. | |
| 762 sendRequest(new PluginShutdownParams()); | 815 sendRequest(new PluginShutdownParams()); |
| 816 new Future.delayed(WAIT_FOR_SHUTDOWN_DURATION, () { | |
| 817 if (channel != null) { | |
| 818 channel.kill(); | |
| 819 channel = null; | |
| 820 } | |
| 821 }); | |
| 763 return pluginStoppedCompleter.future; | 822 return pluginStoppedCompleter.future; |
| 764 } | 823 } |
| 765 } | 824 } |
| 825 | |
| 826 /** | |
| 827 * Information about a request that has been sent but for which a response has | |
| 828 * not yet been received. | |
| 829 */ | |
| 830 class _PendingRequest { | |
| 831 /** | |
| 832 * The method of the request. | |
| 833 */ | |
| 834 final String method; | |
| 835 | |
| 836 /** | |
| 837 * The time at which the request was sent to the plugin. | |
| 838 */ | |
| 839 final int requestTime; | |
|
scheglov
2017/05/18 21:36:16
Or you could use new Stopwatch()..start() instead.
Brian Wilkerson
2017/05/18 21:51:39
I hadn't considered that, but now that I think abo
scheglov
2017/05/18 21:55:55
It's fine with my any way, just thought about othe
| |
| 840 | |
| 841 /** | |
| 842 * The completer that will be used to complete the future when the response is | |
| 843 * received from the plugin. | |
| 844 */ | |
| 845 final Completer<Response> completer; | |
| 846 | |
| 847 /** | |
| 848 * Initialize a pending request. | |
| 849 */ | |
| 850 _PendingRequest(this.method, this.requestTime, this.completer); | |
| 851 } | |
| OLD | NEW |