From dc8b666c5d0f52a56dc49b6020565778d28d4484 Mon Sep 17 00:00:00 2001 From: Jan Buenker Date: Mon, 12 Aug 2024 16:00:42 +0200 Subject: [PATCH] Finish futures when their promise is broken --- support-lib/cpp/Future.hpp | 44 ++++++++++++------- .../objc/tests/DBCppFutureTests.mm | 32 ++++++++++++++ 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/support-lib/cpp/Future.hpp b/support-lib/cpp/Future.hpp index 71db25ab..6e94152e 100644 --- a/support-lib/cpp/Future.hpp +++ b/support-lib/cpp/Future.hpp @@ -54,6 +54,12 @@ namespace djinni { template class Future; +struct BrokenPromiseException final : public std::exception { + inline const char* what() const noexcept final { + return "djinni::Promise was destructed before setting a result"; + } +}; + namespace detail { // A wrapper object to support both void and non-void result types in @@ -117,6 +123,25 @@ using SharedStatePtr = std::shared_ptr>; template class PromiseBase { public: + virtual ~PromiseBase() noexcept { + if (_sharedState) { + setException(BrokenPromiseException{}); + } + } + PromiseBase() = default; + + // moveable + PromiseBase(PromiseBase&&) noexcept = default; + PromiseBase& operator= (PromiseBase&& other) noexcept { + std::swap(other._sharedState, _sharedState); + std::swap(other._sharedStateReadOnly, _sharedStateReadOnly); + return *this; + } + + // not copyable + PromiseBase(const PromiseBase&) = delete; + PromiseBase& operator= (const PromiseBase&) = delete; + Future getFuture(); // Use to immediately resolve a promise and return the resulting future. @@ -223,14 +248,7 @@ class Promise: public detail::PromiseBase { public: using detail::PromiseBase::setValue; using detail::PromiseBase::setException; - // default constructable - Promise() = default; - // moveable - Promise(Promise&&) noexcept = default; - Promise& operator= (Promise&&) noexcept = default; - // not copyable - Promise(const Promise&) = delete; - Promise& operator= (const Promise&) = delete; + using detail::PromiseBase::PromiseBase; }; // Promise with a void result @@ -239,14 +257,8 @@ class Promise: public detail::PromiseBase { public: void setValue() {setValue(true);} using detail::PromiseBase::setException; - // default constructable - Promise() = default; - // moveable - Promise(Promise&&) noexcept = default; - Promise& operator= (Promise&&) noexcept = default; - // not copyable - Promise(const Promise&) = delete; - Promise& operator= (const Promise&) = delete; + using detail::PromiseBase::PromiseBase; + private: // hide the bool version void setValue(const bool&) {detail::PromiseBase::setValue(true);} diff --git a/test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm b/test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm index 584675a4..e562b845 100644 --- a/test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm +++ b/test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm @@ -67,4 +67,36 @@ - (void) testFutureCoroutines_cleanupOrder { } #endif +- (void) testFuture_brokenPromise { + std::optional> promise{std::in_place}; + auto future = promise->getFuture(); + XCTAssertFalse(future.isReady()); + + auto promise2 = std::make_optional(std::move(*promise)); + XCTAssertFalse(future.isReady()); + + promise.reset(); + XCTAssertFalse(future.isReady()); + + promise2.reset(); + XCTAssertTrue(future.isReady()); + if (future.isReady()) { + XCTAssertThrowsSpecific(future.get(), djinni::BrokenPromiseException); + } +} + +- (void) testFuture_brokenPromiseAssignment { + djinni::Promise promise{}; + auto future = promise.getFuture(); + XCTAssertFalse(future.isReady()); + + promise = djinni::Promise{}; + XCTAssertTrue(future.isReady()); + if (future.isReady()) { + XCTAssertThrowsSpecific(future.get(), djinni::BrokenPromiseException); + } + + XCTAssertFalse(promise.getFuture().isReady()); +} + @end