Reduce copying in sending service responses.

Add missing safepoint transitions.

Issue #27092
Likely contributes to Issue #27010

dart2js hello
Peak RSS at first Observatory visit -> Peak RSS after visiting timeline (with endless recorder)
Before 215M -> 321M
After 182M -> 233M

R=johnmccutchan@google.com

Review URL: https://codereview.chromium.org/2254543006 .
This commit is contained in:
Ryan Macnak 2016-08-18 10:34:50 -07:00
parent bfa70e6df2
commit d418dde65b
11 changed files with 133 additions and 55 deletions

View file

@ -51,7 +51,17 @@ class WebSocketClient extends Client {
return;
}
try {
socket.add(result);
if (result is String || result is Uint8List) {
socket.add(result); // String or binary message.
} else {
// String message as external Uint8List.
assert(result is List);
Uint8List cstring = result[0];
// TODO(rmacnak): cstring may be large. Add a way to pass an encoded
// string to a web socket that will be sent as a text message to avoid
// the space overhead of converting cstring to a Dart string.
socket.add(UTF8.decode(cstring));
}
} catch (_) {
print("Ignoring error posting over WebSocket.");
}
@ -79,14 +89,21 @@ class HttpRequestClient extends Client {
close();
}
void post(String result) {
void post(dynamic result) {
if (result == null) {
close();
return;
}
request.response..headers.contentType = jsonContentType
..write(result)
..close();
HttpResponse response = request.response;
response.headers.contentType = jsonContentType;
if (result is String) {
response.write(result);
} else {
assert(result is List);
Uint8List cstring = result[0]; // Already in UTF-8.
response.add(cstring);
}
response.close();
close();
}

View file

@ -9,6 +9,7 @@ import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:_vmservice';
part 'loader.dart';

View file

@ -32,8 +32,8 @@ void TextBuffer::Clear() {
}
const char* TextBuffer::Steal() {
const char* r = buf_;
char* TextBuffer::Steal() {
char* r = buf_;
buf_ = NULL;
buf_size_ = 0;
msg_len_ = 0;

View file

@ -34,7 +34,7 @@ class TextBuffer : ValueObject {
// Steal ownership of the buffer pointer.
// NOTE: TextBuffer is empty afterwards.
const char* Steal();
char* Steal();
private:
void EnsureCapacity(intptr_t len);

View file

@ -6127,7 +6127,7 @@ static bool StreamTraceEvents(Dart_StreamConsumer consumer,
// Steal output from JSONStream.
char* output = NULL;
intptr_t output_length = 0;
js->Steal(const_cast<const char**>(&output), &output_length);
js->Steal(&output, &output_length);
if (output_length < 3) {
// Empty JSON array.
free(output);

View file

@ -4,12 +4,14 @@
#include "platform/assert.h"
#include "include/dart_native_api.h"
#include "vm/dart_entry.h"
#include "vm/debugger.h"
#include "vm/json_stream.h"
#include "vm/message.h"
#include "vm/metrics.h"
#include "vm/object.h"
#include "vm/safepoint.h"
#include "vm/service.h"
#include "vm/service_event.h"
#include "vm/timeline.h"
@ -193,18 +195,19 @@ void JSONStream::PrintError(intptr_t code,
}
static uint8_t* allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) {
void* new_ptr = realloc(reinterpret_cast<void*>(ptr), new_size);
return reinterpret_cast<uint8_t*>(new_ptr);
}
void JSONStream::PostNullReply(Dart_Port port) {
PortMap::PostMessage(new Message(
port, Object::null(), Message::kNormalPriority));
}
static void Finalizer(void* isolate_callback_data,
Dart_WeakPersistentHandle handle,
void* buffer) {
free(buffer);
}
void JSONStream::PostReply() {
Dart_Port port = reply_port();
ASSERT(port != ILLEGAL_PORT);
@ -226,15 +229,33 @@ void JSONStream::PostReply() {
}
buffer_.AddChar('}');
const String& reply = String::Handle(String::New(ToCString()));
ASSERT(!reply.IsNull());
char* cstr;
intptr_t length;
Steal(&cstr, &length);
bool result;
{
TransitionVMToNative transition(Thread::Current());
Dart_CObject bytes;
bytes.type = Dart_CObject_kExternalTypedData;
bytes.value.as_external_typed_data.type = Dart_TypedData_kUint8;
bytes.value.as_external_typed_data.length = length;
bytes.value.as_external_typed_data.data = reinterpret_cast<uint8_t*>(cstr);
bytes.value.as_external_typed_data.peer = cstr;
bytes.value.as_external_typed_data.callback = Finalizer;
Dart_CObject* elements[1];
elements[0] = &bytes;
Dart_CObject message;
message.type = Dart_CObject_kArray;
message.value.as_array.length = 1;
message.value.as_array.values = elements;
result = Dart_PostCObject(port, &message);
}
if (!result) {
free(cstr);
}
uint8_t* data = NULL;
MessageWriter writer(&data, &allocator, false);
writer.WriteMessage(reply);
bool result = PortMap::PostMessage(new Message(port, data,
writer.BytesWritten(),
Message::kNormalPriority));
if (FLAG_trace_service) {
Isolate* isolate = Isolate::Current();
ASSERT(isolate != NULL);
@ -672,7 +693,7 @@ void JSONStream::PrintfProperty(const char* name, const char* format, ...) {
}
void JSONStream::Steal(const char** buffer, intptr_t* buffer_length) {
void JSONStream::Steal(char** buffer, intptr_t* buffer_length) {
ASSERT(buffer != NULL);
ASSERT(buffer_length != NULL);
*buffer_length = buffer_.length();

View file

@ -97,7 +97,7 @@ class JSONStream : ValueObject {
TextBuffer* buffer() { return &buffer_; }
const char* ToCString() { return buffer_.buf(); }
void Steal(const char** buffer, intptr_t* buffer_length);
void Steal(char** buffer, intptr_t* buffer_length);
void set_reply_port(Dart_Port port);

View file

@ -29,6 +29,7 @@
#include "vm/port.h"
#include "vm/profiler_service.h"
#include "vm/reusable_handles.h"
#include "vm/safepoint.h"
#include "vm/service_event.h"
#include "vm/service_isolate.h"
#include "vm/source_report.h"
@ -838,6 +839,7 @@ void Service::InvokeMethod(Isolate* I,
Thread* T = Thread::Current();
ASSERT(I == T->isolate());
ASSERT(I != NULL);
ASSERT(T->execution_state() == Thread::kThreadInVM);
ASSERT(!msg.IsNull());
ASSERT(msg.Length() == 6);
@ -1195,8 +1197,12 @@ void Service::EmbedderHandleMessage(EmbedderServiceHandler* handler,
Dart_ServiceRequestCallback callback = handler->callback();
ASSERT(callback != NULL);
const char* response = NULL;
bool success = callback(js->method(), js->param_keys(), js->param_values(),
js->num_params(), handler->user_data(), &response);
bool success;
{
TransitionVMToNative transition(Thread::Current());
success = callback(js->method(), js->param_keys(), js->param_values(),
js->num_params(), handler->user_data(), &response);
}
ASSERT(response != NULL);
if (!success) {
js->SetupError();

View file

@ -13,6 +13,7 @@
#include "vm/object_id_ring.h"
#include "vm/os.h"
#include "vm/port.h"
#include "vm/safepoint.h"
#include "vm/service.h"
#include "vm/unit_test.h"
@ -37,7 +38,6 @@ class ServiceTestMessageHandler : public MessageHandler {
}
// Parse the message.
String& response = String::Handle();
Object& response_obj = Object::Handle();
if (message->IsRaw()) {
response_obj = message->raw_obj();
@ -46,8 +46,20 @@ class ServiceTestMessageHandler : public MessageHandler {
MessageSnapshotReader reader(message->data(), message->len(), thread);
response_obj = reader.ReadObject();
}
response ^= response_obj.raw();
_msg = strdup(response.ToCString());
if (response_obj.IsString()) {
String& response = String::Handle();
response ^= response_obj.raw();
_msg = strdup(response.ToCString());
} else {
ASSERT(response_obj.IsArray());
Array& response_array = Array::Handle();
response_array ^= response_obj.raw();
ASSERT(response_array.Length() == 1);
ExternalTypedData& response = ExternalTypedData::Handle();
response ^= response_array.At(0);
_msg = strdup(reinterpret_cast<char*>(response.DataAddr(0)));
}
return kOK;
}
@ -115,6 +127,18 @@ static RawClass* GetClass(const Library& lib, const char* name) {
}
static void HandleIsolateMessage(Isolate* isolate, const Array& msg) {
TransitionNativeToVM transition(Thread::Current());
Service::HandleIsolateMessage(isolate, msg);
}
static void HandleRootMessage(const Array& message) {
TransitionNativeToVM transition(Thread::Current());
Service::HandleRootMessage(message);
}
TEST_CASE(Service_IsolateStickyError) {
const char* kScript =
"main() => throw 'HI THERE STICKY';\n";
@ -245,7 +269,7 @@ TEST_CASE(Service_Code) {
// Request an invalid code object.
service_msg =
Eval(lib, "[0, port, '0', 'getObject', ['objectId'], ['code/0']]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_SUBSTRING("\"error\"", handler.msg());
@ -255,7 +279,7 @@ TEST_CASE(Service_Code) {
"['objectId'], ['code/%" Px64"-%" Px "']]",
compile_timestamp,
entry);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_SUBSTRING("\"type\":\"Code\"", handler.msg());
{
@ -276,7 +300,7 @@ TEST_CASE(Service_Code) {
"['objectId'], ['code/%" Px64"-%" Px "']]",
compile_timestamp,
address);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_SUBSTRING("\"error\"", handler.msg());
@ -287,7 +311,7 @@ TEST_CASE(Service_Code) {
"['objectId'], ['code/%" Px64"-%" Px "']]",
compile_timestamp - 1,
address);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_SUBSTRING("\"error\"", handler.msg());
@ -296,7 +320,7 @@ TEST_CASE(Service_Code) {
service_msg = EvalF(lib, "[0, port, '0', 'getObject', "
"['objectId'], ['code/native-%" Px "']]",
address);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// TODO(turnidge): It is pretty broken to return an Instance here. Fix.
EXPECT_SUBSTRING("\"kind\":\"Null\"",
@ -306,7 +330,7 @@ TEST_CASE(Service_Code) {
service_msg = EvalF(lib, "[0, port, '0', 'getObject', ['objectId'], "
"['code/native%" Px "']]",
address);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_SUBSTRING("\"error\"", handler.msg());
}
@ -349,7 +373,7 @@ TEST_CASE(Service_TokenStream) {
// Fetch object.
service_msg = EvalF(lib, "[0, port, '0', 'getObject', "
"['objectId'], ['objects/%" Pd "']]", id);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// Check type.
@ -412,7 +436,7 @@ TEST_CASE(Service_PcDescriptors) {
// Fetch object.
service_msg = EvalF(lib, "[0, port, '0', 'getObject', "
"['objectId'], ['objects/%" Pd "']]", id);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// Check type.
EXPECT_SUBSTRING("\"type\":\"Object\"", handler.msg());
@ -474,7 +498,7 @@ TEST_CASE(Service_LocalVarDescriptors) {
// Fetch object.
service_msg = EvalF(lib, "[0, port, '0', 'getObject', "
"['objectId'], ['objects/%" Pd "']]", id);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// Check type.
EXPECT_SUBSTRING("\"type\":\"Object\"", handler.msg());
@ -534,7 +558,7 @@ TEST_CASE(Service_PersistentHandles) {
// Get persistent handles.
service_msg = Eval(lib, "[0, port, '0', '_getPersistentHandles', [], []]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// Look for a heart beat.
EXPECT_SUBSTRING("\"type\":\"_PersistentHandles\"", handler.msg());
@ -549,7 +573,7 @@ TEST_CASE(Service_PersistentHandles) {
// Get persistent handles (again).
service_msg = Eval(lib, "[0, port, '0', '_getPersistentHandles', [], []]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_SUBSTRING("\"type\":\"_PersistentHandles\"", handler.msg());
// Verify that old persistent handles are not present.
@ -595,7 +619,7 @@ TEST_CASE(Service_Address) {
"['address'], ['%" Px "']]"),
addr);
service_msg = Eval(lib, buf);
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_SUBSTRING(ref ? "\"type\":\"@Instance\"" :
"\"type\":\"Instance\"",
@ -606,7 +630,7 @@ TEST_CASE(Service_Address) {
// Expect null when no object is found.
service_msg = Eval(lib, "[0, port, '0', '_getObjectByAddress', "
"['address'], ['7']]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// TODO(turnidge): Should this be a ServiceException instead?
EXPECT_SUBSTRING("{\"type\":\"Sentinel\",\"kind\":\"Free\","
@ -667,12 +691,12 @@ TEST_CASE(Service_EmbedderRootHandler) {
Array& service_msg = Array::Handle();
service_msg = Eval(lib, "[0, port, '\"', 'alpha', [], []]");
Service::HandleRootMessage(service_msg);
HandleRootMessage(service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_STREQ("{\"jsonrpc\":\"2.0\", \"result\":alpha,\"id\":\"\\\"\"}",
handler.msg());
service_msg = Eval(lib, "[0, port, 1, 'beta', [], []]");
Service::HandleRootMessage(service_msg);
HandleRootMessage(service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_STREQ("{\"jsonrpc\":\"2.0\", \"error\":beta,\"id\":1}",
handler.msg());
@ -707,12 +731,12 @@ TEST_CASE(Service_EmbedderIsolateHandler) {
Array& service_msg = Array::Handle();
service_msg = Eval(lib, "[0, port, '0', 'alpha', [], []]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_STREQ("{\"jsonrpc\":\"2.0\", \"result\":alpha,\"id\":\"0\"}",
handler.msg());
service_msg = Eval(lib, "[0, port, '0', 'beta', [], []]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_STREQ("{\"jsonrpc\":\"2.0\", \"error\":beta,\"id\":\"0\"}",
handler.msg());
@ -747,21 +771,21 @@ TEST_CASE(Service_Profile) {
Array& service_msg = Array::Handle();
service_msg = Eval(lib, "[0, port, '0', '_getCpuProfile', [], []]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// Expect error (tags required).
EXPECT_SUBSTRING("\"error\"", handler.msg());
service_msg =
Eval(lib, "[0, port, '0', '_getCpuProfile', ['tags'], ['None']]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// Expect profile
EXPECT_SUBSTRING("\"type\":\"_CpuProfile\"", handler.msg());
service_msg =
Eval(lib, "[0, port, '0', '_getCpuProfile', ['tags'], ['Bogus']]");
Service::HandleIsolateMessage(isolate, service_msg);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// Expect error.
EXPECT_SUBSTRING("\"error\"", handler.msg());

View file

@ -1180,7 +1180,7 @@ void TimelineEventRecorder::WriteTo(const char* directory) {
// Steal output from JSONStream.
char* output = NULL;
intptr_t output_length = 0;
js.Steal(const_cast<const char**>(&output), &output_length);
js.Steal(&output, &output_length);
(*file_write)(output, output_length, file);
// Free the stolen output.
free(output);

View file

@ -330,6 +330,15 @@ class VMService extends MessageRouter {
return encodeSuccess(message);
}
static responseAsJson(portResponse) {
if (portResponse is String) {
return JSON.decode(portResponse);
} else {
var cstring = portResponse[0];
return JSON.fuse(UTF8).decode(cstring);
}
}
// TODO(johnmccutchan): Turn this into a command line tool that uses the
// service library.
Future<String> _getCrashDump(Message message) async {
@ -353,13 +362,13 @@ class VMService extends MessageRouter {
// Request VM.
var getVM = Uri.parse('getVM');
var getVmResponse = JSON.decode(
var getVmResponse = responseAsJson(
await new Message.fromUri(client, getVM).sendToVM());
responses[getVM.toString()] = getVmResponse['result'];
// Request command line flags.
var getFlagList = Uri.parse('getFlagList');
var getFlagListResponse = JSON.decode(
var getFlagListResponse = responseAsJson(
await new Message.fromUri(client, getFlagList).sendToVM());
responses[getFlagList.toString()] = getFlagListResponse['result'];
@ -369,13 +378,13 @@ class VMService extends MessageRouter {
var message = new Message.forIsolate(client, request, isolate);
// Decode the JSON and and insert it into the map. The map key
// is the request Uri.
var response = JSON.decode(await isolate.route(message));
var response = responseAsJson(await isolate.route(message));
responses[message.toUri().toString()] = response['result'];
}
// Dump the object id ring requests.
var message =
new Message.forIsolate(client, Uri.parse('_dumpIdZone'), isolate);
var response = JSON.decode(await isolate.route(message));
var response = responseAsJson(await isolate.route(message));
// Insert getObject requests into responses map.
for (var object in response['result']['objects']) {
final requestUri =