Skip to content

Commit d4468fb

Browse files
motiz88facebook-github-bot
authored andcommitted
RuntimeTarget refactor - Add this-scoped async executors to all Targets
Summary: Changelog: [Internal] # This diff 1. Provides all Targets with an `executorFromThis()` method, which can be used from within a Target to access a *`this`-scoped main thread executor* = a `std::function` that will execute a callback asynchronously iff the current Target isn't destroyed first. 2. Refactors how (all) Target objects are constructed and retained, from a plain constructor to `static shared_ptr create()`. This is because `executorFromThis()` relies internally on `enable_shared_from_this` plus two-phase construction to populate the executor. 3. Creates utilities for deriving scoped executors from other executors and `shared_ptr`s. The concept is very much like `RuntimeExecutor` in reverse: the #1 use case is moving from the JS thread back to the main thread - where "main thread" is defined loosely as "anywhere it's legal to call methods on Target/Agent objects, access session state, etc". The actual dispatching mechanism is left up to the owner of each `PageTarget` object; for now we only have an iOS integration, where we use `RCTExecuteOnMainQueue`. Coupling the ownership/lifetime semantics with task scheduling is helpful, because it avoids the footgun of accidentally/nondeterministically moving `shared_ptr`s (and destructors!) to a different thread/queue . # This stack I'm refactoring the way the Runtime concept works in the modern CDP backend to bring it in line with the Page/Instance concepts. Overall, this will let us: * Integrate with engines that require us to instantiate a shared Target-like object (e.g. Hermes AsyncDebuggingAPI) in addition to an per-session Agent-like object. * Access JSI in a CDP context (both at target setup/teardown time and during a CDP session) to implement our own engine-agnostic functionality (`console` interception, `Runtime.addBinding`, etc). * Manage CDP execution contexts natively in RN, and (down the line) enable first-class debugging support for multiple Runtimes in an Instance. The core diffs in this stack: * ~~Introduce a `RuntimeTarget` class similar to `{Page,Instance}Target`. ~~ * ~~Make runtime registration explicit (`InstanceTarget::registerRuntime` similar to `PageTarget::registerInstance`). ~~ * ~~Rename the existing `RuntimeAgent` interface to `RuntimeAgentDelegate`.~~ * ~~Create a new concrete `RuntimeAgent` class similar to `{Page,Instance}Agent`.~~ * ~~Provide `RuntimeTarget` and `RuntimeAgent` with primitives for safe JSI access, namely a `RuntimeExecutor` for scheduling work on the JS thread.~~ * Provide RuntimeTarget with mechanism for scheduling work on the "main" thread from the JS thread, for when we need to do more than just send a CDP message (which we can already do with the thread-safe `FrontendChannel`) in response to a JS event. *← This diff* ## Architecture diagrams Before this stack: https://pxl.cl/4h7m0 After this stack: https://pxl.cl/4h7m7 Reviewed By: hoxyq Differential Revision: D53356953 fbshipit-source-id: 152c784eb64e9b217fc2966743b33f61bd8fd97e
1 parent b3a7a13 commit d4468fb

File tree

11 files changed

+253
-54
lines changed

11 files changed

+253
-54
lines changed

packages/react-native/React/Base/RCTBridge.mm

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ @implementation RCTBridge {
209209
NSURL *_delegateBundleURL;
210210

211211
std::unique_ptr<RCTBridgePageTargetDelegate> _inspectorPageDelegate;
212-
std::unique_ptr<facebook::react::jsinspector_modern::PageTarget> _inspectorTarget;
212+
std::shared_ptr<facebook::react::jsinspector_modern::PageTarget> _inspectorTarget;
213213
std::optional<int> _inspectorPageId;
214214
}
215215

@@ -412,7 +412,12 @@ - (void)setUp
412412

