From 7ac2e4b550f117960160a8abf7d600addd567293 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 4 Nov 2026 08:33:28 -0800 Subject: [PATCH] Fix callback and promise teardown races (#59670) Summary: Pull Request resolved: https://github.com/react/react-native/pull/59670 ## Context - Native callback and promise cleanup can race JavaScript VM teardown. Construction-thread identity is unsafe because execution can temporarily move to another thread. ## This diff - Keep weak native handles to callback and promise values rooted by the runtime. - Always defer implicit release; permit inline release only with explicit, current exclusive runtime access. - Release teardown roots and one-shot synchronous callbacks before their runtime-access scope ends. ## Compatibility - Native handle construction requires a dispatch context, and typed calls require that context to remain live. Teardown must clear rooted values before destroying the runtime. This builds on the investigation and reproducer contributed by HyunWoo Lee in https://github.com/react/react-native/pull/59579. ## Changelog: [General][Fixed] - Fix TurboModule callback and async promise cleanup racing runtime teardown [General][Deprecated] - Deprecate the legacy callback wrapper in favour of weak lifetime handles Differential Revision: D128581452 --- .../ReactCommon/tests/TestCallInvoker.h | 4 +- .../react/bridging/CallbackWrapper.h | 5 +- .../ReactCommon/react/bridging/Function.h | 89 +- .../react/bridging/LongLivedObject.cpp | 57 +- .../react/bridging/LongLivedObject.h | 114 +- .../ReactCommon/react/bridging/Promise.h | 53 +- .../react/bridging/tests/BridgingTest.cpp | 1004 ++++++++++++++++- .../react/bridging/tests/BridgingTest.h | 2 +- .../core/ReactCommon/TurboModuleBinding.cpp | 4 +- .../core/tests/TurboModuleTestFixture.h | 1 + .../react/nativemodule/dom/NativeDOM.cpp | 10 +- .../react/nativemodule/dom/NativeDOM.h | 7 +- .../__tests__/ReactNativeElement-itest.js | 15 + .../api-snapshots/ReactAndroidDebugCxx.api | 32 +- .../ReactAndroidFrameworksCxx.api | 32 +- .../api-snapshots/ReactAndroidNewarchCxx.api | 32 +- .../api-snapshots/ReactAndroidReleaseCxx.api | 32 +- .../api-snapshots/ReactAppleDebugCxx.api | 32 +- .../api-snapshots/ReactAppleFrameworksCxx.api | 32 +- .../api-snapshots/ReactAppleNewarchCxx.api | 32 +- .../api-snapshots/ReactAppleReleaseCxx.api | 32 +- .../api-snapshots/ReactCommonDebugCxx.api | 32 +- .../ReactCommonFrameworksCxx.api | 32 +- .../api-snapshots/ReactCommonNewarchCxx.api | 32 +- .../api-snapshots/ReactCommonReleaseCxx.api | 32 +- 25 files changed, 1610 insertions(+), 139 deletions(-) diff --git a/packages/react-native/ReactCommon/callinvoker/ReactCommon/tests/TestCallInvoker.h b/packages/react-native/ReactCommon/callinvoker/ReactCommon/tests/TestCallInvoker.h index 17ef56e51644..9ecbd9e8332e 100644 --- a/packages/react-native/ReactCommon/callinvoker/ReactCommon/tests/TestCallInvoker.h +++ b/packages/react-native/ReactCommon/callinvoker/ReactCommon/tests/TestCallInvoker.h @@ -30,8 +30,10 @@ class TestCallInvoker : public CallInvoker { void flushQueue() { while (!queue_.empty()) { - queue_.front()(runtime_); + auto job = std::move(queue_.front()); queue_.pop_front(); + job(runtime_); + job = {}; runtime_.drainMicrotasks(); // Run microtasks every cycle. } } diff --git a/packages/react-native/ReactCommon/react/bridging/CallbackWrapper.h b/packages/react-native/ReactCommon/react/bridging/CallbackWrapper.h index 445b4a0dccf4..efad212bc4fa 100644 --- a/packages/react-native/ReactCommon/react/bridging/CallbackWrapper.h +++ b/packages/react-native/ReactCommon/react/bridging/CallbackWrapper.h @@ -19,7 +19,10 @@ namespace facebook::react { class CallInvoker; -// Helper for passing jsi::Function arg to other methods. +/** + * @deprecated Use LongLivedObjectHandle instead. + * Access and release this legacy wrapper only on the JavaScript thread. + */ class CallbackWrapper : public LongLivedObject { private: CallbackWrapper(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr jsInvoker) diff --git a/packages/react-native/ReactCommon/react/bridging/Function.h b/packages/react-native/ReactCommon/react/bridging/Function.h index 19aaec53c006..d69454913c69 100644 --- a/packages/react-native/ReactCommon/react/bridging/Function.h +++ b/packages/react-native/ReactCommon/react/bridging/Function.h @@ -10,7 +10,7 @@ #include #include -#include +#include // Retain the legacy transitive include. #include #include @@ -61,16 +61,18 @@ class AsyncCallback { void callWithArgs(std::optional priority, Args... args) const noexcept { - if (auto wrapper = callback_->wrapper_.lock()) { - auto fn = [callback = callback_, + auto callback = callback_; + if (auto jsInvoker = callback->function_.lockInvoker()) { + // The queued function owns the tuple; its lifetime is opaque to the analyzer. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + auto fn = [callback = std::move(callback), argsPtr = std::make_shared>(std::make_tuple(std::forward(args)...))]( jsi::Runtime &) { callback->apply(std::move(*argsPtr)); }; - auto &jsInvoker = wrapper->jsInvoker(); if (priority) { - jsInvoker.invokeAsync(*priority, std::move(fn)); + jsInvoker->invokeAsync(*priority, std::move(fn)); } else { - jsInvoker.invokeAsync(std::move(fn)); + jsInvoker->invokeAsync(std::move(fn)); } } } @@ -79,56 +81,46 @@ class AsyncCallback { std::optional priority, std::function &&callImpl) const noexcept { - if (auto wrapper = callback_->wrapper_.lock()) { - // Capture callback_ and not wrapper_. If callback_ is deallocated or the - // JSVM is shutdown before the async task is scheduled, the underlying - // function will have been deallocated. - auto fn = [callback = callback_, callImpl = std::move(callImpl)](jsi::Runtime &rt) { - if (auto wrapper2 = callback->wrapper_.lock()) { - callImpl(rt, wrapper2->callback()); + auto callback = callback_; + if (auto jsInvoker = callback->function_.lockInvoker()) { + // Retain native ownership until this task is destroyed, without retaining + // a JavaScript value while the task is queued or discarded. + auto fn = [callback = std::move(callback), callImpl = std::move(callImpl)](jsi::Runtime &) { + if (!callback->function_.expired()) { + auto &rt = *callback->runtime_; + if (auto function = callback->function_.lockJSValue(rt)) { + callImpl(rt, function->value); + } } }; - auto &jsInvoker = wrapper->jsInvoker(); if (priority) { - jsInvoker.invokeAsync(*priority, std::move(fn)); + jsInvoker->invokeAsync(*priority, std::move(fn)); } else { - jsInvoker.invokeAsync(std::move(fn)); + jsInvoker->invokeAsync(std::move(fn)); } } } }; -// You must ensure that when invoking this you're located on the JS thread, or -// have exclusive control of the JS VM context. If you cannot ensure this, use -// AsyncCallback instead. +// Invoke only with exclusive JavaScript runtime access. Use AsyncCallback otherwise. template class SyncCallback { public: SyncCallback(jsi::Runtime &rt, jsi::Function function, std::shared_ptr jsInvoker) - : wrapper_(CallbackWrapper::createWeak(std::move(function), rt, std::move(jsInvoker))) + : runtime_(&rt), function_(rt, std::move(function), std::move(jsInvoker)) { } - // Disallow copying, as we can no longer safely destroy the callback - // from the destructor if there's multiple copies SyncCallback(const SyncCallback &) = delete; SyncCallback &operator=(const SyncCallback &) = delete; + SyncCallback(SyncCallback &&) noexcept = default; + SyncCallback &operator=(SyncCallback &&) noexcept = default; + ~SyncCallback() = default; - // Allow move - SyncCallback(SyncCallback &&other) noexcept : wrapper_(std::move(other.wrapper_)) {} - - SyncCallback &operator=(SyncCallback &&other) noexcept + void reset(jsi::Runtime &runtime) noexcept { - wrapper_ = std::move(other.wrapper_); - return *this; - } - - ~SyncCallback() - { - if (auto wrapper = wrapper_.lock()) { - wrapper->destroy(); - } + function_.reset(runtime); } R operator()(Args... args) const @@ -138,11 +130,12 @@ class SyncCallback { R call(Args... args) const { - auto wrapper = wrapper_.lock(); + auto jsInvoker = function_.lockInvoker(); + auto function = jsInvoker ? function_.lockJSValue(*runtime_) : nullptr; - // If the wrapper has been deallocated, we can no longer provide a return - // value consistently, so our only option is to throw - if (!wrapper) { + // If the function has been deallocated, we can no longer provide a return + // value consistently, so our only option is to throw. + if (!function) { if constexpr (std::is_void_v) { return; } else { @@ -150,9 +143,10 @@ class SyncCallback { } } - auto &callback = wrapper->callback(); - auto &rt = wrapper->runtime(); - auto jsInvoker = wrapper->jsInvokerPtr(); + // JavaScript can destroy this native callback during the call; use only + // the retained value and local runtime/invoker references afterward. + auto &callback = function->value; + auto &rt = *runtime_; if constexpr (std::is_void_v) { callback.call(rt, bridging::toJs(rt, std::forward(args), jsInvoker)...); @@ -177,8 +171,8 @@ class SyncCallback { return call(std::move(std::get(args))...); } - // Held weakly so lifetime is managed by LongLivedObjectCollection. - std::weak_ptr wrapper_; + jsi::Runtime *runtime_; + LongLivedObjectHandle function_; }; template @@ -191,7 +185,7 @@ struct Bridging> { static jsi::Function toJs(jsi::Runtime &rt, const AsyncCallback &value) { - return value.callback_->function_.getFunction(rt); + return Bridging>::toJs(rt, *value.callback_); } }; @@ -205,7 +199,10 @@ struct Bridging> { static jsi::Function toJs(jsi::Runtime &rt, const SyncCallback &value) { - return value.function_.getFunction(rt); + if (auto function = value.function_.lockJSValue(rt)) { + return function->value.getFunction(rt); + } + throw jsi::JSError(rt, "Failed to get invalidated callback"); } }; diff --git a/packages/react-native/ReactCommon/react/bridging/LongLivedObject.cpp b/packages/react-native/ReactCommon/react/bridging/LongLivedObject.cpp index aa4b1fcdb589..20ed4614f16f 100644 --- a/packages/react-native/ReactCommon/react/bridging/LongLivedObject.cpp +++ b/packages/react-native/ReactCommon/react/bridging/LongLivedObject.cpp @@ -6,6 +6,8 @@ */ #include "LongLivedObject.h" + +#include #include namespace facebook::react { @@ -35,22 +37,35 @@ void LongLivedObjectCollection::add(std::shared_ptr so) { } void LongLivedObjectCollection::remove(const LongLivedObject* o) { - std::scoped_lock lock(collectionMutex_); - // Empty owner: lookup-only alias, with no ownership or control block. - const std::shared_ptr lookupKey{ - std::shared_ptr{}, const_cast(o)}; - - // A shared_ptr hashes and compares by the pointer it stores, so a - // non-owning shared_ptr to `o` finds its entry without walking the set. - auto entry = collection_.find(lookupKey); - if (entry != collection_.end()) { - collection_.erase(entry); + decltype(collection_)::node_type retired; + { + std::scoped_lock lock(collectionMutex_); + // Empty owner: lookup-only alias, with no ownership or control block. + const std::shared_ptr lookupKey{ + std::shared_ptr{}, + // Lookup never dereferences or mutates the object. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + const_cast(o)}; + + // A shared_ptr hashes and compares by the pointer it stores, so a + // non-owning shared_ptr to `o` finds its entry without walking the set. + auto entry = collection_.find(lookupKey); + if (entry != collection_.end()) { + retired = collection_.extract(entry); + } } } void LongLivedObjectCollection::clear() { - std::scoped_lock lock(collectionMutex_); - collection_.clear(); + decltype(collection_) retired; + { + std::scoped_lock lock(collectionMutex_); + retired.swap(collection_); + } +} + +void LongLivedObjectCollection::clear(jsi::Runtime& /*runtime*/) { + clear(); } size_t LongLivedObjectCollection::size() const { @@ -64,4 +79,22 @@ void LongLivedObject::allowRelease() { LongLivedObjectCollection::get(runtime_).remove(this); } +void detail::releaseLongLivedObjectOnJS( + std::weak_ptr object, + std::weak_ptr jsInvoker) noexcept { + if (object.expired()) { + return; + } + if (auto invoker = jsInvoker.lock()) { + invoker->invokeAsync([object = std::move(object)](jsi::Runtime&) { + if (auto locked = object.lock()) { + locked->allowRelease(); + } + }); + } + // If dispatch is dropped, runtime cleanup releases the collection. Never + // fall back to acquiring or releasing a strong reference on the caller + // thread. +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridging/LongLivedObject.h b/packages/react-native/ReactCommon/react/bridging/LongLivedObject.h index 9af43f4598e7..bfb7c0696d7a 100644 --- a/packages/react-native/ReactCommon/react/bridging/LongLivedObject.h +++ b/packages/react-native/ReactCommon/react/bridging/LongLivedObject.h @@ -13,9 +13,12 @@ #include #include #include +#include namespace facebook::react { +class CallInvoker; + /** * A simple wrapper class that can be registered to a collection that keep it * alive for extended period of time. This object can be removed from the @@ -23,7 +26,8 @@ namespace facebook::react { * * The subclass of this class must be created using std::make_shared(). * After creation, add it to the `LongLivedObjectCollection`. When done with the - * object, call `allowRelease()` to reclaim its memory. + * object, call `allowRelease()` to reclaim its memory. Prefer + * `LongLivedObjectHandle` for automatic JavaScript-value ownership and release. * * When using LongLivedObject to keep JS values alive, ensure you only hold weak * references to the object outside the JS thread to avoid accessing deallocated @@ -41,6 +45,8 @@ class LongLivedObject { /** * A singleton, thread-safe, write-only collection for the `LongLivedObject`s. + * Clear with exclusive access to the still-live runtime during teardown, even + * when native handles outlive it or queued release tasks cannot execute. */ class LongLivedObjectCollection { public: @@ -52,7 +58,9 @@ class LongLivedObjectCollection { void add(std::shared_ptr o); void remove(const LongLivedObject *o); + // Kept for existing embeddings; new cleanup callers should provide runtime access. void clear(); + void clear(jsi::Runtime &runtime); size_t size() const; private: @@ -60,4 +68,108 @@ class LongLivedObjectCollection { mutable std::mutex collectionMutex_; }; +namespace detail { +void releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; + +// A derived weak pointer can transiently acquire strong ownership when converted. +template +void releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +} // namespace detail + +/** + * Owns a collection registration without retaining its JavaScript value on a + * native thread. Construct and lock with exclusive access to the runtime; move + * and destroy on any thread. Implicit release is always deferred; use reset(runtime) + * for synchronous release while holding runtime access. The runtime host owns + * the call invoker; this handle retains it weakly. Clear the collection before + * destroying its runtime. + */ +template +class LongLivedObjectHandle { + public: + // Keep every strong reference to this holder, including copies and destruction, + // within a scope that has exclusive access to the JavaScript runtime. + struct Holder : LongLivedObject { + Holder(jsi::Runtime &runtime, T value) : LongLivedObject(runtime), value(std::move(value)) {} + + T value; + }; + + LongLivedObjectHandle() = default; + + LongLivedObjectHandle(jsi::Runtime &runtime, T value, std::shared_ptr jsInvoker) + { + if (!jsInvoker) { + throw jsi::JSError(runtime, "LongLivedObjectHandle requires a call invoker"); + } + jsInvoker_ = jsInvoker; + auto holder = std::make_shared(runtime, std::move(value)); + LongLivedObjectCollection::get(runtime).add(holder); + holder_ = holder; + } + + LongLivedObjectHandle(const LongLivedObjectHandle &) = delete; + LongLivedObjectHandle &operator=(const LongLivedObjectHandle &) = delete; + LongLivedObjectHandle(LongLivedObjectHandle &&) noexcept = default; + + LongLivedObjectHandle &operator=(LongLivedObjectHandle &&other) noexcept + { + if (this != &other) { + reset(); + holder_ = std::move(other.holder_); + jsInvoker_ = std::move(other.jsInvoker_); + } + return *this; + } + + ~LongLivedObjectHandle() + { + reset(); + } + + bool expired() const noexcept + { + return holder_.expired(); + } + + std::shared_ptr lockInvoker() const noexcept + { + return holder_.expired() ? nullptr : jsInvoker_.lock(); + } + + // Require this handle's runtime as proof that the caller has access to it; + // runtime identity is not checked. Retain and destroy the returned strong + // reference only while that exclusive runtime access remains active. + std::shared_ptr lockJSValue(jsi::Runtime & /*runtime*/) const noexcept + { + return std::static_pointer_cast(holder_.lock()); + } + + // Without proof of current runtime access, even same-thread destruction must + // defer release: synchronous runtime borrowing can change the executing thread. + void reset() noexcept + { + auto holder = std::exchange(holder_, {}); + auto jsInvoker = std::exchange(jsInvoker_, {}); + detail::releaseLongLivedObjectOnJS(std::move(holder), std::move(jsInvoker)); + } + + // The caller must have exclusive access to this runtime for synchronous release. + void reset(jsi::Runtime &runtime) noexcept + { + auto holder = lockJSValue(runtime); + holder_.reset(); + jsInvoker_.reset(); + if (holder) { + holder->allowRelease(); + } + } + + private: + // Converting weak pointers can promote the object; keep the helper's base type + // so off-thread release only moves weak references. + std::weak_ptr holder_; + std::weak_ptr jsInvoker_; +}; + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridging/Promise.h b/packages/react-native/ReactCommon/react/bridging/Promise.h index cdd023ad9549..ce3c3969ed6c 100644 --- a/packages/react-native/ReactCommon/react/bridging/Promise.h +++ b/packages/react-native/ReactCommon/react/bridging/Promise.h @@ -39,61 +39,52 @@ class AsyncPromise { }, jsInvoker)); - auto promiseHolder = std::make_shared(rt, promise.asObject(rt)); - LongLivedObjectCollection::get(rt).add(promiseHolder); - - // The shared state can retain the promise holder weakly now. - state_->promiseHolder = promiseHolder; + state_->promiseHandle = LongLivedObjectHandle(rt, promise.asObject(rt), jsInvoker); } void resolve(T... value) { - std::lock_guard lock(state_->mutex); + std::optional> resolve; + std::optional> reject; + { + std::lock_guard lock(state_->mutex); + resolve = std::exchange(state_->resolve, std::nullopt); + reject = std::exchange(state_->reject, std::nullopt); + } - if (state_->resolve) { - state_->resolve->call(std::forward(value)...); - state_->resolve.reset(); - state_->reject.reset(); + if (resolve) { + resolve->call(std::forward(value)...); } } void reject(Error error) { - std::lock_guard lock(state_->mutex); + std::optional> resolve; + std::optional> reject; + { + std::lock_guard lock(state_->mutex); + resolve = std::exchange(state_->resolve, std::nullopt); + reject = std::exchange(state_->reject, std::nullopt); + } - if (state_->reject) { - state_->reject->call(std::move(error)); - state_->reject.reset(); - state_->resolve.reset(); + if (reject) { + reject->call(std::move(error)); } } jsi::Object get(jsi::Runtime &rt) const { - if (auto holder = state_->promiseHolder.lock()) { - return jsi::Value(rt, holder->promise).asObject(rt); + if (auto holder = state_->promiseHandle.lockJSValue(rt)) { + return jsi::Value(rt, holder->value).asObject(rt); } else { throw jsi::JSError(rt, "Failed to get invalidated promise"); } } private: - struct PromiseHolder : LongLivedObject { - PromiseHolder(jsi::Runtime &runtime, jsi::Object p) : LongLivedObject(runtime), promise(std::move(p)) {} - - jsi::Object promise; - }; - struct SharedState { - ~SharedState() - { - if (auto holder = promiseHolder.lock()) { - holder->allowRelease(); - } - } - std::mutex mutex; - std::weak_ptr promiseHolder; + LongLivedObjectHandle promiseHandle; std::optional> resolve; std::optional> reject; }; diff --git a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp index 0178c7904e83..cf363697b3fb 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -5,8 +5,16 @@ * LICENSE file in the root directory of this source tree. */ +#include #include +#include +#include +#include +#include +#include +#include #include +#include #include #include "BridgingTest.h" @@ -321,7 +329,7 @@ TEST_F(BridgingTest, asyncCallbackInvalidation) { [](jsi::Runtime& rt, jsi::Function& f) { f.call(rt, "hello"); }); // LongLivedObjectCollection goes away before callback is executed - LongLivedObjectCollection::get(rt).clear(); + LongLivedObjectCollection::get(rt).clear(rt); flushQueue(); @@ -1038,4 +1046,998 @@ TEST_F(BridgingTest, highResTimeStampTest) { 1.000001, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6 + 1))); } +namespace { + +template +constexpr bool canReleaseLongLivedObjectOnJS = + requires(std::weak_ptr object, std::weak_ptr invoker) { + detail::releaseLongLivedObjectOnJS(std::move(object), std::move(invoker)); + }; + +static_assert(canReleaseLongLivedObjectOnJS); +static_assert(!canReleaseLongLivedObjectOnJS< + LongLivedObjectHandle::Holder>); + +// `TestCallInvoker` keeps its queue in a bare `std::list`, so it cannot be used +// by the tests below, which schedule work from worker threads. Jobs are taken +// out of the queue under the lock and run (and destroyed) outside it, so a +// release scheduled while the queue drains is picked up by the same flush. +class ConcurrentTestCallInvoker : public CallInvoker { + public: + explicit ConcurrentTestCallInvoker(jsi::Runtime& runtime) + : runtime_(runtime) {} + + void invokeAsync(CallFunc&& func) noexcept override { + std::unique_lock lock(mutex_); + ++dispatchCount_; + dispatched_.notify_all(); + gateOpened_.wait(lock, [this] { return gateOpen_; }); + queue_.push_back(std::move(func)); + } + + void invokeAsync(SchedulerPriority priority, CallFunc&& func) noexcept + override { + { + std::scoped_lock lock(mutex_); + lastPriority_ = priority; + } + invokeAsync(std::move(func)); + } + + void invokeSync(CallFunc&& func) override { + func(runtime_); + } + + void flushQueue() { + while (auto job = takeNextJob()) { + (*job)(runtime_); + job.reset(); + runtime_.drainMicrotasks(); + } + } + + // Throws queued work away without running it, the way a JavaScript queue + // torn down at virtual machine shutdown would. + void discardQueue() { + while (auto job = takeNextJob()) { + job.reset(); + } + } + + size_t queueSize() { + std::scoped_lock lock(mutex_); + return queue_.size(); + } + + std::optional lastPriority() { + std::scoped_lock lock(mutex_); + return lastPriority_; + } + + // Parks every dispatching thread inside `invokeAsync` until `openGate()`, + // which makes the window between "the callback is still alive" and "the task + // is queued" a deterministic point to interleave the JavaScript thread with. + void closeGate() { + std::scoped_lock lock(mutex_); + gateOpen_ = false; + } + + void openGate() { + std::scoped_lock lock(mutex_); + gateOpen_ = true; + gateOpened_.notify_all(); + } + + void waitForDispatch(size_t count) { + std::unique_lock lock(mutex_); + dispatched_.wait(lock, [&] { return dispatchCount_ >= count; }); + } + + std::optional takeNextJob() { + std::scoped_lock lock(mutex_); + if (queue_.empty()) { + return std::nullopt; + } + auto job = std::move(queue_.front()); + queue_.pop_front(); + return job; + } + + private: + jsi::Runtime& runtime_; + std::mutex mutex_; + std::condition_variable gateOpened_; + std::condition_variable dispatched_; + std::list queue_; + std::optional lastPriority_; + size_t dispatchCount_{0}; + bool gateOpen_{true}; +}; + +// Where and how often a value owned by a long lived object was destroyed. +struct ReleaseRecord { + void record() { + std::scoped_lock lock(mutex); + ++count; + thread = std::this_thread::get_id(); + } + + int releaseCount() { + std::scoped_lock lock(mutex); + return count; + } + + std::thread::id releaseThread() { + std::scoped_lock lock(mutex); + return thread; + } + + std::mutex mutex; + int count{0}; + std::thread::id thread{}; +}; + +// A payload that reports its own destruction. Moving it hands the report over, +// so a moved-from temporary never counts as a release. +class ProbePayload { + public: + explicit ProbePayload(std::function onDestroy) + : onDestroy_(std::move(onDestroy)) {} + + ProbePayload(ProbePayload&& other) noexcept + : onDestroy_(std::exchange(other.onDestroy_, nullptr)) {} + + ProbePayload& operator=(ProbePayload&&) = delete; + + ProbePayload(const ProbePayload&) = delete; + ProbePayload& operator=(const ProbePayload&) = delete; + + ~ProbePayload() { + if (onDestroy_) { + onDestroy_(); + } + } + + private: + std::function onDestroy_; +}; + +ProbePayload recordingPayload(const std::shared_ptr& record) { + return ProbePayload([record] { record->record(); }); +} + +} // namespace + +TEST_F(BridgingTest, syncCallbackDestroyedOffJsThreadReleasesOnJsThread) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + + { + // Kept on the heap so that a cleanup job reaching back into the destroyed + // native owner is a use-after-free the sanitizers report, rather than a + // read of stack storage that is still mapped. + auto callback = std::make_unique>( + rt, function("() => {}"), jsInvoker); + EXPECT_EQ(1, collection.size()); + + std::thread worker([&] { callback.reset(); }); + worker.join(); + + // Dropping the last native owner away from the JavaScript thread must not + // reach into the collection: the function stays rooted until a JavaScript + // task releases it. + EXPECT_EQ(1, collection.size()); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, syncCallbackMoveAssignmentReleasesReplacedCallback) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + + { + SyncCallback callback( + rt, function("() => 'first'"), jsInvoker); + + { + SyncCallback replacement( + rt, function("() => 'second'"), jsInvoker); + EXPECT_EQ(2, collection.size()); + + callback = std::move(replacement); + + // Assignment has no runtime access, so even on the JavaScript thread the + // replaced root stays until its queued release runs. + EXPECT_EQ(2, collection.size()); + EXPECT_EQ(1, jsInvoker->queueSize()); + EXPECT_EQ("second"s, callback()); + + jsInvoker->flushQueue(); + EXPECT_EQ(1, collection.size()); + } + + // Destroying the moved-from source must not release what it handed over. + EXPECT_EQ(1, collection.size()); + EXPECT_EQ("second"s, callback()); + + // Assignment resets the destination before taking ownership, so self + // assignment has to be guarded. + auto& alias = callback; + callback = std::move(alias); + EXPECT_EQ(1, collection.size()); + EXPECT_EQ(0, jsInvoker->queueSize()); + EXPECT_EQ("second"s, callback()); + } + + EXPECT_EQ(1, jsInvoker->queueSize()); + jsInvoker->flushQueue(); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncCallbackClearedDuringDispatchNeverCallsJs) { + enum class Path { + Arguments, + ArgumentsWithPriority, + Function, + FunctionWithPriority, + }; + + for (auto path : + {Path::Arguments, + Path::ArgumentsWithPriority, + Path::Function, + Path::FunctionWithPriority}) { + auto jsInvoker = std::make_shared(rt); + std::string output; + auto nativeFunc = std::function( + [&](auto str) { output = std::move(str); }); + + { + auto callback = + bridging::fromJs>( + rt, function("(func, str) => func(str)"), jsInvoker); + + auto callImpl = [nativeFunc, jsInvoker]( + jsi::Runtime& runtime, jsi::Function& jsFunc) { + jsFunc.call( + runtime, + bridging::toJs(runtime, nativeFunc, jsInvoker), + bridging::toJs(runtime, "called", jsInvoker)); + }; + + jsInvoker->closeGate(); + + std::thread worker([&] { + switch (path) { + case Path::Arguments: + callback(nativeFunc, "called"); + break; + case Path::ArgumentsWithPriority: + callback.callWithPriority( + SchedulerPriority::IdlePriority, nativeFunc, "called"); + break; + case Path::Function: + callback.call(callImpl); + break; + case Path::FunctionWithPriority: + callback.callWithPriority( + SchedulerPriority::IdlePriority, callImpl); + break; + } + }); + + // The dispatching thread is now parked inside the invoker, past the + // point where it found the callback alive. + jsInvoker->waitForDispatch(1); + LongLivedObjectCollection::get(rt).clear(rt); + jsInvoker->openGate(); + worker.join(); + + jsInvoker->flushQueue(); + EXPECT_EQ(""s, output) << "dispatch path " << static_cast(path); + } + + jsInvoker->flushQueue(); + } +} + +TEST_F(BridgingTest, asyncCallbackDispatchAfterInvalidationEnqueuesNothing) { + auto jsInvoker = std::make_shared(rt); + std::string output; + auto nativeFunc = std::function( + [&](auto str) { output = std::move(str); }); + + { + auto callback = + bridging::fromJs>( + rt, function("(func, str) => func(str)"), jsInvoker); + + auto callImpl = [nativeFunc, jsInvoker]( + jsi::Runtime& runtime, jsi::Function& jsFunc) { + jsFunc.call( + runtime, + bridging::toJs(runtime, nativeFunc, jsInvoker), + bridging::toJs(runtime, "called", jsInvoker)); + }; + + LongLivedObjectCollection::get(rt).clear(rt); + + callback(nativeFunc, "called"); + callback.callWithPriority( + SchedulerPriority::IdlePriority, nativeFunc, "called"); + callback.call(callImpl); + callback.callWithPriority(SchedulerPriority::IdlePriority, callImpl); + + EXPECT_EQ(0, jsInvoker->queueSize()); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(""s, output); +} + +TEST_F(BridgingTest, asyncCallbackQueuedInvocationOutlivesItsOwner) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + std::string output; + auto nativeFunc = std::function( + [&](auto str) { output = std::move(str); }); + + { + auto callback = + bridging::fromJs>( + rt, function("(func, str) => func(str)"), jsInvoker); + callback.callWithPriority( + SchedulerPriority::IdlePriority, nativeFunc, "late"); + } + + // The queued task owns the callback state, so dropping every public owner + // before it runs neither releases the function nor schedules a release. + EXPECT_EQ(SchedulerPriority::IdlePriority, jsInvoker->lastPriority()); + EXPECT_EQ(1, jsInvoker->queueSize()); + EXPECT_EQ(1, collection.size()); + + jsInvoker->flushQueue(); + EXPECT_EQ("late"s, output); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncPromiseDroppedOffJsThreadReleasesOnJsThread) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + + { + auto promise = std::make_optional>(rt, jsInvoker); + const auto rooted = collection.size(); + EXPECT_GT(rooted, 0u); + + std::thread worker([&] { promise.reset(); }); + worker.join(); + + EXPECT_EQ(rooted, collection.size()); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncPromiseSettlementSurvivesBeingDroppedOffJsThread) { + auto observe = function( + "(promise, obj) => {" + " promise.then(" + " (res) => { obj.res = res; }," + " (err) => { obj.err = err.message; }" + " )" + "}"); + + for (bool rejects : {false, true}) { + auto jsInvoker = std::make_shared(rt); + auto output = jsi::Object(rt); + + { + auto promise = std::make_optional>(rt, jsInvoker); + observe.call(rt, bridging::toJs(rt, *promise, jsInvoker), output); + + if (rejects) { + promise->reject("boom"); + } else { + promise->resolve(42); + } + + // Every public copy is dropped away from the JavaScript thread while + // the settlement is still queued. + std::thread worker([&] { promise.reset(); }); + worker.join(); + } + + jsInvoker->flushQueue(); + + if (rejects) { + ASSERT_TRUE(output.getProperty(rt, "err").isString()); + EXPECT_EQ("boom"s, output.getProperty(rt, "err").asString(rt).utf8(rt)); + } else { + ASSERT_TRUE(output.getProperty(rt, "res").isNumber()); + EXPECT_EQ(42, output.getProperty(rt, "res").asNumber()); + } + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); + } +} + +TEST_F(BridgingTest, asyncPromiseCopiesShareOneSettlement) { + auto jsInvoker = std::make_shared(rt); + auto observe = function( + "(promise, obj) => {" + " obj.count = 0;" + " promise.then(" + " (res) => { obj.count++; obj.res = res; }," + " () => { obj.count++; obj.rejected = true; }" + " )" + "}"); + auto output = jsi::Object(rt); + + { + auto promise = AsyncPromise(rt, jsInvoker); + observe.call(rt, bridging::toJs(rt, promise, jsInvoker), output); + + { + auto copy = promise; + copy.resolve(1); + } + + // Dropping a copy leaves the shared state, and only the first settlement + // counts. + EXPECT_NO_THROW(promise.get(rt)); + EXPECT_NO_THROW(promise.resolve(2)); + EXPECT_NO_THROW(promise.reject("ignored")); + } + + jsInvoker->flushQueue(); + + EXPECT_EQ(1, output.getProperty(rt, "count").asNumber()); + EXPECT_EQ(1, output.getProperty(rt, "res").asNumber()); + EXPECT_FALSE(output.hasProperty(rt, "rejected")); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, asyncPromiseGetFailsAfterInvalidation) { + auto jsInvoker = std::make_shared(rt); + + { + auto promise = AsyncPromise(rt, jsInvoker); + EXPECT_NO_THROW(promise.get(rt)); + + LongLivedObjectCollection::get(rt).clear(rt); + + EXPECT_JSI_THROW(promise.get(rt)); + EXPECT_NO_THROW(promise.resolve(1)); + EXPECT_NO_THROW(promise.reject("ignored")); + EXPECT_EQ(0, jsInvoker->queueSize()); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, longLivedObjectHandleReleasesPayloadOnJsThreadOnce) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + auto record = std::make_shared(); + + { + LongLivedObjectHandle handle( + rt, recordingPayload(record), jsInvoker); + EXPECT_EQ(1, collection.size()); + + std::thread worker([&] { + handle.reset(); + handle.reset(); + }); + worker.join(); + EXPECT_EQ(1, jsInvoker->queueSize()); + + // The payload belongs to the collection, so nothing is destroyed on the + // worker thread however often the handle is reset. + EXPECT_EQ(0, record->releaseCount()); + EXPECT_EQ(1, collection.size()); + } + + jsInvoker->flushQueue(); + + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, longLivedObjectHandleAccessExpiresWithItsRoot) { + auto jsInvoker = std::make_shared(rt); + + LongLivedObjectHandle empty; + EXPECT_TRUE(empty.expired()); + EXPECT_EQ(nullptr, empty.lockInvoker()); + EXPECT_FALSE(static_cast(empty.lockJSValue(rt))); + + { + LongLivedObjectHandle handle(rt, "payload"s, jsInvoker); + EXPECT_FALSE(handle.expired()); + EXPECT_EQ( + static_cast(jsInvoker.get()), handle.lockInvoker().get()); + + { + auto locked = handle.lockJSValue(rt); + ASSERT_TRUE(locked); + locked->value += "!"; + } + + { + // The payload lives in the collection rather than in the handle, so the + // mutation is visible through a second lock. + auto locked = handle.lockJSValue(rt); + ASSERT_TRUE(locked); + EXPECT_EQ("payload!"s, locked->value); + } + + LongLivedObjectCollection::get(rt).clear(rt); + + // An invoker retained elsewhere must not allow dispatch through an + // invalidated handle. + EXPECT_TRUE(handle.expired()); + EXPECT_EQ(nullptr, handle.lockInvoker()); + EXPECT_FALSE(static_cast(handle.lockJSValue(rt))); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, longLivedObjectHandleReleasesInlineOnlyWithRuntime) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + auto immediateRecord = std::make_shared(); + auto deferredRecord = std::make_shared(); + + { + LongLivedObjectHandle immediate( + rt, recordingPayload(immediateRecord), jsInvoker); + LongLivedObjectHandle deferred( + rt, recordingPayload(deferredRecord), jsInvoker); + EXPECT_EQ(2, collection.size()); + + // The runtime proves exclusive access, so the root and the value go + // inline, once, and the queue never sees a cleanup job. + immediate.reset(rt); + immediate.reset(rt); + EXPECT_EQ(0, jsInvoker->queueSize()); + EXPECT_EQ(1, immediateRecord->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), immediateRecord->releaseThread()); + EXPECT_EQ(1, collection.size()); + + // Without the runtime, release is queued even on the JavaScript thread. + deferred.reset(); + EXPECT_EQ(1, jsInvoker->queueSize()); + EXPECT_EQ(0, deferredRecord->releaseCount()); + EXPECT_EQ(1, collection.size()); + } + + // Destroying the emptied handles schedules nothing more. + EXPECT_EQ(1, jsInvoker->queueSize()); + EXPECT_EQ(1, immediateRecord->releaseCount()); + + jsInvoker->flushQueue(); + EXPECT_EQ(1, deferredRecord->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), deferredRecord->releaseThread()); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, longLivedObjectHandleBuiltOnBorrowedThreadDefersRelease) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + auto record = std::make_shared(); + std::optional> handle; + std::barrier phase(2); + + std::thread worker([&] { + // The fixture thread is parked at the barrier, so this thread borrows the + // runtime exclusively, as a synchronous call from another thread would. + handle.emplace(rt, recordingPayload(record), jsInvoker); + phase.arrive_and_wait(); + // The runtime is handed back; building the handle here must not make this + // thread count as the JavaScript thread later on. + phase.arrive_and_wait(); + handle.reset(); + }); + + phase.arrive_and_wait(); + const auto rootsWhileBorrowed = collection.size(); + phase.arrive_and_wait(); + worker.join(); + + EXPECT_EQ(1, rootsWhileBorrowed); + EXPECT_EQ(1, jsInvoker->queueSize()); + EXPECT_EQ(0, record->releaseCount()); + EXPECT_EQ(1, collection.size()); + + jsInvoker->flushQueue(); + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, longLivedObjectHandleReleaseTaskHoldsNoStrongReference) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + auto record = std::make_shared(); + + { + LongLivedObjectHandle handle( + rt, recordingPayload(record), jsInvoker); + } + EXPECT_EQ(1, jsInvoker->queueSize()); + + // A queue that is thrown away destroys its tasks on whichever thread tears + // it down, so a scheduled release must not own the payload. + std::thread worker([&] { jsInvoker->discardQueue(); }); + worker.join(); + + EXPECT_EQ(0, record->releaseCount()); + EXPECT_EQ(1, collection.size()); + + collection.clear(rt); + + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, longLivedObjectCollectionClearDestroysPayloadsUnlocked) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + int reentrantClears = 0; + + { + LongLivedObjectHandle handle( + rt, + ProbePayload([&] { + ++reentrantClears; + LongLivedObjectCollection::get(rt).clear(rt); + }), + jsInvoker); + + // A payload destructor is allowed to reach back into the collection, which + // it cannot do if clear() still holds the lock while destroying. + collection.clear(rt); + } + + EXPECT_EQ(1, reentrantClears); + EXPECT_EQ(0, collection.size()); + + jsInvoker->flushQueue(); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncCallbackRuntimeDestroyedDuringDispatch) { + for (bool customCall : {false, true}) { + auto standaloneRuntime = hermes::makeHermesRuntime(); + auto jsInvoker = + std::make_shared(*standaloneRuntime); + auto callback = std::make_optional>( + *standaloneRuntime, + jsi::Function::createFromHostFunction( + *standaloneRuntime, + jsi::PropNameID::forAscii(*standaloneRuntime, "callback"), + 1, + [](jsi::Runtime&, const jsi::Value&, const jsi::Value*, size_t) { + return jsi::Value::undefined(); + }), + jsInvoker); + + jsInvoker->closeGate(); + std::thread worker([&] { + if (customCall) { + callback->call([](jsi::Runtime& runtime, jsi::Function& function) { + function.call(runtime, 1); + }); + } else { + callback->call(1); + } + }); + + jsInvoker->waitForDispatch(1); + // A native-side strong reference would survive clear() while dispatch is + // parked; address sanitizer detects its invalidation after runtime + // disposal. + LongLivedObjectCollection::get(*standaloneRuntime) + .clear(*standaloneRuntime); + standaloneRuntime.reset(); + jsInvoker->openGate(); + worker.join(); + EXPECT_EQ(1, jsInvoker->queueSize()); + + callback.reset(); + std::thread discard([&] { jsInvoker->discardQueue(); }); + discard.join(); + EXPECT_EQ(0, jsInvoker->queueSize()); + } +} + +TEST_F(BridgingTest, callbackRoundTripPreservesFunctionIdentity) { + auto original = function("(value) => value + 1"); + auto sync = bridging::fromJs>( + rt, original.getFunction(rt), invoker); + auto async = bridging::fromJs>( + rt, original.getFunction(rt), invoker); + + EXPECT_TRUE( + jsi::Object::strictEquals( + rt, original, Bridging::toJs(rt, sync))); + EXPECT_TRUE( + jsi::Object::strictEquals( + rt, original, Bridging::toJs(rt, async))); + + LongLivedObjectCollection::get(rt).clear(rt); + EXPECT_JSI_THROW(Bridging::toJs(rt, sync)); + EXPECT_JSI_THROW(Bridging::toJs(rt, async)); +} + +TEST_F(BridgingTest, longLivedObjectHandleRejectsMissingInvoker) { + EXPECT_JSI_THROW( + LongLivedObjectHandle(rt, jsi::Object(rt), nullptr)); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, syncCallbackMoveConstructionTransfersOwnership) { + { + auto source = std::make_optional>( + rt, function("() => 'moved'"), invoker); + auto moved = std::move(*source); + source.reset(); + flushQueue(); + EXPECT_EQ(1, LongLivedObjectCollection::get(rt).size()); + EXPECT_EQ("moved"s, moved()); + } + flushQueue(); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, syncCallbackCanDestroyItsNativeOwnerDuringCall) { + // Heap allocated so that touching a member after the owner is gone is a + // use-after-free the sanitizers report, not a read of live stack storage. + std::unique_ptr> callback; + auto destroyOwner = std::function([&] { + callback.reset(); + return 42; + }); + callback = std::make_unique>( + rt, bridging::toJs(rt, destroyOwner, invoker), invoker); + + EXPECT_EQ(42, callback->call()); + EXPECT_EQ(nullptr, callback); + flushQueue(); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, longLivedObjectCollectionRemoveDestroysPayloadsUnlocked) { + struct ReentrantObject : LongLivedObject { + ReentrantObject(jsi::Runtime& runtime, int& releases) + : LongLivedObject(runtime), releases_(releases) {} + + ~ReentrantObject() override { + ++releases_; + LongLivedObjectCollection::get(runtime_).clear(runtime_); + } + + int& releases_; + }; + + auto& collection = LongLivedObjectCollection::get(rt); + int releases = 0; + auto object = std::make_shared(rt, releases); + auto address = object.get(); + collection.add(std::move(object)); + collection.remove(address); + + EXPECT_EQ(1, releases); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, longLivedObjectCollectionClearSupersedesQueuedRelease) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + auto record = std::make_shared(); + + { + LongLivedObjectHandle handle( + rt, recordingPayload(record), jsInvoker); + } + EXPECT_EQ(1, jsInvoker->queueSize()); + EXPECT_EQ(0, record->releaseCount()); + + // Runtime cleanup releases the root inline, before its queued release runs. + collection.clear(rt); + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); + EXPECT_EQ(0, collection.size()); + + // The stale release finds nothing left, so the payload is released once. + jsInvoker->flushQueue(); + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncCallbackInvalidationReleasesQueuedArguments) { + for (bool discard : {false, true}) { + auto jsInvoker = std::make_shared(rt); + auto lifetime = std::make_shared(1); + std::weak_ptr weakLifetime = lifetime; + auto argument = + std::function([lifetime] { EXPECT_EQ(1, *lifetime); }); + auto callback = AsyncCallback( + rt, function("(callback, value) => callback()"), jsInvoker); + + callback.callWithPriority( + SchedulerPriority::IdlePriority, argument, "queued"); + argument = {}; + lifetime.reset(); + EXPECT_FALSE(weakLifetime.expired()); + EXPECT_EQ(1, jsInvoker->queueSize()); + + LongLivedObjectCollection::get(rt).clear(rt); + if (discard) { + std::thread worker([&] { jsInvoker->discardQueue(); }); + worker.join(); + } else { + jsInvoker->flushQueue(); + } + EXPECT_TRUE(weakLifetime.expired()); + EXPECT_EQ(0, jsInvoker->queueSize()); + } +} + +TEST_F(BridgingTest, longLivedObjectHandleDoesNotRetainInvoker) { + auto jsInvoker = std::make_shared(rt); + std::weak_ptr weakInvoker = jsInvoker; + auto record = std::make_shared(); + LongLivedObjectHandle handle( + rt, recordingPayload(record), jsInvoker); + + jsInvoker.reset(); + EXPECT_TRUE(weakInvoker.expired()); + EXPECT_FALSE(handle.expired()); + + std::thread worker([&] { handle.reset(); }); + worker.join(); + EXPECT_EQ(0, record->releaseCount()); + + LongLivedObjectCollection::get(rt).clear(rt); + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); +} + +TEST_F(BridgingTest, asyncCallbackQueuedCallStopsWhenInvokerExpires) { + auto jsInvoker = std::make_shared(rt); + std::weak_ptr weakInvoker = jsInvoker; + int calls = 0; + auto nativeCallback = std::function([&] { ++calls; }); + auto callback = AsyncCallback( + rt, function("(callback) => callback()"), jsInvoker); + callback.call(nativeCallback); + + auto queued = jsInvoker->takeNextJob(); + ASSERT_TRUE(queued.has_value()); + jsInvoker.reset(); + ASSERT_TRUE(weakInvoker.expired()); + + EXPECT_NO_THROW((*queued)(rt)); + EXPECT_EQ(0, calls); + EXPECT_EQ(1, LongLivedObjectCollection::get(rt).size()); + LongLivedObjectCollection::get(rt).clear(rt); +} + +TEST_F(BridgingTest, asyncCallbackQueuedCustomCallDoesNotRequireInvoker) { + auto jsInvoker = std::make_shared(rt); + std::weak_ptr weakInvoker = jsInvoker; + int calls = 0; + auto callback = AsyncCallback<>(rt, function("() => {}"), jsInvoker); + callback.call([&](jsi::Runtime&, jsi::Function&) { ++calls; }); + + auto queued = jsInvoker->takeNextJob(); + ASSERT_TRUE(queued.has_value()); + jsInvoker.reset(); + ASSERT_TRUE(weakInvoker.expired()); + + (*queued)(rt); + EXPECT_EQ(1, calls); + EXPECT_EQ(1, LongLivedObjectCollection::get(rt).size()); + LongLivedObjectCollection::get(rt).clear(rt); +} + +TEST_F(BridgingTest, syncCallbackStopsWhenInvokerExpires) { + auto jsInvoker = std::make_shared(rt); + SyncCallback callback(rt, function("() => 42"), jsInvoker); + + jsInvoker.reset(); + EXPECT_THROW(callback.call(), std::runtime_error); + callback.reset(rt); +} + +TEST_F(BridgingTest, asyncCallbackUsesItsStoredRuntime) { + auto otherRuntime = hermes::makeHermesRuntime(); + for (bool customCall : {false, true}) { + auto jsInvoker = std::make_shared(rt); + int calls = 0; + auto nativeCallback = std::function([&] { ++calls; }); + { + auto callback = AsyncCallback( + rt, function("(callback) => callback()"), jsInvoker); + + if (customCall) { + callback.call([&](jsi::Runtime& runtime, jsi::Function& function) { + EXPECT_EQ(&rt, &runtime); + function.call( + runtime, bridging::toJs(runtime, nativeCallback, jsInvoker)); + }); + } else { + callback.call(nativeCallback); + } + + auto queued = jsInvoker->takeNextJob(); + ASSERT_TRUE(queued.has_value()); + (*queued)(*otherRuntime); + EXPECT_EQ(1, calls); + } + jsInvoker->flushQueue(); + } +} + +TEST_F(BridgingTest, longLivedObjectHandleResetRacesRuntimeCleanup) { + constexpr size_t iterations = 10000; + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + std::optional> handle; + std::barrier phase(2); + + std::thread worker([&] { + for (size_t iteration = 0; iteration < iterations; ++iteration) { + phase.arrive_and_wait(); + handle->reset(); + phase.arrive_and_wait(); + } + }); + + size_t wrongReleaseCount = 0; + size_t wrongReleaseThread = 0; + for (size_t iteration = 0; iteration < iterations; ++iteration) { + auto record = std::make_shared(); + handle.emplace(rt, recordingPayload(record), jsInvoker); + + // Repeatedly overlap teardown with native release to cover short-lived + // strong references hidden inside converting weak-pointer constructors. + phase.arrive_and_wait(); + collection.clear(rt); + phase.arrive_and_wait(); + + wrongReleaseCount += static_cast(record->releaseCount() != 1); + wrongReleaseThread += static_cast( + record->releaseThread() != std::this_thread::get_id()); + jsInvoker->discardQueue(); + } + worker.join(); + + EXPECT_EQ(0, wrongReleaseCount); + EXPECT_EQ(0, wrongReleaseThread); + EXPECT_EQ(0, collection.size()); + EXPECT_EQ(0, jsInvoker->queueSize()); +} + +TEST_F(BridgingTest, syncCallbackReleasesExplicitlyWithRuntime) { + auto jsInvoker = std::make_shared(rt); + SyncCallback callback(rt, function("() => 42"), jsInvoker); + EXPECT_EQ(42, callback()); + + callback.reset(rt); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); + EXPECT_EQ(0, jsInvoker->queueSize()); + EXPECT_THROW(callback.call(), std::runtime_error); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.h b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.h index 71a072acd3c1..f9acea900ae7 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.h +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.h @@ -38,7 +38,7 @@ class BridgingTest : public ::testing::Test { ~BridgingTest() override { - LongLivedObjectCollection::get(rt).clear(); + LongLivedObjectCollection::get(rt).clear(rt); } void TearDown() override diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp index 4d02c5a60f59..574d2f79b1f5 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp @@ -161,9 +161,9 @@ void TurboModuleBinding::install( } TurboModuleBinding::~TurboModuleBinding() { - LongLivedObjectCollection::get(runtime_).clear(); + LongLivedObjectCollection::get(runtime_).clear(runtime_); if (longLivedObjectCollection_) { - longLivedObjectCollection_->clear(); + longLivedObjectCollection_->clear(runtime_); } } diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/tests/TurboModuleTestFixture.h b/packages/react-native/ReactCommon/react/nativemodule/core/tests/TurboModuleTestFixture.h index 7043bb30a139..ce39875b78f9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/tests/TurboModuleTestFixture.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/tests/TurboModuleTestFixture.h @@ -84,6 +84,7 @@ class TurboModuleTestFixture : public TurboModuleTestFixtureInternal, public ::t void TearDown() override { module_ = nullptr; + LongLivedObjectCollection::get(*runtime_).clear(*runtime_); jsInvoker_ = nullptr; runtime_ = nullptr; } diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp index d080c1cd892e..3a24d57807e3 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef RN_DISABLE_OSS_PLUGIN_HEADER #include "Plugins.h" @@ -413,7 +414,8 @@ jsi::Value NativeDOM::linkRootNode( void NativeDOM::measure( jsi::Runtime& rt, std::shared_ptr shadowNode, - const MeasureOnSuccessCallback& callback) { + MeasureOnSuccessCallback callback) { + OnScopeExit releaseCallback([&] { callback.reset(rt); }); auto currentRevision = getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); if (currentRevision == nullptr) { @@ -435,7 +437,8 @@ void NativeDOM::measure( void NativeDOM::measureInWindow( jsi::Runtime& rt, std::shared_ptr shadowNode, - const MeasureInWindowOnSuccessCallback& callback) { + MeasureInWindowOnSuccessCallback callback) { + OnScopeExit releaseCallback([&] { callback.reset(rt); }); auto currentRevision = getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); if (currentRevision == nullptr) { @@ -452,7 +455,8 @@ void NativeDOM::measureLayout( std::shared_ptr shadowNode, std::shared_ptr relativeToShadowNode, jsi::Function onFail, - const MeasureLayoutOnSuccessCallback& onSuccess) { + MeasureLayoutOnSuccessCallback onSuccess) { + OnScopeExit releaseCallback([&] { onSuccess.reset(rt); }); auto currentRevision = getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); if (currentRevision == nullptr) { diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h index 2c239875e6c4..c3a3c6e6c38e 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h @@ -95,20 +95,19 @@ class NativeDOM : public NativeDOMCxxSpec { #pragma mark - Legacy layout APIs (for `ReactNativeElement`). - void - measure(jsi::Runtime &rt, std::shared_ptr shadowNode, const MeasureOnSuccessCallback &callback); + void measure(jsi::Runtime &rt, std::shared_ptr shadowNode, MeasureOnSuccessCallback callback); void measureInWindow( jsi::Runtime &rt, std::shared_ptr shadowNode, - const MeasureInWindowOnSuccessCallback &callback); + MeasureInWindowOnSuccessCallback callback); void measureLayout( jsi::Runtime &rt, std::shared_ptr shadowNode, std::shared_ptr relativeToShadowNode, jsi::Function onFail, - const MeasureLayoutOnSuccessCallback &onSuccess); + MeasureLayoutOnSuccessCallback onSuccess); #pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`). diff --git a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js b/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js index b334b3c85662..c5847a06572d 100644 --- a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js +++ b/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js @@ -1427,6 +1427,21 @@ describe('ReactNativeElement', () => { expect(callback.mock.calls).toEqual([[10, 10, 100, 100, 10, 10]]); }); + it('component.measure(...) releases its callback when it throws', () => { + const root = Fantom.createRoot(); + const ref = createRef(); + Fantom.runTask(() => { + root.render(); + }); + + const node = nullthrows(ref.current); + expect(() => + node.measure(() => { + throw new Error('measurement callback failed'); + }), + ).toThrow('measurement callback failed'); + }); + it('unmounted.measure(...) does nothing', () => { const root = Fantom.createRoot(); const ref = createRef(); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index e03c23da7745..a32a7ed8a25e 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1753,6 +1753,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -4154,10 +4155,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -4213,6 +4215,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -5407,6 +5430,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index b9463dbd17f1..8e27c3d15aab 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -2638,6 +2638,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -7277,10 +7278,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -7343,6 +7345,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -8386,8 +8409,11 @@ template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index cc356a256f33..a088d46f5574 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -1752,6 +1752,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -4147,10 +4148,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -4206,6 +4208,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -5256,6 +5279,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index cc356a256f33..a088d46f5574 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1752,6 +1752,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -4147,10 +4148,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -4206,6 +4208,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -5256,6 +5279,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 95b044c2bcb5..be492bbfb235 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -3977,6 +3977,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -6015,10 +6016,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -6056,6 +6058,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -7130,6 +7153,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api index e2574b7a5509..5bd107d78cc7 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -4794,6 +4794,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -9153,10 +9154,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -9201,6 +9203,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -10207,8 +10230,11 @@ template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index d05f330f1907..96987e2404df 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -3969,6 +3969,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -6001,10 +6002,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -6042,6 +6044,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -6982,6 +7005,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 01006d9de4da..7d20be25cce4 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -3976,6 +3976,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -6008,10 +6009,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -6049,6 +6051,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -6989,6 +7012,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index e5147f2de315..44078138a2a0 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -875,6 +875,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -2197,10 +2198,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -2238,6 +2240,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template struct facebook::react::CascadedRectangleCorners { public bool operator==(const facebook::react::CascadedRectangleCorners& rhs) const = default; @@ -2648,6 +2671,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api index 412104d071ea..2f94fbf76ff9 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api @@ -1567,6 +1567,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -5069,10 +5070,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -5117,6 +5119,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template struct facebook::react::AnimatedProp : public facebook::react::AnimatedPropBase { public AnimatedProp() = default; @@ -5600,8 +5623,11 @@ template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 00a90ccf293d..f382d4c95177 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -874,6 +874,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -2190,10 +2191,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -2231,6 +2233,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template struct facebook::react::CascadedRectangleCorners { public bool operator==(const facebook::react::CascadedRectangleCorners& rhs) const = default; @@ -2641,6 +2664,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 00a90ccf293d..f382d4c95177 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -874,6 +874,7 @@ class facebook::react::LongLivedObjectCollection { public static facebook::react::LongLivedObjectCollection& get(facebook::jsi::Runtime& runtime); public void add(std::shared_ptr o); public void clear(); + public void clear(facebook::jsi::Runtime& runtime); public void operator=(const facebook::react::LongLivedObjectCollection&) = delete; public void remove(const facebook::react::LongLivedObject* o); } @@ -2190,10 +2191,11 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~SyncCallback() = default; } template @@ -2231,6 +2233,27 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public void reset(facebook::jsi::Runtime& runtime) noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template struct facebook::react::CascadedRectangleCorners { public bool operator==(const facebook::react::CascadedRectangleCorners& rhs) const = default; @@ -2641,6 +2664,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept;