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