diff --git a/apps/benchmark/src/benchmarks/suite.ts b/apps/benchmark/src/benchmarks/suite.ts index f9affed77..1b2f8223e 100644 --- a/apps/benchmark/src/benchmarks/suite.ts +++ b/apps/benchmark/src/benchmarks/suite.ts @@ -288,6 +288,20 @@ function createObjectBenchmarks( largeBuffer, 'bounce' ), + createBufferBenchmark( + object, + implementation, + 'bounce-native-4-kib', + object.copyBuffer(smallBuffer), + 'bounce' + ), + createBufferBenchmark( + object, + implementation, + 'bounce-native-1-mib', + object.copyBuffer(largeBuffer), + 'bounce' + ), createBufferBenchmark( object, implementation, @@ -339,6 +353,30 @@ function createObjectBenchmarks( return assertNumber(checksum, 'promiseReturnsInstantly') }, }, + { + id: `${prefix}/promise/deferred-worker-with-trigger`, + version: 1, + family: 'promise', + implementation, + kind: 'async', + maxChunkIterations: 5_000, + collectNativeGarbage: + implementation === 'nitro-platform' && Platform.OS === 'android' + ? collectJavaGarbage + : undefined, + expectedChecksum: (iterations) => iterations * 55, + async run(iterations) { + let checksum = 0 + for (let index = 0; index < iterations; index++) { + const promise = object.createPendingPromise() + // Includes the trigger call: completion cannot race ahead of the + // pending Promise's conversion to JS in createPendingPromise(). + object.resolvePendingPromiseOnWorker() + checksum += await promise + } + return assertNumber(checksum, 'resolvePendingPromiseOnWorker') + }, + }, ] } diff --git a/packages/react-native-nitro-modules/NitroModules.podspec b/packages/react-native-nitro-modules/NitroModules.podspec index b73876fc3..eafd810d9 100644 --- a/packages/react-native-nitro-modules/NitroModules.podspec +++ b/packages/react-native-nitro-modules/NitroModules.podspec @@ -45,6 +45,7 @@ Pod::Spec.new do |s| "cpp/jsi/JSIHelpers.hpp", "cpp/platform/NitroLogger.hpp", "cpp/threading/Dispatcher.hpp", + "cpp/threading/ThreadPool.hpp", "cpp/utils/JSCallback.hpp", "cpp/utils/FastVectorCopy.hpp", "cpp/utils/NitroHash.hpp", diff --git a/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridTestObjectKotlin.kt b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridTestObjectKotlin.kt index 506f8a187..009fea855 100644 --- a/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridTestObjectKotlin.kt +++ b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridTestObjectKotlin.kt @@ -11,13 +11,22 @@ import com.margelo.nitro.core.NullType import com.margelo.nitro.core.Promise import com.margelo.nitro.core.resolved import com.margelo.nitro.test.external.HybridSomeExternalObjectSpec +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.math.BigDecimal import java.time.Instant @Keep @DoNotStrip class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() { + private var pendingPromise: Promise? = null + + private companion object { + val promiseScope = CoroutineScope(Dispatchers.Default) + } + override var numberValue: Double = 0.0 override var boolValue: Boolean = false override var stringValue: String = "" @@ -316,6 +325,22 @@ class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() { } } + override fun createPendingPromise(): Promise { + check(pendingPromise == null) { "A pending Promise is already waiting for completion." } + val promise = Promise() + pendingPromise = promise + return promise + } + + override fun resolvePendingPromiseOnWorker() { + val promise = checkNotNull(pendingPromise) { "No pending Promise is waiting for completion." } + // Only JS calls access the slot; the worker owns the extracted Promise. + pendingPromise = null + promiseScope.launch { + promise.resolve(55.0) + } + } + override fun promiseThatResolvesVoidInstantly(): Promise { return Promise.resolved() } diff --git a/packages/react-native-nitro-test/cpp/HybridTestObjectCpp.cpp b/packages/react-native-nitro-test/cpp/HybridTestObjectCpp.cpp index f15402b0d..ddebfb454 100644 --- a/packages/react-native-nitro-test/cpp/HybridTestObjectCpp.cpp +++ b/packages/react-native-nitro-test/cpp/HybridTestObjectCpp.cpp @@ -8,6 +8,7 @@ #include "HybridTestObjectCpp.hpp" #include #include +#include #include #include #include @@ -611,6 +612,23 @@ std::shared_ptr> HybridTestObjectCpp::promiseReturnsInstantlyAsy return Promise::async([=]() { return 55; }); } +std::shared_ptr> HybridTestObjectCpp::createPendingPromise() { + if (_pendingPromise) { + throw std::runtime_error("A pending Promise is already waiting for completion."); + } + _pendingPromise = Promise::create(); + return _pendingPromise; +} + +void HybridTestObjectCpp::resolvePendingPromiseOnWorker() { + if (!_pendingPromise) { + throw std::runtime_error("No pending Promise is waiting for completion."); + } + // Only JS calls access the slot; the worker owns the extracted Promise. + auto promise = std::move(_pendingPromise); + ThreadPool::shared().run([promise = std::move(promise)]() { promise->resolve(55); }); +} + std::shared_ptr> HybridTestObjectCpp::promiseThatResolvesVoidInstantly() { return Promise::resolved(); } diff --git a/packages/react-native-nitro-test/cpp/HybridTestObjectCpp.hpp b/packages/react-native-nitro-test/cpp/HybridTestObjectCpp.hpp index b0eee53a6..2a5766697 100644 --- a/packages/react-native-nitro-test/cpp/HybridTestObjectCpp.hpp +++ b/packages/react-native-nitro-test/cpp/HybridTestObjectCpp.hpp @@ -45,6 +45,7 @@ class HybridTestObjectCpp : public HybridTestObjectCppSpec { std::optional> _optionalCallback; bool _hasBooleanWritable; bool _isBooleanWritable; + std::shared_ptr> _pendingPromise; private: static inline uint64_t calculateFibonacci(int count) noexcept { @@ -205,6 +206,8 @@ class HybridTestObjectCpp : public HybridTestObjectCppSpec { std::shared_ptr> promiseThrows() override; std::shared_ptr> promiseReturnsInstantly() override; std::shared_ptr> promiseReturnsInstantlyAsync() override; + std::shared_ptr> createPendingPromise() override; + void resolvePendingPromiseOnWorker() override; std::shared_ptr> promiseThatResolvesVoidInstantly() override; std::shared_ptr>> promiseThatResolvesToUndefined() override; std::shared_ptr>> awaitNullablePromise() override; diff --git a/packages/react-native-nitro-test/ios/HybridTestObjectSwift.swift b/packages/react-native-nitro-test/ios/HybridTestObjectSwift.swift index 1c9b6fdd9..c87566f5d 100644 --- a/packages/react-native-nitro-test/ios/HybridTestObjectSwift.swift +++ b/packages/react-native-nitro-test/ios/HybridTestObjectSwift.swift @@ -5,10 +5,13 @@ // Created by Marc Rousavy on 11.08.24. // +import Dispatch import NitroModules import NitroTestExternal class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec { + private var pendingPromise: Promise? + var optionalArray: [String]? = [] var someVariant: Variant_Double_String = .first(55) @@ -464,6 +467,26 @@ class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec { } } + func createPendingPromise() throws -> Promise { + guard pendingPromise == nil else { + throw RuntimeError.error(withMessage: "A pending Promise is already waiting for completion.") + } + let promise = Promise() + pendingPromise = promise + return promise + } + + func resolvePendingPromiseOnWorker() throws { + guard let promise = pendingPromise else { + throw RuntimeError.error(withMessage: "No pending Promise is waiting for completion.") + } + // Only JS calls access the slot; the worker owns the extracted Promise. + pendingPromise = nil + DispatchQueue.global().async { + promise.resolve(withResult: 55.0) + } + } + func promiseThatResolvesVoidInstantly() throws -> Promise { return Promise.resolved() } diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.cpp index dd0e8131b..73f12b396 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.cpp @@ -1077,6 +1077,26 @@ namespace margelo::nitro::test { return __promise; }(); } + std::shared_ptr> JHybridTestObjectSwiftKotlinSpec::createPendingPromise() { + static const auto method = _javaPart->javaClassStatic()->getMethod()>("createPendingPromise"); + auto __result = method(_javaPart); + return [&]() { + auto __promise = Promise::create(); + __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { + auto __result = jni::static_ref_cast(__boxedResult); + __promise->resolve(__result->value()); + }); + __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { + jni::JniException __jniError(__throwable); + __promise->reject(std::make_exception_ptr(__jniError)); + }); + return __promise; + }(); + } + void JHybridTestObjectSwiftKotlinSpec::resolvePendingPromiseOnWorker() { + static const auto method = _javaPart->javaClassStatic()->getMethod("resolvePendingPromiseOnWorker"); + method(_javaPart); + } std::shared_ptr> JHybridTestObjectSwiftKotlinSpec::promiseThatResolvesVoidInstantly() { static const auto method = _javaPart->javaClassStatic()->getMethod()>("promiseThatResolvesVoidInstantly"); auto __result = method(_javaPart); diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.hpp index db313a652..f734f5b2b 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridTestObjectSwiftKotlinSpec.hpp @@ -142,6 +142,8 @@ namespace margelo::nitro::test { std::shared_ptr> promiseThrows() override; std::shared_ptr> promiseReturnsInstantly() override; std::shared_ptr> promiseReturnsInstantlyAsync() override; + std::shared_ptr> createPendingPromise() override; + void resolvePendingPromiseOnWorker() override; std::shared_ptr> promiseThatResolvesVoidInstantly() override; std::shared_ptr>> promiseThatResolvesToUndefined() override; std::shared_ptr>> awaitNullablePromise() override; diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridTestObjectSwiftKotlinSpec.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridTestObjectSwiftKotlinSpec.kt index 20fa4c213..6c0d5a87e 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridTestObjectSwiftKotlinSpec.kt +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridTestObjectSwiftKotlinSpec.kt @@ -378,6 +378,14 @@ abstract class HybridTestObjectSwiftKotlinSpec: HybridObject() { @Keep abstract fun promiseReturnsInstantlyAsync(): Promise + @DoNotStrip + @Keep + abstract fun createPendingPromise(): Promise + + @DoNotStrip + @Keep + abstract fun resolvePendingPromiseOnWorker(): Unit + @DoNotStrip @Keep abstract fun promiseThatResolvesVoidInstantly(): Promise diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridTestObjectSwiftKotlinSpecSwift.hpp b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridTestObjectSwiftKotlinSpecSwift.hpp index 28cc1fb17..59b23046f 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridTestObjectSwiftKotlinSpecSwift.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridTestObjectSwiftKotlinSpecSwift.hpp @@ -664,6 +664,20 @@ namespace margelo::nitro::test { auto __value = std::move(__result.value()); return __value; } + inline std::shared_ptr> createPendingPromise() override { + auto __result = _swiftPart.createPendingPromise(); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } + inline void resolvePendingPromiseOnWorker() override { + auto __result = _swiftPart.resolvePendingPromiseOnWorker(); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + } inline std::shared_ptr> promiseThatResolvesVoidInstantly() override { auto __result = _swiftPart.promiseThatResolvesVoidInstantly(); if (__result.hasError()) [[unlikely]] { diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridTestObjectSwiftKotlinSpec.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridTestObjectSwiftKotlinSpec.swift index 47d8a8d5e..35b6bb886 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridTestObjectSwiftKotlinSpec.swift +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridTestObjectSwiftKotlinSpec.swift @@ -84,6 +84,8 @@ public protocol HybridTestObjectSwiftKotlinSpec_protocol: HybridObject { func promiseThrows() throws -> Promise func promiseReturnsInstantly() throws -> Promise func promiseReturnsInstantlyAsync() throws -> Promise + func createPendingPromise() throws -> Promise + func resolvePendingPromiseOnWorker() throws -> Void func promiseThatResolvesVoidInstantly() throws -> Promise func promiseThatResolvesToUndefined() throws -> Promise func awaitNullablePromise() throws -> Promise diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridTestObjectSwiftKotlinSpec_cxx.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridTestObjectSwiftKotlinSpec_cxx.swift index 3ccaecd96..7018fea83 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridTestObjectSwiftKotlinSpec_cxx.swift +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridTestObjectSwiftKotlinSpec_cxx.swift @@ -1564,6 +1564,36 @@ open class HybridTestObjectSwiftKotlinSpec_cxx { } } + @inline(__always) + public final func createPendingPromise() -> bridge.Result_std__shared_ptr_Promise_double___ { + do { + let __result = try self.__implementation.createPendingPromise() + let __resultCpp = { () -> bridge.std__shared_ptr_Promise_double__ in + let __promise = bridge.create_std__shared_ptr_Promise_double__() + let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_double__(__promise) + __result + .then({ __result in __promiseHolder.resolve(__result) }) + .catch({ __error in __promiseHolder.reject(__error.toCpp()) }) + return __promise + }() + return bridge.create_Result_std__shared_ptr_Promise_double___(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_std__shared_ptr_Promise_double___(__exceptionPtr) + } + } + + @inline(__always) + public final func resolvePendingPromiseOnWorker() -> bridge.Result_void_ { + do { + try self.__implementation.resolvePendingPromiseOnWorker() + return bridge.create_Result_void_() + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_void_(__exceptionPtr) + } + } + @inline(__always) public final func promiseThatResolvesVoidInstantly() -> bridge.Result_std__shared_ptr_Promise_void___ { do { diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectCppSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectCppSpec.cpp index 8f5a54e2f..26e4b18e3 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectCppSpec.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectCppSpec.cpp @@ -111,6 +111,8 @@ namespace margelo::nitro::test { prototype.registerHybridMethod("promiseThrows", &HybridTestObjectCppSpec::promiseThrows); prototype.registerHybridMethod("promiseReturnsInstantly", &HybridTestObjectCppSpec::promiseReturnsInstantly); prototype.registerHybridMethod("promiseReturnsInstantlyAsync", &HybridTestObjectCppSpec::promiseReturnsInstantlyAsync); + prototype.registerHybridMethod("createPendingPromise", &HybridTestObjectCppSpec::createPendingPromise); + prototype.registerHybridMethod("resolvePendingPromiseOnWorker", &HybridTestObjectCppSpec::resolvePendingPromiseOnWorker); prototype.registerHybridMethod("promiseThatResolvesVoidInstantly", &HybridTestObjectCppSpec::promiseThatResolvesVoidInstantly); prototype.registerHybridMethod("promiseThatResolvesToUndefined", &HybridTestObjectCppSpec::promiseThatResolvesToUndefined); prototype.registerHybridMethod("awaitNullablePromise", &HybridTestObjectCppSpec::awaitNullablePromise); diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectCppSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectCppSpec.hpp index e36bc5cdd..ac921c6e5 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectCppSpec.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectCppSpec.hpp @@ -220,6 +220,8 @@ namespace margelo::nitro::test { virtual std::shared_ptr> promiseThrows() = 0; virtual std::shared_ptr> promiseReturnsInstantly() = 0; virtual std::shared_ptr> promiseReturnsInstantlyAsync() = 0; + virtual std::shared_ptr> createPendingPromise() = 0; + virtual void resolvePendingPromiseOnWorker() = 0; virtual std::shared_ptr> promiseThatResolvesVoidInstantly() = 0; virtual std::shared_ptr>> promiseThatResolvesToUndefined() = 0; virtual std::shared_ptr>> awaitNullablePromise() = 0; diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.cpp index 1d831d075..5727dab2c 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.cpp @@ -103,6 +103,8 @@ namespace margelo::nitro::test { prototype.registerHybridMethod("promiseThrows", &HybridTestObjectSwiftKotlinSpec::promiseThrows); prototype.registerHybridMethod("promiseReturnsInstantly", &HybridTestObjectSwiftKotlinSpec::promiseReturnsInstantly); prototype.registerHybridMethod("promiseReturnsInstantlyAsync", &HybridTestObjectSwiftKotlinSpec::promiseReturnsInstantlyAsync); + prototype.registerHybridMethod("createPendingPromise", &HybridTestObjectSwiftKotlinSpec::createPendingPromise); + prototype.registerHybridMethod("resolvePendingPromiseOnWorker", &HybridTestObjectSwiftKotlinSpec::resolvePendingPromiseOnWorker); prototype.registerHybridMethod("promiseThatResolvesVoidInstantly", &HybridTestObjectSwiftKotlinSpec::promiseThatResolvesVoidInstantly); prototype.registerHybridMethod("promiseThatResolvesToUndefined", &HybridTestObjectSwiftKotlinSpec::promiseThatResolvesToUndefined); prototype.registerHybridMethod("awaitNullablePromise", &HybridTestObjectSwiftKotlinSpec::awaitNullablePromise); diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.hpp index 7f42afa41..7d2f8daaf 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.hpp @@ -207,6 +207,8 @@ namespace margelo::nitro::test { virtual std::shared_ptr> promiseThrows() = 0; virtual std::shared_ptr> promiseReturnsInstantly() = 0; virtual std::shared_ptr> promiseReturnsInstantlyAsync() = 0; + virtual std::shared_ptr> createPendingPromise() = 0; + virtual void resolvePendingPromiseOnWorker() = 0; virtual std::shared_ptr> promiseThatResolvesVoidInstantly() = 0; virtual std::shared_ptr>> promiseThatResolvesToUndefined() = 0; virtual std::shared_ptr>> awaitNullablePromise() = 0; diff --git a/packages/react-native-nitro-test/src/specs/TestObject.nitro.ts b/packages/react-native-nitro-test/src/specs/TestObject.nitro.ts index 0edc62156..938fd17b8 100644 --- a/packages/react-native-nitro-test/src/specs/TestObject.nitro.ts +++ b/packages/react-native-nitro-test/src/specs/TestObject.nitro.ts @@ -246,6 +246,10 @@ interface SharedTestObjectProps { promiseThrows(): Promise promiseReturnsInstantly(): Promise promiseReturnsInstantlyAsync(): Promise + // Stays pending until the separate JS call schedules its native completion. + // Only one Promise may be waiting for that trigger on each TestObject. + createPendingPromise(): Promise + resolvePendingPromiseOnWorker(): void promiseThatResolvesVoidInstantly(): Promise promiseThatResolvesToUndefined(): Promise awaitNullablePromise(): Promise diff --git a/scripts/performance/report-markdown.ts b/scripts/performance/report-markdown.ts index 18e8b0fcf..ccaaf0e4f 100644 --- a/scripts/performance/report-markdown.ts +++ b/scripts/performance/report-markdown.ts @@ -9,6 +9,10 @@ const OPERATION_NAMES: Readonly> = { 'ascii-short': 'short ASCII string', 'bounce-1-mib': 'bounce(1 MiB)', 'bounce-4-kib': 'bounce(4 KiB)', + 'bounce-native-4-kib': 'bounce native-owned buffer (4 KiB)', + 'bounce-native-1-mib': 'bounce native-owned buffer (1 MiB)', + 'deferred-worker-with-trigger': + 'deferred worker Promise (includes trigger call)', 'copy-1-mib': 'copy(1 MiB)', 'copy-4-kib': 'copy(4 KiB)', 'create': 'create()',