Skip to content

Commit

Permalink
Buffer console messages
Browse files Browse the repository at this point in the history
Summary:
Buffer console messages. When messages are generated,
add them to the console message storage. When a new
client enables the `Runtime` domain, send any buffered
messages.

Reviewed By: dannysu

Differential Revision: D54028594

fbshipit-source-id: 4f0bf6581c23a3813a460e2eb5cd0c65b74c912c
  • Loading branch information
Matt Blagden authored and facebook-github-bot committed Feb 26, 2024
1 parent 865857b commit e6f3178
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 0 deletions.
3 changes: 3 additions & 0 deletions API/hermes/cdp/CDPAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class CDPAgentImpl {
int32_t executionContextID_;
HermesRuntime &runtime_;
debugger::AsyncDebuggerAPI &asyncDebuggerAPI_;
ConsoleMessageStorage &consoleMessageStorage_;
ConsoleMessageDispatcher &consoleMessageDispatcher_;

/// Callback function for sending CDP response back. Same as the one in
Expand Down Expand Up @@ -167,6 +168,7 @@ CDPAgentImpl::DomainAgents::DomainAgents(
: executionContextID_(executionContextID),
runtime_(cdpDebugAPI.runtime()),
asyncDebuggerAPI_(cdpDebugAPI.asyncDebuggerAPI()),
consoleMessageStorage_(cdpDebugAPI.consoleMessageStorage_),
consoleMessageDispatcher_(cdpDebugAPI.consoleMessageDispatcher_),
messageCallback_(std::move(messageCallback)),
objTable_(std::make_shared<RemoteObjectsTable>()) {}
Expand All @@ -183,6 +185,7 @@ void CDPAgentImpl::DomainAgents::initialize() {
runtime_,
messageCallback_,
objTable_,
consoleMessageStorage_,
consoleMessageDispatcher_);
profilerAgent_ = std::make_unique<ProfilerDomainAgent>(
executionContextID_, runtime_, messageCallback_, objTable_);
Expand Down
1 change: 1 addition & 0 deletions API/hermes/cdp/CDPDebugAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ CDPDebugAPI::CDPDebugAPI(HermesRuntime &runtime, size_t maxCachedMessages)

void CDPDebugAPI::addConsoleMessage(ConsoleMessage message) {
consoleMessageDispatcher_.deliverMessage(message);
consoleMessageStorage_.addMessage(std::move(message));
}

} // namespace cdp
Expand Down
7 changes: 7 additions & 0 deletions API/hermes/cdp/ConsoleMessage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ size_t ConsoleMessageStorage::discarded() const {
return numConsoleMessagesDiscardedFromCache_;
}

std::optional<double> ConsoleMessageStorage::oldestTimestamp() const {
if (consoleMessageCache_.size() == 0) {
return std::nullopt;
}
return consoleMessageCache_.front().timestamp;
}

} // namespace cdp
} // namespace hermes
} // namespace facebook
2 changes: 2 additions & 0 deletions API/hermes/cdp/ConsoleMessage.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifndef HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H
#define HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H

#include <optional>
#include <queue>
#include <unordered_map>

