1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:io';
7
8import 'package:vm_service/vm_service.dart' as vms;
9
10import '../common/logging.dart';
11
12const Duration _kConnectTimeout = Duration(seconds: 3);
13final Logger _log = Logger('DartVm');
14
15/// Signature of an asynchronous function for establishing a [vms.VmService]
16/// connection to a [Uri].
17typedef RpcPeerConnectionFunction =
18 Future<vms.VmService> Function(Uri uri, {required Duration timeout});
19
20/// [DartVm] uses this function to connect to the Dart VM on Fuchsia.
21///
22/// This function can be assigned to a different one in the event that a
23/// custom connection function is needed.
24RpcPeerConnectionFunction fuchsiaVmServiceConnectionFunction = _waitAndConnect;
25
26/// Attempts to connect to a Dart VM service.
27///
28/// Gives up after `timeout` has elapsed.
29Future<vms.VmService> _waitAndConnect(Uri uri, {Duration timeout = _kConnectTimeout}) async {
30 int attempts = 0;
31 late WebSocket socket;
32 while (true) {
33 try {
34 socket = await WebSocket.connect(uri.toString());
35 final StreamController<dynamic> controller = StreamController<dynamic>();
36 final Completer<void> streamClosedCompleter = Completer<void>();
37 socket.listen(
38 (dynamic data) => controller.add(data),
39 onDone: () => streamClosedCompleter.complete(),
40 );
41 final vms.VmService service = vms.VmService(
42 controller.stream,
43 socket.add,
44 disposeHandler: () => socket.close(),
45 streamClosed: streamClosedCompleter.future,
46 );
47 // This call is to ensure we are able to establish a connection instead of
48 // keeping on trucking and failing farther down the process.
49 await service.getVersion();
50 return service;
51 } catch (e) {
52 // We should not be catching all errors arbitrarily here, this might hide real errors.
53 // TODO(ianh): Determine which exceptions to catch here.
54 await socket.close();
55 if (attempts > 5) {
56 _log.warning('It is taking an unusually long time to connect to the VM...');
57 }
58 attempts += 1;
59 await Future<void>.delayed(timeout);
60 }
61 }
62}
63
64/// Restores the VM service connection function to the default implementation.
65void restoreVmServiceConnectionFunction() {
66 fuchsiaVmServiceConnectionFunction = _waitAndConnect;
67}
68
69/// An error raised when a malformed RPC response is received from the Dart VM.
70///
71/// A more detailed description of the error is found within the [message]
72/// field.
73class RpcFormatError extends Error {
74 /// Basic constructor outlining the reason for the format error.
75 RpcFormatError(this.message);
76
77 /// The reason for format error.
78 final String message;
79
80 @override
81 String toString() {
82 return '$RpcFormatError: $message\n${super.stackTrace}';
83 }
84}
85
86/// Handles JSON RPC-2 communication with a Dart VM service.
87///
88/// Wraps existing RPC calls to the Dart VM service.
89class DartVm {
90 DartVm._(this._vmService, this.uri);
91
92 final vms.VmService _vmService;
93
94 /// The URL through which this DartVM instance is connected.
95 final Uri uri;
96
97 /// Attempts to connect to the given [Uri].
98 ///
99 /// Throws an error if unable to connect.
100 static Future<DartVm> connect(Uri uri, {Duration timeout = _kConnectTimeout}) async {
101 if (uri.scheme == 'http') {
102 uri = uri.replace(scheme: 'ws', path: '/ws');
103 }
104
105 final vms.VmService service = await fuchsiaVmServiceConnectionFunction(uri, timeout: timeout);
106 return DartVm._(service, uri);
107 }
108
109 /// Returns a [List] of [IsolateRef] objects whose name matches `pattern`.
110 ///
111 /// This is not limited to Isolates running Flutter, but to any Isolate on the
112 /// VM. Therefore, the [pattern] argument should be written to exclude
113 /// matching unintended isolates.
114 Future<List<IsolateRef>> getMainIsolatesByPattern(Pattern pattern) async {
115 final vms.VM vmRef = await _vmService.getVM();
116 final List<IsolateRef> result = <IsolateRef>[];
117 for (final vms.IsolateRef isolateRef in vmRef.isolates!) {
118 if (pattern.matchAsPrefix(isolateRef.name!) != null) {
119 _log.fine('Found Isolate matching "$pattern": "${isolateRef.name}"');
120 result.add(IsolateRef._fromJson(isolateRef.json!, this));
121 }
122 }
123 return result;
124 }
125
126 /// Returns a list of [FlutterView] objects running across all Dart VM's.
127 ///
128 /// If there is no associated isolate with the flutter view (used to determine
129 /// the flutter view's name), then the flutter view's ID will be added
130 /// instead. If none of these things can be found (isolate has no name or the
131 /// flutter view has no ID), then the result will not be added to the list.
132 Future<List<FlutterView>> getAllFlutterViews() async {
133 final List<FlutterView> views = <FlutterView>[];
134 final vms.Response rpcResponse = await _vmService.callMethod('_flutter.listViews');
135 for (final Map<String, dynamic> jsonView
136 in (rpcResponse.json!['views'] as List<dynamic>).cast<Map<String, dynamic>>()) {
137 views.add(FlutterView._fromJson(jsonView));
138 }
139 return views;
140 }
141
142 /// Tests that the connection to the [vms.VmService] is valid.
143 Future<void> ping() async {
144 final vms.Version version = await _vmService.getVersion();
145 _log.fine('DartVM($uri) version check result: $version');
146 }
147
148 /// Disconnects from the Dart VM Service.
149 ///
150 /// After this function completes this object is no longer usable.
151 Future<void> stop() async {
152 await _vmService.dispose();
153 await _vmService.onDone;
154 }
155}
156
157/// Represents an instance of a Flutter view running on a Fuchsia device.
158class FlutterView {
159 FlutterView._(this._name, this._id);
160
161 /// Attempts to construct a [FlutterView] from a json representation.
162 ///
163 /// If there is no isolate and no ID for the view, throws an [RpcFormatError].
164 /// If there is an associated isolate, and there is no name for said isolate,
165 /// also throws an [RpcFormatError].
166 ///
167 /// All other cases return a [FlutterView] instance. The name of the
168 /// view may be null, but the id will always be set.
169 factory FlutterView._fromJson(Map<String, dynamic> json) {
170 final Map<String, dynamic>? isolate = json['isolate'] as Map<String, dynamic>?;
171 final String? id = json['id'] as String?;
172 String? name;
173 if (id == null) {
174 throw RpcFormatError('Unable to find view name for the following JSON structure "$json"');
175 }
176 if (isolate != null) {
177 name = isolate['name'] as String?;
178 if (name == null) {
179 throw RpcFormatError('Unable to find name for isolate "$isolate"');
180 }
181 }
182 return FlutterView._(name, id);
183 }
184
185 /// Determines the name of the isolate associated with this view. If there is
186 /// no associated isolate, this will be set to the view's ID.
187 final String? _name;
188
189 /// The ID of the Flutter view.
190 final String _id;
191
192 /// The ID of the [FlutterView].
193 String get id => _id;
194
195 /// Returns the name of the [FlutterView].
196 ///
197 /// May be null if there is no associated isolate.
198 String? get name => _name;
199}
200
201/// This is a wrapper class for the `@Isolate` RPC object.
202///
203/// See:
204/// https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#isolate
205///
206/// This class contains information about the Isolate like its name and ID, as
207/// well as a reference to the parent DartVM on which it is running.
208class IsolateRef {
209 IsolateRef._(this.name, this.number, this.dartVm);
210
211 factory IsolateRef._fromJson(Map<String, dynamic> json, DartVm dartVm) {
212 final String? number = json['number'] as String?;
213 final String? name = json['name'] as String?;
214 final String? type = json['type'] as String?;
215 if (type == null) {
216 throw RpcFormatError('Unable to find type within JSON "$json"');
217 }
218 if (type != '@Isolate') {
219 throw RpcFormatError('Type "$type" does not match for IsolateRef');
220 }
221 if (number == null) {
222 throw RpcFormatError('Unable to find number for isolate ref within JSON "$json"');
223 }
224 if (name == null) {
225 throw RpcFormatError('Unable to find name for isolate ref within JSON "$json"');
226 }
227 return IsolateRef._(name, int.parse(number), dartVm);
228 }
229
230 /// The full name of this Isolate (not guaranteed to be unique).
231 final String name;
232
233 /// The unique number ID of this isolate.
234 final int number;
235
236 /// The parent [DartVm] on which this Isolate lives.
237 final DartVm dartVm;
238}
239

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com