Skip to content

Commit

Permalink
Merge pull request #187 from jb-gcx/gcx/broken-promises
Browse files Browse the repository at this point in the history
Finish futures when their promise is broken
  • Loading branch information
li-feng-sc authored Aug 14, 2024
2 parents 2383dc1 + dc8b666 commit 971228b
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 16 deletions.
44 changes: 28 additions & 16 deletions support-lib/cpp/Future.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ namespace djinni {
template <typename T>
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
Expand Down Expand Up @@ -117,6 +123,25 @@ using SharedStatePtr = std::shared_ptr<SharedState<T>>;
template <typename T>
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<T> getFuture();

// Use to immediately resolve a promise and return the resulting future.
Expand Down Expand Up @@ -223,14 +248,7 @@ class Promise: public detail::PromiseBase<T> {
public:
using detail::PromiseBase<T>::setValue;
using detail::PromiseBase<T>::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<T>::PromiseBase;
};

// Promise with a void result
Expand All @@ -239,14 +257,8 @@ class Promise<void>: public detail::PromiseBase<void> {
public:
void setValue() {setValue(true);}
using detail::PromiseBase<void>::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<void>::PromiseBase;

private:
// hide the bool version
void setValue(const bool&) {detail::PromiseBase<void>::setValue(true);}
Expand Down
32 changes: 32 additions & 0 deletions test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,36 @@ - (void) testFutureCoroutines_cleanupOrder {
}
#endif

- (void) testFuture_brokenPromise {
std::optional<djinni::Promise<void>> 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<void> promise{};
auto future = promise.getFuture();
XCTAssertFalse(future.isReady());

promise = djinni::Promise<void>{};
XCTAssertTrue(future.isReady());
if (future.isReady()) {
XCTAssertThrowsSpecific(future.get(), djinni::BrokenPromiseException);
}

XCTAssertFalse(promise.getFuture().isReady());
}

@end

0 comments on commit 971228b

Please sign in to comment.