Expand Down Expand Up @@ -62,6 +63,7 @@ class ConsoleMessageStorage {

const std::deque<ConsoleMessage> &messages() const;
size_t discarded() const;
std::optional<double> oldestTimestamp() const;

private:
/// Maximum number of messages to cache.
Expand Down
28 changes: 28 additions & 0 deletions API/hermes/cdp/RuntimeDomainAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,14 @@ RuntimeDomainAgent::RuntimeDomainAgent(
HermesRuntime &runtime,
SynchronizedOutboundCallback messageCallback,
std::shared_ptr<RemoteObjectsTable> objTable,
ConsoleMessageStorage &consoleMessageStorage,
ConsoleMessageDispatcher &consoleMessageDispatcher)
: DomainAgent(
executionContextID,
std::move(messageCallback),
std::move(objTable)),
runtime_(runtime),
consoleMessageStorage_(consoleMessageStorage),
consoleMessageDispatcher_(consoleMessageDispatcher),
enabled_(false) {
consoleMessageRegistration_ = consoleMessageDispatcher_.subscribe(
Expand All @@ -287,6 +289,32 @@ void RuntimeDomainAgent::enable(const m::runtime::EnableRequest &req) {
// Enable
enabled_ = true;
sendResponseToClient(m::makeOkResponse(req.id));

// Send any buffered console messages.
size_t numConsoleMessagesDiscardedFromCache =
consoleMessageStorage_.discarded();

if (numConsoleMessagesDiscardedFromCache != 0) {
std::ostringstream oss;
oss << "Only limited number of console messages can be cached. "
<< numConsoleMessagesDiscardedFromCache
<< (numConsoleMessagesDiscardedFromCache == 1 ? " message was"
: " messages were")

<< " discarded at the beginning.";
jsi::Value arg = jsi::String::createFromAscii(runtime_, oss.str());
std::vector<jsi::Value> args;
args.push_back(std::move(arg));

consoleAPICalled(ConsoleMessage(
*consoleMessageStorage_.oldestTimestamp() - 0.1,
ConsoleAPIType::kWarning,
std::move(args)));
}

for (auto &message : consoleMessageStorage_.messages()) {
consoleAPICalled(message);
}
}

void RuntimeDomainAgent::disable(const m::runtime::DisableRequest &req) {
Expand Down
2 changes: 2 additions & 0 deletions API/hermes/cdp/RuntimeDomainAgent.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class RuntimeDomainAgent : public DomainAgent {
HermesRuntime &runtime,
SynchronizedOutboundCallback messageCallback,
std::shared_ptr<RemoteObjectsTable> objTable,
ConsoleMessageStorage &consoleMessageStorage,
ConsoleMessageDispatcher &consoleMessageDispatcher);
~RuntimeDomainAgent();

Expand Down Expand Up @@ -66,6 +67,7 @@ class RuntimeDomainAgent : public DomainAgent {
bool generatePreview);

HermesRuntime &runtime_;
ConsoleMessageStorage &consoleMessageStorage_;
ConsoleMessageDispatcher &consoleMessageDispatcher_;

/// Whether Runtime.enable was received and wasn't disabled by receiving
Expand Down
78 changes: 78 additions & 0 deletions unittests/API/CDPAgentTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2287,4 +2287,82 @@ TEST_F(CDPAgentTest, ConsoleLog) {
{"__proto__", PropInfo("object")}});
}

TEST_F(CDPAgentTest, ConsoleBuffer) {
int msgId = 1;

constexpr int kExpectedMaxBufferSize = 1000;
constexpr int kNumLogsToTest = kExpectedMaxBufferSize * 2;

// Generate console messages on the runtime thread
waitFor<bool>([this, kNumLogsToTest](auto promise) {
runtimeThread_->add([this, promise, kNumLogsToTest]() {
for (int i = 0; i < kNumLogsToTest; i++) {
jsi::Value value =
jsi::String::createFromUtf8(*runtime_, std::to_string(i));
std::vector<jsi::Value> args;
args.push_back(std::move(value));
cdpDebugAPI_->addConsoleMessage(
ConsoleMessage{0.0, ConsoleAPIType::kLog, std::move(args)});
}

promise->set_value(true);
});
});

bool receivedWarning = false;
std::array<bool, kExpectedMaxBufferSize> received;

// Test for repeated connection by sending Runtime.enable multiple times. It's
// expected that the message cache is always kept around and provided to the
// frontend each time.
for (int numConnect = 0; numConnect < 2; numConnect++) {
receivedWarning = false;
received.fill(false);

sendAndCheckResponse("Runtime.enable", msgId++);

// Loop for 1 iteration more than kExpectedMaxBufferSize because there is a
// warning message given when buffer is exceeded
for (size_t i = 0; i < kExpectedMaxBufferSize + 1; i++) {
auto note = expectNotification("Runtime.consoleAPICalled");
EXPECT_EQ(
jsonScope_.getString(note, {"params", "args", "0", "type"}),
"string");

size_t argCount = jsonScope_.getArray(note, {"params", "args"})->size();
EXPECT_EQ(argCount, 1);

std::string type = jsonScope_.getString(note, {"params", "type"});
std::string value =
jsonScope_.getString(note, {"params", "args", "0", "value"});
try {
// Verify that the latest kExpectedMaxBufferSize number of logs are
// emitted
int nthLog = std::stoi(value);
EXPECT_GT(nthLog, kExpectedMaxBufferSize - 1);
EXPECT_LT(nthLog, kNumLogsToTest);
EXPECT_EQ(type, "log");
received[nthLog % kExpectedMaxBufferSize] = true;
} catch (const std::exception &e) {
EXPECT_EQ(type, "warning");
EXPECT_NE(value.find("discarded"), std::string::npos);
receivedWarning = true;
}
}

// Make sure no more log messages arrive
expectNothing();

// Ensure everything was expected
for (size_t i = 0; i < kExpectedMaxBufferSize; i++) {
EXPECT_TRUE(received[i]);
}
EXPECT_TRUE(receivedWarning);

// Disable the runtime so it can be enabled again in the next iteration of
// the loop
sendAndCheckResponse("Runtime.disable", msgId++);
}
}

#endif // HERMES_ENABLE_DEBUGGER

0 comments on commit e6f3178

Please sign in to comment.