Skip to content

Commit 24d0d44

Browse files
motiz88meta-codesync[bot]
authored andcommitted
Fix __DEBUGGER_SESSION_OBSERVER__ for concurrent sessions (#54083)
Summary: Pull Request resolved: #54083 Changelog: [Internal] Fixes a bug in `__DEBUGGER_SESSION_OBSERVER__` where the wrong state could be reported if more than one session existed. Now we reuse the accurate domain status tracking mechanism added in D83847356. Reviewed By: hoxyq Differential Revision: D84007326 fbshipit-source-id: e7f2c33286aa69c3bd9d5662d35dad579ca3c770
1 parent 5c7cbeb commit 24d0d44

5 files changed

Lines changed: 152 additions & 41 deletions

File tree

packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@ RuntimeAgent::RuntimeAgent(
2929
}
3030
}
3131

32-
if (sessionState_.isRuntimeDomainEnabled &&
33-
sessionState_.isLogDomainEnabled) {
34-
targetController_.notifyDebuggerSessionCreated();
32+
if (sessionState_.isRuntimeDomainEnabled) {
33+
targetController_.notifyDomainStateChanged(
34+
RuntimeTargetController::Domain::Runtime, true, *this);
35+
}
36+
37+
if (sessionState_.isLogDomainEnabled) {
38+
targetController_.notifyDomainStateChanged(
39+
RuntimeTargetController::Domain::Log, true, *this);
3540
}
3641

3742
if (sessionState_.isNetworkDomainEnabled) {
@@ -60,20 +65,20 @@ bool RuntimeAgent::handleRequest(const cdp::PreparsedRequest& req) {
6065
// We are not responding to this request, just processing a side effect.
6166
return false;
6267
}
63-
if (req.method == "Runtime.enable" && sessionState_.isLogDomainEnabled) {
64-
targetController_.notifyDebuggerSessionCreated();
65-
}
66-
if (req.method == "Log.enable" && sessionState_.isRuntimeDomainEnabled) {
67-
targetController_.notifyDebuggerSessionCreated();
68-
}
69-
if (req.method == "Runtime.disable" && sessionState_.isLogDomainEnabled) {
70-
targetController_.notifyDebuggerSessionDestroyed();
71-
}
72-
if (req.method == "Log.disable" && sessionState_.isRuntimeDomainEnabled) {
73-
targetController_.notifyDebuggerSessionDestroyed();
74-
}
75-
76-
if (req.method == "Network.enable" || req.method == "Network.disable") {
68+
if (req.method == "Runtime.enable" || req.method == "Runtime.disable") {
69+
targetController_.notifyDomainStateChanged(
70+
RuntimeTargetController::Domain::Runtime,
71+
sessionState_.isRuntimeDomainEnabled,
72+
*this);
73+
// Fall through
74+
} else if (req.method == "Log.enable" || req.method == "Log.disable") {
75+
targetController_.notifyDomainStateChanged(
76+
RuntimeTargetController::Domain::Log,
77+
sessionState_.isLogDomainEnabled,
78+
*this);
79+
// Fall through
80+
} else if (
81+
req.method == "Network.enable" || req.method == "Network.disable") {
7782
targetController_.notifyDomainStateChanged(
7883
RuntimeTargetController::Domain::Network,
7984
sessionState_.isNetworkDomainEnabled,
@@ -120,9 +125,13 @@ RuntimeAgent::ExportedState RuntimeAgent::getExportedState() {
120125
}
121126

122127
RuntimeAgent::~RuntimeAgent() {
123-
if (sessionState_.isRuntimeDomainEnabled &&
124-
sessionState_.isLogDomainEnabled) {
125-
targetController_.notifyDebuggerSessionDestroyed();
128+
if (sessionState_.isRuntimeDomainEnabled) {
129+
targetController_.notifyDomainStateChanged(
130+
RuntimeTargetController::Domain::Runtime, false, *this);
131+
}
132+
if (sessionState_.isLogDomainEnabled) {
133+
targetController_.notifyDomainStateChanged(
134+
RuntimeTargetController::Domain::Log, false, *this);
126135
}
127136
if (sessionState_.isNetworkDomainEnabled) {
128137
targetController_.notifyDomainStateChanged(

packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,38 @@ void RuntimeTarget::notifyDomainStateChanged(
179179
Domain domain,
180180
bool enabled,
181181
const RuntimeAgent& notifyingAgent) {
182+
bool runtimeAndLogStatusBefore = false, runtimeAndLogStatusAfter = false;
183+
if (domain == Domain::Log || domain == Domain::Runtime) {
184+
runtimeAndLogStatusBefore =
185+
agentsByEnabledDomain_[Domain::Runtime].contains(&notifyingAgent) &&
186+
agentsByEnabledDomain_[Domain::Log].contains(&notifyingAgent);
187+
}
188+
182189
if (enabled) {
183190
agentsByEnabledDomain_[domain].insert(&notifyingAgent);
184191
} else {
185192
agentsByEnabledDomain_[domain].erase(&notifyingAgent);
186193
}
187194
threadSafeDomainStatus_[domain] = !agentsByEnabledDomain_[domain].empty();
195+
196+
if (domain == Domain::Log || domain == Domain::Runtime) {
197+
runtimeAndLogStatusAfter =
198+
agentsByEnabledDomain_[Domain::Runtime].contains(&notifyingAgent) &&
199+
agentsByEnabledDomain_[Domain::Log].contains(&notifyingAgent);
200+
201+
if (runtimeAndLogStatusBefore != runtimeAndLogStatusAfter) {
202+
if (runtimeAndLogStatusAfter) {
203+
if (++agentsWithRuntimeAndLogDomainsEnabled_ == 1) {
204+
emitDebuggerSessionCreated();
205+
}
206+
} else {
207+
assert(agentsWithRuntimeAndLogDomainsEnabled_ > 0);
208+
if (--agentsWithRuntimeAndLogDomainsEnabled_ == 0) {
209+
emitDebuggerSessionDestroyed();
210+
}
211+
}
212+
}
213+
}
188214
}
189215

190216
bool RuntimeTarget::isDomainEnabled(Domain domain) const {
@@ -199,14 +225,6 @@ void RuntimeTargetController::installBindingHandler(
199225
target_.installBindingHandler(bindingName);
200226
}
201227

202-
void RuntimeTargetController::notifyDebuggerSessionCreated() {
203-
target_.emitDebuggerSessionCreated();
204-
}
205-
206-
void RuntimeTargetController::notifyDebuggerSessionDestroyed() {
207-
target_.emitDebuggerSessionDestroyed();
208-
}
209-
210228
void RuntimeTargetController::enableSamplingProfiler() {
211229
target_.enableSamplingProfiler();
212230
}

packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class RuntimeTargetDelegate {
124124
*/
125125
class RuntimeTargetController {
126126
public:
127-
enum class Domain { Network, kMaxValue };
127+
enum class Domain { Network, Runtime, Log, kMaxValue };
128128

129129
explicit RuntimeTargetController(RuntimeTarget& target);
130130

@@ -135,18 +135,6 @@ class RuntimeTargetController {
135135
*/
136136
void installBindingHandler(const std::string& bindingName);
137137

138-
/**
139-
* Notifies the target to emit some message that debugger session is
140-
* created.
141-
*/
142-
void notifyDebuggerSessionCreated();
143-
144-
/**
145-
* Notifies the target to emit some message that debugger session is
146-
* destroyed.
147-
*/
148-
void notifyDebuggerSessionDestroyed();
149-
150138
/**
151139
* Notifies the target that an agent has received an enable or disable
152140
* message for the given domain.
@@ -294,6 +282,12 @@ class JSINSPECTOR_EXPORT RuntimeTarget
294282
*/
295283
EnumArray<Domain, std::atomic<bool>> threadSafeDomainStatus_{};
296284

285+
/**
286+
* The number of agents that currently have both the Log and Runtime domains
287+
* enabled.
288+
*/
289+
size_t agentsWithRuntimeAndLogDomainsEnabled_{0};
290+
297291
/**
298292
* This TracingAgent is owned by the InstanceTracingAgent, both are bound to
299293
* the lifetime of their corresponding targets and the lifetime of the tracing

packages/react-native/ReactCommon/jsinspector-modern/tests/DebuggerSessionObserverTest.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,48 @@ TEST_F(
148148
EXPECT_FALSE(eval("latestStatus").asBool());
149149
}
150150

151+
TEST_F(DebuggerSessionObserverTest, testTwoConcurrentConnections) {
152+
connect();
153+
154+
auto secondary = connectSecondary();
155+
156+
EXPECT_CALL(fromPage(), onMessage(_)).Times(AnyNumber());
157+
EXPECT_CALL(secondary.fromPage(), onMessage(_)).Times(AnyNumber());
158+
159+
// No domains enabled to start with
160+
EXPECT_FALSE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
161+
162+
toPage_->sendMessage(R"({"id": 1, "method": "Runtime.enable"})");
163+
// Primary: Runtime, Secondary: None
164+
EXPECT_FALSE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
165+
166+
secondary.toPage().sendMessage(R"({"id": 2, "method": "Log.enable"})");
167+
// Primary: Runtime, Secondary: Log
168+
EXPECT_FALSE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
169+
170+
secondary.toPage().sendMessage(R"({"id": 3, "method": "Runtime.enable"})");
171+
// Primary: Runtime, Secondary: [Runtime, Log]
172+
EXPECT_TRUE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
173+
174+
toPage_->sendMessage(R"({"id": 4, "method": "Log.enable"})");
175+
// Primary: [Runtime, Log], Secondary: [Runtime, Log]
176+
EXPECT_TRUE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
177+
178+
toPage_->sendMessage(R"({"id": 5, "method": "Runtime.disable"})");
179+
// Primary: Log, Secondary: [Runtime, Log]
180+
EXPECT_TRUE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
181+
182+
secondary.toPage().sendMessage(R"({"id": 6, "method": "Log.disable"})");
183+
// Primary: Log, Secondary: Runtime
184+
EXPECT_FALSE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
185+
186+
secondary.toPage().sendMessage(R"({"id": 7, "method": "Runtime.disable"})");
187+
// Primary: Log, Secondary: None
188+
EXPECT_FALSE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
189+
190+
toPage_->sendMessage(R"({"id": 8, "method": "Log.disable"})");
191+
// Primary: None, Secondary: None
192+
EXPECT_FALSE(eval("__DEBUGGER_SESSION_OBSERVER__.hasActiveSession").asBool());
193+
}
194+
151195
} // namespace facebook::react::jsinspector_modern

packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,52 @@ class JsiIntegrationPortableTestBase : public ::testing::Test,
164164
return engineAdapter_->getRuntime();
165165
}
166166

167+
class SecondaryConnection {
168+
public:
169+
SecondaryConnection(
170+
std::unique_ptr<ILocalConnection> toPage,
171+
JsiIntegrationPortableTestBase<EngineAdapter, Executor>& test,
172+
size_t remoteConnectionIndex)
173+
: toPage_(std::move(toPage)),
174+
remoteConnectionIndex_(remoteConnectionIndex),
175+
test_(test) {}
176+
177+
ILocalConnection& toPage() {
178+
return *toPage_;
179+
}
180+
181+
MockRemoteConnection& fromPage() {
182+
return *test_.remoteConnections_[remoteConnectionIndex_];
183+
}
184+
185+
private:
186+
std::unique_ptr<ILocalConnection> toPage_;
187+
size_t remoteConnectionIndex_;
188+
JsiIntegrationPortableTestBase<EngineAdapter, Executor>& test_;
189+
};
190+
191+
SecondaryConnection connectSecondary() {
192+
auto toPage = page_->connect(remoteConnections_.make_unique());
193+
194+
SecondaryConnection secondary{
195+
std::move(toPage), *this, remoteConnections_.objectsVended() - 1};
196+
197+
using namespace ::testing;
198+
// Default to ignoring console messages originating inside the backend.
199+
EXPECT_CALL(
200+
secondary.fromPage(),
201+
onMessage(JsonParsed(AllOf(
202+
AtJsonPtr("/method", "Runtime.consoleAPICalled"),
203+
AtJsonPtr("/params/context", "main#InstanceAgent")))))
204+
.Times(AnyNumber());
205+
206+
// We'll always get an onDisconnect call when we tear
207+
// down the test. Expect it in order to satisfy the strict mock.
208+
EXPECT_CALL(secondary.fromPage(), onDisconnect());
209+
210+
return secondary;
211+
}
212+
167213
std::shared_ptr<HostTarget> page_;
168214
InstanceTarget* instance_{};
169215
RuntimeTarget* runtimeTarget_{};

0 commit comments

Comments
 (0)