Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Remove usage of JSDispatcher in various built-in modules",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
3 changes: 0 additions & 3 deletions vnext/Desktop/React.Windows.Desktop.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,6 @@
<ClInclude Include="..\Microsoft.ReactNative\JsiWriter.h">
<DependentUpon>..\Microsoft.ReactNative\IJSValueWriter.idl</DependentUpon>
</ClInclude>
<ClInclude Include="..\Microsoft.ReactNative\CallInvokerWriter.h">
<DependentUpon>..\Microsoft.ReactNative\IJSValueWriter.idl</DependentUpon>
</ClInclude>
<ClInclude Include="..\Microsoft.ReactNative\ReactInstanceSettings.h">
<DependentUpon>..\Microsoft.ReactNative\ReactInstanceSettings.idl</DependentUpon>
</ClInclude>
Expand Down
1 change: 1 addition & 0 deletions vnext/Folly/Folly.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Condition="'$(UseFabric)' == 'true'">
<ClCompile Include="$(MSBuildThisFileDirectory)\ThreadNameStub.cpp" />
<ClCompile Include="$(FollyDir)\folly\SharedMutex.cpp" />
<ClCompile Include="$(FollyDir)\folly\concurrency\CacheLocality.cpp" />
<ClCompile Include="$(FollyDir)\folly\detail\Futex.cpp" />
Expand Down
10 changes: 10 additions & 0 deletions vnext/Folly/ThreadNameStub.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <folly/string.h>

