From 8046e3711f3238f674e5a15d447e39e785a23af3 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 26 Aug 2022 09:04:45 -0700 Subject: [PATCH] [0.69] Use LongLivedObjects for TurboModule callbacks (#10436) (#10442) * Use LongLivedObjects for TurboModule callbacks (#10436) * Fix build break --- ...-74efbb30-e443-4b2f-b4d4-482b9fa56fe5.json | 7 + .../JSI/LongLivedJsiValue.h | 84 ++++++ .../Microsoft.ReactNative.Cxx.vcxitems | 1 + ...Microsoft.ReactNative.Cxx.vcxitems.filters | 3 + .../TestEventService.cpp | 33 +++ .../TestEventService.h | 32 ++- .../TurboModuleTests.cpp | 258 +++++++++++++++++- .../TurboModuleTests.js | 220 ++++++++------- .../JSDispatcherWriter.cpp | 82 ++++-- .../JSDispatcherWriter.h | 8 +- .../ReactHost/ReactInstanceWin.cpp | 1 + .../TurboModulesProvider.cpp | 230 ++++++++++------ .../TurboModulesProvider.h | 5 + vnext/Shared/InstanceManager.cpp | 29 ++ vnext/Shared/InstanceManager.h | 14 + vnext/Shared/OInstance.cpp | 14 +- vnext/Shared/OInstance.h | 17 +- 17 files changed, 803 insertions(+), 235 deletions(-) create mode 100644 change/react-native-windows-74efbb30-e443-4b2f-b4d4-482b9fa56fe5.json create mode 100644 vnext/Microsoft.ReactNative.Cxx/JSI/LongLivedJsiValue.h diff --git a/change/react-native-windows-74efbb30-e443-4b2f-b4d4-482b9fa56fe5.json b/change/react-native-windows-74efbb30-e443-4b2f-b4d4-482b9fa56fe5.json new file mode 100644 index 00000000000..6c5b40feb22 --- /dev/null +++ b/change/react-native-windows-74efbb30-e443-4b2f-b4d4-482b9fa56fe5.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Use LongLivedObjects for TurboModule callbacks", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/LongLivedJsiValue.h b/vnext/Microsoft.ReactNative.Cxx/JSI/LongLivedJsiValue.h new file mode 100644 index 00000000000..35ac9e477ab --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/LongLivedJsiValue.h @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#ifndef MICROSOFT_REACTNATIVE_JSI_LONGLIVEDJSIVALUE_ +#define MICROSOFT_REACTNATIVE_JSI_LONGLIVEDJSIVALUE_ + +#include +#include + +namespace winrt::Microsoft::ReactNative { + +// Wrap up JSI Runtime into a LongLivedObject +struct LongLivedJsiRuntime : facebook::react::LongLivedObject { + static std::weak_ptr CreateWeak( + std::shared_ptr const &longLivedObjectCollection, + facebook::jsi::Runtime &runtime) noexcept { + auto value = std::shared_ptr(new LongLivedJsiRuntime(longLivedObjectCollection, runtime)); + longLivedObjectCollection->add(value); + return value; + } + + facebook::jsi::Runtime &Runtime() { + return runtime_; + } + + public: // LongLivedObject overrides + void allowRelease() override { + if (auto longLivedObjectCollection = longLivedObjectCollection_.lock()) { + if (longLivedObjectCollection != nullptr) { + longLivedObjectCollection->remove(this); + return; + } + } + LongLivedObject::allowRelease(); + } + + protected: + LongLivedJsiRuntime( + std::shared_ptr const &longLivedObjectCollection, + facebook::jsi::Runtime &runtime) + : longLivedObjectCollection_(longLivedObjectCollection), runtime_(runtime) {} + + LongLivedJsiRuntime(LongLivedJsiRuntime const &) = delete; + + private: + // Use a weak reference to the collection to avoid reference loops + std::weak_ptr longLivedObjectCollection_; + facebook::jsi::Runtime &runtime_; +}; + +// Wrap up a JSI Value into a LongLivedObject. +template +struct LongLivedJsiValue : LongLivedJsiRuntime { + static std::weak_ptr> CreateWeak( + std::shared_ptr const &longLivedObjectCollection, + facebook::jsi::Runtime &runtime, + TValue &&value) noexcept { + auto valueWrapper = std::shared_ptr>( + new LongLivedJsiValue(longLivedObjectCollection, runtime, std::forward(value))); + longLivedObjectCollection->add(valueWrapper); + return valueWrapper; + } + + TValue &Value() { + return value_; + } + + protected: + template + LongLivedJsiValue( + std::shared_ptr const &longLivedObjectCollection, + facebook::jsi::Runtime &runtime, + TValue2 &&value) + : LongLivedJsiRuntime(longLivedObjectCollection, runtime), value_(std::forward(value)) {} + + private: + TValue value_; +}; + +using LongLivedJsiFunction = LongLivedJsiValue; + +} // namespace winrt::Microsoft::ReactNative + +#endif // MICROSOFT_REACTNATIVE_JSI_LONGLIVEDJSIVALUE_ diff --git a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems index 53b487062ae..32bbb44b623 100644 --- a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems +++ b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems @@ -38,6 +38,7 @@ + diff --git a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters index c8626e0d355..93204bb08e7 100644 --- a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters +++ b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems.filters @@ -151,6 +151,9 @@ JSI + + TurboModule + diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.cpp index 12264fc5713..1899fc021e6 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.cpp @@ -10,6 +10,10 @@ namespace ReactNativeIntegrationTests { using namespace std::literals::chrono_literals; +//============================================================================= +// TestEventService implementation +//============================================================================= + /*static*/ std::mutex TestEventService::s_mutex; /*static*/ std::condition_variable TestEventService::s_cv; /*static*/ std::queue TestEventService::s_eventQueue; @@ -71,4 +75,33 @@ using namespace std::literals::chrono_literals; } } +//============================================================================= +// TestNotificationService implementation +//============================================================================= + +/*static*/ std::mutex TestNotificationService::s_mutex; +/*static*/ std::condition_variable TestNotificationService::s_cv; +/*static*/ std::set> TestNotificationService::s_events; + +/*static*/ void TestNotificationService::Initialize() noexcept { + auto lock = std::scoped_lock{s_mutex}; + s_events = {}; +} + +/*static*/ void TestNotificationService::Set(std::string_view eventName) noexcept { + auto lock = std::scoped_lock{s_mutex}; + s_events.insert(std::string(eventName)); + s_cv.notify_all(); +}; + +/*static*/ void TestNotificationService::Wait(std::string_view eventName) noexcept { + auto lock = std::unique_lock{s_mutex}; + auto it = s_events.end(); + s_cv.wait(lock, [&]() { + it = s_events.find(eventName); + return it != s_events.end(); + }); + s_events.erase(it); +} + } // namespace ReactNativeIntegrationTests diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.h b/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.h index 41a180d31af..47eba9b13df 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.h +++ b/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.h @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace ReactNativeIntegrationTests { @@ -27,16 +28,12 @@ struct TestEvent { JSValue Value; }; +// Ordered test notifications struct TestEventService { - // Sets to the service to the initial state. + // Sets the service to the initial state. static void Initialize() noexcept; - // Logs new event and notifies the observer to check it. - // It blocks current thread until the previous event is observed. - // - // The expectation is that this method is always called on a thread - // different to the one that runs the ObserveEvents method. - // We will have a deadlock if this expectation is not met. + // Logs new event in the queue and notifies queue observers to check it. static void LogEvent(std::string_view eventName, JSValue &&value) noexcept; // Logs new event for value types that need an explicit call to JSValue constructor. @@ -45,14 +42,31 @@ struct TestEventService { LogEvent(eventName, JSValue{std::forward(value)}); } - // Blocks current thread and observes all incoming events until we see them all. + // Blocks current thread and observes all incoming events in the queue until we see them all. static void ObserveEvents(winrt::array_view expectedEvents) noexcept; private: static std::mutex s_mutex; // to synchronize access to the fields below - static std::condition_variable s_cv; // to notify about new event + static std::condition_variable s_cv; // signals about new event static std::queue s_eventQueue; // the event queue static bool s_hasNewEvent; }; +// Unordered test events +struct TestNotificationService { + // Sets the service to the initial state. + static void Initialize() noexcept; + + // Set a new notification. + static void Set(std::string_view eventName) noexcept; + + // Blocks current thread and waits until expected event appears. + static void Wait(std::string_view eventName) noexcept; + + private: + static std::mutex s_mutex; // to synchronize access to the fields below + static std::condition_variable s_cv; // signals about new event + static std::set> s_events; // a set of events +}; + } // namespace ReactNativeIntegrationTests diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp index 5837fa9a1ea..ffe98e10513 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp @@ -99,6 +99,12 @@ struct CppTurboModuleSpec : TurboModuleSpec { SyncMethod{INDEX(c2), L"addSync"}, SyncMethod{INDEX(c2), L"negateSync"}, SyncMethod{INDEX(c2), L"sayHelloSync"}, + + Method) noexcept>{INDEX(c2), L"negateDeferredCallback"}, + Method, Callback) noexcept>{ + INDEX(c2), + L"negateDeferredTwoCallbacks"}, + Method) noexcept>{INDEX(c2), L"negateDeferredPromise"}, }; template @@ -150,6 +156,10 @@ struct CppTurboModuleSpec : TurboModuleSpec { REACT_SHOW_METHOD_SPEC_ERRORS(INDEX(c3), "addSync", "Generated error message with signatures"); REACT_SHOW_METHOD_SPEC_ERRORS(INDEX(c3), "negateSync", "Generated error message with signatures"); REACT_SHOW_METHOD_SPEC_ERRORS(INDEX(c3), "sayHelloSync", "Generated error message with signatures"); + + REACT_SHOW_METHOD_SPEC_ERRORS(INDEX(c3), "negateDeferredCallback", "Generated error message with signatures"); + REACT_SHOW_METHOD_SPEC_ERRORS(INDEX(c3), "negateDeferredTwoCallbacks", "Generated error message with signatures"); + REACT_SHOW_METHOD_SPEC_ERRORS(INDEX(c3), "negateDeferredPromise", "Generated error message with signatures"); } }; @@ -158,7 +168,14 @@ struct CppTurboModule { using ModuleSpec = CppTurboModuleSpec; REACT_INIT(Initialize) - void Initialize(ReactContext const & /*reactContext*/) noexcept {} + void Initialize(ReactContext const &reactContext) noexcept { + m_reactContext = reactContext; + } + + REACT_SYNC_METHOD(GetTestName, L"getTestName") + hstring GetTestName() noexcept { + return m_reactContext.Properties().Get(CppTurboModule::TestName).value(); + } REACT_CONSTANT(ConstantString, L"constantString") const std::string ConstantString{"myConstantString"}; @@ -475,6 +492,61 @@ struct CppTurboModule { std::string SayHelloSync() noexcept { return "Hello"; } + + REACT_METHOD(NegateDeferredCallback, L"negateDeferredCallback") + void NegateDeferredCallback(int x, std::function const &resolve) noexcept { + TestNotificationService::Set("NegateDeferredCallback called"); + auto dispatcher = ReactDispatcher(m_reactContext.Properties().Get(TestDispatcher)); + dispatcher.Post([x, resolve]() noexcept { + TestNotificationService::Set("NegateDeferredCallback call started"); + resolve(-x); + TestNotificationService::Set("NegateDeferredCallback call ended"); + }); + } + + REACT_METHOD(NegateDeferredTwoCallbacks, L"negateDeferredTwoCallbacks") + void NegateDeferredTwoCallbacks( + int x, + std::function const &resolve, + std::function const &reject) noexcept { + TestNotificationService::Set("NegateDeferredTwoCallbacks called"); + auto dispatcher = ReactDispatcher(m_reactContext.Properties().Get(TestDispatcher)); + dispatcher.Post([x, resolve, reject]() noexcept { + if (x >= 0) { + TestNotificationService::Set("NegateDeferredTwoCallbacks resolve started"); + resolve(-x); + } else { + TestNotificationService::Set("NegateDeferredTwoCallbacks reject started"); + reject("Already negative"); + } + TestNotificationService::Set("NegateDeferredTwoCallbacks call ended"); + }); + } + + REACT_METHOD(NegateDeferredPromise, L"negateDeferredPromise") + void NegateDeferredPromise(int x, React::ReactPromise const &result) noexcept { + TestNotificationService::Set("NegateDeferredPromise called"); + auto dispatcher = ReactDispatcher(m_reactContext.Properties().Get(TestDispatcher)); + dispatcher.Post([x, result]() noexcept { + if (x >= 0) { + TestNotificationService::Set("Promise resolve started"); + result.Resolve(-x); + } else { + TestNotificationService::Set("Promise reject started"); + React::ReactError error{}; + error.Message = "Already negative"; + result.Reject(std::move(error)); + } + TestNotificationService::Set("Promise call ended"); + }); + } + + public: + static inline ReactPropertyId TestName{L"TurboModuleTests", L"TestName"}; + static inline ReactPropertyId TestDispatcher{L"TurboModuleTests", L"TestDispatcher"}; + + private: + ReactContext m_reactContext; }; struct CppTurboModulePackageProvider : winrt::implements { @@ -492,6 +564,7 @@ TEST_CLASS (TurboModuleTests) { auto reactNativeHost = TestReactNativeHostHolder(L"TurboModuleTests", [](ReactNativeHost const &host) noexcept { host.PackageProviders().Append(winrt::make()); + ReactPropertyBag(host.InstanceSettings().Properties()).Set(CppTurboModule::TestName, L"ExecuteSampleTurboModule"); }); TestEventService::ObserveEvents({ @@ -561,6 +634,189 @@ TEST_CLASS (TurboModuleTests) { TestEvent{"sayHelloSync", "Hello"}, }); } + + TEST_METHOD(JSDispatcherAfterInstanceUnload) { + TestEventService::Initialize(); + TestNotificationService::Initialize(); + + auto reactNativeHost = TestReactNativeHostHolder(L"TurboModuleTests", [](ReactNativeHost const &host) noexcept { + host.PackageProviders().Append(winrt::make()); + ReactPropertyBag(host.InstanceSettings().Properties()) + .Set(CppTurboModule::TestName, L"JSDispatcherAfterInstanceUnload"); + host.InstanceSettings().InstanceDestroyed( + [&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const & /*args*/) { + TestNotificationService::Set("Instance destroyed event"); + }); + }); + + TestEventService::ObserveEvents({ + TestEvent{"addSync", 42}, + }); + + reactNativeHost.Host().UnloadInstance(); + TestNotificationService::Wait("Instance destroyed event"); + + // JSDispatcher must not process any callbacks + auto jsDispatcher = reactNativeHost.Host() + .InstanceSettings() + .Properties() + .Get(ReactDispatcherHelper::JSDispatcherProperty()) + .as(); + struct CallbackData { + ~CallbackData() { + TestNotificationService::Set("CallbackData destroyed"); + } + }; + bool callbackIsCalled{false}; + jsDispatcher.Post([&callbackIsCalled, data = std::make_shared()] { callbackIsCalled = true; }); + TestNotificationService::Wait("CallbackData destroyed"); + TestCheck(!callbackIsCalled); + } + + TEST_METHOD(DeferCallbackAfterInstanceUnload) { + TestNotificationService::Initialize(); + + auto testDispatcher = ReactDispatcher(ReactDispatcherHelper::CreateSerialDispatcher()); + testDispatcher.Post([] { TestNotificationService::Wait("Resume Dispatcher"); }); + + auto reactNativeHost = + TestReactNativeHostHolder(L"TurboModuleTests", [testDispatcher](ReactNativeHost const &host) noexcept { + host.PackageProviders().Append(winrt::make()); + auto properties = ReactPropertyBag(host.InstanceSettings().Properties()); + properties.Set(CppTurboModule::TestName, L"DeferCallbackAfterInstanceUnload"); + properties.Set(CppTurboModule::TestDispatcher, testDispatcher.Handle()); + host.InstanceSettings().InstanceDestroyed( + [&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const & /*args*/) { + TestNotificationService::Set("Instance destroyed event"); + }); + }); + + TestNotificationService::Wait("NegateDeferredCallback called"); + + reactNativeHost.Host().UnloadInstance(); + TestNotificationService::Wait("Instance destroyed event"); + + // No crash must happen when the Promise is resolved after RN instance unload. + TestNotificationService::Set("Resume Dispatcher"); + TestNotificationService::Wait("NegateDeferredCallback call started"); + TestNotificationService::Wait("NegateDeferredCallback call ended"); + } + + TEST_METHOD(DeferResolveCallbackAfterInstanceUnload) { + TestNotificationService::Initialize(); + + auto testDispatcher = ReactDispatcher(ReactDispatcherHelper::CreateSerialDispatcher()); + testDispatcher.Post([] { TestNotificationService::Wait("Resume Dispatcher"); }); + + auto reactNativeHost = + TestReactNativeHostHolder(L"TurboModuleTests", [testDispatcher](ReactNativeHost const &host) noexcept { + host.PackageProviders().Append(winrt::make()); + auto properties = ReactPropertyBag(host.InstanceSettings().Properties()); + properties.Set(CppTurboModule::TestName, L"DeferResolveCallbackAfterInstanceUnload"); + properties.Set(CppTurboModule::TestDispatcher, testDispatcher.Handle()); + host.InstanceSettings().InstanceDestroyed( + [&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const & /*args*/) { + TestNotificationService::Set("Instance destroyed event"); + }); + }); + + TestNotificationService::Wait("NegateDeferredTwoCallbacks called"); + + reactNativeHost.Host().UnloadInstance(); + TestNotificationService::Wait("Instance destroyed event"); + + // No crash must happen when the Promise is resolved after RN instance unload. + TestNotificationService::Set("Resume Dispatcher"); + TestNotificationService::Wait("NegateDeferredTwoCallbacks resolve started"); + TestNotificationService::Wait("NegateDeferredTwoCallbacks call ended"); + } + + TEST_METHOD(DeferRejectCallbackAfterInstanceUnload) { + TestNotificationService::Initialize(); + + auto testDispatcher = ReactDispatcher(ReactDispatcherHelper::CreateSerialDispatcher()); + testDispatcher.Post([] { TestNotificationService::Wait("Resume Dispatcher"); }); + + auto reactNativeHost = + TestReactNativeHostHolder(L"TurboModuleTests", [testDispatcher](ReactNativeHost const &host) noexcept { + host.PackageProviders().Append(winrt::make()); + auto properties = ReactPropertyBag(host.InstanceSettings().Properties()); + properties.Set(CppTurboModule::TestName, L"DeferRejectCallbackAfterInstanceUnload"); + properties.Set(CppTurboModule::TestDispatcher, testDispatcher.Handle()); + host.InstanceSettings().InstanceDestroyed( + [&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const & /*args*/) { + TestNotificationService::Set("Instance destroyed event"); + }); + }); + + TestNotificationService::Wait("NegateDeferredTwoCallbacks called"); + + reactNativeHost.Host().UnloadInstance(); + TestNotificationService::Wait("Instance destroyed event"); + + // No crash must happen when the Promise is resolved after RN instance unload. + TestNotificationService::Set("Resume Dispatcher"); + TestNotificationService::Wait("NegateDeferredTwoCallbacks reject started"); + TestNotificationService::Wait("NegateDeferredTwoCallbacks call ended"); + } + + TEST_METHOD(DeferPromiseResolveAfterInstanceUnload) { + TestNotificationService::Initialize(); + + auto testDispatcher = ReactDispatcher(ReactDispatcherHelper::CreateSerialDispatcher()); + testDispatcher.Post([] { TestNotificationService::Wait("Resume Dispatcher"); }); + + auto reactNativeHost = + TestReactNativeHostHolder(L"TurboModuleTests", [testDispatcher](ReactNativeHost const &host) noexcept { + host.PackageProviders().Append(winrt::make()); + auto properties = ReactPropertyBag(host.InstanceSettings().Properties()); + properties.Set(CppTurboModule::TestName, L"DeferPromiseResolveAfterInstanceUnload"); + properties.Set(CppTurboModule::TestDispatcher, testDispatcher.Handle()); + host.InstanceSettings().InstanceDestroyed( + [&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const & /*args*/) { + TestNotificationService::Set("Instance destroyed event"); + }); + }); + + TestNotificationService::Wait("NegateDeferredPromise called"); + + reactNativeHost.Host().UnloadInstance(); + TestNotificationService::Wait("Instance destroyed event"); + + // No crash must happen when the Promise is resolved after RN instance unload. + TestNotificationService::Set("Resume Dispatcher"); + TestNotificationService::Wait("Promise resolve started"); + TestNotificationService::Wait("Promise call ended"); + } + + TEST_METHOD(DeferPromiseRejectAfterInstanceUnload) { + TestNotificationService::Initialize(); + + auto testDispatcher = ReactDispatcher(ReactDispatcherHelper::CreateSerialDispatcher()); + testDispatcher.Post([] { TestNotificationService::Wait("Resume Dispatcher"); }); + + auto reactNativeHost = + TestReactNativeHostHolder(L"TurboModuleTests", [testDispatcher](ReactNativeHost const &host) noexcept { + host.PackageProviders().Append(winrt::make()); + auto properties = ReactPropertyBag(host.InstanceSettings().Properties()); + properties.Set(CppTurboModule::TestName, L"DeferPromiseRejectAfterInstanceUnload"); + properties.Set(CppTurboModule::TestDispatcher, testDispatcher.Handle()); + host.InstanceSettings().InstanceDestroyed( + [&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const & /*args*/) { + TestNotificationService::Set("Instance destroyed event"); + }); + }); + + TestNotificationService::Wait("NegateDeferredPromise called"); + + reactNativeHost.Host().UnloadInstance(); + TestNotificationService::Wait("Instance destroyed event"); + + // No crash must happen when the Promise is resolved after RN instance unload. + TestNotificationService::Set("Resume Dispatcher"); + TestNotificationService::Wait("Promise reject started"); + TestNotificationService::Wait("Promise call ended"); + } }; } // namespace ReactNativeIntegrationTests diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.js b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.js index eb41ee59f30..25370ad9bfd 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.js +++ b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.js @@ -12,118 +12,134 @@ const promisify2 = fn => res => fn(...args, result => res(result), result => res(result))); (async function runTests() { + const testName = CppTurboModule.getTestName(); try { - const c = CppTurboModule.getConstants(); - CppTurboModule.logAction("constantString", c.constantString); - CppTurboModule.logAction("constantInt", c.constantInt); - CppTurboModule.logAction("constantString2", c.constantString2); - CppTurboModule.logAction("constantInt2", c.constantInt2); - CppTurboModule.logAction("constantString3", c.constantString3); - CppTurboModule.logAction("constantInt3", c.constantInt3); + if (testName === "ExecuteSampleTurboModule") { + const c = CppTurboModule.getConstants(); + CppTurboModule.logAction("constantString", c.constantString); + CppTurboModule.logAction("constantInt", c.constantInt); + CppTurboModule.logAction("constantString2", c.constantString2); + CppTurboModule.logAction("constantInt2", c.constantInt2); + CppTurboModule.logAction("constantString3", c.constantString3); + CppTurboModule.logAction("constantInt3", c.constantInt3); - let result; - result = await promisify1(CppTurboModule.add)(2, 8); - CppTurboModule.logAction("add", result); - result = await promisify1(CppTurboModule.negate)(10); - CppTurboModule.logAction("negate", result); - result = await promisify1(CppTurboModule.sayHello)(); - CppTurboModule.logAction("sayHello", result); + let result; + result = await promisify1(CppTurboModule.add)(2, 8); + CppTurboModule.logAction("add", result); + result = await promisify1(CppTurboModule.negate)(10); + CppTurboModule.logAction("negate", result); + result = await promisify1(CppTurboModule.sayHello)(); + CppTurboModule.logAction("sayHello", result); - CppTurboModule.sayHello0(); - CppTurboModule.printPoint({ x: 1, y: 2 }); - CppTurboModule.printLine({ x: 1, y: 2 }, { x: 3, y: 4 }); + CppTurboModule.sayHello0(); + CppTurboModule.printPoint({ x: 1, y: 2 }); + CppTurboModule.printLine({ x: 1, y: 2 }, { x: 3, y: 4 }); - result = await promisify1(CppTurboModule.addCallback)(7, 8); - CppTurboModule.logAction("addCallback", result); - result = await promisify1(CppTurboModule.negateCallback)(15); - CppTurboModule.logAction("negateCallback", result); - result = await promisify1(CppTurboModule.negateAsyncCallback)(16); - CppTurboModule.logAction("negateAsyncCallback", result); - result = await promisify1(CppTurboModule.negateDispatchQueueCallback)(17); - CppTurboModule.logAction("negateDispatchQueueCallback", result); - result = await promisify1(CppTurboModule.negateFutureCallback)(18); - CppTurboModule.logAction("negateFutureCallback", result); - result = await promisify1(CppTurboModule.sayHelloCallback)(); - CppTurboModule.logAction("sayHelloCallback", result); + result = await promisify1(CppTurboModule.addCallback)(7, 8); + CppTurboModule.logAction("addCallback", result); + result = await promisify1(CppTurboModule.negateCallback)(15); + CppTurboModule.logAction("negateCallback", result); + result = await promisify1(CppTurboModule.negateAsyncCallback)(16); + CppTurboModule.logAction("negateAsyncCallback", result); + result = await promisify1(CppTurboModule.negateDispatchQueueCallback)(17); + CppTurboModule.logAction("negateDispatchQueueCallback", result); + result = await promisify1(CppTurboModule.negateFutureCallback)(18); + CppTurboModule.logAction("negateFutureCallback", result); + result = await promisify1(CppTurboModule.sayHelloCallback)(); + CppTurboModule.logAction("sayHelloCallback", result); - CppTurboModule.callbackZeroArgs(() => CppTurboModule.logAction("callbackZeroArgs", null)); - CppTurboModule.callbackTwoArgs((x, y) => CppTurboModule.logAction("callbackTwoArgs", { x, y })); - CppTurboModule.callbackThreeArgs((x, y, msg) => CppTurboModule.logAction("callbackThreeArgs", { x, y, msg })); + CppTurboModule.callbackZeroArgs(() => CppTurboModule.logAction("callbackZeroArgs", null)); + CppTurboModule.callbackTwoArgs((x, y) => CppTurboModule.logAction("callbackTwoArgs", { x, y })); + CppTurboModule.callbackThreeArgs((x, y, msg) => CppTurboModule.logAction("callbackThreeArgs", { x, y, msg })); - result = await promisify2(CppTurboModule.divideCallbacks)(10, 5); - CppTurboModule.logAction("divideCallbacks", result); - result = await promisify2(CppTurboModule.divideCallbacks)(10, 0); - CppTurboModule.logAction("divideCallbacks.error", result); - result = await promisify2(CppTurboModule.negateCallbacks)(10); - CppTurboModule.logAction("negateCallbacks", result); - result = await promisify2(CppTurboModule.negateCallbacks)(-10); - CppTurboModule.logAction("negateCallbacks.error", result); - result = await promisify2(CppTurboModule.negateAsyncCallbacks)(10); - CppTurboModule.logAction("negateAsyncCallbacks", result); - result = await promisify2(CppTurboModule.negateAsyncCallbacks)(-10); - CppTurboModule.logAction("negateAsyncCallbacks.error", result); - result = await promisify2(CppTurboModule.negateDispatchQueueCallbacks)(10); - CppTurboModule.logAction("negateDispatchQueueCallbacks", result); - result = await promisify2(CppTurboModule.negateDispatchQueueCallbacks)(-10); - CppTurboModule.logAction("negateDispatchQueueCallbacks.error", result); - result = await promisify2(CppTurboModule.negateFutureCallbacks)(10); - CppTurboModule.logAction("negateFutureCallbacks", result); - result = await promisify2(CppTurboModule.negateFutureCallbacks)(-10); - CppTurboModule.logAction("negateFutureCallbacks.error", result); - result = await promisify2(CppTurboModule.resolveSayHelloCallbacks)(); - CppTurboModule.logAction("resolveSayHelloCallbacks", result); - result = await promisify2(CppTurboModule.rejectSayHelloCallbacks)(); - CppTurboModule.logAction("rejectSayHelloCallbacks.error", result); + result = await promisify2(CppTurboModule.divideCallbacks)(10, 5); + CppTurboModule.logAction("divideCallbacks", result); + result = await promisify2(CppTurboModule.divideCallbacks)(10, 0); + CppTurboModule.logAction("divideCallbacks.error", result); + result = await promisify2(CppTurboModule.negateCallbacks)(10); + CppTurboModule.logAction("negateCallbacks", result); + result = await promisify2(CppTurboModule.negateCallbacks)(-10); + CppTurboModule.logAction("negateCallbacks.error", result); + result = await promisify2(CppTurboModule.negateAsyncCallbacks)(10); + CppTurboModule.logAction("negateAsyncCallbacks", result); + result = await promisify2(CppTurboModule.negateAsyncCallbacks)(-10); + CppTurboModule.logAction("negateAsyncCallbacks.error", result); + result = await promisify2(CppTurboModule.negateDispatchQueueCallbacks)(10); + CppTurboModule.logAction("negateDispatchQueueCallbacks", result); + result = await promisify2(CppTurboModule.negateDispatchQueueCallbacks)(-10); + CppTurboModule.logAction("negateDispatchQueueCallbacks.error", result); + result = await promisify2(CppTurboModule.negateFutureCallbacks)(10); + CppTurboModule.logAction("negateFutureCallbacks", result); + result = await promisify2(CppTurboModule.negateFutureCallbacks)(-10); + CppTurboModule.logAction("negateFutureCallbacks.error", result); + result = await promisify2(CppTurboModule.resolveSayHelloCallbacks)(); + CppTurboModule.logAction("resolveSayHelloCallbacks", result); + result = await promisify2(CppTurboModule.rejectSayHelloCallbacks)(); + CppTurboModule.logAction("rejectSayHelloCallbacks.error", result); - const twoCallbacksZeroArgs = useFirst => CppTurboModule.twoCallbacksZeroArgs(useFirst, - () => CppTurboModule.logAction("twoCallbacksZeroArgs1", "success"), - () => CppTurboModule.logAction("twoCallbacksZeroArgs2", "failure")); - twoCallbacksZeroArgs(true); - twoCallbacksZeroArgs(false); - const twoCallbacksTwoArgs = useFirst => CppTurboModule.twoCallbacksTwoArgs(useFirst, - (x, y) => CppTurboModule.logAction("twoCallbacksTwoArgs1", { x, y }), - (x, y) => CppTurboModule.logAction("twoCallbacksTwoArgs2", { x, y })); - twoCallbacksTwoArgs(true); - twoCallbacksTwoArgs(false); - const twoCallbacksThreeArgs = useFirst => CppTurboModule.twoCallbacksThreeArgs(useFirst, - (x, y, msg) => CppTurboModule.logAction("twoCallbacksThreeArgs1", { x, y, msg }), - (x, y, msg) => CppTurboModule.logAction("twoCallbacksThreeArgs2", { x, y, msg })); - twoCallbacksThreeArgs(true); - twoCallbacksThreeArgs(false); + const twoCallbacksZeroArgs = useFirst => CppTurboModule.twoCallbacksZeroArgs(useFirst, + () => CppTurboModule.logAction("twoCallbacksZeroArgs1", "success"), + () => CppTurboModule.logAction("twoCallbacksZeroArgs2", "failure")); + twoCallbacksZeroArgs(true); + twoCallbacksZeroArgs(false); + const twoCallbacksTwoArgs = useFirst => CppTurboModule.twoCallbacksTwoArgs(useFirst, + (x, y) => CppTurboModule.logAction("twoCallbacksTwoArgs1", { x, y }), + (x, y) => CppTurboModule.logAction("twoCallbacksTwoArgs2", { x, y })); + twoCallbacksTwoArgs(true); + twoCallbacksTwoArgs(false); + const twoCallbacksThreeArgs = useFirst => CppTurboModule.twoCallbacksThreeArgs(useFirst, + (x, y, msg) => CppTurboModule.logAction("twoCallbacksThreeArgs1", { x, y, msg }), + (x, y, msg) => CppTurboModule.logAction("twoCallbacksThreeArgs2", { x, y, msg })); + twoCallbacksThreeArgs(true); + twoCallbacksThreeArgs(false); - await CppTurboModule.dividePromise(10, 2) - .then(r => CppTurboModule.logAction("dividePromise", r)); - await CppTurboModule.dividePromise(10, 0) - .catch(e => CppTurboModule.logAction("dividePromise.error", e.message)); - await CppTurboModule.negatePromise(10) - .then(r => CppTurboModule.logAction("negatePromise", r)); - await CppTurboModule.negatePromise(-10) - .catch(e => CppTurboModule.logAction("negatePromise.error", e.message)); - await CppTurboModule.negateAsyncPromise(10) - .then(r => CppTurboModule.logAction("negateAsyncPromise", r)); - await CppTurboModule.negateAsyncPromise(-10) - .catch(e => CppTurboModule.logAction("negateAsyncPromise.error", e.message)); - await CppTurboModule.negateAsyncPromise(10) - .then(r => CppTurboModule.logAction("negateDispatchQueuePromise", r)); - await CppTurboModule.negateAsyncPromise(-10) - .catch(e => CppTurboModule.logAction("negateDispatchQueuePromise.error", e.message)); - await CppTurboModule.negateAsyncPromise(10) - .then(r => CppTurboModule.logAction("negateFuturePromise", r)); - await CppTurboModule.negateAsyncPromise(-10) - .catch(e => CppTurboModule.logAction("negateFuturePromise.error", e.message)); - await CppTurboModule.voidPromise(2) - .then(() => CppTurboModule.logAction("voidPromise", "success")); - await CppTurboModule.voidPromise(1) - .catch(e => CppTurboModule.logAction("voidPromise.error", e.message)); - await CppTurboModule.resolveSayHelloPromise() - .then(r => CppTurboModule.logAction("resolveSayHelloPromise", r)); - await CppTurboModule.rejectSayHelloPromise() - .catch(e => CppTurboModule.logAction("rejectSayHelloPromise", e.message)); + await CppTurboModule.dividePromise(10, 2) + .then(r => CppTurboModule.logAction("dividePromise", r)); + await CppTurboModule.dividePromise(10, 0) + .catch(e => CppTurboModule.logAction("dividePromise.error", e.message)); + await CppTurboModule.negatePromise(10) + .then(r => CppTurboModule.logAction("negatePromise", r)); + await CppTurboModule.negatePromise(-10) + .catch(e => CppTurboModule.logAction("negatePromise.error", e.message)); + await CppTurboModule.negateAsyncPromise(10) + .then(r => CppTurboModule.logAction("negateAsyncPromise", r)); + await CppTurboModule.negateAsyncPromise(-10) + .catch(e => CppTurboModule.logAction("negateAsyncPromise.error", e.message)); + await CppTurboModule.negateAsyncPromise(10) + .then(r => CppTurboModule.logAction("negateDispatchQueuePromise", r)); + await CppTurboModule.negateAsyncPromise(-10) + .catch(e => CppTurboModule.logAction("negateDispatchQueuePromise.error", e.message)); + await CppTurboModule.negateAsyncPromise(10) + .then(r => CppTurboModule.logAction("negateFuturePromise", r)); + await CppTurboModule.negateAsyncPromise(-10) + .catch(e => CppTurboModule.logAction("negateFuturePromise.error", e.message)); + await CppTurboModule.voidPromise(2) + .then(() => CppTurboModule.logAction("voidPromise", "success")); + await CppTurboModule.voidPromise(1) + .catch(e => CppTurboModule.logAction("voidPromise.error", e.message)); + await CppTurboModule.resolveSayHelloPromise() + .then(r => CppTurboModule.logAction("resolveSayHelloPromise", r)); + await CppTurboModule.rejectSayHelloPromise() + .catch(e => CppTurboModule.logAction("rejectSayHelloPromise", e.message)); - CppTurboModule.logAction("addSync", CppTurboModule.addSync(40, 2)); - CppTurboModule.logAction("negateSync", CppTurboModule.negateSync(12)); - CppTurboModule.logAction("sayHelloSync", CppTurboModule.sayHelloSync()); + CppTurboModule.logAction("addSync", CppTurboModule.addSync(40, 2)); + CppTurboModule.logAction("negateSync", CppTurboModule.negateSync(12)); + CppTurboModule.logAction("sayHelloSync", CppTurboModule.sayHelloSync()); + } else if (testName === "JSDispatcherAfterInstanceUnload") { + CppTurboModule.logAction("addSync", CppTurboModule.addSync(40, 2)); + } else if (testName === "DeferCallbackAfterInstanceUnload") { + CppTurboModule.negateDeferredCallback(4, _ => { }); + } else if (testName === "DeferResolveCallbackAfterInstanceUnload") { + CppTurboModule.negateDeferredTwoCallbacks(4, _ => { }, _ => { }); + } else if (testName === "DeferRejectCallbackAfterInstanceUnload") { + CppTurboModule.negateDeferredTwoCallbacks(-4, _ => { }, _ => { }); + } else if (testName === "DeferPromiseResolveAfterInstanceUnload") { + CppTurboModule.negateDeferredPromise(4); + } else if (testName === "DeferPromiseRejectAfterInstanceUnload") { + CppTurboModule.negateDeferredPromise(-4); + } } catch (err) { CppTurboModule.logAction("Error", err.message); } + })(); diff --git a/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp index 5548ca91d4a..0d934013fa5 100644 --- a/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp +++ b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp @@ -8,35 +8,57 @@ namespace winrt::Microsoft::ReactNative { +// Special IJSValueWriter that does nothing. +// We use it instead of JsiWriter when JSI runtime is not available anymore. +struct JSNoopWriter : winrt::implements { + public: // IJSValueWriter + void WriteNull() noexcept; + void WriteBoolean(bool value) noexcept; + void WriteInt64(int64_t value) noexcept; + void WriteDouble(double value) noexcept; + void WriteString(const winrt::hstring &value) noexcept; + void WriteObjectBegin() noexcept; + void WritePropertyName(const winrt::hstring &name) noexcept; + void WriteObjectEnd() noexcept; + void WriteArrayBegin() noexcept; + void WriteArrayEnd() noexcept; +}; + //=========================================================================== // JSDispatcherWriter implementation //=========================================================================== JSDispatcherWriter::JSDispatcherWriter( IReactDispatcher const &jsDispatcher, - facebook::jsi::Runtime &jsiRuntime) noexcept - : m_jsDispatcher(jsDispatcher), m_jsiRuntime(jsiRuntime) {} + std::weak_ptr jsiRuntimeHolder) noexcept + : m_jsDispatcher(jsDispatcher), m_jsiRuntimeHolder(std::move(jsiRuntimeHolder)) {} void JSDispatcherWriter::WithResultArgs( Mso::Functor handler) noexcept { if (m_jsDispatcher.HasThreadAccess()) { VerifyElseCrash(!m_dynamicWriter); - const facebook::jsi::Value *args{nullptr}; - size_t argCount{0}; - m_jsiWriter->AccessResultAsArgs(args, argCount); - handler(m_jsiRuntime, args, argCount); + if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { + const facebook::jsi::Value *args{nullptr}; + size_t argCount{0}; + m_jsiWriter->AccessResultAsArgs(args, argCount); + handler(jsiRuntimeHolder->Runtime(), args, argCount); + m_jsiWriter = nullptr; + } } else { VerifyElseCrash(!m_jsiWriter); folly::dynamic dynValue = m_dynamicWriter->TakeValue(); - m_jsDispatcher.Post([handler, dynValue, &runtime = m_jsiRuntime]() { - VerifyElseCrash(dynValue.isArray()); - std::vector args; - args.reserve(dynValue.size()); - for (auto const &item : dynValue) { - args.emplace_back(facebook::jsi::valueFromDynamic(runtime, item)); + VerifyElseCrash(dynValue.isArray()); + m_jsDispatcher.Post([handler, dynValue = std::move(dynValue), weakJsiRuntimeHolder = m_jsiRuntimeHolder]() { + if (auto jsiRuntimeHolder = weakJsiRuntimeHolder.lock()) { + std::vector args; + args.reserve(dynValue.size()); + auto &runtime = jsiRuntimeHolder->Runtime(); + for (auto const &item : dynValue) { + args.emplace_back(facebook::jsi::valueFromDynamic(runtime, item)); + } + handler(runtime, args.data(), args.size()); } - handler(runtime, args.data(), args.size()); }); } } @@ -82,20 +104,36 @@ void JSDispatcherWriter::WriteArrayEnd() noexcept { } IJSValueWriter JSDispatcherWriter::GetWriter() noexcept { - if (m_jsDispatcher.HasThreadAccess()) { - VerifyElseCrash(!m_dynamicWriter); - if (!m_jsiWriter) { - m_jsiWriter = winrt::make_self(m_jsiRuntime); - m_writer = m_jsiWriter.as(); - } - } else { - VerifyElseCrash(!m_jsiWriter); - if (!m_dynamicWriter) { + if (!m_writer) { + if (m_jsDispatcher.HasThreadAccess()) { + if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { + m_jsiWriter = winrt::make_self(jsiRuntimeHolder->Runtime()); + m_writer = m_jsiWriter.as(); + } else { + m_writer = winrt::make(); + } + } else { m_dynamicWriter = winrt::make_self(); m_writer = m_dynamicWriter.as(); } } + Debug(VerifyElseCrash(m_dynamicWriter != nullptr || m_jsDispatcher.HasThreadAccess())); return m_writer; } +//=========================================================================== +// JSNoopWriter implementation +//=========================================================================== + +void JSNoopWriter::WriteNull() noexcept {} +void JSNoopWriter::WriteBoolean(bool /*value*/) noexcept {} +void JSNoopWriter::WriteInt64(int64_t /*value*/) noexcept {} +void JSNoopWriter::WriteDouble(double /*value*/) noexcept {} +void JSNoopWriter::WriteString(const winrt::hstring & /*value*/) noexcept {} +void JSNoopWriter::WriteObjectBegin() noexcept {} +void JSNoopWriter::WritePropertyName(const winrt::hstring & /*name*/) noexcept {} +void JSNoopWriter::WriteObjectEnd() noexcept {} +void JSNoopWriter::WriteArrayBegin() noexcept {} +void JSNoopWriter::WriteArrayEnd() noexcept {} + } // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JSDispatcherWriter.h b/vnext/Microsoft.ReactNative/JSDispatcherWriter.h index c710e528948..1599e48905a 100644 --- a/vnext/Microsoft.ReactNative/JSDispatcherWriter.h +++ b/vnext/Microsoft.ReactNative/JSDispatcherWriter.h @@ -2,10 +2,10 @@ // Licensed under the MIT License. #pragma once +#include #include #include "DynamicWriter.h" #include "JsiWriter.h" -#include "folly/dynamic.h" #include "winrt/Microsoft.ReactNative.h" namespace winrt::Microsoft::ReactNative { @@ -14,7 +14,9 @@ namespace winrt::Microsoft::ReactNative { // In case if writing is done outside of JSDispatcher, it uses DynamicWriter to create // folly::dynamic which then is written to JsiWriter in JSDispatcher. struct JSDispatcherWriter : winrt::implements { - JSDispatcherWriter(IReactDispatcher const &jsDispatcher, facebook::jsi::Runtime &jsiRuntime) noexcept; + JSDispatcherWriter( + IReactDispatcher const &jsDispatcher, + std::weak_ptr jsiRuntimeHolder) noexcept; void WithResultArgs(Mso::Functor handler) noexcept; @@ -35,7 +37,7 @@ struct JSDispatcherWriter : winrt::implements m_jsiRuntimeHolder; winrt::com_ptr m_dynamicWriter; winrt::com_ptr m_jsiWriter; IJSValueWriter m_writer; diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index 5f919f668dc..68f34ccff03 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -510,6 +510,7 @@ void ReactInstanceWin::Initialize() noexcept { std::move(bundleRootPath), // bundleRootPath std::move(cxxModules), m_options.TurboModuleProvider, + m_options.TurboModuleProvider->LongLivedObjectCollection(), std::make_unique(weakThis), m_jsMessageThread.Load(), m_nativeMessageThread.Load(), diff --git a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp index 2b9c2f0beca..105dac727b0 100644 --- a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp +++ b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +// // IMPORTANT: Before updating this file // please read react-native-windows repo: // vnext/Microsoft.ReactNative.Cxx/README.md @@ -21,6 +22,7 @@ using namespace winrt; using namespace Windows::Foundation; namespace winrt::Microsoft::ReactNative { + /*------------------------------------------------------------------------------- TurboModuleBuilder -------------------------------------------------------------------------------*/ @@ -56,10 +58,17 @@ struct TurboModuleBuilder : winrt::implements m_methods; - std::unordered_map m_syncMethods; - std::vector m_constantProviders; - bool m_constantsEvaluated{false}; + const std::unordered_map &Methods() const noexcept { + return m_methods; + } + + const std::unordered_map &SyncMethods() const noexcept { + return m_syncMethods; + } + + const std::vector &ConstantProviders() const noexcept { + return m_constantProviders; + } private: void EnsureMemberNotSet(const std::string &key, bool checkingMethod) noexcept { @@ -72,6 +81,10 @@ struct TurboModuleBuilder : winrt::implements m_methods; + std::unordered_map m_syncMethods; + std::vector m_constantProviders; + bool m_constantsEvaluated{false}; }; /*------------------------------------------------------------------------------- @@ -84,11 +97,13 @@ class TurboModuleImpl : public facebook::react::TurboModule { const IReactContext &reactContext, const std::string &name, const std::shared_ptr &jsInvoker, + std::weak_ptr longLivedObjectCollection, const ReactModuleProvider &reactModuleProvider) : facebook::react::TurboModule(name, jsInvoker), m_reactContext(reactContext), - m_moduleBuilder(winrt::make(reactContext)), - m_providedModule(reactModuleProvider(m_moduleBuilder)) { + m_longLivedObjectCollection(std::move(longLivedObjectCollection)), + m_moduleBuilder(winrt::make_self(reactContext)), + m_providedModule(reactModuleProvider(m_moduleBuilder.as())) { if (auto hostObject = m_providedModule.try_as()) { m_hostObjectWrapper = std::make_shared(hostObject); } @@ -99,12 +114,23 @@ class TurboModuleImpl : public facebook::react::TurboModule { return m_hostObjectWrapper->getPropertyNames(rt); } - auto turboModuleBuilder = m_moduleBuilder.as(); std::vector propertyNames; - propertyNames.reserve(turboModuleBuilder->m_methods.size()); - for (auto &methodInfo : turboModuleBuilder->m_methods) { + propertyNames.reserve( + m_moduleBuilder->Methods().size() + m_moduleBuilder->SyncMethods().size() + + (m_moduleBuilder->ConstantProviders().empty() ? 0 : 1)); + + for (auto &methodInfo : m_moduleBuilder->Methods()) { propertyNames.push_back(facebook::jsi::PropNameID::forAscii(rt, methodInfo.first)); } + + for (auto &syncMethodInfo : m_moduleBuilder->SyncMethods()) { + propertyNames.push_back(facebook::jsi::PropNameID::forAscii(rt, syncMethodInfo.first)); + } + + if (!m_moduleBuilder->ConstantProviders().empty()) { + propertyNames.push_back(facebook::jsi::PropNameID::forAscii(rt, "getConstants")); + } + return propertyNames; }; @@ -114,24 +140,23 @@ class TurboModuleImpl : public facebook::react::TurboModule { } // it is not safe to assume that "runtime" never changes, so members are not cached here - auto moduleBuilder = m_moduleBuilder.as(); std::string key = propName.utf8(runtime); - if (key == "getConstants" && !moduleBuilder->m_constantProviders.empty()) { + if (key == "getConstants" && !m_moduleBuilder->ConstantProviders().empty()) { // try to find getConstants if there is any constant return facebook::jsi::Function::createFromHostFunction( runtime, propName, 0, - [moduleBuilder]( + [moduleBuilder = m_moduleBuilder]( facebook::jsi::Runtime &rt, - const facebook::jsi::Value &thisVal, - const facebook::jsi::Value *args, - size_t count) { + const facebook::jsi::Value & /*thisVal*/, + const facebook::jsi::Value * /*args*/, + size_t /*count*/) { // collect all constants to an object auto writer = winrt::make(rt); writer.WriteObjectBegin(); - for (auto constantProvider : moduleBuilder->m_constantProviders) { + for (auto const &constantProvider : moduleBuilder->ConstantProviders()) { constantProvider(writer); } writer.WriteObjectEnd(); @@ -141,21 +166,21 @@ class TurboModuleImpl : public facebook::react::TurboModule { { // try to find a Method - auto it = moduleBuilder->m_methods.find(key); - if (it != moduleBuilder->m_methods.end()) { - TurboModuleMethodInfo methodInfo = it->second; + auto it = m_moduleBuilder->Methods().find(key); + if (it != m_moduleBuilder->Methods().end()) { + TurboModuleMethodInfo const &methodInfo = it->second; switch (methodInfo.ReturnType) { case MethodReturnType::Void: return facebook::jsi::Function::createFromHostFunction( runtime, propName, 0, - [methodInfo]( + [method = methodInfo.Method]( facebook::jsi::Runtime &rt, const facebook::jsi::Value & /*thisVal*/, const facebook::jsi::Value *args, size_t argCount) { - methodInfo.Method(winrt::make(rt, args, argCount), nullptr, nullptr, nullptr); + method(winrt::make(rt, args, argCount), nullptr, nullptr, nullptr); return facebook::jsi::Value::undefined(); }); case MethodReturnType::Callback: @@ -163,17 +188,22 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsDispatcher = m_reactContext.JSDispatcher(), methodInfo]( + [jsDispatcher = m_reactContext.JSDispatcher(), + method = methodInfo.Method, + longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, const facebook::jsi::Value & /*thisVal*/, const facebook::jsi::Value *args, size_t argCount) { VerifyElseCrash(argCount > 0); - methodInfo.Method( - winrt::make(rt, args, argCount - 1), - winrt::make(jsDispatcher, rt), - MakeCallback(rt, {rt, args[argCount - 1]}), - nullptr); + if (auto strongLongLivedObjectCollection = longLivedObjectCollection.lock()) { + auto jsiRuntimeHolder = LongLivedJsiRuntime::CreateWeak(strongLongLivedObjectCollection, rt); + method( + winrt::make(rt, args, argCount - 1), + winrt::make(jsDispatcher, jsiRuntimeHolder), + MakeCallback(rt, strongLongLivedObjectCollection, args[argCount - 1]), + nullptr); + } return facebook::jsi::Value::undefined(); }); case MethodReturnType::TwoCallbacks: @@ -181,17 +211,22 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsDispatcher = m_reactContext.JSDispatcher(), methodInfo]( + [jsDispatcher = m_reactContext.JSDispatcher(), + method = methodInfo.Method, + longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, const facebook::jsi::Value & /*thisVal*/, const facebook::jsi::Value *args, size_t argCount) { VerifyElseCrash(argCount > 1); - methodInfo.Method( - winrt::make(rt, args, argCount - 2), - winrt::make(jsDispatcher, rt), - MakeCallback(rt, {rt, args[argCount - 2]}), - MakeCallback(rt, {rt, args[argCount - 1]})); + if (auto strongLongLivedObjectCollection = longLivedObjectCollection.lock()) { + auto jsiRuntimeHolder = LongLivedJsiRuntime::CreateWeak(strongLongLivedObjectCollection, rt); + method( + winrt::make(rt, args, argCount - 2), + winrt::make(jsDispatcher, jsiRuntimeHolder), + MakeCallback(rt, strongLongLivedObjectCollection, args[argCount - 2]), + MakeCallback(rt, strongLongLivedObjectCollection, args[argCount - 1])); + } return facebook::jsi::Value::undefined(); }); case MethodReturnType::Promise: @@ -199,52 +234,66 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsDispatcher = m_reactContext.JSDispatcher(), methodInfo]( + [jsDispatcher = m_reactContext.JSDispatcher(), + method = methodInfo.Method, + longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, const facebook::jsi::Value & /*thisVal*/, const facebook::jsi::Value *args, size_t count) { - auto argReader = winrt::make(rt, args, count); - auto argWriter = winrt::make(jsDispatcher, rt); - return facebook::react::createPromiseAsJSIValue( - rt, - [methodInfo, argReader, argWriter]( - facebook::jsi::Runtime &runtime, std::shared_ptr promise) { - methodInfo.Method( - argReader, - argWriter, - [promise](const IJSValueWriter &writer) { - writer.as()->WithResultArgs([promise]( - facebook::jsi::Runtime &runtime, - facebook::jsi::Value const *args, - size_t argCount) { - VerifyElseCrash(argCount == 1); - promise->resolve(args[0]); + if (auto strongLongLivedObjectCollection = longLivedObjectCollection.lock()) { + auto jsiRuntimeHolder = LongLivedJsiRuntime::CreateWeak(strongLongLivedObjectCollection, rt); + auto argReader = winrt::make(rt, args, count); + auto argWriter = winrt::make(jsDispatcher, jsiRuntimeHolder); + return facebook::react::createPromiseAsJSIValue( + rt, + [method, argReader, argWriter, strongLongLivedObjectCollection]( + facebook::jsi::Runtime &runtime, std::shared_ptr promise) { + method( + argReader, + argWriter, + [weakResolve = LongLivedJsiFunction::CreateWeak( + strongLongLivedObjectCollection, runtime, std::move(promise->resolve_))]( + const IJSValueWriter &writer) { + writer.as()->WithResultArgs([weakResolve]( + facebook::jsi::Runtime &runtime, + facebook::jsi::Value const *args, + size_t argCount) { + VerifyElseCrash(argCount == 1); + if (auto resolveHolder = weakResolve.lock()) { + resolveHolder->Value().call(runtime, args[0]); + } + }); + }, + [weakReject = LongLivedJsiFunction::CreateWeak( + strongLongLivedObjectCollection, runtime, std::move(promise->reject_))]( + const IJSValueWriter &writer) { + writer.as()->WithResultArgs([weakReject]( + facebook::jsi::Runtime &runtime, + facebook::jsi::Value const *args, + size_t argCount) { + VerifyElseCrash(argCount == 1); + if (auto rejectHolder = weakReject.lock()) { + // To match the Android and iOS TurboModule behavior we create the Error object for + // the Promise rejection the same way as in updateErrorWithErrorData method. + // See react-native/Libraries/BatchedBridge/NativeModules.js for details. + auto error = runtime.global() + .getPropertyAsFunction(runtime, "Error") + .callAsConstructor(runtime, {}); + auto &errorData = args[0]; + if (errorData.isObject()) { + runtime.global() + .getPropertyAsObject(runtime, "Object") + .getPropertyAsFunction(runtime, "assign") + .call(runtime, error, errorData.getObject(runtime)); + } + rejectHolder->Value().call(runtime, args[0]); + } + }); }); - }, - [promise](const IJSValueWriter &writer) { - writer.as()->WithResultArgs([promise]( - facebook::jsi::Runtime &runtime, - facebook::jsi::Value const *args, - size_t argCount) { - VerifyElseCrash(argCount == 1); - // To match the Android and iOS TurboModule behavior we create the Error object for - // the Promise rejection the same way as in updateErrorWithErrorData method. - // See react-native/Libraries/BatchedBridge/NativeModules.js for details. - auto error = runtime.global() - .getPropertyAsFunction(runtime, "Error") - .callAsConstructor(runtime, {}); - auto &errorData = args[0]; - if (errorData.isObject()) { - runtime.global() - .getPropertyAsObject(runtime, "Object") - .getPropertyAsFunction(runtime, "assign") - .call(runtime, error, errorData.getObject(runtime)); - } - promise->reject_.call(runtime, error); - }); - }); - }); + }); + } + return facebook::jsi::Value::undefined(); }); default: VerifyElseCrash(false); @@ -254,8 +303,8 @@ class TurboModuleImpl : public facebook::react::TurboModule { { // try to find a SyncMethod - auto it = moduleBuilder->m_syncMethods.find(key); - if (it != moduleBuilder->m_syncMethods.end()) { + auto it = m_moduleBuilder->SyncMethods().find(key); + if (it != m_moduleBuilder->SyncMethods().end()) { return facebook::jsi::Function::createFromHostFunction( runtime, propName, @@ -287,27 +336,34 @@ class TurboModuleImpl : public facebook::react::TurboModule { } private: - static MethodResultCallback MakeCallback(facebook::jsi::Runtime &runtime, facebook::jsi::Value callback) noexcept { - auto sharedCallback = - std::make_shared(std::move(callback).asObject(runtime).asFunction(runtime)); - return [sharedCallback = std::move(sharedCallback)](const IJSValueWriter &writer) noexcept { + static MethodResultCallback MakeCallback( + facebook::jsi::Runtime &rt, + const std::shared_ptr &longLivedObjectCollection, + const facebook::jsi::Value &callback) noexcept { + auto weakCallback = + LongLivedJsiFunction::CreateWeak(longLivedObjectCollection, rt, callback.getObject(rt).getFunction(rt)); + return [weakCallback = std::move(weakCallback)](const IJSValueWriter &writer) noexcept { writer.as()->WithResultArgs( - [sharedCallback](facebook::jsi::Runtime &rt, facebook::jsi::Value const *args, size_t count) { - sharedCallback->call(rt, args, count); + [weakCallback](facebook::jsi::Runtime &rt, facebook::jsi::Value const *args, size_t count) { + if (auto callback = weakCallback.lock()) { + callback->Value().call(rt, args, count); + } }); }; } private: IReactContext m_reactContext; - IReactModuleBuilder m_moduleBuilder; + winrt::com_ptr m_moduleBuilder; IInspectable m_providedModule; std::shared_ptr m_hostObjectWrapper; + std::weak_ptr m_longLivedObjectCollection; }; /*------------------------------------------------------------------------------- TurboModulesProvider -------------------------------------------------------------------------------*/ + std::shared_ptr TurboModulesProvider::getModule( const std::string &moduleName, const std::shared_ptr &callInvoker) noexcept { @@ -317,7 +373,8 @@ std::shared_ptr TurboModulesProvider::getModule( return nullptr; } - auto tm = std::make_shared(m_reactContext, moduleName, callInvoker, it->second); + auto tm = std::make_shared( + m_reactContext, moduleName, callInvoker, m_longLivedObjectCollection, /*reactModuleProvider*/ it->second); return tm; } @@ -348,4 +405,9 @@ void TurboModulesProvider::AddModuleProvider( } } +std::shared_ptr const & +TurboModulesProvider::LongLivedObjectCollection() noexcept { + return m_longLivedObjectCollection; +} + } // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/TurboModulesProvider.h b/vnext/Microsoft.ReactNative/TurboModulesProvider.h index 2bc66029885..bd40f7fe0c2 100644 --- a/vnext/Microsoft.ReactNative/TurboModulesProvider.h +++ b/vnext/Microsoft.ReactNative/TurboModulesProvider.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include "Base/FollyIncludes.h" #include "winrt/Microsoft.ReactNative.h" @@ -25,8 +26,12 @@ class TurboModulesProvider final : public facebook::react::TurboModuleRegistry { winrt::hstring const &moduleName, ReactModuleProvider const &moduleProvider, bool overwriteExisting) noexcept; + std::shared_ptr const &LongLivedObjectCollection() noexcept; private: + // To keep a list of deferred asynchronous callbacks and promises. + std::shared_ptr m_longLivedObjectCollection{ + std::make_shared()}; std::unordered_map m_moduleProviders; IReactContext m_reactContext; }; diff --git a/vnext/Shared/InstanceManager.cpp b/vnext/Shared/InstanceManager.cpp index 7328d6a0127..2d07430d48c 100644 --- a/vnext/Shared/InstanceManager.cpp +++ b/vnext/Shared/InstanceManager.cpp @@ -41,6 +41,35 @@ std::shared_ptr CreateReactInstance( std::move(jsBundleBasePath), std::move(cxxModules), std::move(turboModuleRegistry), + nullptr, + std::move(callback), + std::move(jsQueue), + std::move(nativeQueue), + std::move(devSettings), + GetSharedDevManager()); + + return inner; +} + +std::shared_ptr CreateReactInstance( + std::shared_ptr &&instance, + std::string &&jsBundleBasePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings) noexcept { + // Now create the instance + std::shared_ptr inner = InstanceImpl::MakeNoBundle( + std::move(instance), + std::move(jsBundleBasePath), + std::move(cxxModules), + std::move(turboModuleRegistry), + std::move(longLivedObjectCollection), std::move(callback), std::move(jsQueue), std::move(nativeQueue), diff --git a/vnext/Shared/InstanceManager.h b/vnext/Shared/InstanceManager.h index 186ee516d15..ee6433fde12 100644 --- a/vnext/Shared/InstanceManager.h +++ b/vnext/Shared/InstanceManager.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -51,6 +52,19 @@ std::shared_ptr CreateReactInstance( std::shared_ptr nativeQueue, std::shared_ptr devSettings) noexcept; +std::shared_ptr CreateReactInstance( + std::shared_ptr &&instance, + std::string &&jsBundleRelativePath, + std::vector< + std::tuple>> + &&cxxModules, + std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, + std::unique_ptr &&callback, + std::shared_ptr jsQueue, + std::shared_ptr nativeQueue, + std::shared_ptr devSettings) noexcept; + std::shared_ptr CreateReactInstance( std::shared_ptr &&instance, std::string &&jsBundleBasePath, diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index 17adb928611..d22c7768c45 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -106,11 +106,13 @@ class OJSIExecutorFactory : public JSExecutorFactory { auto turboModuleManager = std::make_shared(turboModuleRegistry_, jsCallInvoker_); // TODO: The binding here should also add the proxys that convert cxxmodules into turbomodules + // [vmoroz] Note, that we must not use the RN TurboCxxModule.h code because it uses global LongLivedObjectCollection + // instance that prevents us from using multiple RN instance in the same process. auto binding = [turboModuleManager](const std::string &name) -> std::shared_ptr { return turboModuleManager->getModule(name); }; - TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding)); + TurboModuleBinding::install(*runtimeHolder_->getRuntime(), std::function(binding), longLivedObjectCollection_); // init TurboModule for (const auto &moduleName : turboModuleManager->getEagerInitModuleNames()) { @@ -132,17 +134,20 @@ class OJSIExecutorFactory : public JSExecutorFactory { std::shared_ptr runtimeHolder, NativeLoggingHook loggingHook, std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, bool isProfilingEnabled, std::shared_ptr jsCallInvoker) noexcept : runtimeHolder_{std::move(runtimeHolder)}, loggingHook_{std::move(loggingHook)}, turboModuleRegistry_{std::move(turboModuleRegistry)}, + longLivedObjectCollection_{std::move(longLivedObjectCollection)}, jsCallInvoker_{std::move(jsCallInvoker)}, isProfilingEnabled_{isProfilingEnabled} {} private: std::shared_ptr runtimeHolder_; std::shared_ptr turboModuleRegistry_; + std::shared_ptr longLivedObjectCollection_; std::shared_ptr jsCallInvoker_; NativeLoggingHook loggingHook_; bool isProfilingEnabled_; @@ -159,6 +164,7 @@ void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const c std::tuple>> &&cxxModules, std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, std::unique_ptr &&callback, std::shared_ptr jsQueue, std::shared_ptr nativeQueue, @@ -169,6 +175,7 @@ void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const c std::move(jsBundleBasePath), std::move(cxxModules), std::move(turboModuleRegistry), + std::move(longLivedObjectCollection), std::move(callback), std::move(jsQueue), std::move(nativeQueue), @@ -198,6 +205,7 @@ void logMarker(const facebook::react::ReactMarker::ReactMarkerId /*id*/, const c std::move(jsBundleBasePath), std::move(cxxModules), std::move(turboModuleRegistry), + nullptr, std::move(callback), std::move(jsQueue), std::move(nativeQueue), @@ -235,12 +243,14 @@ InstanceImpl::InstanceImpl( std::tuple>> &&cxxModules, std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, std::unique_ptr &&callback, std::shared_ptr jsQueue, std::shared_ptr nativeQueue, std::shared_ptr devSettings, std::shared_ptr devManager) : m_turboModuleRegistry(std::move(turboModuleRegistry)), + m_longLivedObjectCollection(std::move(longLivedObjectCollection)), m_jsThread(std::move(jsQueue)), m_nativeQueue(nativeQueue), m_jsBundleBasePath(std::move(jsBundleBasePath)), @@ -301,6 +311,7 @@ InstanceImpl::InstanceImpl( m_devSettings->jsiRuntimeHolder, m_devSettings->loggingCallback, m_turboModuleRegistry, + m_longLivedObjectCollection, !m_devSettings->useFastRefresh, m_innerInstance->getJSCallInvoker()); } else { @@ -365,6 +376,7 @@ InstanceImpl::InstanceImpl( m_devSettings->jsiRuntimeHolder, m_devSettings->loggingCallback, m_turboModuleRegistry, + m_longLivedObjectCollection, !m_devSettings->useFastRefresh, m_innerInstance->getJSCallInvoker()); } diff --git a/vnext/Shared/OInstance.h b/vnext/Shared/OInstance.h index c6452311ff0..d09e2d25353 100644 --- a/vnext/Shared/OInstance.h +++ b/vnext/Shared/OInstance.h @@ -10,6 +10,7 @@ #include "InstanceManager.h" // React Native +#include #include // Standard Libriary @@ -32,6 +33,7 @@ class InstanceImpl final : public InstanceWrapper, private ::std::enable_shared_ std::tuple>> &&cxxModules, std::shared_ptr turboModuleRegistry, + std::shared_ptr longLivedObjectCollection, std::unique_ptr &&callback, std::shared_ptr jsQueue, std::shared_ptr nativeQueue, @@ -72,19 +74,7 @@ class InstanceImpl final : public InstanceWrapper, private ::std::enable_shared_ std::tuple>> &&cxxModules, std::shared_ptr turboModuleRegistry, - std::unique_ptr &&callback, - std::shared_ptr jsQueue, - std::shared_ptr nativeQueue, - std::shared_ptr devSettings, - std::shared_ptr devManager); - - InstanceImpl( - std::shared_ptr &&instance, - std::string &&jsBundleBasePath, - std::string &&jsBundleRelativePath, - std::vector< - std::tuple>> - &&cxxModules, + std::shared_ptr longLivedObjectCollection, std::unique_ptr &&callback, std::shared_ptr jsQueue, std::shared_ptr nativeQueue, @@ -102,6 +92,7 @@ class InstanceImpl final : public InstanceWrapper, private ::std::enable_shared_ std::string m_jsBundleBasePath; std::shared_ptr m_moduleRegistry; std::shared_ptr m_turboModuleRegistry; + std::shared_ptr m_longLivedObjectCollection; std::shared_ptr m_jsThread; std::shared_ptr m_nativeQueue;