Skip to content

Commit

Permalink
Implement Runtime.releaseObject and releaseObjectGroup
Browse files Browse the repository at this point in the history
Summary:
Original Author: hoxy@meta.com
Original Git: 2e2483e
Original Reviewed By: dannysu, robhogan
Original Revision: D61280394

X-link: facebook/react-native#46032

Changelog: [Internal]

Implements the [`Runtime.releaseObject`](https://cdpstatus.reactnative.dev/devtools-protocol/tot/Runtime#method-releaseObject) and [`Runtime.releaseObjectGroup`](https://cdpstatus.reactnative.dev/devtools-protocol/tot/Runtime#method-releaseObjectGroup) CDP methods. These are used by CDT in a few places to release inspected objects (e.g. as part of the [popover](https://github.com/facebookexperimental/rn-chrome-devtools-frontend/blob/7b143e5d05a102c8c9cc7e282bab7b0751f75d61/front_end/panels/sources/DebuggerPlugin.ts#L766) feature), which is important to prevent memory leaks in long-running Fusebox sessions.

Reviewed By: fbmal7

Differential Revision: D64202109

fbshipit-source-id: 77baac1a3bbddd9d6c4e7348e5bfb38d847a55f0
  • Loading branch information
dannysu authored and facebook-github-bot committed Oct 11, 2024
1 parent 28ebb55 commit 8a44a33
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 11 deletions.
6 changes: 6 additions & 0 deletions API/hermes/cdp/CDPAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,12 @@ void CDPAgentImpl::DomainAgentsImpl::handleCommand(
} else if (method == "discardConsoleEntries") {
runtimeAgent_->discardConsoleEntries(
static_cast<m::runtime::DiscardConsoleEntriesRequest &>(*command));
} else if (method == "releaseObject") {
runtimeAgent_->releaseObject(
static_cast<m::runtime::ReleaseObjectRequest &>(*command));
} else if (method == "releaseObjectGroup") {
runtimeAgent_->releaseObjectGroup(
static_cast<m::runtime::ReleaseObjectGroupRequest &>(*command));
} else {
handled = false;
}
Expand Down
89 changes: 88 additions & 1 deletion API/hermes/cdp/MessageTypes.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
// @generated SignedSource<<1aad3e25cb0b790476e59a2c064e6ccb>>
// @generated SignedSource<<da57bd95099acd0d931a2ed451676ff8>>

#include "MessageTypes.h"

Expand Down Expand Up @@ -137,6 +137,9 @@ std::unique_ptr<Request> Request::fromJson(const std::string &str) {
{"Runtime.getProperties", tryMake<runtime::GetPropertiesRequest>},
{"Runtime.globalLexicalScopeNames",
tryMake<runtime::GlobalLexicalScopeNamesRequest>},
{"Runtime.releaseObject", tryMake<runtime::ReleaseObjectRequest>},
{"Runtime.releaseObjectGroup",
tryMake<runtime::ReleaseObjectGroupRequest>},
{"Runtime.runIfWaitingForDebugger",
tryMake<runtime::RunIfWaitingForDebuggerRequest>},
};
Expand Down Expand Up @@ -1896,6 +1899,90 @@ void runtime::GlobalLexicalScopeNamesRequest::accept(
handler.handle(*this);
}

runtime::ReleaseObjectRequest::ReleaseObjectRequest()
: Request("Runtime.releaseObject") {}

std::unique_ptr<runtime::ReleaseObjectRequest>
runtime::ReleaseObjectRequest::tryMake(const JSONObject *obj) {
std::unique_ptr<runtime::ReleaseObjectRequest> req =
std::make_unique<runtime::ReleaseObjectRequest>();
TRY_ASSIGN(req->id, obj, "id");
TRY_ASSIGN(req->method, obj, "method");

JSONValue *v = obj->get("params");
if (v == nullptr) {
return nullptr;
}
auto convertResult = valueFromJson<JSONObject *>(v);
if (!convertResult) {
return nullptr;
}
auto *params = *convertResult;
TRY_ASSIGN(req->objectId, params, "objectId");
return req;
}

JSONValue *runtime::ReleaseObjectRequest::toJsonVal(
JSONFactory &factory) const {
llvh::SmallVector<JSONFactory::Prop, 1> paramsProps;
put(paramsProps, "objectId", objectId, factory);

llvh::SmallVector<JSONFactory::Prop, 1> props;
put(props, "id", id, factory);
put(props, "method", method, factory);
put(props,
"params",
factory.newObject(paramsProps.begin(), paramsProps.end()),
factory);
return factory.newObject(props.begin(), props.end());
}

void runtime::ReleaseObjectRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}