// Avoid bringing in a bunch of folly threading just for setThreadName
namespace folly {
bool setThreadName(StringPiece)
{
return false;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ struct ReactContextStub : implements<ReactContextStub, IReactContext> {
VerifyElseCrashSz(false, "Not implemented");
}

CallInvoker CallInvoker() noexcept {
VerifyElseCrashSz(false, "Not implemented");
}

IInspectable JSRuntime() noexcept {
VerifyElseCrashSz(false, "Not implemented");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ void ReactModuleBuilderMock::AddInitializer(InitializerDelegate const &initializ
m_initializers.push_back(initializer);
}

void ReactModuleBuilderMock::AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept {
m_jsiinitializers.push_back(initializer);
}

void ReactModuleBuilderMock::AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept {
m_constantProviders.push_back(constantProvider);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ struct ReactModuleBuilderMock {

public: // IReactModuleBuilder
void AddInitializer(InitializerDelegate const &initializer) noexcept;
void AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept;
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
Expand Down Expand Up @@ -100,6 +101,7 @@ struct ReactModuleBuilderMock {
private:
IReactContext m_reactContext{nullptr};
std::vector<InitializerDelegate> m_initializers;
std::vector<JsiInitializerDelegate> m_jsiinitializers;
std::vector<ConstantProviderDelegate> m_constantProviders;
std::map<std::wstring, std::tuple<MethodReturnType, MethodDelegate>> m_methods;
std::map<std::wstring, SyncMethodDelegate> m_syncMethods;
Expand Down Expand Up @@ -132,6 +134,10 @@ struct ReactContextMock : implements<ReactContextMock, IReactContext> {
VerifyElseCrashSz(false, "Not implemented");
}

CallInvoker CallInvoker() noexcept {
VerifyElseCrashSz(false, "Not implemented");
}

IInspectable JSRuntime() noexcept {
VerifyElseCrashSz(false, "Not implemented");
}
Expand Down Expand Up @@ -216,6 +222,7 @@ struct ReactModuleBuilderImpl : implements<ReactModuleBuilderImpl, IReactModuleB

public: // IReactModuleBuilder
void AddInitializer(InitializerDelegate const &initializer) noexcept;
void AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept;
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
Expand Down Expand Up @@ -339,6 +346,10 @@ inline void ReactModuleBuilderImpl::AddInitializer(InitializerDelegate const &in
m_mock.AddInitializer(initializer);
}

inline void ReactModuleBuilderImpl::AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept {
m_mock.AddJsiInitializer(initializer);
}

inline void ReactModuleBuilderImpl::AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept {
m_mock.AddConstantProvider(constantProvider);
}
Expand Down
102 changes: 81 additions & 21 deletions vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "pch.h"
#include "JsiAbiApi.h"
#include <utility>
#include "ReactContext.h"
#include "ReactNonAbiValue.h"
#include "winrt/Windows.Foundation.Collections.h"

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

namespace winrt::Microsoft::ReactNative {

namespace Details {
// Try to get JSI Runtime for the ReactContext
// If it is not found, then create it based on context JSI runtime and store it in the context.Properties().
// The function returns nullptr if the current context does not have JSI runtime.
// It makes sure that the JSI runtime holder is removed when the instance is unloaded.
JsiAbiRuntime *TryGetOrCreateContextRuntime(
winrt::Microsoft::ReactNative::ReactContext const &context,
JsiRuntime const &runtimeHandle) noexcept;
} // namespace Details

// The macro to simplify recording JSI exceptions.
// It looks strange to keep the normal structure of the try/catch in code.
#define JSI_RUNTIME_SET_ERROR(runtime) \
Expand Down Expand Up @@ -132,6 +143,52 @@ std::shared_ptr<facebook::jsi::HostObject> const &JsiHostObjectWrapper::HostObje
return m_hostObject;
}

//===========================================================================
// JsiHostObjectGetOrCreateWrapper implementation
//===========================================================================

JsiHostObjectGetOrCreateWrapper::JsiHostObjectGetOrCreateWrapper(
const winrt::Microsoft::ReactNative::IReactContext &context,
std::shared_ptr<HostObject> &&hostObject) noexcept
: m_hostObject(std::move(hostObject)), m_context(context) {}

JsiValueRef JsiHostObjectGetOrCreateWrapper::GetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name) try {
JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
JsiAbiRuntime::PropNameIDRef nameRef{name};
return JsiAbiRuntime::DetachJsiValueRef(m_hostObject->get(*rt, nameRef));
} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
throw;
}

void JsiHostObjectGetOrCreateWrapper::SetProperty(
JsiRuntime const &runtime,
JsiPropertyIdRef const &name,
JsiValueRef const &value) try {
JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
m_hostObject->set(*rt, JsiAbiRuntime::PropNameIDRef{name}, JsiAbiRuntime::ValueRef(value));
} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
throw;
}

winrt::Windows::Foundation::Collections::IVector<JsiPropertyIdRef> JsiHostObjectGetOrCreateWrapper::GetPropertyIds(
JsiRuntime const &runtime) try {
JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
auto names = m_hostObject->getPropertyNames(*rt);
std::vector<JsiPropertyIdRef> result;
result.reserve(names.size());
for (auto &name : names) {
result.push_back(JsiAbiRuntime::DetachJsiPropertyIdRef(std::move(name)));
}

return winrt::single_threaded_vector<JsiPropertyIdRef>(std::move(result));
} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
throw;
}

std::shared_ptr<facebook::jsi::HostObject> const &JsiHostObjectGetOrCreateWrapper::HostObjectSharedPtr() noexcept {
return m_hostObject;
}

//===========================================================================
// JsiHostFunctionWrapper implementation
//===========================================================================
Expand Down Expand Up @@ -162,38 +219,41 @@ JsiValueRef JsiHostFunctionWrapper::operator()(
// JsiAbiRuntime implementation
//===========================================================================

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

JsiAbiRuntime::JsiAbiRuntime(JsiRuntime const &runtime) noexcept : m_runtime{runtime} {
VerifyElseCrashSz(runtime, "JSI runtime is null");
VerifyElseCrashSz(
GetFromJsiRuntime(runtime) == nullptr,
"We can have only one instance of JsiAbiRuntime for JsiRuntime in the thread.");
if (!tls_jsiAbiRuntimeMap) {
tls_jsiAbiRuntimeMap = new std::map<void *, JsiAbiRuntime *>();

{
std::lock_guard<std::recursive_mutex> guard(s_jsiRuntimeMapMutex);
VerifyElseCrashSz(
GetFromJsiRuntime(runtime) == nullptr, "We can have only one instance of JsiAbiRuntime for a JsiRuntime.");

if (!s_jsiAbiRuntimeMap) {
s_jsiAbiRuntimeMap = new std::map<void *, JsiAbiRuntime *>();
}
s_jsiAbiRuntimeMap->try_emplace(get_abi(runtime), this);
}
tls_jsiAbiRuntimeMap->try_emplace(get_abi(runtime), this);
}

JsiAbiRuntime::~JsiAbiRuntime() {
VerifyElseCrashSz(
GetFromJsiRuntime(m_runtime) != nullptr, "JsiAbiRuntime must be called in the same thread where it was created.");
tls_jsiAbiRuntimeMap->erase(get_abi(m_runtime));
if (tls_jsiAbiRuntimeMap->empty()) {
delete tls_jsiAbiRuntimeMap;
tls_jsiAbiRuntimeMap = nullptr;
std::lock_guard<std::recursive_mutex> guard(s_jsiRuntimeMapMutex);
s_jsiAbiRuntimeMap->erase(get_abi(m_runtime));
if (s_jsiAbiRuntimeMap->empty()) {
delete s_jsiAbiRuntimeMap;
s_jsiAbiRuntimeMap = nullptr;
}
}

/*static*/ JsiAbiRuntime *JsiAbiRuntime::GetFromJsiRuntime(JsiRuntime const &runtime) noexcept {
if (tls_jsiAbiRuntimeMap && runtime) {
auto it = tls_jsiAbiRuntimeMap->find(get_abi(runtime));
if (it != tls_jsiAbiRuntimeMap->end()) {
std::lock_guard<std::recursive_mutex> guard(s_jsiRuntimeMapMutex);
if (s_jsiAbiRuntimeMap && runtime) {
auto it = s_jsiAbiRuntimeMap->find(get_abi(runtime));
if (it != s_jsiAbiRuntimeMap->end()) {
return it->second;
}
}
Expand Down
20 changes: 20 additions & 0 deletions vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ struct JsiHostObjectWrapper : implements<JsiHostObjectWrapper, IJsiHostObject> {
std::shared_ptr<facebook::jsi::HostObject> m_hostObject;
};

// An ABI-safe wrapper for facebook::jsi::HostObject, similar to JsiHostObjectWrapper,
// but uses GetOrCreate for the AbiRuntime to ensure its created on first use
// This is important for use with TurboModules, which may not go through the ReactContext.CallInvoker
struct JsiHostObjectGetOrCreateWrapper : implements<JsiHostObjectGetOrCreateWrapper, IJsiHostObject> {
JsiHostObjectGetOrCreateWrapper(
const winrt::Microsoft::ReactNative::IReactContext &context,
std::shared_ptr<facebook::jsi::HostObject> &&hostObject) noexcept;

JsiValueRef GetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name);
void SetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name, JsiValueRef const &value);
winrt::Windows::Foundation::Collections::IVector<JsiPropertyIdRef> GetPropertyIds(JsiRuntime const &runtime);

std::shared_ptr<facebook::jsi::HostObject> const &HostObjectSharedPtr() noexcept;

private:
std::shared_ptr<facebook::jsi::HostObject> m_hostObject;
winrt::Microsoft::ReactNative::IReactContext m_context;
};

// The function object that wraps up the facebook::jsi::HostFunctionType
struct JsiHostFunctionWrapper {
// We only support new and move constructors.
Expand Down Expand Up @@ -226,6 +245,7 @@ struct JsiAbiRuntime : facebook::jsi::Runtime {
// Allow access to the helper function
friend struct JsiByteBufferWrapper;
friend struct JsiHostObjectWrapper;
friend struct JsiHostObjectGetOrCreateWrapper;
friend struct JsiHostFunctionWrapper;
friend struct AbiJSError;
friend struct AbiJSINativeException;
Expand Down
56 changes: 45 additions & 11 deletions vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,12 @@ extern "C" IMAGE_DOS_HEADER __ImageBase;

namespace winrt::Microsoft::ReactNative {

// Try to get JSI Runtime for the current JS dispatcher thread.
namespace Details {
// Try to get JSI Runtime for the ReactContext
// If it is not found, then create it based on context JSI runtime and store it in the context.Properties().
// The function returns nullptr if the current context does not have JSI runtime.
// It makes sure that the JSI runtime holder is removed when the instance is unloaded.
facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept {
ReactDispatcher jsDispatcher = context.JSDispatcher();
VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread");

// The JSI runtime is not available if we do Web debugging when JS is running in web browser.
JsiRuntime abiJsiRuntime = context.Handle().JSRuntime().as<JsiRuntime>();
if (!abiJsiRuntime) {
return nullptr;
}

JsiAbiRuntime *TryGetOrCreateContextRuntime(ReactContext const &context, JsiRuntime const &abiJsiRuntime) noexcept {
// See if the JSI runtime was previously created.
JsiAbiRuntime *runtime = JsiAbiRuntime::GetFromJsiRuntime(abiJsiRuntime);
if (!runtime) {
Expand Down Expand Up @@ -57,6 +49,48 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context
return runtime;
}

} // namespace Details

facebook::jsi::Runtime *TryGetOrCreateContextRuntime(
ReactContext const &context,
winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept {
if (!runtimeHandle) {
return nullptr;
}

// The JSI runtime is not available if we do Web debugging when JS is running in web browser.
JsiRuntime abiJsiRuntime = runtimeHandle.as<JsiRuntime>();
if (!abiJsiRuntime) {
return nullptr;
}

return Details::TryGetOrCreateContextRuntime(context, abiJsiRuntime);
}

// Note: deprecated in favor of TryGetOrCreateContextRuntime with Handle parameter
facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept {
#ifdef DEBUG
ReactDispatcher jsDispatcher = context.JSDispatcher();
VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread");
#endif

if (auto runtimeHandle = context.Handle().JSRuntime()) {
return TryGetOrCreateContextRuntime(context, runtimeHandle);
}

return nullptr;
}

// Calls TryGetOrCreateContextRuntime to get JSI runtime.
// It crashes when TryGetOrCreateContextRuntime returns null.
[[deprecated]] facebook::jsi::Runtime &GetOrCreateContextRuntime(
ReactContext const &context,
winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept {
facebook::jsi::Runtime *runtime = TryGetOrCreateContextRuntime(context, runtimeHandle);
VerifyElseCrashSz(runtime, "JSI runtime is not available");
return *runtime;
}

// Calls TryGetOrCreateContextRuntime to get JSI runtime.
// It crashes when TryGetOrCreateContextRuntime returns null.
// Note: deprecated in favor of TryGetOrCreateContextRuntime.
Expand Down
6 changes: 6 additions & 0 deletions vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@

namespace winrt::Microsoft::ReactNative {

facebook::jsi::Runtime &GetOrCreateContextRuntime(
ReactContext const &context,
winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept;

// Try to get JSI Runtime for the current JS dispatcher thread.
// If it is not found, then create it based on context JSI runtime and store it in the context.Properties().
// The function returns nullptr if the current context does not have JSI runtime.
// It makes sure that the JSI runtime holder is removed when the instance is unloaded.
// Deprecated -- will remove once we remove the JSRuntime property from ReactContext
facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept;

// Calls TryGetOrCreateContextRuntime to get JSI runtime.
Expand All @@ -25,6 +30,7 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context
// Call provided lambda with the facebook::jsi::Runtime& parameter.
// For example: ExecuteJsi(context, [](facebook::jsi::Runtime& runtime){...})
// The code is executed synchronously if it is already in JSDispatcher, or asynchronously otherwise.
// New Arch should use ReactContext.CallInvoker instead
template <class TCodeWithRuntime>
void ExecuteJsi(ReactContext const &context, TCodeWithRuntime const &code, ReactPromise<void> *callStatus = nullptr) {
ReactDispatcher jsDispatcher = context.JSDispatcher();
Expand Down
Loading
Loading