blob: 0aa5322b6250444ac9602119e3c7952715b7b6e4 [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#pragma once
#include <pulsar/defines.h>
#include <pulsar/st/Expected.h>
#include <pulsar/st/detail/SharedState.h>
#include <chrono>
#include <coroutine>
#include <memory>
#include <optional>
#include <type_traits>
#include <utility>
namespace pulsar::st {
namespace detail {
template <typename T>
class Promise;
}
/**
* The result of an asynchronous operation, available now or later.
*
* Unlike `std::future`, this is **continuation-capable**: `addListener()` runs a
* callback when the operation completes, so you can react without blocking. The
* callback receives an `Expected<T>` (value or error). `get()` is available when
* you do want to block. On a C++20 toolchain the future is also `co_await`-able.
*
* A Future is cheap to copy (it shares the underlying state). Listeners run on
* whichever thread completes the operation — do not block inside one.
*
* @tparam T the type of the value the operation produces (`void` for value-less
* operations, which complete with an `Expected<void>`).
*/
template <typename T>
class Future {
public:
/** Callback type accepted by `addListener`, invoked with `const Expected<T>&`. */
using Listener = typename detail::SharedState<T>::Listener;
/**
* Register a continuation to run when the operation completes.
*
* @p listener is invoked with the `Expected<T>` result (value or error) on the
* thread that completes the operation, or synchronously on the calling thread if
* the operation has already completed. It does not block. Do not block inside
* the listener. Multiple listeners may be registered.
*
* @param listener the callback to invoke on completion.
* @return `*this`, to allow chaining.
*/
Future& addListener(Listener listener) {
state_->addListener(std::move(listener));
return *this;
}
/**
* Block the calling thread until the operation completes and return its result.
*
* @return the `Expected<T>` result, holding either the value or the error.
*/
Expected<T> get() const { return state_->get(); }
/**
* Block until the operation completes or @p timeout elapses, whichever first.
*
* @tparam Rep the `std::chrono::duration` representation type.
* @tparam Period the `std::chrono::duration` period type.
* @param timeout the maximum time to wait for completion.
* @return the `Expected<T>` result if it completed in time, or `std::nullopt`
* if @p timeout elapsed first.
*/
template <typename Rep, typename Period>
std::optional<Expected<T>> get(std::chrono::duration<Rep, Period> timeout) const {
return state_->get(timeout);
}
/**
* Whether the operation has already completed.
*
* @return `true` if the result is available (so `get()` would not block),
* `false` otherwise.
*/
bool isReady() const { return state_->isReady(); }
/**
* Return a new Future whose value is `f` applied to this one's value on
* completion; an error propagates unchanged. Lets a typed facade map a Future
* of a (non-templated) core into a Future of its typed wrapper.
*
* @p f is applied on whichever thread completes this Future. If this Future
* completes with an error, @p f is not called and the error is forwarded to the
* returned Future. Only participates in overload resolution when `T` is not
* `void`.
*
* @tparam F a callable taking `const T&`; may be move-only, and may return
* `void` (producing a `Future<void>` that completes once `f` runs).
* @tparam U defaults to `T`; an implementation detail of the `void` constraint.
* @param f the mapping function to apply to the value.
* @return a `Future` of `f`'s return type, completed with the mapped value (or
* with success when `f` returns `void`) on success, or the propagated
* error on failure.
*/
template <typename F, typename U = T, std::enable_if_t<!std::is_void_v<U>, int> = 0>
Future<std::invoke_result_t<F, const U&>> thenApply(F f) const {
using R = std::invoke_result_t<F, const U&>;
detail::Promise<R> promise;
// Hold f in a shared_ptr so the copyable std::function listener can carry a
// move-only mapper; f is invoked at most once.
auto fp = std::make_shared<F>(std::move(f));
state_->addListener([promise, fp](const Expected<T>& result) {
if (!result) {
promise.setError(result.error());
return;
}
if constexpr (std::is_void_v<R>) {
(*fp)(*result);
promise.setSuccess();
} else {
promise.setValue((*fp)(*result));
}
});
return promise.getFuture();
}
/**
* Coroutine support: whether the awaiting coroutine may skip suspension.
*
* Part of the C++20 awaitable interface so a `Future` can be used as
* `Expected<T> r = co_await someFuture;`. Not called directly.
*
* @return `true` if the result is already available, `false` otherwise.
*/
bool await_ready() const { return state_->isReady(); }
/**
* Coroutine support: suspend the awaiting coroutine until completion.
*
* Atomically registers a continuation that resumes @p handle on completion, or —
* if the result is already available — returns `false` so the coroutine resumes
* immediately rather than being resumed from inside `await_suspend` (which could
* run and destroy this awaiter before it returns). Part of the C++20 awaitable
* interface; not called directly.
*
* @param handle the suspended coroutine to resume on completion.
* @return `true` to stay suspended (resumed later on the completing thread),
* `false` to resume immediately because the result is already available.
*/
bool await_suspend(std::coroutine_handle<> handle) {
return state_->addListenerOrReady([handle](const Expected<T>&) { handle.resume(); });
}
/**
* Coroutine support: produce the value of a `co_await` expression.
*
* Part of the C++20 awaitable interface; not called directly.
*
* @return the `Expected<T>` result of the awaited operation.
*/
Expected<T> await_resume() const { return state_->get(); }
private:
template <typename U>
friend class detail::Promise;
explicit Future(std::shared_ptr<detail::SharedState<T>> state) : state_(std::move(state)) {}
std::shared_ptr<detail::SharedState<T>> state_;
};
namespace detail {
/**
* INTERNAL producing side of a `Future<T>`. The SDK fulfils these to complete the
* futures it returns; applications only ever consume `Future<T>` and never build
* a Promise, so it lives in `detail` rather than on the public surface.
*/
template <typename T>
class Promise {
// Shared by every copy of a Promise. When the last copy is destroyed its
// destructor fails the future if nothing ever completed it, so an abandoned
// producer surfaces as an error instead of a get()/listener blocking forever.
// complete() is idempotent, so this is a no-op once the promise was fulfilled.
struct Guard {
explicit Guard(std::shared_ptr<SharedState<T>> s) : state(std::move(s)) {}
Guard(const Guard&) = delete;
Guard& operator=(const Guard&) = delete;
~Guard() {
state->complete(Expected<T>(Error{ResultUnknownError, "promise abandoned before completion"}));
}
std::shared_ptr<SharedState<T>> state;
};
public:
Promise() : state_(std::make_shared<SharedState<T>>()), guard_(std::make_shared<Guard>(state_)) {}
Future<T> getFuture() const { return Future<T>(state_); }
bool complete(Expected<T> result) const { return state_->complete(std::move(result)); }
bool setError(Error error) const { return state_->complete(Expected<T>(std::move(error))); }
template <typename U = T, std::enable_if_t<!std::is_void_v<U>, int> = 0>
bool setValue(U value) const {
return state_->complete(Expected<T>(std::move(value)));
}
template <typename U = T, std::enable_if_t<std::is_void_v<U>, int> = 0>
bool setSuccess() const {
return state_->complete(Expected<void>());
}
private:
std::shared_ptr<SharedState<T>> state_;
std::shared_ptr<Guard> guard_;
};
} // namespace detail
} // namespace pulsar::st