Skip to content

Commit

Permalink
[0.69] Use LongLivedObjects for TurboModule callbacks (#10436) (#10442)
Browse files Browse the repository at this point in the history
* Use LongLivedObjects for TurboModule callbacks (#10436)

* Fix build break
  • Loading branch information
vmoroz committed Aug 26, 2022
1 parent 94729e7 commit 8046e37
Show file tree
Hide file tree
Showing 17 changed files with 803 additions and 235 deletions.
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Use LongLivedObjects for TurboModule callbacks",
"packageName": "react-native-windows",
"email": "vmorozov@microsoft.com",
"dependentChangeType": "patch"
}
84 changes: 84 additions & 0 deletions 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 <ReactCommon/LongLivedObject.h>
#include <jsi/jsi.h>

namespace winrt::Microsoft::ReactNative {

// Wrap up JSI Runtime into a LongLivedObject
struct LongLivedJsiRuntime : facebook::react::LongLivedObject {
static std::weak_ptr<LongLivedJsiRuntime> CreateWeak(
std::shared_ptr<facebook::react::LongLivedObjectCollection> const &longLivedObjectCollection,
facebook::jsi::Runtime &runtime) noexcept {
auto value = std::shared_ptr<LongLivedJsiRuntime>(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<facebook::react::LongLivedObjectCollection> 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<facebook::react::LongLivedObjectCollection> longLivedObjectCollection_;
facebook::jsi::Runtime &runtime_;
};

// Wrap up a JSI Value into a LongLivedObject.
template <typename TValue>
struct LongLivedJsiValue : LongLivedJsiRuntime {
static std::weak_ptr<LongLivedJsiValue<TValue>> CreateWeak(
std::shared_ptr<facebook::react::LongLivedObjectCollection> const &longLivedObjectCollection,
facebook::jsi::Runtime &runtime,
TValue &&value) noexcept {
auto valueWrapper = std::shared_ptr<LongLivedJsiValue<TValue>>(
new LongLivedJsiValue<TValue>(longLivedObjectCollection, runtime, std::forward<TValue>(value)));
longLivedObjectCollection->add(valueWrapper);
return valueWrapper;
}

TValue &Value() {
return value_;
}

protected:
template <typename TValue2>
LongLivedJsiValue(
std::shared_ptr<facebook::react::LongLivedObjectCollection> const &longLivedObjectCollection,
facebook::jsi::Runtime &runtime,
TValue2 &&value)
: LongLivedJsiRuntime(longLivedObjectCollection, runtime), value_(std::forward<TValue2>(value)) {}

private:
TValue value_;
};

using LongLivedJsiFunction = LongLivedJsiValue<facebook::jsi::Function>;

} // namespace winrt::Microsoft::ReactNative

#endif // MICROSOFT_REACTNATIVE_JSI_LONGLIVEDJSIVALUE_
Expand Up @@ -38,6 +38,7 @@
<ClInclude Include="$(JSI_SourcePath)\jsi\jsi-inl.h" />
<ClInclude Include="$(JSI_SourcePath)\jsi\jsi.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)DesktopWindowBridge.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\LongLivedJsiValue.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\NodeApiJsiRuntime.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)TurboModuleProvider.h" />
<ClInclude Include="$(CallInvoker_SourcePath)\ReactCommon\CallInvoker.h" />
Expand Down
Expand Up @@ -151,6 +151,9 @@
<Filter>JSI</Filter>
</ClInclude>
<ClInclude Include="$(Bridging_SourcePath)\CallbackWrapper.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\LongLivedJsiValue.h">
<Filter>TurboModule</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="JSI">
Expand Down
33 changes: 33 additions & 0 deletions vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.cpp
Expand Up @@ -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<TestEvent> TestEventService::s_eventQueue;
Expand Down Expand Up @@ -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<std::string, std::less<>> 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
32 changes: 23 additions & 9 deletions vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.h
Expand Up @@ -12,6 +12,7 @@
#include <winrt/base.h>
#include <mutex>
#include <queue>
#include <set>
#include <string_view>

namespace ReactNativeIntegrationTests {
Expand All @@ -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.
Expand All @@ -45,14 +42,31 @@ struct TestEventService {
LogEvent(eventName, JSValue{std::forward<TValue>(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<const TestEvent> 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<TestEvent> 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<std::string, std::less<>> s_events; // a set of events
};

} // namespace ReactNativeIntegrationTests

0 comments on commit 8046e37

Please sign in to comment.