| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, 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 vmservice; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:convert'; | |
| 9 import 'dart:isolate'; | |
| 10 import 'dart:typed_data'; | |
| 11 | |
| 12 part 'client.dart'; | |
| 13 part 'constants.dart'; | |
| 14 part 'running_isolate.dart'; | |
| 15 part 'running_isolates.dart'; | |
| 16 part 'message.dart'; | |
| 17 part 'message_router.dart'; | |
| 18 | |
| 19 final RawReceivePort isolateLifecyclePort = new RawReceivePort(); | |
| 20 final RawReceivePort scriptLoadPort = new RawReceivePort(); | |
| 21 | |
| 22 typedef ShutdownCallback(); | |
| 23 | |
| 24 // These must be kept in sync with the declarations in vm/json_stream.h. | |
| 25 const kInvalidParams = -32602; | |
| 26 const kInternalError = -32603; | |
| 27 const kStreamAlreadySubscribed = 103; | |
| 28 const kStreamNotSubscribed = 104; | |
| 29 | |
| 30 var _errorMessages = { | |
| 31 kInvalidParams: 'Invalid params', | |
| 32 kInternalError: 'Internal error', | |
| 33 kStreamAlreadySubscribed: 'Stream already subscribed', | |
| 34 kStreamNotSubscribed: 'Stream not subscribed', | |
| 35 }; | |
| 36 | |
| 37 String encodeRpcError(Message message, int code, {String details}) { | |
| 38 var response = { | |
| 39 'jsonrpc': '2.0', | |
| 40 'id' : message.serial, | |
| 41 'error' : { | |
| 42 'code': code, | |
| 43 'message': _errorMessages[code], | |
| 44 }, | |
| 45 }; | |
| 46 if (details != null) { | |
| 47 response['error']['data'] = { | |
| 48 'details': details, | |
| 49 }; | |
| 50 } | |
| 51 return JSON.encode(response); | |
| 52 } | |
| 53 | |
| 54 String encodeResult(Message message, Map result) { | |
| 55 var response = { | |
| 56 'jsonrpc': '2.0', | |
| 57 'id' : message.serial, | |
| 58 'result' : result, | |
| 59 }; | |
| 60 return JSON.encode(response); | |
| 61 } | |
| 62 | |
| 63 | |
| 64 class VMService extends MessageRouter { | |
| 65 static VMService _instance; | |
| 66 | |
| 67 /// Collection of currently connected clients. | |
| 68 final Set<Client> clients = new Set<Client>(); | |
| 69 | |
| 70 /// Collection of currently running isolates. | |
| 71 RunningIsolates runningIsolates = new RunningIsolates(); | |
| 72 | |
| 73 /// A port used to receive events from the VM. | |
| 74 final RawReceivePort eventPort; | |
| 75 | |
| 76 ShutdownCallback onShutdown; | |
| 77 | |
| 78 void _addClient(Client client) { | |
| 79 assert(client.streams.isEmpty); | |
| 80 clients.add(client); | |
| 81 } | |
| 82 | |
| 83 void _removeClient(Client client) { | |
| 84 clients.remove(client); | |
| 85 for (var streamId in client.streams) { | |
| 86 if (!_isAnyClientSubscribed(streamId)) { | |
| 87 _vmCancelStream(streamId); | |
| 88 } | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 void _eventMessageHandler(List eventMessage) { | |
| 93 var streamId = eventMessage[0]; | |
| 94 var event = eventMessage[1]; | |
| 95 for (var client in clients) { | |
| 96 if (client.sendEvents && client.streams.contains(streamId)) { | |
| 97 client.post(event); | |
| 98 } | |
| 99 } | |
| 100 } | |
| 101 | |
| 102 void _controlMessageHandler(int code, | |
| 103 int portId, | |
| 104 SendPort sp, | |
| 105 String name) { | |
| 106 switch (code) { | |
| 107 case Constants.ISOLATE_STARTUP_MESSAGE_ID: | |
| 108 runningIsolates.isolateStartup(portId, sp, name); | |
| 109 break; | |
| 110 case Constants.ISOLATE_SHUTDOWN_MESSAGE_ID: | |
| 111 runningIsolates.isolateShutdown(portId, sp); | |
| 112 break; | |
| 113 } | |
| 114 } | |
| 115 | |
| 116 void _exit() { | |
| 117 isolateLifecyclePort.close(); | |
| 118 scriptLoadPort.close(); | |
| 119 // Create a copy of the set as a list because client.disconnect() will | |
| 120 // alter the connected clients set. | |
| 121 var clientsList = clients.toList(); | |
| 122 for (var client in clientsList) { | |
| 123 client.disconnect(); | |
| 124 } | |
| 125 // Call embedder shutdown hook after the internal shutdown. | |
| 126 if (onShutdown != null) { | |
| 127 onShutdown(); | |
| 128 } | |
| 129 _onExit(); | |
| 130 } | |
| 131 | |
| 132 void messageHandler(message) { | |
| 133 if (message is List) { | |
| 134 if (message.length == 2) { | |
| 135 // This is an event. | |
| 136 assert(message[0] is String); | |
| 137 assert(message[1] is String || message[1] is Uint8List); | |
| 138 _eventMessageHandler(message); | |
| 139 return; | |
| 140 } | |
| 141 if (message.length == 1) { | |
| 142 // This is a control message directing the vm service to exit. | |
| 143 assert(message[0] == Constants.SERVICE_EXIT_MESSAGE_ID); | |
| 144 _exit(); | |
| 145 return; | |
| 146 } | |
| 147 if (message.length == 4) { | |
| 148 // This is a message informing us of the birth or death of an | |
| 149 // isolate. | |
| 150 _controlMessageHandler(message[0], message[1], message[2], message[3]); | |
| 151 return; | |
| 152 } | |
| 153 } | |
| 154 Logger.root.severe( | |
| 155 'Internal vm-service error: ignoring illegal message: $message'); | |
| 156 } | |
| 157 | |
| 158 void _notSupported(_) { | |
| 159 throw new UnimplementedError('Service script loading not supported.'); | |
| 160 } | |
| 161 | |
| 162 VMService._internal() | |
| 163 : eventPort = isolateLifecyclePort { | |
| 164 scriptLoadPort.handler = _notSupported; | |
| 165 eventPort.handler = messageHandler; | |
| 166 } | |
| 167 | |
| 168 factory VMService() { | |
| 169 if (VMService._instance == null) { | |
| 170 VMService._instance = new VMService._internal(); | |
| 171 _onStart(); | |
| 172 } | |
| 173 return _instance; | |
| 174 } | |
| 175 | |
| 176 void _clientCollection(Message message) { | |
| 177 var members = []; | |
| 178 var result = {}; | |
| 179 clients.forEach((client) { | |
| 180 members.add(client.toJson()); | |
| 181 }); | |
| 182 result['type'] = 'ClientList'; | |
| 183 result['members'] = members; | |
| 184 message.setResponse(JSON.encode(result)); | |
| 185 } | |
| 186 | |
| 187 bool _isAnyClientSubscribed(String streamId) { | |
| 188 for (var client in clients) { | |
| 189 if (client.streams.contains(streamId)) { | |
| 190 return true; | |
| 191 } | |
| 192 } | |
| 193 return false; | |
| 194 } | |
| 195 | |
| 196 Future<String> _streamListen(Message message) async { | |
| 197 var client = message.client; | |
| 198 var streamId = message.params['streamId']; | |
| 199 | |
| 200 if (client.streams.contains(streamId)) { | |
| 201 return encodeRpcError(message, kStreamAlreadySubscribed); | |
| 202 } | |
| 203 if (!_isAnyClientSubscribed(streamId)) { | |
| 204 if (!_vmListenStream(streamId)) { | |
| 205 return encodeRpcError( | |
| 206 message, kInvalidParams, | |
| 207 details:"streamListen: invalid 'streamId' parameter: ${streamId}"); | |
| 208 } | |
| 209 } | |
| 210 client.streams.add(streamId); | |
| 211 | |
| 212 var result = { 'type' : 'Success' }; | |
| 213 return encodeResult(message, result); | |
| 214 } | |
| 215 | |
| 216 Future<String> _streamCancel(Message message) async { | |
| 217 var client = message.client; | |
| 218 var streamId = message.params['streamId']; | |
| 219 | |
| 220 if (!client.streams.contains(streamId)) { | |
| 221 return encodeRpcError(message, kStreamNotSubscribed); | |
| 222 } | |
| 223 client.streams.remove(streamId); | |
| 224 if (!_isAnyClientSubscribed(streamId)) { | |
| 225 _vmCancelStream(streamId); | |
| 226 } | |
| 227 | |
| 228 var result = { 'type' : 'Success' }; | |
| 229 return encodeResult(message, result); | |
| 230 } | |
| 231 | |
| 232 // TODO(johnmccutchan): Turn this into a command line tool that uses the | |
| 233 // service library. | |
| 234 Future<String> _getCrashDump(Message message) async { | |
| 235 var client = message.client; | |
| 236 final perIsolateRequests = [ | |
| 237 // ?isolateId=<isolate id> will be appended to each of these requests. | |
| 238 // Isolate information. | |
| 239 Uri.parse('getIsolate'), | |
| 240 // State of heap. | |
| 241 Uri.parse('_getAllocationProfile'), | |
| 242 // Call stack + local variables. | |
| 243 Uri.parse('getStack?_full=true'), | |
| 244 ]; | |
| 245 | |
| 246 // Snapshot of running isolates. | |
| 247 var isolates = runningIsolates.isolates.values.toList(); | |
| 248 | |
| 249 // Collect the mapping from request uris to responses. | |
| 250 var responses = { | |
| 251 }; | |
| 252 | |
| 253 // Request VM. | |
| 254 var getVM = Uri.parse('getVM'); | |
| 255 var getVmResponse = JSON.decode( | |
| 256 await new Message.fromUri(client, getVM).sendToVM()); | |
| 257 responses[getVM.toString()] = getVmResponse['result']; | |
| 258 | |
| 259 // Request command line flags. | |
| 260 var getFlagList = Uri.parse('getFlagList'); | |
| 261 var getFlagListResponse = JSON.decode( | |
| 262 await new Message.fromUri(client, getFlagList).sendToVM()); | |
| 263 responses[getFlagList.toString()] = getFlagListResponse['result']; | |
| 264 | |
| 265 // Make requests to each isolate. | |
| 266 for (var isolate in isolates) { | |
| 267 for (var request in perIsolateRequests) { | |
| 268 var message = new Message.forIsolate(client, request, isolate); | |
| 269 // Decode the JSON and and insert it into the map. The map key | |
| 270 // is the request Uri. | |
| 271 var response = JSON.decode(await isolate.route(message)); | |
| 272 responses[message.toUri().toString()] = response['result']; | |
| 273 } | |
| 274 // Dump the object id ring requests. | |
| 275 var message = | |
| 276 new Message.forIsolate(client, Uri.parse('_dumpIdZone'), isolate); | |
| 277 var response = JSON.decode(await isolate.route(message)); | |
| 278 // Insert getObject requests into responses map. | |
| 279 for (var object in response['result']['objects']) { | |
| 280 final requestUri = | |
| 281 'getObject&isolateId=${isolate.serviceId}?objectId=${object["id"]}'; | |
| 282 responses[requestUri] = object; | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 // Encode the entire crash dump. | |
| 287 return encodeResult(message, responses); | |
| 288 } | |
| 289 | |
| 290 Future<String> route(Message message) { | |
| 291 if (message.completed) { | |
| 292 return message.response; | |
| 293 } | |
| 294 // TODO(turnidge): Update to json rpc. BEFORE SUBMIT. | |
| 295 if ((message.path.length == 1) && (message.path[0] == 'clients')) { | |
| 296 _clientCollection(message); | |
| 297 return message.response; | |
| 298 } | |
| 299 if (message.method == '_getCrashDump') { | |
| 300 return _getCrashDump(message); | |
| 301 } | |
| 302 if (message.method == 'streamListen') { | |
| 303 return _streamListen(message); | |
| 304 } | |
| 305 if (message.method == 'streamCancel') { | |
| 306 return _streamCancel(message); | |
| 307 } | |
| 308 if (message.params['isolateId'] != null) { | |
| 309 return runningIsolates.route(message); | |
| 310 } | |
| 311 return message.sendToVM(); | |
| 312 } | |
| 313 } | |
| 314 | |
| 315 RawReceivePort boot() { | |
| 316 // Return the port we expect isolate startup and shutdown messages on. | |
| 317 return isolateLifecyclePort; | |
| 318 } | |
| 319 | |
| 320 void _registerIsolate(int port_id, SendPort sp, String name) { | |
| 321 var service = new VMService(); | |
| 322 service.runningIsolates.isolateStartup(port_id, sp, name); | |
| 323 } | |
| 324 | |
| 325 void _onStart() native "VMService_OnStart"; | |
| 326 | |
| 327 void _onExit() native "VMService_OnExit"; | |
| 328 | |
| 329 bool _vmListenStream(String streamId) native "VMService_ListenStream"; | |
| 330 | |
| 331 void _vmCancelStream(String streamId) native "VMService_CancelStream"; | |
| OLD | NEW |