Skip to content

Commit 05f2089

Browse files
authored
perf: cover deferred worker promises and native-owned buffers (#1607)
* perf: cover deferred native Promises and native-owned buffer bounce * fix(ios): expose ThreadPool in the NitroModules framework
1 parent 0f31c92 commit 05f2089

18 files changed

Lines changed: 200 additions & 0 deletions

‎apps/benchmark/src/benchmarks/suite.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,20 @@ function createObjectBenchmarks(
288288
largeBuffer,
289289
'bounce'
290290
),
291+
createBufferBenchmark(
292+
object,
293+
implementation,
294+
'bounce-native-4-kib',
295+
object.copyBuffer(smallBuffer),
296+
'bounce'
297+
),
298+
createBufferBenchmark(
299+
object,
300+
implementation,
301+
'bounce-native-1-mib',
302+
object.copyBuffer(largeBuffer),
303+
'bounce'
304+
),
291305
createBufferBenchmark(
292306
object,
293307
implementation,
@@ -339,6 +353,30 @@ function createObjectBenchmarks(
339353
return assertNumber(checksum, 'promiseReturnsInstantly')
340354
},
341355
},
356+
{
357+
id: `${prefix}/promise/deferred-worker-with-trigger`,
358+
version: 1,
359+
family: 'promise',
360+
implementation,
361+
kind: 'async',
362+
maxChunkIterations: 5_000,
363+
collectNativeGarbage:
364+
implementation === 'nitro-platform' && Platform.OS === 'android'
365+
? collectJavaGarbage
366+
: undefined,
367+
expectedChecksum: (iterations) => iterations * 55,
368+
async run(iterations) {
369+
let checksum = 0
370+
for (let index = 0; index < iterations; index++) {
371+
const promise = object.createPendingPromise()
372+
// Includes the trigger call: completion cannot race ahead of the
373+
// pending Promise's conversion to JS in createPendingPromise().
374+
object.resolvePendingPromiseOnWorker()
375+
checksum += await promise
376+
}
377+
return assertNumber(checksum, 'resolvePendingPromiseOnWorker')
378+
},
379+
},
342380
]
343381
}
344382

‎packages/react-native-nitro-modules/NitroModules.podspec‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Pod::Spec.new do |s|
4545
"cpp/jsi/JSIHelpers.hpp",
4646
"cpp/platform/NitroLogger.hpp",
4747
"cpp/threading/Dispatcher.hpp",
48+
"cpp/threading/ThreadPool.hpp",
4849
"cpp/utils/JSCallback.hpp",
4950
"cpp/utils/FastVectorCopy.hpp",
5051
"cpp/utils/NitroHash.hpp",

‎packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridTestObjectKotlin.kt‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@ import com.margelo.nitro.core.NullType
1111
import com.margelo.nitro.core.Promise
1212
import com.margelo.nitro.core.resolved
1313
import com.margelo.nitro.test.external.HybridSomeExternalObjectSpec
14+
import kotlinx.coroutines.CoroutineScope
15+
import kotlinx.coroutines.Dispatchers
1416
import kotlinx.coroutines.delay
17+
import kotlinx.coroutines.launch
1518
import java.math.BigDecimal
1619
import java.time.Instant
1720

1821
@Keep
1922
@DoNotStrip
2023
class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() {
24+
private var pendingPromise: Promise<Double>? = null
25+
26+
private companion object {
27+
val promiseScope = CoroutineScope(Dispatchers.Default)
28+
}
29+
2130
override var numberValue: Double = 0.0
2231
override var boolValue: Boolean = false
2332
override var stringValue: String = ""
@@ -316,6 +325,22 @@ class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() {
316325
}
317326
}
318327

328+
override fun createPendingPromise(): Promise<Double> {
329+
check(pendingPromise == null) { "A pending Promise is already waiting for completion." }
330+
val promise = Promise<Double>()
331+
pendingPromise = promise
332+
return promise
333+
}
334+
335+
override fun resolvePendingPromiseOnWorker() {
336+
val promise = checkNotNull(pendingPromise) { "No pending Promise is waiting for completion." }
337+
// Only JS calls access the slot; the worker owns the extracted Promise.
338+
pendingPromise = null
339+
promiseScope.launch {
340+
promise.resolve(55.0)
341+
}
342+
}
343+
319344
override fun promiseThatResolvesVoidInstantly(): Promise<Unit> {
320345
return Promise.resolved()
321346
}

