| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// A wrapper around an analysis server instance |
| library services.analysis_servers; |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:logging/logging.dart'; |
| import 'package:pedantic/pedantic.dart'; |
| |
| import 'analysis_server.dart'; |
| import 'common_server_impl.dart' show BadRequest; |
| import 'flutter_web.dart'; |
| import 'protos/dart_services.pb.dart' as proto; |
| import 'pub.dart'; |
| import 'sdk_manager.dart'; |
| |
| final Logger _logger = Logger('analysis_servers'); |
| |
| class AnalysisServersWrapper { |
| AnalysisServersWrapper(); |
| |
| FlutterWebManager _flutterWebManager; |
| DartAnalysisServerWrapper _dartAnalysisServer; |
| FlutterAnalysisServerWrapper _flutterAnalysisServer; |
| |
| bool get running => |
| _dartAnalysisServer.analysisServer != null && |
| _flutterAnalysisServer.analysisServer != null; |
| |
| // If non-null, this value indicates that the server is starting/restarting |
| // and holds the time at which that process began. If null, the server is |
| // ready to handle requests. |
| DateTime _restartingSince = DateTime.now(); |
| |
| bool get isRestarting => (_restartingSince != null); |
| |
| // If the server has been trying and failing to restart for more than a half |
| // hour, something is seriously wrong. |
| bool get isHealthy => (_restartingSince == null || |
| DateTime.now().difference(_restartingSince).inMinutes < 30); |
| |
| Future<void> warmup() async { |
| _logger.info('Beginning AnalysisServersWrapper init().'); |
| _dartAnalysisServer = DartAnalysisServerWrapper(); |
| _flutterWebManager = FlutterWebManager(SdkManager.flutterSdk); |
| _flutterAnalysisServer = FlutterAnalysisServerWrapper(_flutterWebManager); |
| |
| await _flutterWebManager.warmup(); |
| _logger.info('FlutterWebManager warmed up'); |
| |
| await _dartAnalysisServer.init(); |
| _logger.info('Dart analysis server initialized.'); |
| |
| await _flutterAnalysisServer.init(); |
| _logger.info('Flutter analysis server initialized.'); |
| |
| unawaited(_dartAnalysisServer.onExit.then((int code) { |
| _logger.severe('dartAnalysisServer exited, code: $code'); |
| if (code != 0) { |
| exit(code); |
| } |
| })); |
| |
| unawaited(_flutterAnalysisServer.onExit.then((int code) { |
| _logger.severe('flutterAnalysisServer exited, code: $code'); |
| if (code != 0) { |
| exit(code); |
| } |
| })); |
| |
| _restartingSince = null; |
| |
| return Future.wait(<Future<dynamic>>[ |
| _flutterAnalysisServer.warmup(), |
| _dartAnalysisServer.warmup(), |
| ]); |
| } |
| |
| Future<void> _restart() async { |
| _logger.warning('Restarting'); |
| await shutdown(); |
| _logger.info('shutdown'); |
| |
| await warmup(); |
| _logger.warning('Restart complete'); |
| } |
| |
| Future<dynamic> shutdown() { |
| _restartingSince = DateTime.now(); |
| |
| return Future.wait(<Future<dynamic>>[ |
| _flutterWebManager.dispose(), |
| _flutterAnalysisServer.shutdown(), |
| _dartAnalysisServer.shutdown(), |
| ]); |
| } |
| |
| AnalysisServerWrapper _getCorrectAnalysisServer(String source) { |
| final imports = getAllImportsFor(source); |
| return _flutterWebManager.usesFlutterWeb(imports) |
| ? _flutterAnalysisServer |
| : _dartAnalysisServer; |
| } |
| |
| Future<proto.AnalysisResults> analyze(String source) => _perfLogAndRestart( |
| source, |
| () => _getCorrectAnalysisServer(source).analyze(source), |
| 'analysis', |
| 'Error during analyze on "$source"'); |
| |
| Future<proto.CompleteResponse> complete(String source, int offset) => |
| _perfLogAndRestart( |
| source, |
| () => _getCorrectAnalysisServer(source).complete(source, offset), |
| 'completions', |
| 'Error during complete on "$source" at $offset'); |
| |
| Future<proto.FixesResponse> getFixes(String source, int offset) => |
| _perfLogAndRestart( |
| source, |
| () => _getCorrectAnalysisServer(source).getFixes(source, offset), |
| 'fixes', |
| 'Error during fixes on "$source" at $offset'); |
| |
| Future<proto.AssistsResponse> getAssists(String source, int offset) => |
| _perfLogAndRestart( |
| source, |
| () => _getCorrectAnalysisServer(source).getAssists(source, offset), |
| 'assists', |
| 'Error during assists on "$source" at $offset'); |
| |
| Future<proto.FormatResponse> format(String source, int offset) => |
| _perfLogAndRestart( |
| source, |
| () => _getCorrectAnalysisServer(source).format(source, offset), |
| 'format', |
| 'Error during format on "$source" at $offset'); |
| |
| Future<Map<String, String>> dartdoc(String source, int offset) => |
| _perfLogAndRestart( |
| source, |
| () => _getCorrectAnalysisServer(source).dartdoc(source, offset), |
| 'dartdoc', |
| 'Error during dartdoc on "$source" at $offset'); |
| |
| Future<T> _perfLogAndRestart<T>(String source, Future<T> Function() body, |
| String action, String errorDescription) async { |
| await _checkPackageReferences(source); |
| try { |
| final watch = Stopwatch()..start(); |
| final response = await body(); |
| _logger.info('PERF: Computed $action in ${watch.elapsedMilliseconds}ms.'); |
| return response; |
| } catch (e, st) { |
| _logger.severe(errorDescription, e, st); |
| await _restart(); |
| rethrow; |
| } |
| } |
| |
| /// Check that the set of packages referenced is valid. |
| Future<void> _checkPackageReferences(String source) async { |
| final imports = getAllImportsFor(source); |
| |
| if (_flutterWebManager.hasUnsupportedImport(imports)) { |
| throw BadRequest( |
| 'Unsupported input: ${_flutterWebManager.getUnsupportedImport(imports)}'); |
| } |
| } |
| } |