mirror of
https://github.com/dart-lang/sdk
synced 2024-10-02 23:59:16 +00:00
Provide stdout and stderr output in the Observatory debugger.
Implement two new streams, 'Stdout' and 'Stderr' in the service protocol. BUG= R=johnmccutchan@google.com Review URL: https://codereview.chromium.org//1232193003 .
This commit is contained in:
parent
a02c7694c5
commit
e4684c7627
|
@ -7,6 +7,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "include/dart_api.h"
|
||||
#include "include/dart_tools_api.h"
|
||||
|
||||
#include "platform/assert.h"
|
||||
|
||||
|
@ -119,6 +120,9 @@ const uint8_t* Builtin::NativeSymbol(Dart_NativeFunction nf) {
|
|||
}
|
||||
|
||||
|
||||
extern bool capture_stdout;
|
||||
|
||||
|
||||
// Implementation of native functions which are used for some
|
||||
// test/debug functionality in standalone dart mode.
|
||||
void FUNCTION_NAME(Builtin_PrintString)(Dart_NativeArguments args) {
|
||||
|
@ -126,18 +130,19 @@ void FUNCTION_NAME(Builtin_PrintString)(Dart_NativeArguments args) {
|
|||
uint8_t* chars = NULL;
|
||||
Dart_Handle str = Dart_GetNativeArgument(args, 0);
|
||||
Dart_Handle result = Dart_StringToUTF8(str, &chars, &length);
|
||||
if (Dart_IsError(result)) {
|
||||
// TODO(turnidge): Consider propagating some errors here. What if
|
||||
// an isolate gets interrupted by the embedder in the middle of
|
||||
// Dart_StringToUTF8? We need to make sure not to swallow the
|
||||
// interrupt.
|
||||
fprintf(stdout, "%s\n", Dart_GetError(result));
|
||||
} else {
|
||||
// Uses fwrite to support printing NUL bytes.
|
||||
fwrite(chars, 1, length, stdout);
|
||||
fputs("\n", stdout);
|
||||
}
|
||||
if (Dart_IsError(result)) Dart_PropagateError(result);
|
||||
|
||||
// Uses fwrite to support printing NUL bytes.
|
||||
fwrite(chars, 1, length, stdout);
|
||||
fputs("\n", stdout);
|
||||
fflush(stdout);
|
||||
if (capture_stdout) {
|
||||
// For now we report print output on the Stdout stream.
|
||||
uint8_t newline[] = { '\n' };
|
||||
Dart_ServiceSendDataEvent("Stdout", "WriteEvent", chars, length);
|
||||
Dart_ServiceSendDataEvent("Stdout", "WriteEvent",
|
||||
newline, sizeof(newline));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace bin
|
||||
|
|
|
@ -10,12 +10,22 @@
|
|||
#include "bin/utils.h"
|
||||
|
||||
#include "include/dart_api.h"
|
||||
#include "include/dart_tools_api.h"
|
||||
|
||||
namespace dart {
|
||||
namespace bin {
|
||||
|
||||
static const int kMSPerSecond = 1000;
|
||||
|
||||
// Are we capturing output from either stdout or stderr for the VM Service?
|
||||
bool capture_stdio = false;
|
||||
|
||||
// Are we capturing output from stdout for the VM service?
|
||||
bool capture_stdout = false;
|
||||
|
||||
// Are we capturing output from stderr for the VM service?
|
||||
bool capture_stderr = false;
|
||||
|
||||
|
||||
// The file pointer has been passed into Dart as an intptr_t and it is safe
|
||||
// to pull it out of Dart as a 64-bit integer, cast it to an intptr_t and
|
||||
|
@ -52,6 +62,18 @@ bool File::WriteFully(const void* buffer, int64_t num_bytes) {
|
|||
remaining -= bytes_written; // Reduce the number of remaining bytes.
|
||||
current_buffer += bytes_written; // Move the buffer forward.
|
||||
}
|
||||
if (capture_stdio) {
|
||||
intptr_t fd = GetFD();
|
||||
if (fd == STDOUT_FILENO && capture_stdout) {
|
||||
Dart_ServiceSendDataEvent("Stdout", "WriteEvent",
|
||||
reinterpret_cast<const uint8_t*>(buffer),
|
||||
num_bytes);
|
||||
} else if (fd == STDERR_FILENO && capture_stderr) {
|
||||
Dart_ServiceSendDataEvent("Stderr", "WriteEvent",
|
||||
reinterpret_cast<const uint8_t*>(buffer),
|
||||
num_bytes);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -581,7 +603,7 @@ void FUNCTION_NAME(File_ResolveSymbolicLinks)(Dart_NativeArguments args) {
|
|||
|
||||
void FUNCTION_NAME(File_OpenStdio)(Dart_NativeArguments args) {
|
||||
int64_t fd = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 0));
|
||||
ASSERT(fd == 0 || fd == 1 || fd == 2);
|
||||
ASSERT(fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO);
|
||||
File* file = File::OpenStdio(static_cast<int>(fd));
|
||||
Dart_SetReturnValue(args, Dart_NewInteger(reinterpret_cast<intptr_t>(file)));
|
||||
}
|
||||
|
@ -589,7 +611,7 @@ void FUNCTION_NAME(File_OpenStdio)(Dart_NativeArguments args) {
|
|||
|
||||
void FUNCTION_NAME(File_GetStdioHandleType)(Dart_NativeArguments args) {
|
||||
int64_t fd = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 0));
|
||||
ASSERT(fd == 0 || fd == 1 || fd == 2);
|
||||
ASSERT(fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO);
|
||||
File::StdioHandleType type = File::GetStdioHandleType(static_cast<int>(fd));
|
||||
Dart_SetReturnValue(args, Dart_NewInteger(type));
|
||||
}
|
||||
|
|
|
@ -859,6 +859,37 @@ static const char* ServiceGetIOHandler(
|
|||
}
|
||||
|
||||
|
||||
extern bool capture_stdio;
|
||||
extern bool capture_stdout;
|
||||
extern bool capture_stderr;
|
||||
static const char* kStdoutStreamId = "Stdout";
|
||||
static const char* kStderrStreamId = "Stderr";
|
||||
|
||||
|
||||
static bool ServiceStreamListenCallback(const char* stream_id) {
|
||||
if (strcmp(stream_id, kStdoutStreamId) == 0) {
|
||||
capture_stdio = true;
|
||||
capture_stdout = true;
|
||||
return true;
|
||||
} else if (strcmp(stream_id, kStderrStreamId) == 0) {
|
||||
capture_stdio = true;
|
||||
capture_stderr = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void ServiceStreamCancelCallback(const char* stream_id) {
|
||||
if (strcmp(stream_id, kStdoutStreamId) == 0) {
|
||||
capture_stdout = false;
|
||||
} else if (strcmp(stream_id, kStderrStreamId) == 0) {
|
||||
capture_stderr = false;
|
||||
}
|
||||
capture_stdio = (capture_stdout || capture_stderr);
|
||||
}
|
||||
|
||||
|
||||
void main(int argc, char** argv) {
|
||||
char* script_name;
|
||||
const int EXTRA_VM_ARGUMENTS = 2;
|
||||
|
@ -947,6 +978,8 @@ void main(int argc, char** argv) {
|
|||
|
||||
Dart_RegisterIsolateServiceRequestCallback(
|
||||
"getIO", &ServiceGetIOHandler, NULL);
|
||||
Dart_SetServiceStreamCallbacks(&ServiceStreamListenCallback,
|
||||
&ServiceStreamCancelCallback);
|
||||
|
||||
// Call CreateIsolateAndSetup which creates an isolate and loads up
|
||||
// the specified application script.
|
||||
|
|
|
@ -805,6 +805,74 @@ DART_EXPORT void Dart_RegisterRootServiceRequestCallback(
|
|||
void* user_data);
|
||||
|
||||
|
||||
/*
|
||||
* ========
|
||||
* Event Streams
|
||||
* ========
|
||||
*/
|
||||
|
||||
/**
|
||||
* A callback invoked when the VM service gets a request to listen to
|
||||
* some stream.
|
||||
*
|
||||
* \return Returns true iff the embedder supports the named stream id.
|
||||
*/
|
||||
typedef bool (*Dart_ServiceStreamListenCallback)(const char* stream_id);
|
||||
|
||||
/**
|
||||
* A callback invoked when the VM service gets a request to cancel
|
||||
* some stream.
|
||||
*/
|
||||
typedef void (*Dart_ServiceStreamCancelCallback)(const char* stream_id);
|
||||
|
||||
/**
|
||||
* Adds VM service stream callbacks.
|
||||
*
|
||||
* \param listen_callback A function pointer to a listen callback function.
|
||||
* A listen callback function should not be already set when this function
|
||||
* is called. A NULL value removes the existing listen callback function
|
||||
* if any.
|
||||
*
|
||||
* \param cancel_callback A function pointer to a cancel callback function.
|
||||
* A cancel callback function should not be already set when this function
|
||||
* is called. A NULL value removes the existing cancel callback function
|
||||
* if any.
|
||||
*
|
||||
* \return Success if the callbacks were added. Otherwise, returns an
|
||||
* error handle.
|
||||
*/
|
||||
DART_EXPORT Dart_Handle Dart_SetServiceStreamCallbacks(
|
||||
Dart_ServiceStreamListenCallback listen_callback,
|
||||
Dart_ServiceStreamCancelCallback cancel_callback);
|
||||
|
||||
/**
|
||||
* Sends a data event to clients of the VM Service.
|
||||
*
|
||||
* A data event is used to pass an array of bytes to subscribed VM
|
||||
* Service clients. For example, in the standalone embedder, this is
|
||||
* function used to provide WriteEvents on the Stdout and Stderr
|
||||
* streams.
|
||||
*
|
||||
* If the embedder passes in a stream id for which no client is
|
||||
* subscribed, then the event is ignored.
|
||||
*
|
||||
* \param stream_id The id of the stream on which to post the event.
|
||||
*
|
||||
* \param event_kind A string identifying what kind of event this is.
|
||||
* For example, 'WriteEvent'.
|
||||
*
|
||||
* \param bytes A pointer to an array of bytes.
|
||||
*
|
||||
* \param bytes_length The length of the byte array.
|
||||
*
|
||||
* \return Success if the arguments are well formed. Otherwise, returns an
|
||||
* error handle.
|
||||
*/
|
||||
DART_EXPORT Dart_Handle Dart_ServiceSendDataEvent(const char* stream_id,
|
||||
const char* event_kind,
|
||||
const uint8_t* bytes,
|
||||
intptr_t bytes_length);
|
||||
|
||||
/*
|
||||
* ========
|
||||
* Timeline
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
library service;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:math' as math;
|
||||
|
||||
|
|
|
@ -854,6 +854,67 @@ class RefreshCommand extends DebuggerCommand {
|
|||
'Syntax: refresh <subcommand>\n';
|
||||
}
|
||||
|
||||
class _VMStreamPrinter {
|
||||
ObservatoryDebugger _debugger;
|
||||
|
||||
_VMStreamPrinter(this._debugger);
|
||||
|
||||
String _savedStream;
|
||||
String _savedIsolate;
|
||||
String _savedLine;
|
||||
List<String> _buffer = [];
|
||||
|
||||
void onEvent(String streamName, ServiceEvent event) {
|
||||
String isolateName = event.isolate.name;
|
||||
// If we get a line from a different isolate/stream, flush
|
||||
// any pending output, even if it is not newline-terminated.
|
||||
if ((_savedIsolate != null && isolateName != _savedIsolate) ||
|
||||
(_savedStream != null && streamName != _savedStream)) {
|
||||
flush();
|
||||
}
|
||||
String data = event.bytesAsString;
|
||||
bool hasNewline = data.endsWith('\n');
|
||||
if (_savedLine != null) {
|
||||
data = _savedLine + data;
|
||||
_savedIsolate = null;
|
||||
_savedStream = null;
|
||||
_savedLine = null;
|
||||
}
|
||||
var lines = data.split('\n').where((line) => line != '').toList();
|
||||
if (lines.isEmpty) {
|
||||
return;
|
||||
}
|
||||
int limit = (hasNewline ? lines.length : lines.length - 1);
|
||||
for (int i = 0; i < limit; i++) {
|
||||
_buffer.add(_format(isolateName, streamName, lines[i]));
|
||||
}
|
||||
// If there is no newline, we save the last line of output for next time.
|
||||
if (!hasNewline) {
|
||||
_savedIsolate = isolateName;
|
||||
_savedStream = streamName;
|
||||
_savedLine = lines[lines.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
void flush() {
|
||||
// If there is any saved output, flush it now.
|
||||
if (_savedLine != null) {
|
||||
_buffer.add(_format(_savedIsolate, _savedStream, _savedLine));
|
||||
_savedIsolate = null;
|
||||
_savedStream = null;
|
||||
_savedLine = null;
|
||||
}
|
||||
if (_buffer.isNotEmpty) {
|
||||
_debugger.console.printStdio(_buffer);
|
||||
_buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
String _format(String isolateName, String streamName, String line) {
|
||||
return '${isolateName}:${streamName}> ${line}';
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks the state for an isolate debugging session.
|
||||
class ObservatoryDebugger extends Debugger {
|
||||
RootCommand cmd;
|
||||
|
@ -898,6 +959,7 @@ class ObservatoryDebugger extends Debugger {
|
|||
new IsolateCommand(this),
|
||||
new RefreshCommand(this),
|
||||
]);
|
||||
_stdioPrinter = new _VMStreamPrinter(this);
|
||||
}
|
||||
|
||||
VM get vm => page.app.vm;
|
||||
|
@ -997,6 +1059,7 @@ class ObservatoryDebugger extends Debugger {
|
|||
}
|
||||
|
||||
void reportStatus() {
|
||||
flushStdio();
|
||||
if (_isolate == null) {
|
||||
console.print('No current isolate');
|
||||
} else if (_isolate.idle) {
|
||||
|
@ -1116,6 +1179,7 @@ class ObservatoryDebugger extends Debugger {
|
|||
case ServiceEvent.kPauseException:
|
||||
if (event.owner == isolate) {
|
||||
_refreshStack(event).then((_) {
|
||||
flushStdio();
|
||||
_reportPause(event);
|
||||
});
|
||||
}
|
||||
|
@ -1123,6 +1187,7 @@ class ObservatoryDebugger extends Debugger {
|
|||
|
||||
case ServiceEvent.kResume:
|
||||
if (event.owner == isolate) {
|
||||
flushStdio();
|
||||
console.print('Continuing...');
|
||||
}
|
||||
break;
|
||||
|
@ -1147,6 +1212,20 @@ class ObservatoryDebugger extends Debugger {
|
|||
}
|
||||
}
|
||||
|
||||
_VMStreamPrinter _stdioPrinter;
|
||||
|
||||
void flushStdio() {
|
||||
_stdioPrinter.flush();
|
||||
}
|
||||
|
||||
void onStdout(ServiceEvent event) {
|
||||
_stdioPrinter.onEvent('stdout', event);
|
||||
}
|
||||
|
||||
void onStderr(ServiceEvent event) {
|
||||
_stdioPrinter.onEvent('stderr', event);
|
||||
}
|
||||
|
||||
static String _commonPrefix(String a, String b) {
|
||||
int pos = 0;
|
||||
while (pos < a.length && pos < b.length) {
|
||||
|
@ -1236,6 +1315,8 @@ class DebuggerPageElement extends ObservatoryElement {
|
|||
|
||||
Future<StreamSubscription> _isolateSubscriptionFuture;
|
||||
Future<StreamSubscription> _debugSubscriptionFuture;
|
||||
Future<StreamSubscription> _stdoutSubscriptionFuture;
|
||||
Future<StreamSubscription> _stderrSubscriptionFuture;
|
||||
|
||||
@override
|
||||
void attached() {
|
||||
|
@ -1269,6 +1350,13 @@ class DebuggerPageElement extends ObservatoryElement {
|
|||
app.vm.listenEventStream(VM.kIsolateStream, debugger.onEvent);
|
||||
_debugSubscriptionFuture =
|
||||
app.vm.listenEventStream(VM.kDebugStream, debugger.onEvent);
|
||||
_stdoutSubscriptionFuture =
|
||||
app.vm.listenEventStream(VM.kStdoutStream, debugger.onStdout);
|
||||
_stderrSubscriptionFuture =
|
||||
app.vm.listenEventStream(VM.kStderrStream, debugger.onStderr);
|
||||
|
||||
// Turn on the periodic poll timer for this page.
|
||||
pollPeriod = const Duration(milliseconds:100);
|
||||
|
||||
onClick.listen((event) {
|
||||
// Random clicks should focus on the text box. If the user selects
|
||||
|
@ -1280,12 +1368,20 @@ class DebuggerPageElement extends ObservatoryElement {
|
|||
});
|
||||
}
|
||||
|
||||
void onPoll() {
|
||||
debugger.flushStdio();
|
||||
}
|
||||
|
||||
@override
|
||||
void detached() {
|
||||
cancelFutureSubscription(_isolateSubscriptionFuture);
|
||||
_isolateSubscriptionFuture = null;
|
||||
cancelFutureSubscription(_debugSubscriptionFuture);
|
||||
_debugSubscriptionFuture = null;
|
||||
cancelFutureSubscription(_stdoutSubscriptionFuture);
|
||||
_stdoutSubscriptionFuture = null;
|
||||
cancelFutureSubscription(_stderrSubscriptionFuture);
|
||||
_stderrSubscriptionFuture = null;
|
||||
super.detached();
|
||||
}
|
||||
}
|
||||
|
@ -1665,6 +1761,21 @@ class DebuggerConsoleElement extends ObservatoryElement {
|
|||
span.scrollIntoView();
|
||||
}
|
||||
|
||||
void printStdio(List<String> lines) {
|
||||
var lastSpan;
|
||||
for (var line in lines) {
|
||||
var span = new SpanElement();
|
||||
span.classes.add('green');
|
||||
span.appendText(line);
|
||||
span.appendText('\n');
|
||||
$['consoleText'].children.add(span);
|
||||
lastSpan = span;
|
||||
}
|
||||
if (lastSpan != null) {
|
||||
lastSpan.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
void printRef(Instance ref, { bool newline:true }) {
|
||||
var refElement = new Element.tag('instance-ref');
|
||||
refElement.ref = ref;
|
||||
|
|
|
@ -420,13 +420,18 @@
|
|||
line-height: 125%;
|
||||
color: red;
|
||||
}
|
||||
.green {
|
||||
font: normal 14px consolas, courier, monospace;
|
||||
white-space: pre;
|
||||
line-height: 125%;
|
||||
color: green;
|
||||
}
|
||||
.spacer {
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
<div id="consoleText" class="console">
|
||||
<!-- Console output is added programmatically here using the 'normal'
|
||||
and 'bold' styles. -->
|
||||
<!-- Console output is added programmatically -->
|
||||
</div>
|
||||
</template>
|
||||
</polymer-element>
|
||||
|
|
|
@ -697,6 +697,8 @@ abstract class VM extends ServiceObjectOwner {
|
|||
static const kIsolateStream = 'Isolate';
|
||||
static const kDebugStream = 'Debug';
|
||||
static const kGCStream = 'GC';
|
||||
static const kStdoutStream = 'Stdout';
|
||||
static const kStderrStream = 'Stderr';
|
||||
static const _kGraphStream = '_Graph';
|
||||
|
||||
/// Returns a single-subscription Stream object for a VM event stream.
|
||||
|
@ -1625,6 +1627,7 @@ class ServiceEvent extends ServiceObject {
|
|||
@observable int count;
|
||||
@observable String reason;
|
||||
@observable String exceptions;
|
||||
@observable String bytesAsString;
|
||||
int chunkIndex, chunkCount, nodeCount;
|
||||
|
||||
@observable bool get isPauseEvent {
|
||||
|
@ -1680,6 +1683,10 @@ class ServiceEvent extends ServiceObject {
|
|||
map['_debuggerSettings']['_exceptions'] != null) {
|
||||
exceptions = map['_debuggerSettings']['_exceptions'];
|
||||
}
|
||||
if (map['bytes'] != null) {
|
||||
var bytes = decodeBase64(map['bytes']);
|
||||
bytesAsString = UTF8.decode(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
String toString() {
|
||||
|
|
89
runtime/observatory/tests/service/capture_stdio_test.dart
Normal file
89
runtime/observatory/tests/service/capture_stdio_test.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
// VMOptions=--compile-all --error_on_bad_type --error_on_bad_override
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
void test() {
|
||||
debugger();
|
||||
stdout.write('stdout');
|
||||
|
||||
debugger();
|
||||
print('print');
|
||||
|
||||
debugger();
|
||||
stderr.write('stderr');
|
||||
}
|
||||
|
||||
var tests = [
|
||||
hasStoppedAtBreakpoint,
|
||||
|
||||
(Isolate isolate) async {
|
||||
Completer completer = new Completer();
|
||||
var stdoutSub;
|
||||
stdoutSub = await isolate.vm.listenEventStream(
|
||||
VM.kStdoutStream,
|
||||
(ServiceEvent event) {
|
||||
expect(event.kind, equals('WriteEvent'));
|
||||
expect(event.bytesAsString, equals('stdout'));
|
||||
cancelFutureSubscription(stdoutSub).then((_) {
|
||||
completer.complete();
|
||||
});
|
||||
});
|
||||
await isolate.resume();
|
||||
await completer.future;
|
||||
},
|
||||
|
||||
hasStoppedAtBreakpoint,
|
||||
|
||||
(Isolate isolate) async {
|
||||
Completer completer = new Completer();
|
||||
var stdoutSub;
|
||||
int eventNumber = 1;
|
||||
stdoutSub = await isolate.vm.listenEventStream(
|
||||
VM.kStdoutStream,
|
||||
(ServiceEvent event) {
|
||||
expect(event.kind, equals('WriteEvent'));
|
||||
if (eventNumber == 1) {
|
||||
expect(event.bytesAsString, equals('print'));
|
||||
} else if (eventNumber == 2) {
|
||||
expect(event.bytesAsString, equals('\n'));
|
||||
cancelFutureSubscription(stdoutSub).then((_) {
|
||||
completer.complete();
|
||||
});
|
||||
} else {
|
||||
expect(true, false);
|
||||
}
|
||||
eventNumber++;
|
||||
});
|
||||
await isolate.resume();
|
||||
await completer.future;
|
||||
},
|
||||
|
||||
hasStoppedAtBreakpoint,
|
||||
|
||||
(Isolate isolate) async {
|
||||
Completer completer = new Completer();
|
||||
var stderrSub;
|
||||
stderrSub = await isolate.vm.listenEventStream(
|
||||
VM.kStderrStream,
|
||||
(ServiceEvent event) {
|
||||
expect(event.kind, equals('WriteEvent'));
|
||||
expect(event.bytesAsString, equals('stderr'));
|
||||
cancelFutureSubscription(stderrSub).then((_) {
|
||||
completer.complete();
|
||||
});
|
||||
});
|
||||
await isolate.resume();
|
||||
await completer.future;
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
main(args) => runIsolateTests(args, tests, testeeConcurrent: test);
|
|
@ -5692,6 +5692,67 @@ DART_EXPORT void Dart_RegisterRootServiceRequestCallback(
|
|||
}
|
||||
|
||||
|
||||
DART_EXPORT Dart_Handle Dart_SetServiceStreamCallbacks(
|
||||
Dart_ServiceStreamListenCallback listen_callback,
|
||||
Dart_ServiceStreamCancelCallback cancel_callback) {
|
||||
if (listen_callback != NULL) {
|
||||
if (Service::stream_listen_callback() != NULL) {
|
||||
return Api::NewError(
|
||||
"%s permits only one listen callback to be registered, please "
|
||||
"remove the existing callback and then add this callback",
|
||||
CURRENT_FUNC);
|
||||
}
|
||||
} else {
|
||||
if (Service::stream_listen_callback() == NULL) {
|
||||
return Api::NewError(
|
||||
"%s expects 'listen_callback' to be present in the callback set.",
|
||||
CURRENT_FUNC);
|
||||
}
|
||||
}
|
||||
if (cancel_callback != NULL) {
|
||||
if (Service::stream_cancel_callback() != NULL) {
|
||||
return Api::NewError(
|
||||
"%s permits only one cancel callback to be registered, please "
|
||||
"remove the existing callback and then add this callback",
|
||||
CURRENT_FUNC);
|
||||
}
|
||||
} else {
|
||||
if (Service::stream_cancel_callback() == NULL) {
|
||||
return Api::NewError(
|
||||
"%s expects 'cancel_callback' to be present in the callback set.",
|
||||
CURRENT_FUNC);
|
||||
}
|
||||
}
|
||||
Service::SetEmbedderStreamCallbacks(listen_callback, cancel_callback);
|
||||
return Api::Success();
|
||||
}
|
||||
|
||||
|
||||
DART_EXPORT Dart_Handle Dart_ServiceSendDataEvent(const char* stream_id,
|
||||
const char* event_kind,
|
||||
const uint8_t* bytes,
|
||||
intptr_t bytes_length) {
|
||||
Isolate* isolate = Isolate::Current();
|
||||
DARTSCOPE(isolate);
|
||||
if (stream_id == NULL) {
|
||||
RETURN_NULL_ERROR(stream_id);
|
||||
}
|
||||
if (event_kind == NULL) {
|
||||
RETURN_NULL_ERROR(event_kind);
|
||||
}
|
||||
if (bytes == NULL) {
|
||||
RETURN_NULL_ERROR(bytes);
|
||||
}
|
||||
if (bytes_length < 0) {
|
||||
return Api::NewError("%s expects argument 'bytes_length' to be >= 0.",
|
||||
CURRENT_FUNC);
|
||||
}
|
||||
Service::SendEmbedderEvent(isolate, stream_id, event_kind,
|
||||
bytes, bytes_length);
|
||||
return Api::Success();
|
||||
}
|
||||
|
||||
|
||||
DART_EXPORT Dart_Handle Dart_TimelineDuration(const char* label,
|
||||
int64_t start_micros,
|
||||
int64_t end_micros) {
|
||||
|
|
|
@ -236,8 +236,8 @@ ActivationFrame::ActivationFrame(
|
|||
|
||||
bool Debugger::HasEventHandler() {
|
||||
return ((event_handler_ != NULL) ||
|
||||
Service::NeedsIsolateEvents() ||
|
||||
Service::NeedsDebugEvents());
|
||||
Service::isolate_stream.enabled() ||
|
||||
Service::debug_stream.enabled());
|
||||
}
|
||||
|
||||
|
||||
|
@ -251,11 +251,11 @@ static bool ServiceNeedsDebuggerEvent(DebuggerEvent::EventType type) {
|
|||
case DebuggerEvent::kBreakpointReached:
|
||||
case DebuggerEvent::kExceptionThrown:
|
||||
case DebuggerEvent::kIsolateInterrupted:
|
||||
return Service::NeedsDebugEvents();
|
||||
return Service::debug_stream.enabled();
|
||||
|
||||
case DebuggerEvent::kIsolateCreated:
|
||||
case DebuggerEvent::kIsolateShutdown:
|
||||
return Service::NeedsIsolateEvents();
|
||||
return Service::isolate_stream.enabled();
|
||||
|
||||
default:
|
||||
UNREACHABLE();
|
||||
|
@ -324,10 +324,10 @@ void Debugger::SignalIsolateInterrupted() {
|
|||
|
||||
// The vm service handles breakpoint notifications in a different way
|
||||
// than the regular debugger breakpoint notifications.
|
||||
static void SendServiceBreakpointEvent(ServiceEvent::EventType type,
|
||||
static void SendServiceBreakpointEvent(ServiceEvent::EventKind kind,
|
||||
Breakpoint* bpt) {
|
||||
if (Service::NeedsDebugEvents()) {
|
||||
ServiceEvent service_event(Isolate::Current(), type);
|
||||
if (Service::debug_stream.enabled()) {
|
||||
ServiceEvent service_event(Isolate::Current(), kind);
|
||||
service_event.set_breakpoint(bpt);
|
||||
Service::HandleEvent(&service_event);
|
||||
}
|
||||
|
|
|
@ -680,7 +680,7 @@ void Heap::RecordAfterGC() {
|
|||
stats_.after_.old_ = old_space_->GetCurrentUsage();
|
||||
ASSERT(gc_in_progress_);
|
||||
gc_in_progress_ = false;
|
||||
if (Service::NeedsGCEvents()) {
|
||||
if (Service::gc_stream.enabled()) {
|
||||
ServiceEvent event(Isolate::Current(), ServiceEvent::kGC);
|
||||
event.set_gc_stats(&stats_);
|
||||
Service::HandleEvent(&event);
|
||||
|
|
|
@ -485,7 +485,7 @@ bool IsolateMessageHandler::HandleMessage(Message* message) {
|
|||
|
||||
|
||||
void IsolateMessageHandler::NotifyPauseOnStart() {
|
||||
if (Service::NeedsDebugEvents()) {
|
||||
if (Service::debug_stream.enabled()) {
|
||||
StartIsolateScope start_isolate(isolate());
|
||||
StackZone zone(I);
|
||||
HandleScope handle_scope(I);
|
||||
|
@ -496,7 +496,7 @@ void IsolateMessageHandler::NotifyPauseOnStart() {
|
|||
|
||||
|
||||
void IsolateMessageHandler::NotifyPauseOnExit() {
|
||||
if (Service::NeedsDebugEvents()) {
|
||||
if (Service::debug_stream.enabled()) {
|
||||
StartIsolateScope start_isolate(isolate());
|
||||
StackZone zone(I);
|
||||
HandleScope handle_scope(I);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "vm/service.h"
|
||||
|
||||
#include "include/dart_api.h"
|
||||
#include "include/dart_native_api.h"
|
||||
#include "platform/globals.h"
|
||||
|
||||
#include "vm/compiler.h"
|
||||
|
@ -74,32 +75,46 @@ EmbedderServiceHandler* Service::root_service_handler_head_ = NULL;
|
|||
struct ServiceMethodDescriptor;
|
||||
ServiceMethodDescriptor* FindMethod(const char* method_name);
|
||||
|
||||
// TODO(turnidge): Build a general framework later. For now, we have
|
||||
// a small set of well-known streams.
|
||||
bool Service::needs_isolate_events_ = false;
|
||||
bool Service::needs_debug_events_ = false;
|
||||
bool Service::needs_gc_events_ = false;
|
||||
bool Service::needs_echo_events_ = false;
|
||||
bool Service::needs_graph_events_ = false;
|
||||
|
||||
void Service::ListenStream(const char* stream_id) {
|
||||
// Support for streams defined in embedders.
|
||||
Dart_ServiceStreamListenCallback Service::stream_listen_callback_ = NULL;
|
||||
Dart_ServiceStreamCancelCallback Service::stream_cancel_callback_ = NULL;
|
||||
|
||||
|
||||
// These are the set of streams known to the core VM.
|
||||
StreamInfo Service::isolate_stream("Isolate");
|
||||
StreamInfo Service::debug_stream("Debug");
|
||||
StreamInfo Service::gc_stream("GC");
|
||||
StreamInfo Service::echo_stream("_Echo");
|
||||
StreamInfo Service::graph_stream("_Graph");
|
||||
|
||||
|
||||
static StreamInfo* streams_[] = {
|
||||
&Service::isolate_stream,
|
||||
&Service::debug_stream,
|
||||
&Service::gc_stream,
|
||||
&Service::echo_stream,
|
||||
&Service::graph_stream,
|
||||
};
|
||||
|
||||
|
||||
bool Service::ListenStream(const char* stream_id) {
|
||||
if (FLAG_trace_service) {
|
||||
OS::Print("vm-service: starting stream '%s'\n",
|
||||
stream_id);
|
||||
}
|
||||
if (strcmp(stream_id, "Isolate") == 0) {
|
||||
needs_isolate_events_ = true;
|
||||
} else if (strcmp(stream_id, "Debug") == 0) {
|
||||
needs_debug_events_ = true;
|
||||
} else if (strcmp(stream_id, "GC") == 0) {
|
||||
needs_gc_events_ = true;
|
||||
} else if (strcmp(stream_id, "_Echo") == 0) {
|
||||
needs_echo_events_ = true;
|
||||
} else if (strcmp(stream_id, "_Graph") == 0) {
|
||||
needs_graph_events_ = true;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
intptr_t num_streams = sizeof(streams_) /
|
||||
sizeof(streams_[0]);
|
||||
for (intptr_t i = 0; i < num_streams; i++) {
|
||||
if (strcmp(stream_id, streams_[i]->id()) == 0) {
|
||||
streams_[i]->set_enabled(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (stream_listen_callback_) {
|
||||
return (*stream_listen_callback_)(stream_id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Service::CancelStream(const char* stream_id) {
|
||||
|
@ -107,18 +122,16 @@ void Service::CancelStream(const char* stream_id) {
|
|||
OS::Print("vm-service: stopping stream '%s'\n",
|
||||
stream_id);
|
||||
}
|
||||
if (strcmp(stream_id, "Isolate") == 0) {
|
||||
needs_isolate_events_ = false;
|
||||
} else if (strcmp(stream_id, "Debug") == 0) {
|
||||
needs_debug_events_ = false;
|
||||
} else if (strcmp(stream_id, "GC") == 0) {
|
||||
needs_gc_events_ = false;
|
||||
} else if (strcmp(stream_id, "_Echo") == 0) {
|
||||
needs_echo_events_ = false;
|
||||
} else if (strcmp(stream_id, "_Graph") == 0) {
|
||||
needs_graph_events_ = false;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
intptr_t num_streams = sizeof(streams_) /
|
||||
sizeof(streams_[0]);
|
||||
for (intptr_t i = 0; i < num_streams; i++) {
|
||||
if (strcmp(stream_id, streams_[i]->id()) == 0) {
|
||||
streams_[i]->set_enabled(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (stream_cancel_callback_) {
|
||||
return (*stream_cancel_callback_)(stream_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,6 +636,7 @@ void Service::SendEvent(const char* stream_id,
|
|||
}
|
||||
|
||||
|
||||
// TODO(turnidge): Rewrite this method to use Post_CObject instead.
|
||||
void Service::SendEventWithData(const char* stream_id,
|
||||
const char* event_type,
|
||||
const String& meta,
|
||||
|
@ -658,6 +672,9 @@ void Service::HandleEvent(ServiceEvent* event) {
|
|||
if (ServiceIsolate::IsServiceIsolateDescendant(event->isolate())) {
|
||||
return;
|
||||
}
|
||||
if (!ServiceIsolate::IsRunning()) {
|
||||
return;
|
||||
}
|
||||
JSONStream js;
|
||||
const char* stream_id = event->stream_id();
|
||||
ASSERT(stream_id != NULL);
|
||||
|
@ -666,9 +683,35 @@ void Service::HandleEvent(ServiceEvent* event) {
|
|||
jsobj.AddProperty("event", event);
|
||||
jsobj.AddProperty("streamId", stream_id);
|
||||
}
|
||||
const String& message = String::Handle(String::New(js.ToCString()));
|
||||
SendEvent(stream_id, ServiceEvent::EventTypeToCString(event->type()),
|
||||
message);
|
||||
|
||||
// Message is of the format [<stream id>, <json string>].
|
||||
//
|
||||
// Build the event message in the C heap to avoid dart heap
|
||||
// allocation. This method can be called while we have acquired a
|
||||
// direct pointer to typed data, so we can't allocate here.
|
||||
Dart_CObject list_cobj;
|
||||
Dart_CObject* list_values[2];
|
||||
list_cobj.type = Dart_CObject_kArray;
|
||||
list_cobj.value.as_array.length = 2;
|
||||
list_cobj.value.as_array.values = list_values;
|
||||
|
||||
Dart_CObject stream_id_cobj;
|
||||
stream_id_cobj.type = Dart_CObject_kString;
|
||||
stream_id_cobj.value.as_string = const_cast<char*>(stream_id);
|
||||
list_values[0] = &stream_id_cobj;
|
||||
|
||||
Dart_CObject json_cobj;
|
||||
json_cobj.type = Dart_CObject_kString;
|
||||
json_cobj.value.as_string = const_cast<char*>(js.ToCString());
|
||||
list_values[1] = &json_cobj;
|
||||
|
||||
if (FLAG_trace_service) {
|
||||
OS::Print(
|
||||
"vm-service: Pushing event of type %s to stream %s\n",
|
||||
event->KindAsCString(), stream_id);
|
||||
}
|
||||
|
||||
Dart_PostCObject(ServiceIsolate::Port(), &list_cobj);
|
||||
}
|
||||
|
||||
|
||||
|
@ -792,6 +835,14 @@ void Service::RegisterRootEmbedderCallback(
|
|||
}
|
||||
|
||||
|
||||
void Service::SetEmbedderStreamCallbacks(
|
||||
Dart_ServiceStreamListenCallback listen_callback,
|
||||
Dart_ServiceStreamCancelCallback cancel_callback) {
|
||||
stream_listen_callback_ = listen_callback;
|
||||
stream_cancel_callback_ = cancel_callback;
|
||||
}
|
||||
|
||||
|
||||
EmbedderServiceHandler* Service::FindRootEmbedderHandler(
|
||||
const char* name) {
|
||||
EmbedderServiceHandler* current = root_service_handler_head_;
|
||||
|
@ -875,16 +926,16 @@ void Service::SendEchoEvent(Isolate* isolate, const char* text) {
|
|||
event.AddProperty("text", text);
|
||||
}
|
||||
}
|
||||
jsobj.AddProperty("streamId", "_Echo");
|
||||
jsobj.AddProperty("streamId", echo_stream.id());
|
||||
}
|
||||
const String& message = String::Handle(String::New(js.ToCString()));
|
||||
uint8_t data[] = {0, 128, 255};
|
||||
SendEventWithData("_Echo", "_Echo", message, data, sizeof(data));
|
||||
SendEventWithData(echo_stream.id(), "_Echo", message, data, sizeof(data));
|
||||
}
|
||||
|
||||
|
||||
static bool TriggerEchoEvent(Isolate* isolate, JSONStream* js) {
|
||||
if (Service::NeedsEchoEvents()) {
|
||||
if (Service::echo_stream.enabled()) {
|
||||
Service::SendEchoEvent(isolate, js->LookupParam("text"));
|
||||
}
|
||||
JSONObject jsobj(js);
|
||||
|
@ -2190,7 +2241,7 @@ static bool Resume(Isolate* isolate, JSONStream* js) {
|
|||
const char* step_param = js->LookupParam("step");
|
||||
if (isolate->message_handler()->paused_on_start()) {
|
||||
isolate->message_handler()->set_pause_on_start(false);
|
||||
if (Service::NeedsDebugEvents()) {
|
||||
if (Service::debug_stream.enabled()) {
|
||||
ServiceEvent event(isolate, ServiceEvent::kResume);
|
||||
Service::HandleEvent(&event);
|
||||
}
|
||||
|
@ -2384,7 +2435,7 @@ static const MethodParameter* request_heap_snapshot_params[] = {
|
|||
|
||||
|
||||
static bool RequestHeapSnapshot(Isolate* isolate, JSONStream* js) {
|
||||
if (Service::NeedsGraphEvents()) {
|
||||
if (Service::graph_stream.enabled()) {
|
||||
Service::SendGraphEvent(isolate);
|
||||
}
|
||||
// TODO(koda): Provide some id that ties this request to async response(s).
|
||||
|
@ -2419,7 +2470,7 @@ void Service::SendGraphEvent(Isolate* isolate) {
|
|||
event.AddProperty("chunkCount", num_chunks);
|
||||
event.AddProperty("nodeCount", node_count);
|
||||
}
|
||||
jsobj.AddProperty("streamId", "_Graph");
|
||||
jsobj.AddProperty("streamId", graph_stream.id());
|
||||
}
|
||||
|
||||
const String& message = String::Handle(String::New(js.ToCString()));
|
||||
|
@ -2429,13 +2480,14 @@ void Service::SendGraphEvent(Isolate* isolate) {
|
|||
? stream.bytes_written() - (i * kChunkSize)
|
||||
: kChunkSize;
|
||||
|
||||
SendEventWithData("_Graph", "_Graph", message, chunk_start, chunk_size);
|
||||
SendEventWithData(graph_stream.id(), "_Graph", message,
|
||||
chunk_start, chunk_size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Service::SendInspectEvent(Isolate* isolate, const Object& inspectee) {
|
||||
if (!Service::NeedsDebugEvents()) {
|
||||
if (!Service::debug_stream.enabled()) {
|
||||
return;
|
||||
}
|
||||
ServiceEvent event(isolate, ServiceEvent::kInspect);
|
||||
|
@ -2444,6 +2496,22 @@ void Service::SendInspectEvent(Isolate* isolate, const Object& inspectee) {
|
|||
}
|
||||
|
||||
|
||||
void Service::SendEmbedderEvent(Isolate* isolate,
|
||||
const char* stream_id,
|
||||
const char* event_kind,
|
||||
const uint8_t* bytes,
|
||||
intptr_t bytes_len) {
|
||||
if (!Service::debug_stream.enabled()) {
|
||||
return;
|
||||
}
|
||||
ServiceEvent event(isolate, ServiceEvent::kEmbedder);
|
||||
event.set_embedder_kind(event_kind);
|
||||
event.set_embedder_stream_id(stream_id);
|
||||
event.set_bytes(bytes, bytes_len);
|
||||
Service::HandleEvent(&event);
|
||||
}
|
||||
|
||||
|
||||
class ContainsAddressVisitor : public FindObjectVisitor {
|
||||
public:
|
||||
ContainsAddressVisitor(Isolate* isolate, uword addr)
|
||||
|
@ -2726,7 +2794,7 @@ static bool SetExceptionPauseInfo(Isolate* isolate, JSONStream* js) {
|
|||
}
|
||||
|
||||
isolate->debugger()->SetExceptionPauseInfo(info);
|
||||
if (Service::NeedsDebugEvents()) {
|
||||
if (Service::debug_stream.enabled()) {
|
||||
ServiceEvent event(isolate, ServiceEvent::kDebuggerSettingsUpdate);
|
||||
Service::HandleEvent(&event);
|
||||
}
|
||||
|
@ -2812,7 +2880,7 @@ static const MethodParameter* set_name_params[] = {
|
|||
|
||||
static bool SetName(Isolate* isolate, JSONStream* js) {
|
||||
isolate->set_debugger_name(js->LookupParam("name"));
|
||||
if (Service::NeedsIsolateEvents()) {
|
||||
if (Service::isolate_stream.enabled()) {
|
||||
ServiceEvent event(isolate, ServiceEvent::kIsolateUpdate);
|
||||
Service::HandleEvent(&event);
|
||||
}
|
||||
|
|
|
@ -59,6 +59,21 @@ class RingServiceIdZone : public ServiceIdZone {
|
|||
};
|
||||
|
||||
|
||||
class StreamInfo {
|
||||
public:
|
||||
explicit StreamInfo(const char* id) : id_(id), enabled_(false) {}
|
||||
|
||||
const char* id() { return id_; }
|
||||
|
||||
void set_enabled(bool value) { enabled_ = value; }
|
||||
bool enabled() { return enabled_; }
|
||||
|
||||
private:
|
||||
const char* id_;
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
|
||||
class Service : public AllStatic {
|
||||
public:
|
||||
// Handles a message which is not directed to an isolate.
|
||||
|
@ -67,15 +82,6 @@ class Service : public AllStatic {
|
|||
// Handles a message which is directed to a particular isolate.
|
||||
static void HandleIsolateMessage(Isolate* isolate, const Array& message);
|
||||
|
||||
static bool NeedsIsolateEvents() { return needs_isolate_events_; }
|
||||
static bool NeedsDebugEvents() { return needs_debug_events_; }
|
||||
static bool NeedsGCEvents() { return needs_gc_events_; }
|
||||
static bool NeedsEchoEvents() { return needs_echo_events_; }
|
||||
static bool NeedsGraphEvents() { return needs_graph_events_; }
|
||||
|
||||
static void ListenStream(const char* stream_id);
|
||||
static void CancelStream(const char* stream_id);
|
||||
|
||||
static void HandleEvent(ServiceEvent* event);
|
||||
|
||||
static void RegisterIsolateEmbedderCallback(
|
||||
|
@ -88,10 +94,37 @@ class Service : public AllStatic {
|
|||
Dart_ServiceRequestCallback callback,
|
||||
void* user_data);
|
||||
|
||||
static void SetEmbedderStreamCallbacks(
|
||||
Dart_ServiceStreamListenCallback listen_callback,
|
||||
Dart_ServiceStreamCancelCallback cancel_callback);
|
||||
|
||||
static void SendEchoEvent(Isolate* isolate, const char* text);
|
||||
static void SendGraphEvent(Isolate* isolate);
|
||||
static void SendInspectEvent(Isolate* isolate, const Object& inspectee);
|
||||
|
||||
static void SendEmbedderEvent(Isolate* isolate,
|
||||
const char* stream_id,
|
||||
const char* event_kind,
|
||||
const uint8_t* bytes,
|
||||
intptr_t bytes_len);
|
||||
|
||||
// Well-known streams.
|
||||
static StreamInfo isolate_stream;
|
||||
static StreamInfo debug_stream;
|
||||
static StreamInfo gc_stream;
|
||||
static StreamInfo echo_stream;
|
||||
static StreamInfo graph_stream;
|
||||
|
||||
static bool ListenStream(const char* stream_id);
|
||||
static void CancelStream(const char* stream_id);
|
||||
|
||||
static Dart_ServiceStreamListenCallback stream_listen_callback() {
|
||||
return stream_listen_callback_;
|
||||
}
|
||||
static Dart_ServiceStreamCancelCallback stream_cancel_callback() {
|
||||
return stream_cancel_callback_;
|
||||
}
|
||||
|
||||
private:
|
||||
static void InvokeMethod(Isolate* isolate, const Array& message);
|
||||
|
||||
|
@ -113,6 +146,8 @@ class Service : public AllStatic {
|
|||
|
||||
static EmbedderServiceHandler* isolate_service_handler_head_;
|
||||
static EmbedderServiceHandler* root_service_handler_head_;
|
||||
static Dart_ServiceStreamListenCallback stream_listen_callback_;
|
||||
static Dart_ServiceStreamCancelCallback stream_cancel_callback_;
|
||||
|
||||
static bool needs_isolate_events_;
|
||||
static bool needs_debug_events_;
|
||||
|
|
|
@ -620,6 +620,15 @@ Isolate | IsolateStart, IsolateExit, IsolateUpdate
|
|||
Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, Inspect
|
||||
GC | GC
|
||||
|
||||
Additionally, some embedders provide the _Stdout_ and _Stderr_
|
||||
streams. These streams allow the client to subscribe to writes to
|
||||
stdout and stderr.
|
||||
|
||||
streamId | event types provided
|
||||
-------- | -----------
|
||||
Stdout | WriteEvent
|
||||
Stderr | WriteEvent
|
||||
|
||||
It is considered a _backwards compatible_ change to add a new type of event to an existing stream.
|
||||
Clients should be written to handle this gracefully, perhaps by warning and ignoring.
|
||||
|
||||
|
@ -992,6 +1001,11 @@ class Event extends Response {
|
|||
// The exception associated with this event, if this is a
|
||||
// PauseException event.
|
||||
@Instance exception [optional];
|
||||
|
||||
// An array of bytes, encoded as a base64 string.
|
||||
//
|
||||
// This is provided for the WriteEvent event.
|
||||
string bytes [optional];
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1044,7 +1058,10 @@ enum EventKind {
|
|||
BreakpointRemoved,
|
||||
|
||||
// A garbage collection event.
|
||||
GC
|
||||
GC,
|
||||
|
||||
// Notification of bytes written, for example, to stdout/stderr.
|
||||
WriteEvent
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -178,11 +178,6 @@ class VMService extends MessageRouter {
|
|||
return JSON.encode(response);
|
||||
}
|
||||
|
||||
bool _isValidStream(String streamId) {
|
||||
final validStreams = [ 'Isolate', 'Debug', 'GC', '_Echo', '_Graph' ];
|
||||
return validStreams.contains(streamId);
|
||||
}
|
||||
|
||||
bool _isAnyClientSubscribed(String streamId) {
|
||||
for (var client in clients) {
|
||||
if (client.streams.contains(streamId)) {
|
||||
|
@ -196,16 +191,15 @@ class VMService extends MessageRouter {
|
|||
var client = message.client;
|
||||
var streamId = message.params['streamId'];
|
||||
|
||||
if (!_isValidStream(streamId)) {
|
||||
return _encodeError(
|
||||
message, _kInvalidParams,
|
||||
details:"streamListen: invalid 'streamId' parameter: ${streamId}");
|
||||
}
|
||||
if (client.streams.contains(streamId)) {
|
||||
return _encodeError(message, _kStreamAlreadySubscribed);
|
||||
}
|
||||
if (!_isAnyClientSubscribed(streamId)) {
|
||||
_vmListenStream(streamId);
|
||||
if (!_vmListenStream(streamId)) {
|
||||
return _encodeError(
|
||||
message, _kInvalidParams,
|
||||
details:"streamListen: invalid 'streamId' parameter: ${streamId}");
|
||||
}
|
||||
}
|
||||
client.streams.add(streamId);
|
||||
|
||||
|
@ -217,11 +211,6 @@ class VMService extends MessageRouter {
|
|||
var client = message.client;
|
||||
var streamId = message.params['streamId'];
|
||||
|
||||
if (!_isValidStream(streamId)) {
|
||||
return _encodeError(
|
||||
message, _kInvalidParams,
|
||||
details:"streamCancel: invalid 'streamId' parameter: ${streamId}");
|
||||
}
|
||||
if (!client.streams.contains(streamId)) {
|
||||
return _encodeError(message, _kStreamNotSubscribed);
|
||||
}
|
||||
|
@ -331,6 +320,6 @@ void _onStart() native "VMService_OnStart";
|
|||
|
||||
void _onExit() native "VMService_OnExit";
|
||||
|
||||
void _vmListenStream(String streamId) native "VMService_ListenStream";
|
||||
bool _vmListenStream(String streamId) native "VMService_ListenStream";
|
||||
|
||||
void _vmCancelStream(String streamId) native "VMService_CancelStream";
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
namespace dart {
|
||||
|
||||
// Translate from the legacy DebugEvent to a ServiceEvent.
|
||||
static ServiceEvent::EventType TranslateEventType(
|
||||
DebuggerEvent::EventType type) {
|
||||
switch (type) {
|
||||
static ServiceEvent::EventKind TranslateEventKind(
|
||||
DebuggerEvent::EventType kind) {
|
||||
switch (kind) {
|
||||
case DebuggerEvent::kIsolateCreated:
|
||||
return ServiceEvent::kIsolateStart;
|
||||
|
||||
|
@ -24,7 +24,6 @@ static ServiceEvent::EventType TranslateEventType(
|
|||
|
||||
case DebuggerEvent::kExceptionThrown:
|
||||
return ServiceEvent::kPauseException;
|
||||
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return ServiceEvent::kIllegal;
|
||||
|
@ -33,12 +32,14 @@ static ServiceEvent::EventType TranslateEventType(
|
|||
|
||||
ServiceEvent::ServiceEvent(const DebuggerEvent* debugger_event)
|
||||
: isolate_(debugger_event->isolate()),
|
||||
type_(TranslateEventType(debugger_event->type())),
|
||||
kind_(TranslateEventKind(debugger_event->type())),
|
||||
breakpoint_(NULL),
|
||||
top_frame_(NULL),
|
||||
exception_(NULL),
|
||||
inspectee_(NULL),
|
||||
gc_stats_(NULL) {
|
||||
gc_stats_(NULL),
|
||||
bytes_(NULL),
|
||||
bytes_length_(0) {
|
||||
DebuggerEvent::EventType type = debugger_event->type();
|
||||
if (type == DebuggerEvent::kBreakpointReached) {
|
||||
set_breakpoint(debugger_event->breakpoint());
|
||||
|
@ -54,8 +55,8 @@ ServiceEvent::ServiceEvent(const DebuggerEvent* debugger_event)
|
|||
}
|
||||
|
||||
|
||||
const char* ServiceEvent::EventTypeToCString(EventType type) {
|
||||
switch (type) {
|
||||
const char* ServiceEvent::KindAsCString() const {
|
||||
switch (kind()) {
|
||||
case kIsolateStart:
|
||||
return "IsolateStart";
|
||||
case kIsolateExit:
|
||||
|
@ -84,6 +85,8 @@ const char* ServiceEvent::EventTypeToCString(EventType type) {
|
|||
return "GC"; // TODO(koda): Change to GarbageCollected.
|
||||
case kInspect:
|
||||
return "Inspect";
|
||||
case kEmbedder:
|
||||
return embedder_kind();
|
||||
case kDebuggerSettingsUpdate:
|
||||
return "_DebuggerSettingsUpdate";
|
||||
case kIllegal:
|
||||
|
@ -96,11 +99,11 @@ const char* ServiceEvent::EventTypeToCString(EventType type) {
|
|||
|
||||
|
||||
const char* ServiceEvent::stream_id() const {
|
||||
switch (type()) {
|
||||
switch (kind()) {
|
||||
case kIsolateStart:
|
||||
case kIsolateExit:
|
||||
case kIsolateUpdate:
|
||||
return "Isolate";
|
||||
return Service::isolate_stream.id();
|
||||
|
||||
case kPauseStart:
|
||||
case kPauseExit:
|
||||
|
@ -113,10 +116,13 @@ const char* ServiceEvent::stream_id() const {
|
|||
case kBreakpointRemoved:
|
||||
case kInspect:
|
||||
case kDebuggerSettingsUpdate:
|
||||
return "Debug";
|
||||
return Service::debug_stream.id();
|
||||
|
||||
case kGC:
|
||||
return "GC";
|
||||
return Service::gc_stream.id();
|
||||
|
||||
case kEmbedder:
|
||||
return embedder_stream_id_;
|
||||
|
||||
default:
|
||||
UNREACHABLE();
|
||||
|
@ -128,9 +134,9 @@ const char* ServiceEvent::stream_id() const {
|
|||
void ServiceEvent::PrintJSON(JSONStream* js) const {
|
||||
JSONObject jsobj(js);
|
||||
jsobj.AddProperty("type", "Event");
|
||||
jsobj.AddProperty("kind", EventTypeToCString(type()));
|
||||
jsobj.AddProperty("kind", KindAsCString());
|
||||
jsobj.AddProperty("isolate", isolate());
|
||||
if (type() == kPauseBreakpoint) {
|
||||
if (kind() == kPauseBreakpoint) {
|
||||
JSONArray jsarr(&jsobj, "pauseBreakpoints");
|
||||
// TODO(rmacnak): If we are paused at more than one breakpoint,
|
||||
// provide it here.
|
||||
|
@ -142,7 +148,7 @@ void ServiceEvent::PrintJSON(JSONStream* js) const {
|
|||
jsobj.AddProperty("breakpoint", breakpoint());
|
||||
}
|
||||
}
|
||||
if (type() == kDebuggerSettingsUpdate) {
|
||||
if (kind() == kDebuggerSettingsUpdate) {
|
||||
JSONObject jssettings(&jsobj, "_debuggerSettings");
|
||||
isolate()->debugger()->PrintSettingsToJSONObject(&jssettings);
|
||||
}
|
||||
|
@ -163,6 +169,9 @@ void ServiceEvent::PrintJSON(JSONStream* js) const {
|
|||
isolate()->heap()->PrintToJSONObject(Heap::kNew, &jsobj);
|
||||
isolate()->heap()->PrintToJSONObject(Heap::kOld, &jsobj);
|
||||
}
|
||||
if (bytes() != NULL) {
|
||||
jsobj.AddPropertyBase64("bytes", bytes(), bytes_length());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace dart {
|
|||
|
||||
class ServiceEvent {
|
||||
public:
|
||||
enum EventType {
|
||||
enum EventKind {
|
||||
kIsolateStart, // New isolate has started
|
||||
kIsolateExit, // Isolate has exited
|
||||
kIsolateUpdate, // Isolate identity information has changed
|
||||
|
@ -32,34 +32,52 @@ class ServiceEvent {
|
|||
|
||||
kGC,
|
||||
|
||||
kEmbedder,
|
||||
|
||||
kIllegal,
|
||||
};
|
||||
|
||||
ServiceEvent(Isolate* isolate, EventType event_type)
|
||||
ServiceEvent(Isolate* isolate, EventKind event_kind)
|
||||
: isolate_(isolate),
|
||||
type_(event_type),
|
||||
kind_(event_kind),
|
||||
embedder_kind_(NULL),
|
||||
embedder_stream_id_(NULL),
|
||||
breakpoint_(NULL),
|
||||
top_frame_(NULL),
|
||||
exception_(NULL),
|
||||
inspectee_(NULL),
|
||||
gc_stats_(NULL) {}
|
||||
gc_stats_(NULL),
|
||||
bytes_(NULL),
|
||||
bytes_length_(0) {}
|
||||
|
||||
explicit ServiceEvent(const DebuggerEvent* debugger_event);
|
||||
|
||||
Isolate* isolate() const { return isolate_; }
|
||||
|
||||
EventType type() const { return type_; }
|
||||
EventKind kind() const { return kind_; }
|
||||
|
||||
const char* embedder_kind() const { return embedder_kind_; }
|
||||
|
||||
const char* KindAsCString() const;
|
||||
|
||||
void set_embedder_kind(const char* embedder_kind) {
|
||||
embedder_kind_ = embedder_kind;
|
||||
}
|
||||
|
||||
const char* stream_id() const;
|
||||
|
||||
void set_embedder_stream_id(const char* stream_id) {
|
||||
embedder_stream_id_ = stream_id;
|
||||
}
|
||||
|
||||
Breakpoint* breakpoint() const {
|
||||
return breakpoint_;
|
||||
}
|
||||
void set_breakpoint(Breakpoint* bpt) {
|
||||
ASSERT(type() == kPauseBreakpoint ||
|
||||
type() == kBreakpointAdded ||
|
||||
type() == kBreakpointResolved ||
|
||||
type() == kBreakpointRemoved);
|
||||
ASSERT(kind() == kPauseBreakpoint ||
|
||||
kind() == kBreakpointAdded ||
|
||||
kind() == kBreakpointResolved ||
|
||||
kind() == kBreakpointRemoved);
|
||||
breakpoint_ = bpt;
|
||||
}
|
||||
|
||||
|
@ -67,10 +85,10 @@ class ServiceEvent {
|
|||
return top_frame_;
|
||||
}
|
||||
void set_top_frame(ActivationFrame* frame) {
|
||||
ASSERT(type() == kPauseBreakpoint ||
|
||||
type() == kPauseInterrupted ||
|
||||
type() == kPauseException ||
|
||||
type() == kResume);
|
||||
ASSERT(kind() == kPauseBreakpoint ||
|
||||
kind() == kPauseInterrupted ||
|
||||
kind() == kPauseException ||
|
||||
kind() == kResume);
|
||||
top_frame_ = frame;
|
||||
}
|
||||
|
||||
|
@ -78,7 +96,7 @@ class ServiceEvent {
|
|||
return exception_;
|
||||
}
|
||||
void set_exception(const Object* exception) {
|
||||
ASSERT(type_ == kPauseException);
|
||||
ASSERT(kind_ == kPauseException);
|
||||
exception_ = exception;
|
||||
}
|
||||
|
||||
|
@ -86,7 +104,7 @@ class ServiceEvent {
|
|||
return inspectee_;
|
||||
}
|
||||
void set_inspectee(const Object* inspectee) {
|
||||
ASSERT(type_ == kInspect);
|
||||
ASSERT(kind_ == kInspect);
|
||||
inspectee_ = inspectee;
|
||||
}
|
||||
|
||||
|
@ -98,18 +116,33 @@ class ServiceEvent {
|
|||
gc_stats_ = gc_stats;
|
||||
}
|
||||
|
||||
void PrintJSON(JSONStream* js) const;
|
||||
const uint8_t* bytes() const {
|
||||
return bytes_;
|
||||
}
|
||||
|
||||
static const char* EventTypeToCString(EventType type);
|
||||
intptr_t bytes_length() const {
|
||||
return bytes_length_;
|
||||
}
|
||||
|
||||
void set_bytes(const uint8_t* bytes, intptr_t bytes_length) {
|
||||
bytes_ = bytes;
|
||||
bytes_length_ = bytes_length;
|
||||
}
|
||||
|
||||
void PrintJSON(JSONStream* js) const;
|
||||
|
||||
private:
|
||||
Isolate* isolate_;
|
||||
EventType type_;
|
||||
EventKind kind_;
|
||||
const char* embedder_kind_;
|
||||
const char* embedder_stream_id_;
|
||||
Breakpoint* breakpoint_;
|
||||
ActivationFrame* top_frame_;
|
||||
const Object* exception_;
|
||||
const Object* inspectee_;
|
||||
const Heap::GCStats* gc_stats_;
|
||||
const uint8_t* bytes_;
|
||||
intptr_t bytes_length_;
|
||||
};
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -304,7 +304,8 @@ class ServiceIsolateNatives : public AllStatic {
|
|||
Zone* zone = stack_zone.GetZone(); // Used by GET_NON_NULL_NATIVE_ARGUMENT.
|
||||
HANDLESCOPE(isolate);
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(String, stream_id, arguments->NativeArgAt(0));
|
||||
Service::ListenStream(stream_id.ToCString());
|
||||
bool result = Service::ListenStream(stream_id.ToCString());
|
||||
arguments->SetReturn(Bool::Get(result));
|
||||
}
|
||||
|
||||
static void CancelStream(Dart_NativeArguments args) {
|
||||
|
|
Loading…
Reference in a new issue