‎packages/react-native-nitro-test/cpp/HybridTestObjectCpp.cpp‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "HybridTestObjectCpp.hpp"
99
#include <NitroModules/AnyMap.hpp>
1010
#include <NitroModules/NitroLogger.hpp>
11+
#include <NitroModules/ThreadPool.hpp>
1112
#include <chrono>
1213
#include <sstream>
1314
#include <thread>
@@ -611,6 +612,23 @@ std::shared_ptr<Promise<double>> HybridTestObjectCpp::promiseReturnsInstantlyAsy
611612
return Promise<double>::async([=]() { return 55; });
612613
}
613614

615+
std::shared_ptr<Promise<double>> HybridTestObjectCpp::createPendingPromise() {
616+
if (_pendingPromise) {
617+
throw std::runtime_error("A pending Promise is already waiting for completion.");
618+
}
619+
_pendingPromise = Promise<double>::create();
620+
return _pendingPromise;
621+
}
622+
623+
void HybridTestObjectCpp::resolvePendingPromiseOnWorker() {
624+
if (!_pendingPromise) {
625+
throw std::runtime_error("No pending Promise is waiting for completion.");
626+
}
627+
// Only JS calls access the slot; the worker owns the extracted Promise.
628+
auto promise = std::move(_pendingPromise);
629+
ThreadPool::shared().run([promise = std::move(promise)]() { promise->resolve(55); });
630+
}
631+
614632
std::shared_ptr<Promise<void>> HybridTestObjectCpp::promiseThatResolvesVoidInstantly() {
615633
return Promise<void>::resolved();
616634
}

