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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2217,7 +2217,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
FBLazyVector: 7c1d69992204c5ec452eeefa4a24b0ff311709c8
hermes-engine: 16e781d7fca74c8bb3ca59b99527d9633ee9ee36
hermes-engine: b69aade2dcf59e8c2203c748ca79c7fa4c875053
NitroModules: ea6ec1c94ca32c73b22d726b113e570d2abc8b5d
NitroTest: cbbc53e62ed086d1993bbac0fcec3cc77c0b4617
NitroTestExternal: 31816d957144245da0b24b4f95fb3427533e73d7
Expand All @@ -2229,7 +2229,7 @@ SPEC CHECKSUMS:
React: f6f8fc5c01e77349cdfaf49102bcb928ac31d8ed
React-callinvoker: 3e62a849bda1522c6422309c02f5dc3210bc5359
React-Core: 0b765bc7c2b83dff178c2b03ed8a0390df26f18c
React-Core-prebuilt: 88810feb58457484bff17e9e91a15453407d745a
React-Core-prebuilt: 1ed3b91c7035f715bf741f1b8a2c87e94d126d38
React-CoreModules: 55b932564ba696301cb683a86385be6a6f137e57
React-cxxreact: 5bfb95256fde56cc0f9ce425f38cfaa085e71ad2
React-debug: f9dda2791d3ebe2078bc6102641edab77917efb7
Expand Down Expand Up @@ -2292,7 +2292,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 23e2bca1661f8781e55fcc05a151fc1df97bc1fb
ReactCodegen: c049d7e966ed24be56d8e21cb1b8880316975e76
ReactCommon: 89ccc6cb100ca5a0303b46483037ef8f3e06e2e0
ReactNativeDependencies: 1a7e3c3ffa57533d8118dd9bc01790ffa9e02a3b
ReactNativeDependencies: 69de62c8a59d21e7b6af8bdfb62b61e798f65351
RNScreens: dd61bc3a3e6f6901ad833efa411917d44827cf51
Yoga: 21f482cbc18b56cdc477cd3a0c5b8c2c83ac27ce

Expand Down
51 changes: 50 additions & 1 deletion example/src/getTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export function getTests(
): TestRunner[] {
const backend = options.backend ?? throwingBackend
const { it } = createTestRunner(backend)
const createTest = createCreateTest(it)
const createTest = createCreateTest()

return [
// Basic prototype tests
Expand Down Expand Up @@ -1478,6 +1478,55 @@ export function getTests(
.didNotThrow()
.equals(10_000)
),
createTest('HybridObjects dont leak memory', () =>
it(() => {
const baselineAllocations =
NitroModules.debug_getTotalAllocatedHybridObjects()

const BATCH_SIZE = 1000
const LOOP_COUNT = 10
const TOTAL_ALLOCATIONS = BATCH_SIZE * LOOP_COUNT

const objects: Array<TestObjectCpp | TestObjectSwiftKotlin> = []
for (let i = 0; i < TOTAL_ALLOCATIONS; i++) {
const object = testObject.newTestObject()
object.numberValue = i
objects.push(object)

if (objects.length >= BATCH_SIZE) {
objects.length = 0
gc()
}
}

objects.length = 0
gc()
gc()
gc()

const currentAllocations =
NitroModules.debug_getTotalAllocatedHybridObjects()
const remainingAllocations = currentAllocations - baselineAllocations
// make sure that less than 10% of the total allocations are remaining, indicating GC ran for most of it.
const didDeleteMostObjects =
remainingAllocations < TOTAL_ALLOCATIONS * 0.1
const result: {
baselineAllocations: number
currentAllocations: number
isEqual?: boolean
} = {
baselineAllocations: baselineAllocations,
currentAllocations: currentAllocations,
isEqual: didDeleteMostObjects,
}
if (!didDeleteMostObjects) {
delete result.isEqual
}
return result
})
.didNotThrow()
.toContain('isEqual')
),
createTest('callWithOptional(undefined)', async () =>
(
await it<number | undefined>(() => {
Expand Down
13 changes: 12 additions & 1 deletion packages/react-native-nitro-modules/cpp/core/HybridObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@

#include "HybridObject.hpp"
#include "CommonGlobals.hpp"
#include "HybridNitroModulesProxy.hpp"
#include "JSIConverter.hpp"
#include "NitroDefines.hpp"

namespace margelo::nitro {

HybridObject::HybridObject(const char* NON_NULL name) : HybridObjectPrototype(), _name(name) {}
HybridObject::HybridObject(const char* NON_NULL name) : HybridObjectPrototype(), _name(name) {
#ifdef NITRO_DEBUG
HybridNitroModulesProxy::debug_notifyHybridObjectAllocated();
#endif
}

HybridObject::~HybridObject() {
#ifdef NITRO_DEBUG
HybridNitroModulesProxy::debug_notifyHybridObjectDeallocated();
#endif
}

std::string HybridObject::toString() {
return "[HybridObject " + std::string(_name) + "]";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class HybridObject : public virtual jsi::NativeState, public HybridObjectPrototy
* Called when no more references to the given `HybridObject` exist in both C++ and JS.
* JS might keep references for longer, as it is a garbage collected language.
*/
~HybridObject() override = default;
~HybridObject() override;
/**
* HybridObjects cannot be copied.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ void HybridNitroModulesProxy::loadHybridMethods() {

prototype.registerHybridGetter("buildType", &HybridNitroModulesProxy::getBuildType);
prototype.registerHybridGetter("version", &HybridNitroModulesProxy::getVersion);

prototype.registerHybridMethod("debug_getTotalAllocatedHybridObjects", &HybridNitroModulesProxy::debug_getTotalAllocatedHybridObjects);
});
}

Expand Down Expand Up @@ -94,4 +96,19 @@ std::string HybridNitroModulesProxy::getVersion() {
return NITRO_VERSION;
}

// Allocation tests
static std::atomic_size_t _hybridObjectInstancesCount{0};
double HybridNitroModulesProxy::debug_getTotalAllocatedHybridObjects() {
size_t count = _hybridObjectInstancesCount.load(std::memory_order_relaxed);
return static_cast<double>(count);
}

void HybridNitroModulesProxy::debug_notifyHybridObjectAllocated() {
_hybridObjectInstancesCount.fetch_add(1, std::memory_order_relaxed);
}

void HybridNitroModulesProxy::debug_notifyHybridObjectDeallocated() {
_hybridObjectInstancesCount.fetch_sub(1, std::memory_order_relaxed);
}

} // namespace margelo::nitro
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ class HybridNitroModulesProxy final : public HybridObject {
std::string getBuildType();
std::string getVersion();

// Allocation tests
double debug_getTotalAllocatedHybridObjects();
static void debug_notifyHybridObjectAllocated();
static void debug_notifyHybridObjectDeallocated();

private:
static constexpr auto TAG = "NitroModulesProxy";
};
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native-nitro-modules/src/NitroModulesProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,10 @@ export interface NitroModulesProxy
* owns it - not JS.
*/
createNativeArrayBuffer(size: number): ArrayBuffer

/**
* Gets a total number of currently allocated {@linkcode HybridObject}s.
* @internal
*/
debug_getTotalAllocatedHybridObjects(): number
}
Loading