413413
auto &inspectorFlags = facebook::react::jsinspector_modern::InspectorFlags::getInstance();
414414
if (inspectorFlags.getEnableModernCDPRegistry() && !_inspectorPageId.has_value()) {
415-
_inspectorTarget = std::make_unique<facebook::react::jsinspector_modern::PageTarget>(*_inspectorPageDelegate);
415+
_inspectorTarget =
416+
facebook::react::jsinspector_modern::PageTarget::create(*_inspectorPageDelegate, [](auto callback) {
417+
RCTExecuteOnMainQueue(^{
418+
callback();
419+
});
420+
});
416421
__weak RCTBridge *weakSelf = self;
417422
_inspectorPageId = facebook::react::jsinspector_modern::getInspectorInstance().addPage(
418423
"React Native Bridge (Experimental)",

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212

1313
namespace facebook::react::jsinspector_modern {
1414

15+
std::shared_ptr<InstanceTarget> InstanceTarget::create(
16+
InstanceTargetDelegate& delegate,
17+
VoidExecutor executor) {
18+
std::shared_ptr<InstanceTarget> instanceTarget{new InstanceTarget(delegate)};
19+
instanceTarget->setExecutor(executor);
20+
return instanceTarget;
21+
}
22+
1523
InstanceTarget::InstanceTarget(InstanceTargetDelegate& delegate)
1624
: delegate_(delegate) {
1725
(void)delegate_;
@@ -24,8 +32,7 @@ std::shared_ptr<InstanceAgent> InstanceTarget::createAgent(
2432
SessionState& sessionState) {
2533
auto instanceAgent =
2634
std::make_shared<InstanceAgent>(channel, *this, sessionState);
27-
instanceAgent->setCurrentRuntime(
28-
currentRuntime_.has_value() ? &*currentRuntime_ : nullptr);
35+
instanceAgent->setCurrentRuntime(currentRuntime_.get());
2936
agents_.push_back(instanceAgent);
3037
return instanceAgent;
3138
}
@@ -47,9 +54,11 @@ InstanceTarget::~InstanceTarget() {
4754

4855
RuntimeTarget& InstanceTarget::registerRuntime(
4956
RuntimeTargetDelegate& delegate,
50-
RuntimeExecutor executor) {
57+
RuntimeExecutor jsExecutor) {
5158
assert(!currentRuntime_ && "Only one Runtime allowed");
52-
currentRuntime_.emplace(delegate, executor);
59+
currentRuntime_ = RuntimeTarget::create(
60+
delegate, jsExecutor, makeVoidExecutor(executorFromThis()));
61+
5362
forEachAgent([currentRuntime = &*currentRuntime_](InstanceAgent& agent) {
5463
agent.setCurrentRuntime(currentRuntime);
5564
});
@@ -58,7 +67,7 @@ RuntimeTarget& InstanceTarget::registerRuntime(
5867

5968
void InstanceTarget::unregisterRuntime(RuntimeTarget& Runtime) {
6069
assert(
61-
currentRuntime_.has_value() && &currentRuntime_.value() == &Runtime &&
70+
currentRuntime_ && currentRuntime_.get() == &Runtime &&
6271
"Invalid unregistration");
6372
forEachAgent([](InstanceAgent& agent) { agent.setCurrentRuntime(nullptr); });
6473
currentRuntime_.reset();

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#pragma once
99

1010
#include "RuntimeTarget.h"
11+
#include "ScopedExecutor.h"
1112
#include "SessionState.h"
1213

1314
#include <jsinspector-modern/InspectorInterfaces.h>
@@ -40,14 +41,20 @@ class InstanceTargetDelegate {
4041
/**
4142
* A Target that represents a single instance of React Native.
4243
*/
43-
class InstanceTarget final {
44+
class InstanceTarget : public EnableExecutorFromThis<InstanceTarget> {
4445
public:
4546
/**
47+
* Constructs a new InstanceTarget.
4648
* \param delegate The object that will receive events from this target.
4749
* The caller is responsible for ensuring that the delegate outlives this
4850
* object.
51+
* \param executor An executor that may be used to call methods on this
52+
* InstanceTarget while it exists. \c create additionally guarantees that the
53+
* executor will not be called after the InstanceTarget is destroyed.
4954
*/
50-
explicit InstanceTarget(InstanceTargetDelegate& delegate);
55+
static std::shared_ptr<InstanceTarget> create(
56+
InstanceTargetDelegate& delegate,
57+
VoidExecutor executor);
5158

5259
InstanceTarget(const InstanceTarget&) = delete;
5360
InstanceTarget(InstanceTarget&&) = delete;
@@ -65,8 +72,17 @@ class InstanceTarget final {
6572
void unregisterRuntime(RuntimeTarget& runtime);
6673

6774
private:
75+
/**
76+
* Constructs a new InstanceTarget. The caller must call setExecutor
77+
* immediately afterwards.
78+
* \param delegate The object that will receive events from this target.
79+
* The caller is responsible for ensuring that the delegate outlives this
80+
* object.
81+
*/
82+
InstanceTarget(InstanceTargetDelegate& delegate);
83+
6884
InstanceTargetDelegate& delegate_;
69-
std::optional<RuntimeTarget> currentRuntime_{std::nullopt};
85+
std::shared_ptr<RuntimeTarget> currentRuntime_{nullptr};
7086
std::list<std::weak_ptr<InstanceAgent>> agents_;
7187

7288
/**

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,22 @@ class PageTargetSession {
9999
SessionState state_;
100100
};
101101

102+
std::shared_ptr<PageTarget> PageTarget::create(
103+
PageTargetDelegate& delegate,
104+
VoidExecutor executor) {
105+
std::shared_ptr<PageTarget> pageTarget{new PageTarget(delegate)};
106+
pageTarget->setExecutor(executor);
107+
return pageTarget;
108+
}
109+
102110
PageTarget::PageTarget(PageTargetDelegate& delegate) : delegate_(delegate) {}
103111

104112
std::unique_ptr<ILocalConnection> PageTarget::connect(
105113
std::unique_ptr<IRemoteConnection> connectionToFrontend,
106114
SessionMetadata sessionMetadata) {
107115
auto session = std::make_shared<PageTargetSession>(
108116
std::move(connectionToFrontend), controller_, std::move(sessionMetadata));
109-
session->setCurrentInstance(currentInstance_ ? &*currentInstance_ : nullptr);
117+
session->setCurrentInstance(currentInstance_.get());
110118
sessions_.push_back(std::weak_ptr(session));
111119
return std::make_unique<CallbackLocalConnection>(
112120
[session](std::string message) { (*session)(message); });
@@ -131,7 +139,8 @@ PageTargetDelegate::~PageTargetDelegate() {}
131139

132140
InstanceTarget& PageTarget::registerInstance(InstanceTargetDelegate& delegate) {
133141
assert(!currentInstance_ && "Only one instance allowed");
134-
currentInstance_.emplace(delegate);
142+
currentInstance_ =
143+
InstanceTarget::create(delegate, makeVoidExecutor(executorFromThis()));
135144
forEachSession(
136145
[currentInstance = &*currentInstance_](PageTargetSession& session) {
137146
session.setCurrentInstance(currentInstance);
@@ -141,7 +150,7 @@ InstanceTarget& PageTarget::registerInstance(InstanceTargetDelegate& delegate) {
141150

142151
void PageTarget::unregisterInstance(InstanceTarget& instance) {
143152
assert(
144-
currentInstance_.has_value() && &currentInstance_.value() == &instance &&
153+
currentInstance_ && currentInstance_.get() == &instance &&
145154
"Invalid unregistration");
146155
forEachSession(
147156
[](PageTargetSession& session) { session.setCurrentInstance(nullptr); });

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

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#pragma once
99

10+
#include "ScopedExecutor.h"
11+
1012
#include <jsinspector-modern/InspectorInterfaces.h>
1113
#include <jsinspector-modern/InstanceTarget.h>
1214

@@ -95,19 +97,25 @@ class PageTargetController final {
9597
* "Host" in React Native's architecture - the entity that manages the
9698
* lifecycle of a React Instance.
9799
*/
98-
class JSINSPECTOR_EXPORT PageTarget final {
100+
class JSINSPECTOR_EXPORT PageTarget
101+
: public EnableExecutorFromThis<PageTarget> {
99102
public:
100103
struct SessionMetadata {
101104
std::optional<std::string> integrationName;
102105
};
103106

104107
/**
105108
* Constructs a new PageTarget.
106-
* \param delegate The PageTargetDelegate that will receive events from
107-
* this PageTarget. The caller is responsible for ensuring that the
108-
* PageTargetDelegate outlives this object.
109+
* \param delegate The PageTargetDelegate that will
110+
* receive events from this PageTarget. The caller is responsible for ensuring
111+
* that the PageTargetDelegate outlives this object.
112+
* \param executor An executor that may be used to call methods on this
113+
* PageTarget while it exists. \c create additionally guarantees that the
114+
* executor will not be called after the PageTarget is destroyed.
109115
*/
110-
explicit PageTarget(PageTargetDelegate& delegate);
116+
static std::shared_ptr<PageTarget> create(
117+
PageTargetDelegate& delegate,
118+
VoidExecutor executor);
111119

112120
PageTarget(const PageTarget&) = delete;
113121
PageTarget(PageTarget&&) = delete;
@@ -146,11 +154,19 @@ class JSINSPECTOR_EXPORT PageTarget final {
146154
void unregisterInstance(InstanceTarget& instance);
147155

148156
private:
149-
std::list<std::weak_ptr<PageTargetSession>> sessions_;
157+
/**
158+
* Constructs a new PageTarget.
159+
* The caller must call setExecutor immediately afterwards.
160+
* \param delegate The PageTargetDelegate that will
161+
* receive events from this PageTarget. The caller is responsible for ensuring
162+
* that the PageTargetDelegate outlives this object.
163+
*/
164+
PageTarget(PageTargetDelegate& delegate);
150165

151166
PageTargetDelegate& delegate_;
167+
std::list<std::weak_ptr<PageTargetSession>> sessions_;
152168
PageTargetController controller_{*this};
153-
std::optional<InstanceTarget> currentInstance_{std::nullopt};
169+
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
154170

155171
/**
156172
* Call the given function for every active session, and clean up any
@@ -175,7 +191,7 @@ class JSINSPECTOR_EXPORT PageTarget final {
175191
}
176192

177193
inline bool hasInstance() const {
178-
return currentInstance_.has_value();
194+
return currentInstance_ != nullptr;
179195
}
180196

181197
// Necessary to allow PageAgent to access PageTarget's internals in a

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@
1111
#include <jsinspector-modern/InstanceTarget.h>
1212
#include <jsinspector-modern/PageTarget.h>
1313
#include <jsinspector-modern/RuntimeTarget.h>
14+
#include <jsinspector-modern/ScopedExecutor.h>
1415
#include <jsinspector-modern/SessionState.h>

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,20 @@
99

1010
namespace facebook::react::jsinspector_modern {
1111

12+
std::shared_ptr<RuntimeTarget> RuntimeTarget::create(
13+
RuntimeTargetDelegate& delegate,
14+
RuntimeExecutor jsExecutor,
15+
VoidExecutor selfExecutor) {
16+
std::shared_ptr<RuntimeTarget> runtimeTarget{
17+
new RuntimeTarget(delegate, jsExecutor)};
18+
runtimeTarget->setExecutor(selfExecutor);
19+
return runtimeTarget;
20+
}
21+
1222
RuntimeTarget::RuntimeTarget(
1323
RuntimeTargetDelegate& delegate,
14-
RuntimeExecutor executor)
15-
: delegate_(delegate), executor_(executor) {}
24+
RuntimeExecutor jsExecutor)
25+
: delegate_(delegate), jsExecutor_(jsExecutor) {}
1626

1727
std::shared_ptr<RuntimeAgent> RuntimeTarget::createAgent(
1828
FrontendChannel channel,

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <ReactCommon/RuntimeExecutor.h>
1111
#include "InspectorInterfaces.h"
1212
#include "RuntimeAgent.h"
13+
#include "ScopedExecutor.h"
1314
#include "SessionState.h"
1415

1516
#include <list>
@@ -48,18 +49,27 @@ class RuntimeTargetDelegate {
4849
/**
4950
* A Target corresponding to a JavaScript runtime.
5051
*/
51-
class JSINSPECTOR_EXPORT RuntimeTarget final {
52+
class JSINSPECTOR_EXPORT RuntimeTarget
53+
: public EnableExecutorFromThis<RuntimeTarget> {
5254
public:
5355
/**
56+
* Constructs a new RuntimeTarget. The caller must call setExecutor
57+
* immediately afterwards.
5458
* \param delegate The object that will receive events from this target.
5559
* The caller is responsible for ensuring that the delegate outlives this
5660
* object.
57-
* \param executor A RuntimeExecutor that can be used to schedule work on
61+
* \param jsExecutor A RuntimeExecutor that can be used to schedule work on
5862
* the JS runtime's thread. The executor's queue should be empty when
5963
* RuntimeTarget is constructed (i.e. anything scheduled during the
6064
* constructor should be executed before any user code is run).
65+
* \param selfExecutor An executor that may be used to call methods on this
66+
* RuntimeTarget while it exists. \c create additionally guarantees that the
67+
* executor will not be called after the RuntimeTarget is destroyed.
6168
*/
62-
RuntimeTarget(RuntimeTargetDelegate& delegate, RuntimeExecutor executor);
69+
static std::shared_ptr<RuntimeTarget> create(
70+
RuntimeTargetDelegate& delegate,
71+
RuntimeExecutor jsExecutor,
72+
VoidExecutor selfExecutor);
6373

6474
RuntimeTarget(const RuntimeTarget&) = delete;
6575
RuntimeTarget(RuntimeTarget&&) = delete;
@@ -81,8 +91,21 @@ class JSINSPECTOR_EXPORT RuntimeTarget final {
8191
SessionState& sessionState);
8292

8393
private:
94+
/**
95+
* Constructs a new RuntimeTarget. The caller must call setExecutor
96+
* immediately afterwards.
97+
* \param delegate The object that will receive events from this target.
98+
* The caller is responsible for ensuring that the delegate outlives this
99+
* object.
100+
* \param jsExecutor A RuntimeExecutor that can be used to schedule work on
101+
* the JS runtime's thread. The executor's queue should be empty when
102+
* RuntimeTarget is constructed (i.e. anything scheduled during the
103+
* constructor should be executed before any user code is run).
104+
*/
105+
RuntimeTarget(RuntimeTargetDelegate& delegate, RuntimeExecutor jsExecutor);
106+
84107
RuntimeTargetDelegate& delegate_;
85-
RuntimeExecutor executor_;
108+
RuntimeExecutor jsExecutor_;
86109
std::list<std::weak_ptr<RuntimeAgent>> agents_;
87110

88111
/**

0 commit comments

Comments
 (0)