‎packages/react-native-nitro-test/cpp/HybridTestObjectCpp.hpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class HybridTestObjectCpp : public HybridTestObjectCppSpec {
4545
std::optional<std::function<void(double)>> _optionalCallback;
4646
bool _hasBooleanWritable;
4747
bool _isBooleanWritable;
48+
std::shared_ptr<Promise<double>> _pendingPromise;
4849

4950
private:
5051
static inline uint64_t calculateFibonacci(int count) noexcept {
@@ -205,6 +206,8 @@ class HybridTestObjectCpp : public HybridTestObjectCppSpec {
205206
std::shared_ptr<Promise<void>> promiseThrows() override;
206207
std::shared_ptr<Promise<double>> promiseReturnsInstantly() override;
207208
std::shared_ptr<Promise<double>> promiseReturnsInstantlyAsync() override;
209+
std::shared_ptr<Promise<double>> createPendingPromise() override;
210+
void resolvePendingPromiseOnWorker() override;
208211
std::shared_ptr<Promise<void>> promiseThatResolvesVoidInstantly() override;
209212
std::shared_ptr<Promise<std::optional<double>>> promiseThatResolvesToUndefined() override;
210213
std::shared_ptr<Promise<std::optional<double>>> awaitNullablePromise() override;

‎packages/react-native-nitro-test/ios/HybridTestObjectSwift.swift‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
// Created by Marc Rousavy on 11.08.24.
66
//
77

8+
import Dispatch
89
import NitroModules
910
import NitroTestExternal
1011

1112
class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec {
13+
private var pendingPromise: Promise<Double>?
14+
1215
var optionalArray: [String]? = []
1316

1417
var someVariant: Variant_Double_String = .first(55)
@@ -464,6 +467,26 @@ class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec {
464467
}
465468
}
466469

470+
func createPendingPromise() throws -> Promise<Double> {
471+
guard pendingPromise == nil else {
472+
throw RuntimeError.error(withMessage: "A pending Promise is already waiting for completion.")
473+
}
474+
let promise = Promise<Double>()
475+
pendingPromise = promise
476+
return promise
477+
}
478+
479+
func resolvePendingPromiseOnWorker() throws {
480+
guard let promise = pendingPromise else {
481+
throw RuntimeError.error(withMessage: "No pending Promise is waiting for completion.")
482+
}
483+
// Only JS calls access the slot; the worker owns the extracted Promise.
484+
pendingPromise = nil
485+
DispatchQueue.global().async {
486+
promise.resolve(withResult: 55.0)
487+
}
488+
}
489+
467490
func promiseThatResolvesVoidInstantly() throws -> Promise<Void> {
468491
return Promise.resolved()
469492
}

‎packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.cpp‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,26 @@ namespace margelo::nitro::test {
10771077
return __promise;
10781078
}();
10791079
}
1080+
std::shared_ptr<Promise<double>> JHybridTestObjectSwiftKotlinSpec::createPendingPromise() {
1081+
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("createPendingPromise");
1082+
auto __result = method(_javaPart);
1083+
return [&]() {
1084+
auto __promise = Promise<double>::create();
1085+
__result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
1086+
auto __result = jni::static_ref_cast<jni::JDouble>(__boxedResult);
1087+
__promise->resolve(__result->value());
1088+
});
1089+
__result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
1090+
jni::JniException __jniError(__throwable);
1091+
__promise->reject(std::make_exception_ptr(__jniError));
1092+
});
1093+
return __promise;
1094+
}();
1095+
}
1096+
void JHybridTestObjectSwiftKotlinSpec::resolvePendingPromiseOnWorker() {
1097+
static const auto method = _javaPart->javaClassStatic()->getMethod<void()>("resolvePendingPromiseOnWorker");
1098+
method(_javaPart);
1099+
}
10801100
std::shared_ptr<Promise<void>> JHybridTestObjectSwiftKotlinSpec::promiseThatResolvesVoidInstantly() {
10811101
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("promiseThatResolvesVoidInstantly");
10821102
auto __result = method(_javaPart);

‎packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.hpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ namespace margelo::nitro::test {
142142
std::shared_ptr<Promise<void>> promiseThrows() override;
143143
std::shared_ptr<Promise<double>> promiseReturnsInstantly() override;
144144
std::shared_ptr<Promise<double>> promiseReturnsInstantlyAsync() override;
145+
std::shared_ptr<Promise<double>> createPendingPromise() override;
146+
void resolvePendingPromiseOnWorker() override;
145147
std::shared_ptr<Promise<void>> promiseThatResolvesVoidInstantly() override;
146148
std::shared_ptr<Promise<std::optional<double>>> promiseThatResolvesToUndefined() override;
147149
std::shared_ptr<Promise<std::optional<double>>> awaitNullablePromise() override;

‎packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridTestObjectSwiftKotlinSpec.kt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,14 @@ abstract class HybridTestObjectSwiftKotlinSpec: HybridObject() {
378378
@Keep
379379
abstract fun promiseReturnsInstantlyAsync(): Promise<Double>
380380

381+
@DoNotStrip
382+
@Keep
383+
abstract fun createPendingPromise(): Promise<Double>
384+
385+
@DoNotStrip
386+
@Keep
387+
abstract fun resolvePendingPromiseOnWorker(): Unit
388+
381389
@DoNotStrip
382390
@Keep
383391
abstract fun promiseThatResolvesVoidInstantly(): Promise<Unit>

‎packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridTestObjectSwiftKotlinSpecSwift.hpp‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,20 @@ namespace margelo::nitro::test {
664664
auto __value = std::move(__result.value());
665665
return __value;
666666
}
667+
inline std::shared_ptr<Promise<double>> createPendingPromise() override {
668+
auto __result = _swiftPart.createPendingPromise();
669+
if (__result.hasError()) [[unlikely]] {
670+
std::rethrow_exception(__result.error());
671+
}
672+
auto __value = std::move(__result.value());
673+
return __value;
674+
}
675+
inline void resolvePendingPromiseOnWorker() override {
676+
auto __result = _swiftPart.resolvePendingPromiseOnWorker();
677+
if (__result.hasError()) [[unlikely]] {
678+
std::rethrow_exception(__result.error());
679+
}
680+
}
667681
inline std::shared_ptr<Promise<void>> promiseThatResolvesVoidInstantly() override {
668682
auto __result = _swiftPart.promiseThatResolvesVoidInstantly();
669683
if (__result.hasError()) [[unlikely]] {

0 commit comments

Comments
 (0)