libs/promises/api/celix/Deferred.h (150 lines of code) (raw):
/**
*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 <exception>
#include <utility>
#include "celix/impl/SharedPromiseState.h"
#include "celix/Promise.h"
#include "celix/PromiseIllegalStateException.h"
namespace celix {
/**
* @brief A Deferred Promise resolution.
*
* <p>
* Instances of this class can be used to create a {@link celix::Promise} that can be
* resolved in the future. The {@link #getPromise() associated} Promise can be
* successfully resolved with {@link #resolve(T)} or resolved with a
* failure with {@link #fail(const std::exception&)}. It can also be resolved with the
* resolution of another promise using {@link #resolveWith(Promise)}.
*
* <p>
* The associated Promise can be provided to any one, but the Deferred object
* should be made available only to the party that will responsible for
* resolving the Promise.
*
* @tparam <T> The value type associated with the created Promise.
*/
template<typename T>
class Deferred {
public:
using type = T;
explicit Deferred(std::shared_ptr<celix::impl::SharedPromiseState<T>> state);
/**
* @brief Fail the Promise associated with this Deferred.
*
* <p/>
* After the associated Promise is resolved with the specified failure, all registered callbacks are called and any
* chained Promises are resolved.
* <p/>
* Resolving the associated Promise happens-before any registered callback is called. That is, in a registered
* callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block.
*
* If the associated promise is already resolved, the call will be ignored.
*
* @param failure The failure in the form of an exception pointer.
*/
void fail(const std::exception_ptr& failure);
/**
* @brief Try to fail the Promise associated with this Deferred.
*
* Same as `fail`, but will return `true` if the associated promise was successfully failed and `false` if
* the associated promise was already resolved.
*/
bool tryFail(const std::exception_ptr& failure);
/**
* @brief Fail the Promise associated with this Deferred.
*
* <p/>
* After the associated Promise is resolved with the specified failure, all registered callbacks are called and any
* chained Promises are resolved.
* <p/>
* Resolving the associated Promise happens-before any registered callback is called. That is, in a registered
* callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block.
*
* If the associated promise is already resolved, the call will be ignored.
*
* @param failure The failure in the form of an const std::exception reference.
*/
template<typename E, typename std::enable_if_t< std::is_base_of_v<std::exception, E>, bool> = true >
void fail(const E& failure);
/**
* @brief Try to fail the Promise associated with this Deferred.
*
* Same as `fail`, but will return `true` if the associated promise was successfully failed and `false` if
* the associated promise was already resolved.
*/
template<typename E, typename std::enable_if_t< std::is_base_of_v<std::exception, E>, bool> = true >
bool tryFail(const E& failure);
/**
* @brief Returns the Promise associated with this Deferred.
*
* <p>
* All Promise objects created by the associated Promise will use the
* executors of the associated Promise.
* </p>
*
* @return The Promise associated with this Deferred.
*/
[[nodiscard]] Promise<T> getPromise();
/**
* @brief Resolve the Promise associated with this Deferred.
*
* <p/>
* After the associated Promise is resolved with the specified value, all registered callbacks are called and any
* chained Promises are resolved.
* <p/>
* Resolving the associated Promise happens-before any registered callback is called. That is, in a registered
* callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block.
*
* If the associated promise is already resolved, the call will be ignored.
*
* @param value The value of the resolved Promise.
*/
void resolve(T&& value);
/**
* @brief Resolve the Promise associated with this Deferred.
*
* <p/>
* After the associated Promise is resolved with the specified value, all registered callbacks are called and any
* chained Promises are resolved.
* <p/>
* Resolving the associated Promise happens-before any registered callback is called. That is, in a registered
* callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block.
*
* If the associated promise is already resolved, the call will be ignored.
*
* @param value The value of the resolved Promise.
*/
void resolve(const T& value);
/**
* @brief Try to resolve the Promise associated with this Deferred.
*
* Same as `resolve`, but will return `true` if the associated promise was successfully resolved and `false` if
* the associated promise was already resolved.
*/
bool tryResolve(T&& value);
/**
* @brief Try to resolve the Promise associated with this Deferred.
*
* Same as `resolve`, but will return `true` if the associated promise was successfully resolved and `false` if
* the associated promise was already resolved.
*/
bool tryResolve(const T& value);
/**
* @brief Resolve the Promise associated with this Deferred with the specified Promise.
*
* <p/>
* If the specified Promise is successfully resolved, the associated Promise is resolved with the value of the
* specified Promise. If the specified Promise is resolved with a failure, the associated Promise is resolved with
* the failure of the specified Promise.
* <p/>
* After the associated Promise is resolved with the specified Promise, all registered callbacks are called and any
* chained Promises are resolved.
* <p/>
* Resolving the associated Promise happens-before any registered callback is called. That is, in a registered
* callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block
*
* @tparam U The promise type must be assignable from T.
* @param with A Promise whose value or failure will be used to resolve the associated Promise. Must not be null.
* @return A Promise that is resolved only when the associated Promise is resolved by the specified Promise. The
* returned Promise will be successfully resolved, with the value null, if the associated Promise was resolved by
* the specified Promise. The returned Promise will be resolved with a failure of IllegalStateException if the
* associated Promise was already resolved when the specified Promise was resolved.
*/
template<typename U>
celix::Promise<void> resolveWith(celix::Promise<U> with);
private:
std::shared_ptr<celix::impl::SharedPromiseState<T>> state;
};
template<>
class Deferred<void> {
public:
using type = void;
explicit Deferred(std::shared_ptr<celix::impl::SharedPromiseState<void>> state);
/**
* @brief Fail the Promise associated with this Deferred.
*
* <p/>
* After the associated Promise is resolved with the specified failure, all registered callbacks are called and any
* chained Promises are resolved.
* <p/>
* Resolving the associated Promise happens-before any registered callback is called. That is, in a registered
* callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block.
*
* If the associated promise is already resolved, the call will be ignored.
*
* @param failure The failure in the form of an exception pointer.
*/
void fail(const std::exception_ptr& failure);
/**
* @brief Try to fail the Promise associated with this Deferred.
*
* Same as `fail`, but will return `true` if the associated promise was successfully failed and `false` if
* the associated promise was already resolved.
*/
bool tryFail(const std::exception_ptr& failure);
/**
* @brief Fail the Promise associated with this Deferred.
*
* <p/>
* After the associated Promise is resolved with the specified failure, all registered callbacks are called and any
* chained Promises are resolved.
* <p/>
* Resolving the associated Promise happens-before any registered callback is called. That is, in a registered
* callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block.
*
* If the associated promise is already resolved, the call will be ignored.
*
* @param failure The failure in the form of an const std::exception reference.
*/
template<typename E, typename std::enable_if_t< std::is_base_of_v<std::exception, E>, bool> = true >
void fail(const E& failure);
/**
* @brief Try to fail the Promise associated with this Deferred.
*
* Same as `fail`, but will return `true` if the associated promise was successfully failed and `false` if
* the associated promise was already resolved.
*/
template<typename E, typename std::enable_if_t< std::is_base_of_v<std::exception, E>, bool> = true >
bool tryFail(const E& failure);
/**
* @brief Returns the Promise associated with this Deferred.
*
* <p>
* All Promise objects created by the associated Promise will use the
* executors of the associated Promise.
* </p>
*
* @return The Promise associated with this Deferred.
*/
[[nodiscard]] Promise<void> getPromise();
/**
* @brief Resolve the Promise associated with this Deferred.
*
* <p/>
* After the associated Promise is resolved with the specified value, all registered callbacks are called and any
* chained Promises are resolved.
* <p/>
* Resolving the associated Promise happens-before any registered callback is called. That is, in a registered
* callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block.
*
* If the associated promise is already resolved, the call will be ignored.
*
* @param value The value of the resolved Promise.
*/
void resolve();
/**
* @brief Try to resolve the Promise associated with this Deferred.
*
* Same as `resolve`, but will return `true` if the associated promise was successfully resolved and `false` if
* the associated promise was already resolved.
*/
bool tryResolve();
template<typename U>
celix::Promise<void> resolveWith(celix::Promise<U> with);
private:
std::shared_ptr<celix::impl::SharedPromiseState<void>> state;
};
}
/*********************************************************************************
Implementation
*********************************************************************************/
template<typename T>
celix::Deferred<T>::Deferred(std::shared_ptr<celix::impl::SharedPromiseState<T>> _state) : state{std::move(_state)} {}
inline celix::Deferred<void>::Deferred(std::shared_ptr<celix::impl::SharedPromiseState<void>> _state) : state{std::move(_state)} {}
template<typename T>
void celix::Deferred<T>::fail(const std::exception_ptr& failure) {
state->tryFail(failure);
}
template<typename T>
bool celix::Deferred<T>::tryFail(const std::exception_ptr& failure) {
return state->tryFail(failure);
}
inline void celix::Deferred<void>::fail(const std::exception_ptr& failure) {
state->tryFail(failure);
}
inline bool celix::Deferred<void>::tryFail(const std::exception_ptr& failure) {
return state->tryFail(failure);
}
template<typename T>
template<typename E, typename std::enable_if_t< std::is_base_of_v<std::exception, E>, bool>>
void celix::Deferred<T>::fail(const E& failure) {
state->template tryFail<E>(failure);
}
template<typename T>
template<typename E, typename std::enable_if_t< std::is_base_of_v<std::exception, E>, bool>>
bool celix::Deferred<T>::tryFail(const E& failure) {
return state->template tryFail<E>(failure);
}
template<typename E, typename std::enable_if_t< std::is_base_of_v<std::exception, E>, bool>>
void celix::Deferred<void>::fail(const E& failure) {
state->tryFail<E>(failure);
}
template<typename E, typename std::enable_if_t< std::is_base_of_v<std::exception, E>, bool>>
bool celix::Deferred<void>::tryFail(const E& failure) {
return state->tryFail<E>(failure);
}
template<typename T>
celix::Promise<T> celix::Deferred<T>::getPromise() {
return celix::Promise<T>{state};
}
inline celix::Promise<void> celix::Deferred<void>::getPromise() {
return celix::Promise<void>{state};
}
template<typename T>
template<typename U>
celix::Promise<void> celix::Deferred<T>::resolveWith(celix::Promise<U> with) {
auto p = celix::impl::SharedPromiseState<void>::create(state->getExecutor(), state->getScheduledExecutor(), state->getPriority());
with.onResolve([s = state, with, p] () mutable {
bool resolved;
if (with.isSuccessfullyResolved()) {
resolved = s->tryResolve(with.moveOrGetValue());
} else {
resolved = s->tryFail(with.getFailure());
}
if (resolved) {
p->tryResolve();
} else {
//s was already resolved
p->tryFail(std::make_exception_ptr(celix::PromiseIllegalStateException{}));
}
});
return celix::Promise<void>{p};
}
template<typename U>
inline celix::Promise<void> celix::Deferred<void>::resolveWith(celix::Promise<U> with) {
auto p = celix::impl::SharedPromiseState<void>::create(state->getExecutor(), state->getScheduledExecutor(), state->getPriority());
with.onResolve([s = state, with, p] {
bool resolved;
if (with.isSuccessfullyResolved()) {
with.getValue();
resolved = s->tryResolve();
} else {
resolved = s->tryFail(with.getFailure());
}
if (resolved) {
p->tryResolve();
} else {
//s was already resolved
p->tryFail(std::make_exception_ptr(celix::PromiseIllegalStateException{}));
}
});
return celix::Promise<void>{p};
}
template<typename T>
void celix::Deferred<T>::resolve(T&& value) {
state->tryResolve(std::forward<T>(value));
}
template<typename T>
void celix::Deferred<T>::resolve(const T& value) {
state->tryResolve(value);
}
inline void celix::Deferred<void>::resolve() {
state->tryResolve();
}
template<typename T>
bool celix::Deferred<T>::tryResolve(T&& value) {
return state->tryResolve(std::forward<T>(value));
}
template<typename T>
bool celix::Deferred<T>::tryResolve(const T& value) {
return state->tryResolve(value);
}
inline bool celix::Deferred<void>::tryResolve() {
return state->tryResolve();
}