Skip to content

Commit 766da73

Browse files
authored
Implement CallInvoker as replacement for JSDispatcher (#14648)
* Remove usage of JSDispatcher in various built-in modules * Change files * format * Implement public JSDispatcher on top of CallInvoker * format * Add ReactContext.CallInvoker and JsiInitializers * format * format * build fix * build fixes * Cleanup * Add runtime handle to instance created and instance loaded events * fix * fix * fix * fix * Remove unused JSDispatcherWriter
1 parent a9521a1 commit 766da73

File tree

61 files changed

+1327
-219
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1327
-219
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Remove usage of JSDispatcher in various built-in modules",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

vnext/Desktop/React.Windows.Desktop.vcxproj

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,6 @@
243243
<ClInclude Include="..\Microsoft.ReactNative\JsiWriter.h">
244244
<DependentUpon>..\Microsoft.ReactNative\IJSValueWriter.idl</DependentUpon>
245245
</ClInclude>
246-
<ClInclude Include="..\Microsoft.ReactNative\CallInvokerWriter.h">
247-
<DependentUpon>..\Microsoft.ReactNative\IJSValueWriter.idl</DependentUpon>
248-
</ClInclude>
249246
<ClInclude Include="..\Microsoft.ReactNative\ReactInstanceSettings.h">
250247
<DependentUpon>..\Microsoft.ReactNative\ReactInstanceSettings.idl</DependentUpon>
251248
</ClInclude>

vnext/Folly/Folly.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<ConfigurationType>StaticLibrary</ConfigurationType>
5151
</PropertyGroup>
5252
<ItemGroup Condition="'$(UseFabric)' == 'true'">
53+
<ClCompile Include="$(MSBuildThisFileDirectory)\ThreadNameStub.cpp" />
5354
<ClCompile Include="$(FollyDir)\folly\SharedMutex.cpp" />
5455
<ClCompile Include="$(FollyDir)\folly\concurrency\CacheLocality.cpp" />
5556
<ClCompile Include="$(FollyDir)\folly\detail\Futex.cpp" />

vnext/Folly/ThreadNameStub.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <folly/string.h>
2+
3+
// Avoid bringing in a bunch of folly threading just for setThreadName
4+
namespace folly {
5+
bool setThreadName(StringPiece)
6+
{
7+
return false;
8+
}
9+
}
10+

vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ struct ReactContextStub : implements<ReactContextStub, IReactContext> {
2828
VerifyElseCrashSz(false, "Not implemented");
2929
}
3030

31+
CallInvoker CallInvoker() noexcept {
32+
VerifyElseCrashSz(false, "Not implemented");
33+
}
34+
3135
IInspectable JSRuntime() noexcept {
3236
VerifyElseCrashSz(false, "Not implemented");
3337
}

vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ void ReactModuleBuilderMock::AddInitializer(InitializerDelegate const &initializ
6969
m_initializers.push_back(initializer);
7070
}
7171

72+
void ReactModuleBuilderMock::AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept {
73+
m_jsiinitializers.push_back(initializer);
74+
}
75+
7276
void ReactModuleBuilderMock::AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept {
7377
m_constantProviders.push_back(constantProvider);
7478
}

vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ struct ReactModuleBuilderMock {
7070

7171
public: // IReactModuleBuilder
7272
void AddInitializer(InitializerDelegate const &initializer) noexcept;
73+
void AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept;
7374
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
7475
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
7576
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
@@ -100,6 +101,7 @@ struct ReactModuleBuilderMock {
100101
private:
101102
IReactContext m_reactContext{nullptr};
102103
std::vector<InitializerDelegate> m_initializers;
104+
std::vector<JsiInitializerDelegate> m_jsiinitializers;
103105
std::vector<ConstantProviderDelegate> m_constantProviders;
104106
std::map<std::wstring, std::tuple<MethodReturnType, MethodDelegate>> m_methods;
105107
std::map<std::wstring, SyncMethodDelegate> m_syncMethods;
@@ -132,6 +134,10 @@ struct ReactContextMock : implements<ReactContextMock, IReactContext> {
132134
VerifyElseCrashSz(false, "Not implemented");
133135
}
134136

137+
CallInvoker CallInvoker() noexcept {
138+
VerifyElseCrashSz(false, "Not implemented");
139+
}
140+
135141
IInspectable JSRuntime() noexcept {
136142
VerifyElseCrashSz(false, "Not implemented");
137143
}
@@ -216,6 +222,7 @@ struct ReactModuleBuilderImpl : implements<ReactModuleBuilderImpl, IReactModuleB
216222

217223
public: // IReactModuleBuilder
218224
void AddInitializer(InitializerDelegate const &initializer) noexcept;
225+
void AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept;
219226
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
220227
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
221228
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
@@ -339,6 +346,10 @@ inline void ReactModuleBuilderImpl::AddInitializer(InitializerDelegate const &in
339346
m_mock.AddInitializer(initializer);
340347
}
341348

349+
inline void ReactModuleBuilderImpl::AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept {
350+
m_mock.AddJsiInitializer(initializer);
351+
}
352+
342353
inline void ReactModuleBuilderImpl::AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept {
343354
m_mock.AddConstantProvider(constantProvider);
344355
}

vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "pch.h"
55
#include "JsiAbiApi.h"
66
#include <utility>
7+
#include "ReactContext.h"
78
#include "ReactNonAbiValue.h"
89
#include "winrt/Windows.Foundation.Collections.h"
910

@@ -14,6 +15,16 @@ using namespace facebook::jsi;
1415

1516
namespace winrt::Microsoft::ReactNative {
1617

18+
namespace Details {
19+
// Try to get JSI Runtime for the ReactContext
20+
// If it is not found, then create it based on context JSI runtime and store it in the context.Properties().
21+
// The function returns nullptr if the current context does not have JSI runtime.
22+
// It makes sure that the JSI runtime holder is removed when the instance is unloaded.
23+
JsiAbiRuntime *TryGetOrCreateContextRuntime(
24+
winrt::Microsoft::ReactNative::ReactContext const &context,
25+
JsiRuntime const &runtimeHandle) noexcept;
26+
} // namespace Details
27+
1728
// The macro to simplify recording JSI exceptions.
1829
// It looks strange to keep the normal structure of the try/catch in code.
1930
#define JSI_RUNTIME_SET_ERROR(runtime) \
@@ -132,6 +143,52 @@ std::shared_ptr<facebook::jsi::HostObject> const &JsiHostObjectWrapper::HostObje
132143
return m_hostObject;
133144
}
134145

146+
//===========================================================================
147+
// JsiHostObjectGetOrCreateWrapper implementation
148+
//===========================================================================
149+
150+
JsiHostObjectGetOrCreateWrapper::JsiHostObjectGetOrCreateWrapper(
151+
const winrt::Microsoft::ReactNative::IReactContext &context,
152+
std::shared_ptr<HostObject> &&hostObject) noexcept
153+
: m_hostObject(std::move(hostObject)), m_context(context) {}
154+
155+
JsiValueRef JsiHostObjectGetOrCreateWrapper::GetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name) try {
156+
JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
157+
JsiAbiRuntime::PropNameIDRef nameRef{name};
158+
return JsiAbiRuntime::DetachJsiValueRef(m_hostObject->get(*rt, nameRef));
159+
} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
160+
throw;
161+
}
162+
163+
void JsiHostObjectGetOrCreateWrapper::SetProperty(
164+
JsiRuntime const &runtime,
165+
JsiPropertyIdRef const &name,
166+
JsiValueRef const &value) try {
167+
JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
168+
m_hostObject->set(*rt, JsiAbiRuntime::PropNameIDRef{name}, JsiAbiRuntime::ValueRef(value));
169+
} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
170+
throw;
171+
}
172+
173+
winrt::Windows::Foundation::Collections::IVector<JsiPropertyIdRef> JsiHostObjectGetOrCreateWrapper::GetPropertyIds(
174+
JsiRuntime const &runtime) try {
175+
JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
176+
auto names = m_hostObject->getPropertyNames(*rt);
177+
std::vector<JsiPropertyIdRef> result;
178+
result.reserve(names.size());
179+
for (auto &name : names) {
180+
result.push_back(JsiAbiRuntime::DetachJsiPropertyIdRef(std::move(name)));
181+
}
182+
183+
return winrt::single_threaded_vector<JsiPropertyIdRef>(std::move(result));
184+
} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
185+
throw;
186+
}
187+
188+
std::shared_ptr<facebook::jsi::HostObject> const &JsiHostObjectGetOrCreateWrapper::HostObjectSharedPtr() noexcept {
189+
return m_hostObject;
190+
}
191+
135192
//===========================================================================
136193
// JsiHostFunctionWrapper implementation
137194
//===========================================================================
@@ -162,38 +219,41 @@ JsiValueRef JsiHostFunctionWrapper::operator()(
162219
// JsiAbiRuntime implementation
163220
//===========================================================================
164221

165-
// The tls_jsiAbiRuntimeMap map allows us to associate JsiAbiRuntime with JsiRuntime.
166-
// The association is thread-specific and DLL-specific.
167-
// It is thread specific because we want to have the safe access only in JS thread.
168-
// It is DLL-specific because JsiAbiRuntime is not ABI-safe and each module DLL will
169-
// have their own JsiAbiRuntime instance.
170-
static thread_local std::map<void *, JsiAbiRuntime *> *tls_jsiAbiRuntimeMap{nullptr};
222+
// The s_jsiAbiRuntimeMap map allows us to associate JsiAbiRuntime with JsiRuntime.
223+
// The association is DLL-specific because JsiAbiRuntime is not ABI-safe and each module DLL will have their own
224+
// JsiAbiRuntime instance.
225+
static std::map<void *, JsiAbiRuntime *> *s_jsiAbiRuntimeMap{nullptr};
226+
static std::recursive_mutex s_jsiRuntimeMapMutex;
171227

172228
JsiAbiRuntime::JsiAbiRuntime(JsiRuntime const &runtime) noexcept : m_runtime{runtime} {
173229
VerifyElseCrashSz(runtime, "JSI runtime is null");
174-
VerifyElseCrashSz(
175-
GetFromJsiRuntime(runtime) == nullptr,
176-
"We can have only one instance of JsiAbiRuntime for JsiRuntime in the thread.");
177-
if (!tls_jsiAbiRuntimeMap) {
178-
tls_jsiAbiRuntimeMap = new std::map<void *, JsiAbiRuntime *>();
230+
231+
{
232+
std::lock_guard<std::recursive_mutex> guard(s_jsiRuntimeMapMutex);
233+
VerifyElseCrashSz(
234+
GetFromJsiRuntime(runtime) == nullptr, "We can have only one instance of JsiAbiRuntime for a JsiRuntime.");
235+
236+
if (!s_jsiAbiRuntimeMap) {
237+
s_jsiAbiRuntimeMap = new std::map<void *, JsiAbiRuntime *>();
238+
}
239+
s_jsiAbiRuntimeMap->try_emplace(get_abi(runtime), this);
179240
}
180-
tls_jsiAbiRuntimeMap->try_emplace(get_abi(runtime), this);
181241
}
182242

183243
JsiAbiRuntime::~JsiAbiRuntime() {
184-
VerifyElseCrashSz(
185-
GetFromJsiRuntime(m_runtime) != nullptr, "JsiAbiRuntime must be called in the same thread where it was created.");
186-
tls_jsiAbiRuntimeMap->erase(get_abi(m_runtime));
187-
if (tls_jsiAbiRuntimeMap->empty()) {
188-
delete tls_jsiAbiRuntimeMap;
189-
tls_jsiAbiRuntimeMap = nullptr;
244+
std::lock_guard<std::recursive_mutex> guard(s_jsiRuntimeMapMutex);
245+
s_jsiAbiRuntimeMap->erase(get_abi(m_runtime));
246+
if (s_jsiAbiRuntimeMap->empty()) {
247+
delete s_jsiAbiRuntimeMap;
248+
s_jsiAbiRuntimeMap = nullptr;
190249
}
191250
}
192251

193252
/*static*/ JsiAbiRuntime *JsiAbiRuntime::GetFromJsiRuntime(JsiRuntime const &runtime) noexcept {
194-
if (tls_jsiAbiRuntimeMap && runtime) {
195-
auto it = tls_jsiAbiRuntimeMap->find(get_abi(runtime));
196-
if (it != tls_jsiAbiRuntimeMap->end()) {
253+
std::lock_guard<std::recursive_mutex> guard(s_jsiRuntimeMapMutex);
254+
if (s_jsiAbiRuntimeMap && runtime) {
255+
auto it = s_jsiAbiRuntimeMap->find(get_abi(runtime));
256+
if (it != s_jsiAbiRuntimeMap->end()) {
197257
return it->second;
198258
}
199259
}

vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,25 @@ struct JsiHostObjectWrapper : implements<JsiHostObjectWrapper, IJsiHostObject> {
5454
std::shared_ptr<facebook::jsi::HostObject> m_hostObject;
5555
};
5656

57+
// An ABI-safe wrapper for facebook::jsi::HostObject, similar to JsiHostObjectWrapper,
58+
// but uses GetOrCreate for the AbiRuntime to ensure its created on first use
59+
// This is important for use with TurboModules, which may not go through the ReactContext.CallInvoker
60+
struct JsiHostObjectGetOrCreateWrapper : implements<JsiHostObjectGetOrCreateWrapper, IJsiHostObject> {
61+
JsiHostObjectGetOrCreateWrapper(
62+
const winrt::Microsoft::ReactNative::IReactContext &context,
63+
std::shared_ptr<facebook::jsi::HostObject> &&hostObject) noexcept;
64+
65+
JsiValueRef GetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name);
66+
void SetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name, JsiValueRef const &value);
67+
winrt::Windows::Foundation::Collections::IVector<JsiPropertyIdRef> GetPropertyIds(JsiRuntime const &runtime);
68+
69+
std::shared_ptr<facebook::jsi::HostObject> const &HostObjectSharedPtr() noexcept;
70+
71+
private:
72+
std::shared_ptr<facebook::jsi::HostObject> m_hostObject;
73+
winrt::Microsoft::ReactNative::IReactContext m_context;
74+
};
75+
5776
// The function object that wraps up the facebook::jsi::HostFunctionType
5877
struct JsiHostFunctionWrapper {
5978
// We only support new and move constructors.
@@ -226,6 +245,7 @@ struct JsiAbiRuntime : facebook::jsi::Runtime {
226245
// Allow access to the helper function
227246
friend struct JsiByteBufferWrapper;
228247
friend struct JsiHostObjectWrapper;
248+
friend struct JsiHostObjectGetOrCreateWrapper;
229249
friend struct JsiHostFunctionWrapper;
230250
friend struct AbiJSError;
231251
friend struct AbiJSINativeException;

vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,12 @@ extern "C" IMAGE_DOS_HEADER __ImageBase;
1010

1111
namespace winrt::Microsoft::ReactNative {
1212

13-
// Try to get JSI Runtime for the current JS dispatcher thread.
13+
namespace Details {
14+
// Try to get JSI Runtime for the ReactContext
1415
// If it is not found, then create it based on context JSI runtime and store it in the context.Properties().
1516
// The function returns nullptr if the current context does not have JSI runtime.
1617
// It makes sure that the JSI runtime holder is removed when the instance is unloaded.
17-
facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept {
18-
ReactDispatcher jsDispatcher = context.JSDispatcher();
19-
VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread");
20-
21-
// The JSI runtime is not available if we do Web debugging when JS is running in web browser.
22-
JsiRuntime abiJsiRuntime = context.Handle().JSRuntime().as<JsiRuntime>();
23-
if (!abiJsiRuntime) {
24-
return nullptr;
25-
}
26-
18+
JsiAbiRuntime *TryGetOrCreateContextRuntime(ReactContext const &context, JsiRuntime const &abiJsiRuntime) noexcept {
2719
// See if the JSI runtime was previously created.
2820
JsiAbiRuntime *runtime = JsiAbiRuntime::GetFromJsiRuntime(abiJsiRuntime);
2921
if (!runtime) {
@@ -57,6 +49,48 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context
5749
return runtime;
5850
}
5951

52+
} // namespace Details
53+
54+
facebook::jsi::Runtime *TryGetOrCreateContextRuntime(
55+
ReactContext const &context,
56+
winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept {
57+
if (!runtimeHandle) {
58+
return nullptr;
59+
}
60+
61+
// The JSI runtime is not available if we do Web debugging when JS is running in web browser.
62+
JsiRuntime abiJsiRuntime = runtimeHandle.as<JsiRuntime>();
63+
if (!abiJsiRuntime) {
64+
return nullptr;
65+
}
66+
67+
return Details::TryGetOrCreateContextRuntime(context, abiJsiRuntime);
68+
}
69+
70+
// Note: deprecated in favor of TryGetOrCreateContextRuntime with Handle parameter
71+
facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept {
72+
#ifdef DEBUG
73+
ReactDispatcher jsDispatcher = context.JSDispatcher();
74+
VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread");
75+
#endif
76+
77+
if (auto runtimeHandle = context.Handle().JSRuntime()) {
78+
return TryGetOrCreateContextRuntime(context, runtimeHandle);
79+
}
80+
81+
return nullptr;
82+
}
83+
84+
// Calls TryGetOrCreateContextRuntime to get JSI runtime.
85+
// It crashes when TryGetOrCreateContextRuntime returns null.
86+
[[deprecated]] facebook::jsi::Runtime &GetOrCreateContextRuntime(
87+
ReactContext const &context,
88+
winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept {
89+
facebook::jsi::Runtime *runtime = TryGetOrCreateContextRuntime(context, runtimeHandle);
90+
VerifyElseCrashSz(runtime, "JSI runtime is not available");
91+
return *runtime;
92+
}
93+
6094
// Calls TryGetOrCreateContextRuntime to get JSI runtime.
6195
// It crashes when TryGetOrCreateContextRuntime returns null.
6296
// Note: deprecated in favor of TryGetOrCreateContextRuntime.

0 commit comments

Comments
 (0)