runtime::ReleaseObjectGroupRequest::ReleaseObjectGroupRequest()
: Request("Runtime.releaseObjectGroup") {}

std::unique_ptr<runtime::ReleaseObjectGroupRequest>
runtime::ReleaseObjectGroupRequest::tryMake(const JSONObject *obj) {
std::unique_ptr<runtime::ReleaseObjectGroupRequest> req =
std::make_unique<runtime::ReleaseObjectGroupRequest>();
TRY_ASSIGN(req->id, obj, "id");
TRY_ASSIGN(req->method, obj, "method");

JSONValue *v = obj->get("params");
if (v == nullptr) {
return nullptr;
}
auto convertResult = valueFromJson<JSONObject *>(v);
if (!convertResult) {
return nullptr;
}
auto *params = *convertResult;
TRY_ASSIGN(req->objectGroup, params, "objectGroup");
return req;
}

JSONValue *runtime::ReleaseObjectGroupRequest::toJsonVal(
JSONFactory &factory) const {
llvh::SmallVector<JSONFactory::Prop, 1> paramsProps;
put(paramsProps, "objectGroup", objectGroup, factory);

llvh::SmallVector<JSONFactory::Prop, 1> props;
put(props, "id", id, factory);
put(props, "method", method, factory);
put(props,
"params",
factory.newObject(paramsProps.begin(), paramsProps.end()),
factory);
return factory.newObject(props.begin(), props.end());
}

void runtime::ReleaseObjectGroupRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}

runtime::RunIfWaitingForDebuggerRequest::RunIfWaitingForDebuggerRequest()
: Request("Runtime.runIfWaitingForDebugger") {}

Expand Down
29 changes: 28 additions & 1 deletion API/hermes/cdp/MessageTypes.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
// @generated SignedSource<<31f8b58788b6a7fcfcd45dd78a74c3f9>>
// @generated SignedSource<<cedec18119ebdcfb5d5e9616cf9b28bd>>

#pragma once

Expand Down Expand Up @@ -79,6 +79,8 @@ struct InternalPropertyDescriptor;
struct ObjectPreview;
struct PropertyDescriptor;
struct PropertyPreview;
struct ReleaseObjectGroupRequest;
struct ReleaseObjectRequest;
struct RemoteObject;
using RemoteObjectId = std::string;
struct RunIfWaitingForDebuggerRequest;
Expand Down Expand Up @@ -161,6 +163,8 @@ struct RequestHandler {
virtual void handle(const runtime::GetHeapUsageRequest &req) = 0;
virtual void handle(const runtime::GetPropertiesRequest &req) = 0;
virtual void handle(const runtime::GlobalLexicalScopeNamesRequest &req) = 0;
virtual void handle(const runtime::ReleaseObjectRequest &req) = 0;
virtual void handle(const runtime::ReleaseObjectGroupRequest &req) = 0;
virtual void handle(const runtime::RunIfWaitingForDebuggerRequest &req) = 0;
};

Expand Down Expand Up @@ -204,6 +208,8 @@ struct NoopRequestHandler : public RequestHandler {
void handle(const runtime::GetHeapUsageRequest &req) override {}
void handle(const runtime::GetPropertiesRequest &req) override {}
void handle(const runtime::GlobalLexicalScopeNamesRequest &req) override {}
void handle(const runtime::ReleaseObjectRequest &req) override {}
void handle(const runtime::ReleaseObjectGroupRequest &req) override {}
void handle(const runtime::RunIfWaitingForDebuggerRequest &req) override {}
};

Expand Down Expand Up @@ -923,6 +929,27 @@ struct runtime::GlobalLexicalScopeNamesRequest : public Request {
std::optional<runtime::ExecutionContextId> executionContextId;
};

struct runtime::ReleaseObjectRequest : public Request {
ReleaseObjectRequest();
static std::unique_ptr<ReleaseObjectRequest> tryMake(const JSONObject *obj);

JSONValue *toJsonVal(JSONFactory &factory) const override;
void accept(RequestHandler &handler) const override;

runtime::RemoteObjectId objectId{};
};

