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:
johnmccutchan@google.com
2015-05-04 20:20:44 +00:00
parent c464ba7dda
commit 2f7cff7170
19 changed files with 725 additions and 31 deletions
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
+13
View File
@@ -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);
+9
View File
@@ -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:
+114
View File
@@ -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
+32
View File
@@ -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_;
+39
View File
@@ -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
+35
View File
@@ -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_;
+14
View File
@@ -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();
+28 -1
View File
@@ -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
View File
@@ -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;
}
+3 -1
View File
@@ -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.
+1 -1
View File
@@ -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)