5534568ff1
Status: - Currently the getIsolate API tries to guess the status of the isolate when it is in between the IsolateRunnable and PauseStart, by reporting it as already in the PauseStart status. This can potentially make flaky all the tests that uses PauseStart to synchronize with the actual status of the Isolate. - In the previous situation the timestamp of the event is guessed too, potentially reporting a wrong value. - Even with the previous fix the is still a race condition that can lead to a getIsolate responde with a PauseStart status to be sent before the actual PauseStart event. Changes: - Fallback to None pause event if the isolate is in between IsolateRunnable and PauseStart, avoiding double posting. - Send the PauseStart event before the actual status change, partially avoiding the race conditions. - Set the pause timestamp before the actual status change, fully avoiding the race condition. Closes https://github.com/dart-lang/sdk/issues/28624 R=asiva@google.com, rmacnak@google.com Review-Url: https://codereview.chromium.org/3006883002 .
279 lines
7.9 KiB
C++
279 lines
7.9 KiB
C++
// 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.
|
|
|
|
#include "vm/service_event.h"
|
|
|
|
#include "vm/debugger.h"
|
|
#include "vm/message_handler.h"
|
|
#include "vm/service_isolate.h"
|
|
|
|
namespace dart {
|
|
|
|
#ifndef PRODUCT
|
|
|
|
ServiceEvent::ServiceEvent(Isolate* isolate, EventKind event_kind)
|
|
: isolate_(isolate),
|
|
kind_(event_kind),
|
|
embedder_kind_(NULL),
|
|
embedder_stream_id_(NULL),
|
|
breakpoint_(NULL),
|
|
top_frame_(NULL),
|
|
timeline_event_block_(NULL),
|
|
extension_rpc_(NULL),
|
|
exception_(NULL),
|
|
reload_error_(NULL),
|
|
spawn_token_(NULL),
|
|
spawn_error_(NULL),
|
|
at_async_jump_(false),
|
|
inspectee_(NULL),
|
|
gc_stats_(NULL),
|
|
bytes_(NULL),
|
|
bytes_length_(0),
|
|
timestamp_(OS::GetCurrentTimeMillis()) {
|
|
// We should never generate events for the vm or service isolates.
|
|
ASSERT(isolate_ != Dart::vm_isolate());
|
|
ASSERT(isolate == NULL ||
|
|
!ServiceIsolate::IsServiceIsolateDescendant(isolate_));
|
|
|
|
if ((event_kind == ServiceEvent::kPauseStart) ||
|
|
(event_kind == ServiceEvent::kPauseExit)) {
|
|
timestamp_ = isolate->message_handler()->paused_timestamp();
|
|
} else if (event_kind == ServiceEvent::kResume) {
|
|
timestamp_ = isolate->last_resume_timestamp();
|
|
}
|
|
ASSERT(timestamp_ > -1);
|
|
}
|
|
|
|
void ServiceEvent::UpdateTimestamp() {
|
|
timestamp_ = OS::GetCurrentTimeMillis();
|
|
}
|
|
|
|
const char* ServiceEvent::KindAsCString() const {
|
|
switch (kind()) {
|
|
case kVMUpdate:
|
|
return "VMUpdate";
|
|
case kIsolateStart:
|
|
return "IsolateStart";
|
|
case kIsolateRunnable:
|
|
return "IsolateRunnable";
|
|
case kIsolateExit:
|
|
return "IsolateExit";
|
|
case kIsolateUpdate:
|
|
return "IsolateUpdate";
|
|
case kServiceExtensionAdded:
|
|
return "ServiceExtensionAdded";
|
|
case kIsolateReload:
|
|
return "IsolateReload";
|
|
case kIsolateSpawn:
|
|
return "IsolateSpawn";
|
|
case kPauseStart:
|
|
return "PauseStart";
|
|
case kPauseExit:
|
|
return "PauseExit";
|
|
case kPauseBreakpoint:
|
|
return "PauseBreakpoint";
|
|
case kPauseInterrupted:
|
|
return "PauseInterrupted";
|
|
case kPauseException:
|
|
return "PauseException";
|
|
case kPausePostRequest:
|
|
return "PausePostRequest";
|
|
case kNone:
|
|
return "None";
|
|
case kResume:
|
|
return "Resume";
|
|
case kBreakpointAdded:
|
|
return "BreakpointAdded";
|
|
case kBreakpointResolved:
|
|
return "BreakpointResolved";
|
|
case kBreakpointRemoved:
|
|
return "BreakpointRemoved";
|
|
case kGC:
|
|
return "GC"; // TODO(koda): Change to GarbageCollected.
|
|
case kInspect:
|
|
return "Inspect";
|
|
case kEmbedder:
|
|
return embedder_kind();
|
|
case kLogging:
|
|
return "_Logging";
|
|
case kDebuggerSettingsUpdate:
|
|
return "_DebuggerSettingsUpdate";
|
|
case kIllegal:
|
|
return "Illegal";
|
|
case kExtension:
|
|
return "Extension";
|
|
case kTimelineEvents:
|
|
return "TimelineEvents";
|
|
default:
|
|
UNREACHABLE();
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
const StreamInfo* ServiceEvent::stream_info() const {
|
|
switch (kind()) {
|
|
case kVMUpdate:
|
|
return &Service::vm_stream;
|
|
|
|
case kIsolateStart:
|
|
case kIsolateRunnable:
|
|
case kIsolateExit:
|
|
case kIsolateUpdate:
|
|
case kIsolateReload:
|
|
case kIsolateSpawn:
|
|
case kServiceExtensionAdded:
|
|
return &Service::isolate_stream;
|
|
|
|
case kPauseStart:
|
|
case kPauseExit:
|
|
case kPauseBreakpoint:
|
|
case kPauseInterrupted:
|
|
case kPauseException:
|
|
case kPausePostRequest:
|
|
case kNone:
|
|
case kResume:
|
|
case kBreakpointAdded:
|
|
case kBreakpointResolved:
|
|
case kBreakpointRemoved:
|
|
case kInspect:
|
|
case kDebuggerSettingsUpdate:
|
|
return &Service::debug_stream;
|
|
|
|
case kGC:
|
|
return &Service::gc_stream;
|
|
|
|
case kLogging:
|
|
return &Service::logging_stream;
|
|
|
|
case kExtension:
|
|
return &Service::extension_stream;
|
|
|
|
case kTimelineEvents:
|
|
return &Service::timeline_stream;
|
|
|
|
case kEmbedder:
|
|
return NULL;
|
|
|
|
default:
|
|
UNREACHABLE();
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
const char* ServiceEvent::stream_id() const {
|
|
const StreamInfo* stream = stream_info();
|
|
if (stream == NULL) {
|
|
ASSERT(kind() == kEmbedder);
|
|
return embedder_stream_id_;
|
|
} else {
|
|
return stream->id();
|
|
}
|
|
}
|
|
|
|
void ServiceEvent::PrintJSON(JSONStream* js) const {
|
|
JSONObject jsobj(js);
|
|
PrintJSONHeader(&jsobj);
|
|
if (kind() == kIsolateReload) {
|
|
if (reload_error_ == NULL) {
|
|
jsobj.AddProperty("status", "success");
|
|
} else {
|
|
jsobj.AddProperty("status", "failure");
|
|
jsobj.AddProperty("reloadError", *(reload_error()));
|
|
}
|
|
}
|
|
if (kind() == kIsolateSpawn) {
|
|
ASSERT(spawn_token() != NULL);
|
|
jsobj.AddPropertyStr("spawnToken", *(spawn_token()));
|
|
if (spawn_error_ == NULL) {
|
|
jsobj.AddProperty("status", "success");
|
|
} else {
|
|
jsobj.AddProperty("status", "failure");
|
|
jsobj.AddPropertyStr("spawnError", *(spawn_error()));
|
|
}
|
|
}
|
|
if (kind() == kServiceExtensionAdded) {
|
|
ASSERT(extension_rpc_ != NULL);
|
|
jsobj.AddProperty("extensionRPC", extension_rpc_->ToCString());
|
|
}
|
|
if (kind() == kPauseBreakpoint) {
|
|
JSONArray jsarr(&jsobj, "pauseBreakpoints");
|
|
// TODO(rmacnak): If we are paused at more than one breakpoint,
|
|
// provide it here.
|
|
if (breakpoint() != NULL) {
|
|
jsarr.AddValue(breakpoint());
|
|
}
|
|
} else {
|
|
if (breakpoint() != NULL) {
|
|
jsobj.AddProperty("breakpoint", breakpoint());
|
|
}
|
|
}
|
|
if (kind() == kTimelineEvents) {
|
|
jsobj.AddProperty("timelineEvents", timeline_event_block_);
|
|
}
|
|
if (kind() == kDebuggerSettingsUpdate) {
|
|
JSONObject jssettings(&jsobj, "_debuggerSettings");
|
|
isolate()->debugger()->PrintSettingsToJSONObject(&jssettings);
|
|
}
|
|
if ((top_frame() != NULL) && Isolate::Current()->compilation_allowed()) {
|
|
JSONObject jsFrame(&jsobj, "topFrame");
|
|
top_frame()->PrintToJSONObject(&jsFrame);
|
|
intptr_t index = 0; // Avoid ambiguity in call to AddProperty.
|
|
jsFrame.AddProperty("index", index);
|
|
}
|
|
if (exception() != NULL) {
|
|
jsobj.AddProperty("exception", *(exception()));
|
|
}
|
|
if (at_async_jump()) {
|
|
jsobj.AddProperty("atAsyncSuspension", true);
|
|
}
|
|
if (inspectee() != NULL) {
|
|
jsobj.AddProperty("inspectee", *(inspectee()));
|
|
}
|
|
if (gc_stats() != NULL) {
|
|
jsobj.AddProperty("reason", Heap::GCReasonToString(gc_stats()->reason_));
|
|
isolate()->heap()->PrintToJSONObject(Heap::kNew, &jsobj);
|
|
isolate()->heap()->PrintToJSONObject(Heap::kOld, &jsobj);
|
|
}
|
|
if (bytes() != NULL) {
|
|
jsobj.AddPropertyBase64("bytes", bytes(), bytes_length());
|
|
}
|
|
if (kind() == kLogging) {
|
|
JSONObject logRecord(&jsobj, "logRecord");
|
|
logRecord.AddProperty64("sequenceNumber", log_record_.sequence_number);
|
|
logRecord.AddPropertyTimeMillis("time", log_record_.timestamp);
|
|
logRecord.AddProperty64("level", log_record_.level);
|
|
logRecord.AddProperty("loggerName", *(log_record_.name));
|
|
logRecord.AddProperty("message", *(log_record_.message));
|
|
logRecord.AddProperty("zone", *(log_record_.zone));
|
|
logRecord.AddProperty("error", *(log_record_.error));
|
|
logRecord.AddProperty("stackTrace", *(log_record_.stack_trace));
|
|
}
|
|
if (kind() == kExtension) {
|
|
js->AppendSerializedObject("extensionData",
|
|
extension_event_.event_data->ToCString());
|
|
}
|
|
}
|
|
|
|
void ServiceEvent::PrintJSONHeader(JSONObject* jsobj) const {
|
|
ASSERT(jsobj != NULL);
|
|
jsobj->AddProperty("type", "Event");
|
|
jsobj->AddProperty("kind", KindAsCString());
|
|
if (kind() == kExtension) {
|
|
ASSERT(extension_event_.event_kind != NULL);
|
|
jsobj->AddProperty("extensionKind",
|
|
extension_event_.event_kind->ToCString());
|
|
}
|
|
if (isolate() == NULL) {
|
|
jsobj->AddPropertyVM("vm");
|
|
} else {
|
|
jsobj->AddProperty("isolate", isolate());
|
|
}
|
|
ASSERT(timestamp_ != -1);
|
|
jsobj->AddPropertyTimeMillis("timestamp", timestamp_);
|
|
}
|
|
|
|
#endif // !PRODUCT
|
|
|
|
} // namespace dart
|