struct runtime::ReleaseObjectGroupRequest : public Request {
ReleaseObjectGroupRequest();
static std::unique_ptr<ReleaseObjectGroupRequest> tryMake(
const JSONObject *obj);

JSONValue *toJsonVal(JSONFactory &factory) const override;
void accept(RequestHandler &handler) const override;

std::string objectGroup;
};

struct runtime::RunIfWaitingForDebuggerRequest : public Request {
RunIfWaitingForDebuggerRequest();
static std::unique_ptr<RunIfWaitingForDebuggerRequest> tryMake(
Expand Down
14 changes: 8 additions & 6 deletions API/hermes/cdp/RemoteObjectsTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,19 @@ std::string RemoteObjectsTable::getObjectGroup(const std::string &objId) const {
return it->second;
}

void RemoteObjectsTable::releaseObject(int64_t id) {
bool RemoteObjectsTable::releaseObject(int64_t id) {
if (::isScopeId(id)) {
scopes_.erase(id);
} else if (isValueId(id)) {
values_.erase(id);
return scopes_.erase(id) != 0;
}
if (isValueId(id)) {
return values_.erase(id) != 0;
}
return false;
}

void RemoteObjectsTable::releaseObject(const std::string &objId) {
bool RemoteObjectsTable::releaseObject(const std::string &objId) {
int64_t id = toId(objId);
releaseObject(id);
return releaseObject(id);
}

void RemoteObjectsTable::releaseObjectGroup(const std::string &objectGroup) {
Expand Down
6 changes: 3 additions & 3 deletions API/hermes/cdp/RemoteObjectsTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ class RemoteObjectsTable {

/**
* Removes the scope or JSI value backed by the provided object ID from the
* table.
* table. \return true if the object was removed, false if it was not found.
*/
void releaseObject(const std::string &objId);
bool releaseObject(const std::string &objId);

/**
* Removes all objects that are part of the provided object group from the
Expand All @@ -112,7 +112,7 @@ class RemoteObjectsTable {
void releaseObjectGroup(const std::string &objectGroup);

private:
void releaseObject(int64_t id);
bool releaseObject(int64_t id);

int64_t scopeId_ = -1;
int64_t valueId_ = 1;
Expand Down
22 changes: 22 additions & 0 deletions API/hermes/cdp/RuntimeDomainAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,28 @@ void RuntimeDomainAgent::consoleAPICalled(
sendNotificationToClient(note);
}

void RuntimeDomainAgent::releaseObject(
const m::runtime::ReleaseObjectRequest &req) {
// Allow this to be used when domain is not enabled to match V8 behavior

if (objTable_->releaseObject(req.objectId)) {
sendResponseToClient(m::makeOkResponse(req.id));
} else {
sendResponseToClient(m::makeErrorResponse(
req.id,
m::ErrorCode::ServerError,
"Could not find an object with the given ID"));
}
}

void RuntimeDomainAgent::releaseObjectGroup(
const m::runtime::ReleaseObjectGroupRequest &req) {
// Allow this to be used when domain is not enabled to match V8 behavior.

objTable_->releaseObjectGroup(req.objectGroup);
sendResponseToClient(m::makeOkResponse(req.id));
}

RuntimeDomainAgent::Helpers::Helpers(jsi::Runtime &runtime)
: // TODO(moti): The best place to read and cache these helpers is in
// CDPDebugAPI, before user code ever runs.
Expand Down
6 changes: 6 additions & 0 deletions API/hermes/cdp/RuntimeDomainAgent.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ class RuntimeDomainAgent : public DomainAgent {
void callFunctionOn(const m::runtime::CallFunctionOnRequest &req);
/// Dispatches a Runtime.consoleAPICalled notification
void consoleAPICalled(const ConsoleMessage &message, bool isBuffered);
/// Handles Runtime.releaseObject request
/// @cdp Runtime.releaseObject Allowed even if domain is not enabled.
void releaseObject(const m::runtime::ReleaseObjectRequest &req);
/// Handles Runtime.releaseObjectGroup request
/// @cdp Runtime.releaseObjectGroup Allowed even if domain is not enabled.
void releaseObjectGroup(const m::runtime::ReleaseObjectGroupRequest &req);

private:
struct Helpers {
Expand Down
2 changes: 2 additions & 0 deletions API/hermes/cdp/tools/message_types.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ Runtime.runIfWaitingForDebugger
Runtime.globalLexicalScopeNames
Runtime.compileScript
Runtime.inspectRequested
Runtime.releaseObject
Runtime.releaseObjectGroup

0 comments on commit 8a44a33

Please sign in to comment.