Display isolate message queue in Observatory debugger
- Log the parameters to RPCs
- Display messages like stack frames
- handler function and script visible
- message preview instead of local variables
- Expanded and previewed messages do not collapse when stepping within the existing message
- Mark patch classes as finalized to avoid ASSERT on class_finalizer.cc:2358 **
- Support iterating over message queues
- Support printing the message queue as JSON
- Inhibit OOB messages from being handled if we are iterating over the message queue
- avoids dead lock when looking up port handler (done in Dart code with message handler locked) and a stack overflow trigger to handle OOB messages
- make getObject understand message ids
- Fix dartbug.com/23355
** Need to discuss with Ivan and Matthias (reviewed code differs from committed code but matches my fix):
https://codereview.chromium.org//828353002
https://code.google.com/p/dart/source/detail?r=42612
R=turnidge@google.com
Review URL: https://codereview.chromium.org//1122503003
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@45505 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
@@ -316,7 +316,7 @@ abstract class CommonWebSocketVM extends VM {
|
||||
request.method != 'getIsolateMetric' &&
|
||||
request.method != 'getVMMetric') {
|
||||
Logger.root.info(
|
||||
'GET [${serial}] ${request.method} from ${target.networkAddress}');
|
||||
'GET [${serial}] ${request.method}(${request.params}) from ${target.networkAddress}');
|
||||
}
|
||||
// Send message.
|
||||
_webSocket.send(message);
|
||||
|
||||
@@ -115,7 +115,7 @@ class LocationManager extends Observable {
|
||||
/// queryParameters present in [updateParameters], then generate a new uri
|
||||
/// and navigate to that.
|
||||
goReplacingParameters(Map updatedParameters, [bool addToBrowserHistory = true]) {
|
||||
go(makeLinkReplacingParameter(updatedParameters), addToBrowserHistory);
|
||||
go(makeLinkReplacingParameters(updatedParameters), addToBrowserHistory);
|
||||
}
|
||||
|
||||
makeLinkReplacingParameters(Map updatedParameters) {
|
||||
|
||||
@@ -1207,6 +1207,7 @@ class DebuggerPageElement extends ObservatoryElement {
|
||||
class DebuggerStackElement extends ObservatoryElement {
|
||||
@published Isolate isolate;
|
||||
@observable bool hasStack = false;
|
||||
@observable bool hasMessages = false;
|
||||
@observable bool isSampled = false;
|
||||
@observable int currentFrame;
|
||||
ObservatoryDebugger debugger;
|
||||
@@ -1228,7 +1229,18 @@ class DebuggerStackElement extends ObservatoryElement {
|
||||
frameList.insert(0, li);
|
||||
}
|
||||
|
||||
void updateStack(ServiceMap newStack, ServiceEvent pauseEvent) {
|
||||
_addMessage(List messageList, ServiceMap messageInfo) {
|
||||
DebuggerMessageElement messageElement = new Element.tag('debugger-message');
|
||||
messageElement.message = messageInfo;
|
||||
|
||||
var li = new LIElement();
|
||||
li.classes.add('list-group-item');
|
||||
li.children.insert(0, messageElement);
|
||||
|
||||
messageList.add(li);
|
||||
}
|
||||
|
||||
void updateStackFrames(ServiceMap newStack) {
|
||||
List frameElements = $['frameList'].children;
|
||||
List newFrames = newStack['frames'];
|
||||
|
||||
@@ -1275,10 +1287,47 @@ class DebuggerStackElement extends ObservatoryElement {
|
||||
}
|
||||
}
|
||||
|
||||
isSampled = pauseEvent == null;
|
||||
hasStack = frameElements.isNotEmpty;
|
||||
}
|
||||
|
||||
void updateStackMessages(ServiceMap newStack) {
|
||||
List messageElements = $['messageList'].children;
|
||||
List newMessages = newStack['messages'];
|
||||
|
||||
// Remove any extra message elements.
|
||||
if (messageElements.length > newMessages.length) {
|
||||
// Remove old messages from the front of the queue.
|
||||
int removeCount = messageElements.length - newMessages.length;
|
||||
for (int i = 0; i < removeCount; i++) {
|
||||
messageElements.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any new messages to the tail of the queue.
|
||||
int newStartingIndex = messageElements.length;
|
||||
if (messageElements.length < newMessages.length) {
|
||||
for (int i = newStartingIndex; i < newMessages.length; i++) {
|
||||
_addMessage(messageElements, newMessages[i]);
|
||||
}
|
||||
}
|
||||
assert(messageElements.length == newMessages.length);
|
||||
|
||||
if (messageElements.isNotEmpty) {
|
||||
// Update old messages.
|
||||
for (int i = 0; i < newStartingIndex; i++) {
|
||||
messageElements[i].children[0].updateMessage(newMessages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
hasMessages = messageElements.isNotEmpty;
|
||||
}
|
||||
|
||||
void updateStack(ServiceMap newStack, ServiceEvent pauseEvent) {
|
||||
updateStackFrames(newStack);
|
||||
updateStackMessages(newStack);
|
||||
isSampled = pauseEvent == null;
|
||||
}
|
||||
|
||||
void setCurrentFrame(int value) {
|
||||
currentFrame = value;
|
||||
List frameElements = $['frameList'].children;
|
||||
@@ -1400,6 +1449,100 @@ class DebuggerFrameElement extends ObservatoryElement {
|
||||
}
|
||||
}
|
||||
|
||||
@CustomTag('debugger-message')
|
||||
class DebuggerMessageElement extends ObservatoryElement {
|
||||
@published ServiceMap message;
|
||||
@observable ServiceObject preview;
|
||||
|
||||
// Is this the current message?
|
||||
bool _current = false;
|
||||
|
||||
// Has this message been pinned open?
|
||||
bool _pinned = false;
|
||||
|
||||
void setCurrent(bool value) {
|
||||
_current = value;
|
||||
var messageOuter = $['messageOuter'];
|
||||
if (_current) {
|
||||
messageOuter.classes.add('current');
|
||||
expanded = true;
|
||||
messageOuter.classes.add('shadow');
|
||||
scrollIntoView();
|
||||
} else {
|
||||
messageOuter.classes.remove('current');
|
||||
if (_pinned) {
|
||||
expanded = true;
|
||||
messageOuter.classes.add('shadow');
|
||||
} else {
|
||||
expanded = false;
|
||||
messageOuter.classes.remove('shadow');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@observable String scriptHeight;
|
||||
@observable bool expanded = false;
|
||||
@observable bool busy = false;
|
||||
|
||||
DebuggerMessageElement.created() : super.created();
|
||||
|
||||
void updateMessage(ServiceMap newMessage) {
|
||||
bool messageChanged =
|
||||
(message['messageObjectId'] != newMessage['messageObjectId']);
|
||||
message['depth'] = newMessage['depth'];
|
||||
message['handlerFunction'] = newMessage['handlerFunction'];
|
||||
message['messageObjectId'] = newMessage['messageObjectId'];
|
||||
if (messageChanged) {
|
||||
// Message object id has changed: clear preview and collapse.
|
||||
preview = null;
|
||||
if (expanded) {
|
||||
toggleExpand(null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void attached() {
|
||||
super.attached();
|
||||
int windowHeight = window.innerHeight;
|
||||
scriptHeight = '${windowHeight ~/ 1.6}px';
|
||||
}
|
||||
|
||||
void toggleExpand(var a, var b, var c) {
|
||||
if (busy) {
|
||||
return;
|
||||
}
|
||||
busy = true;
|
||||
var function = message['handlerFunction'];
|
||||
var loadedFunction;
|
||||
if (function == null) {
|
||||
// Complete immediately.
|
||||
loadedFunction = new Future.value(null);
|
||||
} else {
|
||||
loadedFunction = function.load();
|
||||
}
|
||||
loadedFunction.then((_) {
|
||||
_pinned = !_pinned;
|
||||
var messageOuter = $['messageOuter'];
|
||||
if (_pinned) {
|
||||
expanded = true;
|
||||
messageOuter.classes.add('shadow');
|
||||
} else {
|
||||
expanded = false;
|
||||
messageOuter.classes.remove('shadow');
|
||||
}
|
||||
busy = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<ServiceObject> previewMessage(_) {
|
||||
return message.isolate.getObject(message['messageObjectId']).then((result) {
|
||||
preview = result;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@CustomTag('debugger-console')
|
||||
class DebuggerConsoleElement extends ObservatoryElement {
|
||||
@published Isolate isolate;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<link rel="import" href="../../../../packages/polymer/polymer.html">
|
||||
<link rel="import" href="function_ref.html">
|
||||
<link rel="import" href="nav_bar.html">
|
||||
<link rel="import" href="eval_link.html">
|
||||
<link rel="import" href="observatory_element.html">
|
||||
<link rel="import" href="script_inset.html">
|
||||
<link rel="import" href="script_ref.html">
|
||||
@@ -145,7 +146,7 @@
|
||||
font-size: 1px;
|
||||
border-bottom: 1px dashed #888;
|
||||
}
|
||||
.noStack {
|
||||
.noMessages .noStack {
|
||||
margin: 0px 20px 10px 20px;
|
||||
font: normal 14px consolas, courier, monospace;
|
||||
line-height: 125%;
|
||||
@@ -170,6 +171,13 @@
|
||||
<ul id="frameList" class="list-group">
|
||||
<!-- debugger-frame elements are added programmatically -->
|
||||
</ul>
|
||||
<hr>
|
||||
<template if="{{ !hasMessages }}">
|
||||
<div class="noMessages">No messages</div>
|
||||
</template>
|
||||
<ul id="messageList" class="list-group">
|
||||
<!-- debugger-message elements are added programmatically -->
|
||||
</ul>
|
||||
</template>
|
||||
</polymer-element>
|
||||
|
||||
@@ -277,6 +285,104 @@
|
||||
</template>
|
||||
</polymer-element>
|
||||
|
||||
<polymer-element name="debugger-message" extends="observatory-element">
|
||||
<template>
|
||||
<link rel="stylesheet" href="css/shared.css">
|
||||
<style>
|
||||
.messageOuter {
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
border: 1px solid white;
|
||||
}
|
||||
.messageOuter:hover {
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
.shadow {
|
||||
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16),
|
||||
0 2px 5px 0 rgba(0, 0, 0, 0.26);
|
||||
}
|
||||
.current {
|
||||
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.26),
|
||||
0 2px 5px 0 rgba(0, 0, 0, 0.46);
|
||||
border: 1px solid #444;
|
||||
}
|
||||
.messageSummaryText {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
}
|
||||
.messageId {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
.messageOuter .messageExpander {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
display: none;
|
||||
}
|
||||
.messageOuter:hover .messageExpander {
|
||||
display: inline-block;
|
||||
}
|
||||
.messageContractor {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
<div id="messageOuter" class="messageOuter">
|
||||
<a on-click="{{ toggleExpand }}">
|
||||
<div class="messageSummary">
|
||||
<div class="messageSummaryText">
|
||||
<div class="messageId"><b>message {{ message['depth'] }}</b></div>
|
||||
<function-ref ref="{{ message['handlerFunction'] }}"></function-ref>
|
||||
( <script-ref ref="{{ message['handlerScript'] }}"
|
||||
pos="{{ message['handlerTokenPos'] }}">
|
||||
</script-ref> )
|
||||
</div>
|
||||
<template if="{{ !expanded }}">
|
||||
<div class="messageExpander">
|
||||
<icon-expand-more></icon-expand-more>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<template if="{{expanded}}">
|
||||
<div class="messageDetails">
|
||||
<div class="flex-row">
|
||||
<div class="flex-item-60-percent">
|
||||
<template if="{{ message['handlerFunction'] != null }}">
|
||||
<script-inset height="{{ scriptHeight }}"
|
||||
script="{{ message['handlerFunction'].script }}"
|
||||
startPos="{{ message['handlerFunction'].tokenPos }}"
|
||||
endPos="{{ message['handlerFunction'].endTokenPos }}"
|
||||
inDebuggerContext="{{ true }}">
|
||||
</script-inset>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex-item-40-percent">
|
||||
<div class="memberItem">
|
||||
<div class="memberName"></div>
|
||||
<div class="memberValue">
|
||||
<eval-link callback="{{ previewMessage }}" label="[preview]" result="{{ preview }}"></eval-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="messageContractor">
|
||||
<template if="{{expanded}}">
|
||||
<a on-click="{{ toggleExpand }}">
|
||||
<icon-expand-less></icon-expand-less>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</polymer-element>
|
||||
|
||||
<polymer-element name="debugger-console" extends="observatory-element">
|
||||
<template>
|
||||
<link rel="stylesheet" href="css/shared.css">
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'service_ref.dart';
|
||||
|
||||
@CustomTag('script-ref')
|
||||
class ScriptRefElement extends ServiceRefElement {
|
||||
@published int pos = -1;
|
||||
@published int pos;
|
||||
|
||||
String get hoverText {
|
||||
if (ref == null) {
|
||||
@@ -34,7 +34,7 @@ class ScriptRefElement extends ServiceRefElement {
|
||||
if (ref == null) {
|
||||
return super.name;
|
||||
}
|
||||
if (pos >= 0) {
|
||||
if ((pos != null) && (pos >= 0)) {
|
||||
if (ref.loaded) {
|
||||
// Script is loaded, get the line number.
|
||||
Script script = ref;
|
||||
@@ -51,7 +51,7 @@ class ScriptRefElement extends ServiceRefElement {
|
||||
if (ref == null) {
|
||||
return super.url;
|
||||
}
|
||||
if (pos >= 0) {
|
||||
if ((pos != null) && (pos >= 0)) {
|
||||
if (ref.loaded) {
|
||||
return '${super.url}---pos=${pos}';
|
||||
} else {
|
||||
|
||||
@@ -79,6 +79,7 @@ abstract class ServiceObject extends Observable {
|
||||
bool get isNull => type == 'null';
|
||||
bool get isSentinel => type == 'Sentinel';
|
||||
bool get isString => type == 'String';
|
||||
bool get isMessage => type == 'Message';
|
||||
|
||||
// Kinds of Instance.
|
||||
bool get isMirrorReference => vmType == 'MirrorReference';
|
||||
@@ -2209,6 +2210,7 @@ class Script extends ServiceObject with Coverage {
|
||||
if (mapIsRef) {
|
||||
return;
|
||||
}
|
||||
_loaded = true;
|
||||
lineOffset = map['lineOffset'];
|
||||
columnOffset = map['columnOffset'];
|
||||
_parseTokenPosTable(map['tokenPosTable']);
|
||||
@@ -2282,8 +2284,6 @@ class Script extends ServiceObject with Coverage {
|
||||
}
|
||||
|
||||
void _processSource(String source) {
|
||||
// Preemptyively mark that this is not loaded.
|
||||
_loaded = false;
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
@@ -2291,8 +2291,6 @@ class Script extends ServiceObject with Coverage {
|
||||
if (sourceLines.length == 0) {
|
||||
return;
|
||||
}
|
||||
// We have the source to the script. This is now loaded.
|
||||
_loaded = true;
|
||||
lines.clear();
|
||||
Logger.root.info('Adding ${sourceLines.length} source lines for ${_url}');
|
||||
for (var i = 0; i < sourceLines.length; i++) {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// 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 'package:observatory/service_io.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'test_helper.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
int counter = 0;
|
||||
const stoppedAtLine = 23;
|
||||
var port = new RawReceivePort(msgHandler);
|
||||
|
||||
// This name is used in a test below.
|
||||
void msgHandler(_) {
|
||||
}
|
||||
|
||||
void periodicTask(_) {
|
||||
counter++;
|
||||
port.sendPort.send(34);
|
||||
counter++; // Line 23. We set our breakpoint here.
|
||||
counter++;
|
||||
if (counter % 300 == 0) {
|
||||
print('counter = $counter');
|
||||
}
|
||||
}
|
||||
|
||||
void startTimer() {
|
||||
new Timer.periodic(const Duration(milliseconds:10), periodicTask);
|
||||
}
|
||||
|
||||
var tests = [
|
||||
|
||||
// Add breakpoint
|
||||
(Isolate isolate) async {
|
||||
await isolate.rootLib.load();
|
||||
|
||||
// Set up a listener to wait for breakpoint events.
|
||||
Completer completer = new Completer();
|
||||
var subscription;
|
||||
subscription = isolate.vm.events.stream.listen((ServiceEvent event) {
|
||||
if (event.eventType == ServiceEvent.kPauseBreakpoint) {
|
||||
print('Breakpoint reached');
|
||||
subscription.cancel();
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
|
||||
var script = isolate.rootLib.scripts[0];
|
||||
await script.load();
|
||||
|
||||
// Add the breakpoint.
|
||||
var result = await isolate.addBreakpoint(script, stoppedAtLine);
|
||||
expect(result is Breakpoint, isTrue);
|
||||
Breakpoint bpt = result;
|
||||
expect(bpt.type, equals('Breakpoint'));
|
||||
expect(bpt.script.id, equals(script.id));
|
||||
expect(bpt.script.tokenToLine(bpt.tokenPos), equals(stoppedAtLine));
|
||||
expect(isolate.breakpoints.length, equals(1));
|
||||
|
||||
await completer.future; // Wait for breakpoint events.
|
||||
},
|
||||
|
||||
// Get stack
|
||||
(Isolate isolate) async {
|
||||
var stack = await isolate.getStack();
|
||||
expect(stack.type, equals('Stack'));
|
||||
|
||||
// Sanity check.
|
||||
expect(stack['frames'].length, greaterThanOrEqualTo(1));
|
||||
Script script = stack['frames'][0]['script'];
|
||||
expect(script.tokenToLine(stack['frames'][0]['tokenPos']),
|
||||
equals(stoppedAtLine));
|
||||
|
||||
// Iterate over frames.
|
||||
var frameDepth = 0;
|
||||
for (var frame in stack['frames']) {
|
||||
print('checking frame $frameDepth');
|
||||
expect(frame.type, equals('Frame'));
|
||||
expect(frame['depth'], equals(frameDepth++));
|
||||
expect(frame['code'].type, equals('Code'));
|
||||
expect(frame['function'].type, equals('Function'));
|
||||
expect(frame['script'].type, equals('Script'));
|
||||
expect(frame['tokenPos'], isNotNull);
|
||||
}
|
||||
|
||||
// Sanity check.
|
||||
expect(stack['messages'].length, greaterThanOrEqualTo(1));
|
||||
|
||||
// Iterate over messages.
|
||||
var messageDepth = 0;
|
||||
// objectId of message to be handled by msgHandler.
|
||||
var msgHandlerObjectId;
|
||||
for (var message in stack['messages']) {
|
||||
print('checking message $messageDepth');
|
||||
expect(message.type, equals('Message'));
|
||||
expect(message['_destinationPort'], isNotNull);
|
||||
expect(message['depth'], equals(messageDepth++));
|
||||
expect(message['name'], isNotNull);
|
||||
expect(message['size'], greaterThanOrEqualTo(1));
|
||||
expect(message['priority'], isNotNull);
|
||||
expect(message['handlerFunction'].type, equals('Function'));
|
||||
if (message['handlerFunction'].name.contains('msgHandler')) {
|
||||
msgHandlerObjectId = message['messageObjectId'];
|
||||
}
|
||||
}
|
||||
expect(msgHandlerObjectId, isNotNull);
|
||||
|
||||
// Get object.
|
||||
var object = await isolate.getObject(msgHandlerObjectId);
|
||||
expect(object.valueAsString, equals('34'));
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
main(args) => runIsolateTests(args, tests, testeeBefore: startTimer);
|
||||
@@ -2320,7 +2320,7 @@ void ClassFinalizer::FinalizeClass(const Class& cls) {
|
||||
if (cls.is_finalized()) {
|
||||
return;
|
||||
}
|
||||
if (false && cls.is_patch()) {
|
||||
if (cls.is_patch()) {
|
||||
// The fields and functions of a patch class are copied to the
|
||||
// patched class after parsing. There is nothing to finalize.
|
||||
ASSERT(Array::Handle(cls.functions()).Length() == 0);
|
||||
|
||||
@@ -273,6 +273,12 @@ void JSONStream::PrintValue(Metric* metric) {
|
||||
}
|
||||
|
||||
|
||||
void JSONStream::PrintValue(MessageQueue* queue) {
|
||||
PrintCommaIfNeeded();
|
||||
queue->PrintJSON(this);
|
||||
}
|
||||
|
||||
|
||||
void JSONStream::PrintValue(Isolate* isolate, bool ref) {
|
||||
PrintCommaIfNeeded();
|
||||
isolate->PrintJSON(this, ref);
|
||||
@@ -342,6 +348,13 @@ void JSONStream::PrintProperty(const char* name, Metric* metric) {
|
||||
PrintValue(metric);
|
||||
}
|
||||
|
||||
|
||||
void JSONStream::PrintProperty(const char* name, MessageQueue* queue) {
|
||||
PrintPropertyName(name);
|
||||
PrintValue(queue);
|
||||
}
|
||||
|
||||
|
||||
void JSONStream::PrintProperty(const char* name, Isolate* isolate) {
|
||||
PrintPropertyName(name);
|
||||
PrintValue(isolate);
|
||||
|
||||
@@ -17,6 +17,7 @@ class GrowableObjectArray;
|
||||
class Instance;
|
||||
class JSONArray;
|
||||
class JSONObject;
|
||||
class MessageQueue;
|
||||
class Metric;
|
||||
class Object;
|
||||
class ServiceEvent;
|
||||
@@ -91,6 +92,7 @@ class JSONStream : ValueObject {
|
||||
void PrintValue(SourceBreakpoint* bpt);
|
||||
void PrintValue(const ServiceEvent* event);
|
||||
void PrintValue(Metric* metric);
|
||||
void PrintValue(MessageQueue* queue);
|
||||
void PrintValue(Isolate* isolate, bool ref = true);
|
||||
bool PrintValueStr(const String& s, intptr_t limit);
|
||||
|
||||
@@ -108,6 +110,7 @@ class JSONStream : ValueObject {
|
||||
void PrintProperty(const char* name, const ServiceEvent* event);
|
||||
void PrintProperty(const char* name, SourceBreakpoint* bpt);
|
||||
void PrintProperty(const char* name, Metric* metric);
|
||||
void PrintProperty(const char* name, MessageQueue* queue);
|
||||
void PrintProperty(const char* name, Isolate* isolate);
|
||||
void PrintPropertyName(const char* name);
|
||||
void PrintCommaIfNeeded();
|
||||
@@ -183,6 +186,9 @@ class JSONObject : public ValueObject {
|
||||
void AddProperty(const char* name, Metric* metric) const {
|
||||
stream_->PrintProperty(name, metric);
|
||||
}
|
||||
void AddProperty(const char* name, MessageQueue* queue) const {
|
||||
stream_->PrintProperty(name, queue);
|
||||
}
|
||||
void AddProperty(const char* name, Isolate* isolate) const {
|
||||
stream_->PrintProperty(name, isolate);
|
||||
}
|
||||
@@ -234,6 +240,9 @@ class JSONArray : public ValueObject {
|
||||
void AddValue(Metric* metric) const {
|
||||
stream_->PrintValue(metric);
|
||||
}
|
||||
void AddValue(MessageQueue* queue) const {
|
||||
stream_->PrintValue(queue);
|
||||
}
|
||||
void AddValueF(const char* format, ...) const PRINTF_ATTRIBUTE(2, 3);
|
||||
|
||||
private:
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
#include "vm/message.h"
|
||||
|
||||
#include "vm/dart_entry.h"
|
||||
#include "vm/json_stream.h"
|
||||
#include "vm/object.h"
|
||||
#include "vm/port.h"
|
||||
|
||||
namespace dart {
|
||||
@@ -18,6 +21,26 @@ bool Message::RedirectToDeliveryFailurePort() {
|
||||
}
|
||||
|
||||
|
||||
intptr_t Message::Id() const {
|
||||
// Messages are allocated on the C heap. Use the raw address as the id.
|
||||
return reinterpret_cast<intptr_t>(this);
|
||||
}
|
||||
|
||||
const char* Message::PriorityAsString(Priority priority) {
|
||||
switch (priority) {
|
||||
case kNormalPriority:
|
||||
return "Normal";
|
||||
break;
|
||||
case kOOBPriority:
|
||||
return "OOB";
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MessageQueue::MessageQueue() {
|
||||
head_ = NULL;
|
||||
tail_ = NULL;
|
||||
@@ -106,4 +129,95 @@ void MessageQueue::Clear() {
|
||||
}
|
||||
|
||||
|
||||
MessageQueue::Iterator::Iterator(const MessageQueue* queue)
|
||||
: next_(NULL) {
|
||||
Reset(queue);
|
||||
}
|
||||
|
||||
|
||||
MessageQueue::Iterator::~Iterator() {
|
||||
}
|
||||
|
||||
void MessageQueue::Iterator::Reset(const MessageQueue* queue) {
|
||||
ASSERT(queue != NULL);
|
||||
next_ = queue->head_;
|
||||
}
|
||||
|
||||
// returns false when there are no more messages left.
|
||||
bool MessageQueue::Iterator::HasNext() {
|
||||
return next_ != NULL;
|
||||
}
|
||||
|
||||
// Returns the current message and moves forward.
|
||||
Message* MessageQueue::Iterator::Next() {
|
||||
Message* current = next_;
|
||||
next_ = next_->next_;
|
||||
return current;
|
||||
}
|
||||
|
||||
|
||||
intptr_t MessageQueue::Length() const {
|
||||
MessageQueue::Iterator it(this);
|
||||
intptr_t length = 0;
|
||||
while (it.HasNext()) {
|
||||
it.Next();
|
||||
length++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
Message* MessageQueue::FindMessageById(intptr_t id) {
|
||||
MessageQueue::Iterator it(this);
|
||||
while (it.HasNext()) {
|
||||
Message* current = it.Next();
|
||||
ASSERT(current != NULL);
|
||||
if (current->Id() == id) {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void MessageQueue::PrintJSON(JSONStream* stream) {
|
||||
Isolate* isolate = Isolate::Current();
|
||||
JSONArray messages(stream);
|
||||
|
||||
Object& msg_handler = Object::Handle(isolate);
|
||||
|
||||
MessageQueue::Iterator it(this);
|
||||
intptr_t depth = 0;
|
||||
while (it.HasNext()) {
|
||||
Message* current = it.Next();
|
||||
JSONObject message(&messages);
|
||||
message.AddProperty("type", "Message");
|
||||
message.AddPropertyF("name", "Isolate Message (%" Px ")", current->Id());
|
||||
message.AddPropertyF("messageObjectId", "messages/%" Px "",
|
||||
current->Id());
|
||||
message.AddProperty("size", current->len());
|
||||
message.AddProperty("depth", depth++);
|
||||
message.AddProperty("_destinationPort",
|
||||
static_cast<intptr_t>(current->dest_port()));
|
||||
message.AddProperty("priority",
|
||||
Message::PriorityAsString(current->priority()));
|
||||
// TODO(johnmccutchan): Move port -> handler map out of Dart and into the
|
||||
// VM, that way we can lookup the handler without invoking Dart code.
|
||||
msg_handler = DartLibraryCalls::LookupHandler(current->dest_port());
|
||||
if (msg_handler.IsInstance() && Instance::Cast(msg_handler).IsClosure()) {
|
||||
// Grab function from closure.
|
||||
msg_handler = Closure::function(Instance::Cast(msg_handler));
|
||||
}
|
||||
if (!msg_handler.IsFunction()) {
|
||||
// No handler function.
|
||||
continue;
|
||||
}
|
||||
const Function& function = Function::Cast(msg_handler);
|
||||
const Script& script = Script::Handle(function.script());
|
||||
message.AddProperty("handlerFunction", function);
|
||||
message.AddProperty("handlerScript", script);
|
||||
message.AddProperty("handlerTokenPos", function.token_pos());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dart
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define VM_MESSAGE_H_
|
||||
|
||||
#include "platform/assert.h"
|
||||
#include "vm/allocation.h"
|
||||
#include "vm/globals.h"
|
||||
|
||||
// Duplicated from dart_api.h to avoid including the whole header.
|
||||
@@ -13,6 +14,8 @@ typedef int64_t Dart_Port;
|
||||
|
||||
namespace dart {
|
||||
|
||||
class JSONStream;
|
||||
|
||||
class Message {
|
||||
public:
|
||||
typedef enum {
|
||||
@@ -68,6 +71,10 @@ class Message {
|
||||
|
||||
bool RedirectToDeliveryFailurePort();
|
||||
|
||||
intptr_t Id() const;
|
||||
|
||||
static const char* PriorityAsString(Priority priority);
|
||||
|
||||
private:
|
||||
friend class MessageQueue;
|
||||
|
||||
@@ -98,6 +105,31 @@ class MessageQueue {
|
||||
// Clear all messages from the message queue.
|
||||
void Clear();
|
||||
|
||||
// Iterator class.
|
||||
class Iterator : public ValueObject {
|
||||
public:
|
||||
explicit Iterator(const MessageQueue* queue);
|
||||
virtual ~Iterator();
|
||||
|
||||
void Reset(const MessageQueue* queue);
|
||||
|
||||
// Returns false when there are no more messages left.
|
||||
bool HasNext();
|
||||
|
||||
// Returns the current message and moves forward.
|
||||
Message* Next();
|
||||
|
||||
private:
|
||||
Message* next_;
|
||||
};
|
||||
|
||||
intptr_t Length() const;
|
||||
|
||||
// Returns the message with id or NULL.
|
||||
Message* FindMessageById(intptr_t id);
|
||||
|
||||
void PrintJSON(JSONStream* stream);
|
||||
|
||||
private:
|
||||
Message* head_;
|
||||
Message* tail_;
|
||||
|
||||
@@ -34,6 +34,7 @@ class MessageHandlerTask : public ThreadPool::Task {
|
||||
MessageHandler::MessageHandler()
|
||||
: queue_(new MessageQueue()),
|
||||
oob_queue_(new MessageQueue()),
|
||||
oob_message_handling_allowed_(true),
|
||||
live_ports_(0),
|
||||
paused_(0),
|
||||
pause_on_start_(false),
|
||||
@@ -208,6 +209,9 @@ bool MessageHandler::HandleNextMessage() {
|
||||
|
||||
|
||||
bool MessageHandler::HandleOOBMessages() {
|
||||
if (!oob_message_handling_allowed_) {
|
||||
return true;
|
||||
}
|
||||
MonitorLocker ml(&monitor_);
|
||||
#if defined(DEBUG)
|
||||
CheckAccess();
|
||||
@@ -333,4 +337,39 @@ void MessageHandler::decrement_live_ports() {
|
||||
live_ports_--;
|
||||
}
|
||||
|
||||
|
||||
MessageHandler::AcquiredQueues::AcquiredQueues()
|
||||
: handler_(NULL) {
|
||||
}
|
||||
|
||||
|
||||
MessageHandler::AcquiredQueues::~AcquiredQueues() {
|
||||
Reset(NULL);
|
||||
}
|
||||
|
||||
|
||||
void MessageHandler::AcquiredQueues::Reset(MessageHandler* handler) {
|
||||
if (handler_ != NULL) {
|
||||
// Release ownership. The OOB flag is set without holding the monitor.
|
||||
handler_->monitor_.Exit();
|
||||
handler_->oob_message_handling_allowed_ = true;
|
||||
}
|
||||
handler_ = handler;
|
||||
if (handler_ == NULL) {
|
||||
return;
|
||||
}
|
||||
ASSERT(handler_ != NULL);
|
||||
// Take ownership. The OOB flag is set without holding the monitor.
|
||||
handler_->oob_message_handling_allowed_ = false;
|
||||
handler_->monitor_.Enter();
|
||||
}
|
||||
|
||||
|
||||
void MessageHandler::AcquireQueues(AcquiredQueues* acquired_queues) {
|
||||
ASSERT(acquired_queues != NULL);
|
||||
// No double dipping.
|
||||
ASSERT(acquired_queues->handler_ == NULL);
|
||||
acquired_queues->Reset(this);
|
||||
}
|
||||
|
||||
} // namespace dart
|
||||
|
||||
@@ -96,6 +96,38 @@ class MessageHandler {
|
||||
return paused_on_exit_;
|
||||
}
|
||||
|
||||
class AcquiredQueues : public ValueObject {
|
||||
public:
|
||||
AcquiredQueues();
|
||||
|
||||
~AcquiredQueues();
|
||||
|
||||
MessageQueue* queue() {
|
||||
if (handler_ == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return handler_->queue_;
|
||||
}
|
||||
|
||||
MessageQueue* oob_queue() {
|
||||
if (handler_ == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return handler_->oob_queue_;
|
||||
}
|
||||
|
||||
private:
|
||||
void Reset(MessageHandler* handler);
|
||||
|
||||
MessageHandler* handler_;
|
||||
|
||||
friend class MessageHandler;
|
||||
};
|
||||
|
||||
// Gives temporary ownership of |queue| and |oob_queue|. Calling this
|
||||
// has the side effect that no OOB messages will be handled if a stack
|
||||
// overflow interrupt is delivered.
|
||||
void AcquireQueues(AcquiredQueues* acquired_queue);
|
||||
|
||||
#if defined(DEBUG)
|
||||
// Check that it is safe to access this message handler.
|
||||
@@ -166,6 +198,9 @@ class MessageHandler {
|
||||
Monitor monitor_; // Protects all fields in MessageHandler.
|
||||
MessageQueue* queue_;
|
||||
MessageQueue* oob_queue_;
|
||||
// This flag is not thread safe and can only reliably be accessed on a single
|
||||
// thread.
|
||||
bool oob_message_handling_allowed_;
|
||||
intptr_t live_ports_; // The number of open ports, including control ports.
|
||||
intptr_t paused_; // The number of pause messages received.
|
||||
bool pause_on_start_;
|
||||
|
||||
@@ -158,11 +158,25 @@ UNIT_TEST_CASE(MessageHandler_HasOOBMessages) {
|
||||
Message* message = new Message(1, NULL, 0, Message::kNormalPriority);
|
||||
handler_peer.PostMessage(message);
|
||||
EXPECT(!handler.HasOOBMessages());
|
||||
{
|
||||
// Acquire ownership of message handler queues, verify one regular message.
|
||||
MessageHandler::AcquiredQueues aq;
|
||||
handler.AcquireQueues(&aq);
|
||||
EXPECT(aq.queue()->Length() == 1);
|
||||
}
|
||||
|
||||
// Post an oob message.
|
||||
message = new Message(1, NULL, 0, Message::kOOBPriority);
|
||||
handler_peer.PostMessage(message);
|
||||
EXPECT(handler.HasOOBMessages());
|
||||
{
|
||||
// Acquire ownership of message handler queues, verify one regular and one
|
||||
// OOB message.
|
||||
MessageHandler::AcquiredQueues aq;
|
||||
handler.AcquireQueues(&aq);
|
||||
EXPECT(aq.queue()->Length() == 1);
|
||||
EXPECT(aq.oob_queue()->Length() == 1);
|
||||
}
|
||||
|
||||
// Delete all pending messages.
|
||||
handler_peer.CloseAllPorts();
|
||||
|
||||
@@ -17,6 +17,9 @@ static uint8_t* AllocMsg(const char* str) {
|
||||
TEST_CASE(MessageQueue_BasicOperations) {
|
||||
MessageQueue queue;
|
||||
EXPECT(queue.IsEmpty());
|
||||
MessageQueue::Iterator it(&queue);
|
||||
// Queue is empty.
|
||||
EXPECT(!it.HasNext());
|
||||
|
||||
Dart_Port port = 1;
|
||||
|
||||
@@ -31,19 +34,43 @@ TEST_CASE(MessageQueue_BasicOperations) {
|
||||
Message* msg1 = new Message(
|
||||
port, AllocMsg(str1), strlen(str1) + 1, Message::kNormalPriority);
|
||||
queue.Enqueue(msg1, false);
|
||||
EXPECT(queue.Length() == 1);
|
||||
EXPECT(!queue.IsEmpty());
|
||||
it.Reset(&queue);
|
||||
EXPECT(it.HasNext());
|
||||
EXPECT(it.Next() == msg1);
|
||||
EXPECT(!it.HasNext());
|
||||
|
||||
Message* msg2 = new Message(
|
||||
port, AllocMsg(str2), strlen(str2) + 1, Message::kNormalPriority);
|
||||
queue.Enqueue(msg2, false);
|
||||
EXPECT(queue.Length() == 2);
|
||||
EXPECT(!queue.IsEmpty());
|
||||
it.Reset(&queue);
|
||||
EXPECT(it.HasNext());
|
||||
EXPECT(it.Next() == msg1);
|
||||
EXPECT(it.HasNext());
|
||||
EXPECT(it.Next() == msg2);
|
||||
EXPECT(!it.HasNext());
|
||||
|
||||
// Remove two messages.
|
||||
// Lookup messages by id.
|
||||
EXPECT(queue.FindMessageById(reinterpret_cast<intptr_t>(msg1)) == msg1);
|
||||
EXPECT(queue.FindMessageById(reinterpret_cast<intptr_t>(msg2)) == msg2);
|
||||
|
||||
// Lookup bad id.
|
||||
EXPECT(queue.FindMessageById(0x1) == NULL);
|
||||
|
||||
// Remove message 1
|
||||
Message* msg = queue.Dequeue();
|
||||
EXPECT(msg != NULL);
|
||||
EXPECT_STREQ(str1, reinterpret_cast<char*>(msg->data()));
|
||||
EXPECT(!queue.IsEmpty());
|
||||
|
||||
it.Reset(&queue);
|
||||
EXPECT(it.HasNext());
|
||||
EXPECT(it.Next() == msg2);
|
||||
|
||||
// Remove message 2
|
||||
msg = queue.Dequeue();
|
||||
EXPECT(msg != NULL);
|
||||
EXPECT_STREQ(str2, reinterpret_cast<char*>(msg->data()));
|
||||
|
||||
+59
-15
@@ -733,17 +733,26 @@ static bool GetStack(Isolate* isolate, JSONStream* js) {
|
||||
const bool full = BoolParameter::Parse(js->LookupParam("full"), false);
|
||||
JSONObject jsobj(js);
|
||||
jsobj.AddProperty("type", "Stack");
|
||||
JSONArray jsarr(&jsobj, "frames");
|
||||
{
|
||||
JSONArray jsarr(&jsobj, "frames");
|
||||
|
||||
intptr_t num_frames = stack->Length();
|
||||
for (intptr_t i = 0; i < num_frames; i++) {
|
||||
ActivationFrame* frame = stack->FrameAt(i);
|
||||
JSONObject jsobj(&jsarr);
|
||||
frame->PrintToJSONObject(&jsobj, full);
|
||||
// TODO(turnidge): Implement depth differently -- differentiate
|
||||
// inlined frames.
|
||||
jsobj.AddProperty("depth", i);
|
||||
intptr_t num_frames = stack->Length();
|
||||
for (intptr_t i = 0; i < num_frames; i++) {
|
||||
ActivationFrame* frame = stack->FrameAt(i);
|
||||
JSONObject jsobj(&jsarr);
|
||||
frame->PrintToJSONObject(&jsobj, full);
|
||||
// TODO(turnidge): Implement depth differently -- differentiate
|
||||
// inlined frames.
|
||||
jsobj.AddProperty("depth", i);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MessageHandler::AcquiredQueues aq;
|
||||
isolate->message_handler()->AcquireQueues(&aq);
|
||||
jsobj.AddProperty("messages", aq.queue());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1163,14 +1172,11 @@ static void PrintSentinel(JSONStream* js,
|
||||
|
||||
static SourceBreakpoint* LookupBreakpoint(Isolate* isolate, const char* id) {
|
||||
size_t end_pos = strcspn(id, "/");
|
||||
const char* rest = NULL;
|
||||
if (end_pos < strlen(id)) {
|
||||
rest = id + end_pos + 1; // +1 for '/'.
|
||||
if (end_pos == strlen(id)) {
|
||||
return false;
|
||||
}
|
||||
const char* rest = id + end_pos + 1; // +1 for '/'.
|
||||
if (strncmp("breakpoints", id, end_pos) == 0) {
|
||||
if (rest == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
intptr_t bpt_id = 0;
|
||||
SourceBreakpoint* bpt = NULL;
|
||||
if (GetIntegerId(rest, &bpt_id)) {
|
||||
@@ -1182,6 +1188,40 @@ static SourceBreakpoint* LookupBreakpoint(Isolate* isolate, const char* id) {
|
||||
}
|
||||
|
||||
|
||||
// Scans |isolate|'s message queue looking for a message with |id|.
|
||||
// If found, the message is printed to |js| and true is returned.
|
||||
// If not found, false is returned.
|
||||
static bool PrintMessage(JSONStream* js, Isolate* isolate, const char* id) {
|
||||
size_t end_pos = strcspn(id, "/");
|
||||
if (end_pos == strlen(id)) {
|
||||
return false;
|
||||
}
|
||||
const char* rest = id + end_pos + 1; // +1 for '/'.
|
||||
if (strncmp("messages", id, end_pos) == 0) {
|
||||
uword message_id = 0;
|
||||
if (GetUnsignedIntegerId(rest, &message_id, 16)) {
|
||||
MessageHandler::AcquiredQueues aq;
|
||||
isolate->message_handler()->AcquireQueues(&aq);
|
||||
Message* message = aq.queue()->FindMessageById(message_id);
|
||||
if (message == NULL) {
|
||||
printf("Could not find message %" Px "\n", message_id);
|
||||
// Not found.
|
||||
return false;
|
||||
}
|
||||
SnapshotReader reader(message->data(),
|
||||
message->len(),
|
||||
Snapshot::kMessage,
|
||||
isolate,
|
||||
isolate->current_zone());
|
||||
const Object& msg_obj = Object::Handle(reader.ReadObject());
|
||||
msg_obj.PrintJSON(js);
|
||||
return true;
|
||||
} else {
|
||||
printf("Could not get id from %s\n", rest);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool PrintInboundReferences(Isolate* isolate,
|
||||
@@ -2331,6 +2371,10 @@ static bool GetObject(Isolate* isolate, JSONStream* js) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PrintMessage(js, isolate, id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PrintError(js, "Unrecognized object id: %s\n", id);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,9 @@ class VMService extends MessageRouter {
|
||||
void _exit() {
|
||||
isolateLifecyclePort.close();
|
||||
scriptLoadPort.close();
|
||||
for (var client in clients) {
|
||||
// Create a copy of the set as a list because client.close() alters the set.
|
||||
var clientsList = clients.toList();
|
||||
for (var client in clientsList) {
|
||||
client.close();
|
||||
}
|
||||
// Call embedder shutdown hook after the internal shutdown.
|
||||
|
||||
@@ -506,6 +506,7 @@ TEST_CASE(Service_EmbedderRootHandler) {
|
||||
EXPECT_STREQ("{\"result\":beta, \"id\":\"0\"}", handler.msg());
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE(Service_EmbedderIsolateHandler) {
|
||||
const char* kScript =
|
||||
"var port;\n" // Set to our mock port by C++.
|
||||
@@ -543,7 +544,6 @@ TEST_CASE(Service_EmbedderIsolateHandler) {
|
||||
EXPECT_STREQ("{\"result\":beta, \"id\":\"0\"}", handler.msg());
|
||||
}
|
||||
|
||||
|
||||
// TODO(zra): Remove when tests are ready to enable.
|
||||
#if !defined(TARGET_ARCH_ARM64)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user