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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-native-nitro-modules/NitroModules.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Pod::Spec.new do |s|
"cpp/utils/NitroHash.hpp",
"cpp/utils/NitroDefines.hpp",
"cpp/utils/PropNameIDCache.hpp",
"cpp/utils/WeakReferenceCache.hpp",
"cpp/views/CachedProp.hpp",
"cpp/views/RawPropsCompat.hpp",
"cpp/views/ReactProp.hpp",
Expand Down
24 changes: 7 additions & 17 deletions packages/react-native-nitro-modules/cpp/jsi/JSICache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,17 @@

namespace margelo::nitro {

template <typename T>
inline void destroyReferences(const std::vector<WeakReference<T>>& references) {
for (auto& func : references) {
BorrowingReference<T> reference = func.lock();
if (reference) {
// Destroy all functions that we might still have in cache, some callbacks and Promises may now become invalid.
reference.destroy();
}
}
}

JSICache::~JSICache() {
Logger::log(LogLevel::Info, TAG, "Destroying JSICache...");
std::unique_lock lock(_mutex);

destroyReferences(_valueCache);
destroyReferences(_objectCache);
destroyReferences(_functionCache);
destroyReferences(_weakObjectCache);
destroyReferences(_propNameIDCache);
destroyReferences(_arrayBufferCache);
// Some callbacks and Promises may now become invalid.
_valueCache.destroyAll();
_objectCache.destroyAll();
_functionCache.destroyAll();
_weakObjectCache.destroyAll();
_propNameIDCache.destroyAll();
_arrayBufferCache.destroyAll();
}

JSICacheReference JSICache::getOrCreateCache(jsi::Runtime& runtime) {
Expand Down
26 changes: 13 additions & 13 deletions packages/react-native-nitro-modules/cpp/jsi/JSICache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
#include "BorrowingReference.hpp"
#include "NitroLogger.hpp"
#include "WeakReference.hpp"
#include "WeakReferenceCache.hpp"
#include <jsi/jsi.h>
#include <cstddef>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>

namespace margelo::nitro {

Expand Down Expand Up @@ -60,12 +60,12 @@ class JSICache final : public jsi::NativeState {

private:
std::mutex _mutex;
std::vector<WeakReference<jsi::Value>> _valueCache;
std::vector<WeakReference<jsi::Object>> _objectCache;
std::vector<WeakReference<jsi::Function>> _functionCache;
std::vector<WeakReference<jsi::WeakObject>> _weakObjectCache;
std::vector<WeakReference<jsi::PropNameID>> _propNameIDCache;
std::vector<WeakReference<jsi::ArrayBuffer>> _arrayBufferCache;
WeakReferenceCache<jsi::Value> _valueCache;
WeakReferenceCache<jsi::Object> _objectCache;
WeakReferenceCache<jsi::Function> _functionCache;
WeakReferenceCache<jsi::WeakObject> _weakObjectCache;
WeakReferenceCache<jsi::PropNameID> _propNameIDCache;
WeakReferenceCache<jsi::ArrayBuffer> _arrayBufferCache;

private:
static inline std::unordered_map<jsi::Runtime*, std::weak_ptr<JSICache>> _globalCache;
Expand All @@ -87,32 +87,32 @@ class JSICacheReference final {
public:
BorrowingReference<jsi::Value> makeShared(jsi::Value&& value) {
BorrowingReference<jsi::Value> owning(new jsi::Value(std::move(value)));
_strongCache->_valueCache.push_back(owning.weak());
_strongCache->_valueCache.push(owning.weak());
return owning;
}
BorrowingReference<jsi::Object> makeShared(jsi::Object&& value) {
BorrowingReference<jsi::Object> owning(new jsi::Object(std::move(value)));
_strongCache->_objectCache.push_back(owning.weak());
_strongCache->_objectCache.push(owning.weak());
return owning;
}
BorrowingReference<jsi::Function> makeShared(jsi::Function&& value) {
BorrowingReference<jsi::Function> owning(new jsi::Function(std::move(value)));
_strongCache->_functionCache.push_back(owning.weak());
_strongCache->_functionCache.push(owning.weak());
return owning;
}
BorrowingReference<jsi::WeakObject> makeShared(jsi::WeakObject&& value) {
BorrowingReference<jsi::WeakObject> owning(new jsi::WeakObject(std::move(value)));
_strongCache->_weakObjectCache.push_back(owning.weak());
_strongCache->_weakObjectCache.push(owning.weak());
return owning;
}
BorrowingReference<jsi::PropNameID> makeShared(jsi::PropNameID&& value) {
BorrowingReference<jsi::PropNameID> owning(new jsi::PropNameID(std::move(value)));
_strongCache->_propNameIDCache.push_back(owning.weak());
_strongCache->_propNameIDCache.push(owning.weak());
return owning;
}
BorrowingReference<jsi::ArrayBuffer> makeShared(jsi::ArrayBuffer&& value) {
BorrowingReference<jsi::ArrayBuffer> owning(new jsi::ArrayBuffer(std::move(value)));
_strongCache->_arrayBufferCache.push_back(owning.weak());
_strongCache->_arrayBufferCache.push(owning.weak());
return owning;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// WeakReferenceCache.hpp
// react-native-nitro
//
// Created by Patrick Kabwe on 25.08.26.
//

#pragma once

#include "BorrowingReference.hpp"
#include "WeakReference.hpp"
#include <algorithm>
#include <cstddef>
#include <utility>
#include <vector>

namespace margelo::nitro {

/**
* Holds `WeakReference`s to values that have to be destroyed once their
* `jsi::Runtime` goes away.
*
* A `WeakReference` keeps its `ReferenceState` alive after its value has been
* destroyed. The cache periodically removes expired references to release that
* state before Runtime teardown.
*
* Compaction runs at geometrically increasing size thresholds instead of on
* every insert, which keeps `push(...)` amortized O(1). Expired entries can
* remain until a later insertion reaches the next threshold.
*/
template <typename T>
class WeakReferenceCache final {
public:
WeakReferenceCache() = default;

WeakReferenceCache(const WeakReferenceCache&) = delete;
WeakReferenceCache(WeakReferenceCache&&) = delete;

void push(WeakReference<T>&& reference) {
_references.push_back(std::move(reference));
if (_references.size() >= _nextPruneThreshold) [[unlikely]] {
pruneExpired();
}
}

void destroyAll() {
for (const WeakReference<T>& reference : _references) {
BorrowingReference<T> value = reference.lock();
if (value != nullptr) {
value.destroy();
}
}
}

[[nodiscard]]
std::size_t size() const noexcept {
return _references.size();
}

private:
void pruneExpired() {
const auto isExpired = [](const WeakReference<T>& reference) {
return reference.lock() == nullptr;
};
_references.erase(std::remove_if(_references.begin(), _references.end(), isExpired), _references.end());
_nextPruneThreshold = std::max(MIN_PRUNE_BATCH_SIZE, _references.size() * 2);
}

private:
// Minimum number of cached weak refs to accumulate before pruning expired
// entries. This keeps insertion amortized O(1) while bounding retained
// ReferenceState slack.
static constexpr std::size_t MIN_PRUNE_BATCH_SIZE = 64;

private:
std::vector<WeakReference<T>> _references;
std::size_t _nextPruneThreshold = MIN_PRUNE_BATCH_SIZE;
};

} // namespace margelo::nitro
Loading