blob: a8c8d16a8282bee282e75aa6f1903cfbeffe5183 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_LIB_FASYNC_INCLUDE_LIB_FASYNC_POLL_H_
#define SRC_LIB_FASYNC_INCLUDE_LIB_FASYNC_POLL_H_
#include <lib/fasync/internal/compiler.h>
LIB_FASYNC_CPP_VERSION_COMPAT_BEGIN
#include <lib/fasync/type_traits.h>
#include <lib/fitx/result.h>
// |fasync::poll|: Type to be returned by futures to indicate their state of completion.
//
// |fasync::poll| can be seen as a more specialized version of |std::optional|: when an executor
// polls a future, the |fasync::poll| value is either still pending (no output) or is ready with a
// value of the output type. It has a specialization for |fitx::result| so that only one
// discriminator is used in this tri-state case.
//
// To make an |fasync::poll|:
//
// fitx::ready(ready_value) // Ready for |fasync::poll<T>|.
// fitx::ready() // Ready for |fasync::poll<>| (no output value).
// fitx::done(ready_value) // Ready for |fasync::poll<T>|.
// fitx::done() // Ready for |fasync::poll<>| (no output value).
//
// fasync::pending() // Pending.
//
// General functions that can always be called:
//
// bool is_ready()
// bool is_pending()
//
// Available only when is_ready() (will assert otherwise).
//
// T& output() // Accesses the output.
// T&& output() // Moves the output.
namespace fasync {
// |fasync::pending|: What futures should return if they have not completed their work.
//
// In order not to be abandoned, the future must also arrange to be woken up later via
// |fasync::suspended_task|.
//
// Example:
//
// fasync::make_future(
// [this](fasync::context& context) -> fasync::poll<std::string> {
// const char* string = GetString();
// if (string == nullptr) {
// // Will be woken up to try again
// context.suspend_task().resume();
// return fasync::pending();
// }
// return fasync::ready(string);
// })
//
// fasync::make_future(
// [this](fasync::context& context) -> fasync::poll<std::string> {
// const char* string = GetString();
// if (string == nullptr) {
// // Will be abandoned
// return fasync::pending();
// }
// return fasync::ready(string);
// })
//
struct LIB_FASYNC_NODISCARD pending final {};
// |fasync::ready|: Type representing a value of type T to return as the final result of a future's
// work. Returning a value through |fasync::poll| always requires using |fasync::ready| to
// distinguish the ready state from the pending state.
//
// |fasync::poll<T>| is implicitly constructible from any |fasync::ready<U>|, where T is
// constructible from U. This simplifies returning values when the T has converting constructors.
//
// Example:
// fasync::make_future(
// [](fasync::context&) -> fasync::poll<int> {
// // Resolve immediately
// return fasync::ready(42);
// })
//
template <typename...>
class ready;
template <typename T>
class LIB_FASYNC_NODISCARD LIB_FASYNC_OWNER_OF(T) ready<T> {
static_assert(!cpp17::is_reference_v<T>, "Cannot create a ready<T> where T is a reference.");
public:
using output_type = T;
template <typename... Args,
::fasync::internal::requires_conditions<std::is_constructible<T, Args...>> = true>
explicit constexpr ready(Args&&... args) : value_(std::forward<Args>(args)...) {}
template <typename U, ::fasync::internal::requires_conditions<
cpp17::negation<std::is_same<ready<T>, ready<U>>>,
std::is_constructible<T, U>> = true>
constexpr ready(ready<U>&& other) : value_(std::move(other.value_)) {}
~ready() = default;
constexpr ready(const ready&) = default;
constexpr ready& operator=(const ready&) = default;
constexpr ready(ready&&) = default;
constexpr ready& operator=(ready&&) = default;
private:
template <typename... Us>
friend class ready;
template <typename... Us>
friend class poll;
// TODO(schottm): look into [[no_unique_address]]. clang claims to support it in C++11 onwards
T value_;
};
// Specialization of |fasync::ready| for empty values.
template <>
class LIB_FASYNC_NODISCARD ready<> {
public:
using output_type = void;
constexpr ready() = default;
~ready() = default;
constexpr ready(const ready&) = default;
constexpr ready& operator=(const ready&) = default;
constexpr ready(ready&&) = default;
constexpr ready& operator=(ready&&) = default;
};
#if LIB_FASYNC_HAS_CPP_FEATURE(deduction_guides)
ready()->ready<>;
template <typename T>
ready(T&&) -> ready<std::decay_t<T>>;
#endif
template <typename E = ::fitx::failed, typename... Ts>
using try_ready = ready<::fitx::result<E, Ts...>>;
// Returns |fasync::ready<T>| for the given value, where T is deduced from the argument type. This
// utility is a C++14 compatible alternative to the C++17 deduction guide above.
//
// Example:
//
// fasync::make_future(
// [this](fasync::context& context) -> fasync::poll<std::string> {
// const char* string = GetString();
// if (string == nullptr) {
// context.suspend_task().resume();
// return fasync::pending();
// }
// return fasync::done(string);
// })
//
inline constexpr ready<> done() { return ready<>(); }
template <typename T>
constexpr ready<std::decay_t<T>> done(T&& output) {
return ready<std::decay_t<T>>(std::forward<T>(output));
}
template <typename...>
class poll;
// |fasync::poll<Ts...>| is the type returned by futures to signal either completion or a pending
// state. It can be constructed from either |fasync::pending| or |fasync::ready<Ts...>| and in the
// latter case the future's output can be accessed via |.output()|.
// Specialization of |fasync::poll| for empty values.
template <>
class LIB_FASYNC_NODISCARD poll<> {
public:
using output_type = void;
constexpr poll(const poll&) = default;
constexpr poll& operator=(const poll&) = default;
constexpr poll(poll&&) = default;
constexpr poll& operator=(poll&&) = default;
constexpr poll(ready<>) : pending_(false) {}
constexpr poll(pending) : pending_(true) {}
constexpr bool is_pending() const { return pending_; }
constexpr bool is_ready() const { return !is_pending(); }
constexpr void swap(poll& other) {
if (&other != this) {
using std::swap;
swap(pending_, other.pending_);
}
}
private:
bool pending_;
};
#if LIB_FASYNC_HAS_CPP_FEATURE(deduction_guides)
poll()->poll<>;
poll(pending)->poll<>;
poll(ready<>)->poll<>;
#endif
template <typename T>
class LIB_FASYNC_NODISCARD LIB_FASYNC_OWNER_OF(T) poll<T> {
static_assert(!cpp17::is_reference_v<T>, "Cannot create a poll<T> where T is a reference.");
public:
using output_type = T;
constexpr poll(const poll&) = default;
constexpr poll& operator=(const poll&) = default;
constexpr poll(poll&&) = default;
constexpr poll& operator=(poll&&) = default;
constexpr poll(pending) {}
// Since we only have one parameter, we pretend it's an error so we don't have to pass void as the
// first template parameter to ::fitx::internal::storage
template <typename U, ::fasync::internal::requires_conditions<std::is_constructible<T, U>> = true>
constexpr poll(ready<U>&& ready) : storage_(::fitx::internal::error_v, std::move(ready.value_)) {}
template <typename U,
::fasync::internal::requires_conditions<cpp17::negation<std::is_same<poll<T>, poll<U>>>,
std::is_constructible<T, U>> = true>
constexpr poll(poll<U>&& other) : storage_(std::move(other.storage_)) {}
constexpr bool is_pending() const { return storage_.state == ::fitx::internal::state_e::empty; }
constexpr bool is_ready() const { return !is_pending(); }
constexpr T& output() & {
LIB_FASYNC_LIKELY if (!is_pending()) { return storage_.error_or_value.error; }
__builtin_abort();
}
constexpr const T& output() const& {
LIB_FASYNC_LIKELY if (!is_pending()) { return storage_.error_or_value.error; }
__builtin_abort();
}
constexpr T&& output() && {
LIB_FASYNC_LIKELY if (!is_pending()) { return std::move(storage_.error_or_value.error); }
__builtin_abort();
}
constexpr const T&& output() const&& {
LIB_FASYNC_LIKELY if (!is_pending()) { return std::move(storage_.error_or_value.error); }
__builtin_abort();
}
constexpr void swap(poll& other) {
if (&other != this) {
using std::swap;
swap(storage_, other.storage_);
}
}
private:
template <typename... Us>
friend class poll;
::fitx::internal::storage<T> storage_;
};
#if LIB_FASYNC_HAS_CPP_FEATURE(deduction_guides)
template <typename T>
poll(ready<T>&&) -> poll<T>;
template <typename T>
poll(poll<T>&&) -> poll<T>;
#endif
// |fasync::try_poll<E, Ts...>| is a convenience wrapper to make it easier to deal with
// |fasync::poll|s with an |::output_type| of |fitx::result<E, Ts...>|.
template <typename E = ::fitx::failed, typename... Ts>
using try_poll = poll<::fitx::result<E, Ts...>>;
template <typename... Ts>
constexpr void swap(poll<Ts...>& p, poll<Ts...>& q) {
p.swap(q);
}
template <typename... Ts>
constexpr bool operator==(const poll<Ts...>& lhs, const poll<>& rhs) {
return lhs.is_pending() == rhs.is_pending();
}
template <typename... Ts>
constexpr bool operator!=(const poll<Ts...>& lhs, const poll<>& rhs) {
return !(lhs == rhs);
}
template <typename... Ts>
constexpr bool operator==(const poll<>& lhs, const poll<Ts...>& rhs) {
return lhs.is_pending() == rhs.is_pending();
}
template <typename... Ts>
constexpr bool operator!=(const poll<>& lhs, const poll<Ts...>& rhs) {
return !(lhs == rhs);
}
inline constexpr bool operator==(const poll<>& lhs, const poll<>& rhs) {
return lhs.is_pending() == rhs.is_pending();
}
inline constexpr bool operator!=(const poll<>& lhs, const poll<>& rhs) { return !(lhs == rhs); }
template <typename... Ts>
constexpr bool operator==(const poll<Ts...>& lhs, const ready<>&) {
return lhs.is_ready();
}
template <typename... Ts>
constexpr bool operator!=(const poll<Ts...>& lhs, const ready<>&) {
return !lhs.is_ready();
}
template <typename... Ts>
constexpr bool operator==(const ready<>&, const poll<Ts...>& rhs) {
return rhs.is_ready();
}
template <typename... Ts>
constexpr bool operator!=(const ready<>&, const poll<Ts...>& rhs) {
return !rhs.is_ready();
}
template <typename... Ts>
constexpr bool operator==(const poll<Ts...>& lhs, pending) {
return lhs.is_pending();
}
template <typename... Ts>
constexpr bool operator!=(const poll<Ts...>& lhs, pending) {
return !lhs.is_pending();
}
template <typename... Ts>
constexpr bool operator==(pending, const poll<Ts...>& rhs) {
return rhs.is_pending();
}
template <typename... Ts>
constexpr bool operator!=(pending, const poll<Ts...>& rhs) {
return !rhs.is_pending();
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() == std::declval<U>())> = true>
constexpr bool operator==(const poll<T>& lhs, const poll<U>& rhs) {
return lhs.is_pending() == rhs.is_pending() && (lhs.is_pending() || lhs.output() == rhs.output());
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<poll<T>>() ==
std::declval<poll<U>>())> = true>
constexpr bool operator!=(const poll<T>& lhs, const poll<U>& rhs) {
return !(lhs == rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() == std::declval<U>()),
cpp17::negation<is_poll<U>>> = true>
constexpr bool operator==(const poll<T>& lhs, const U& rhs) {
return lhs.is_ready() && lhs.output() == rhs;
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<poll<T>>() == std::declval<U>()),
cpp17::negation<is_poll<U>>> = true>
constexpr bool operator!=(const poll<T>& lhs, const U& rhs) {
return !(lhs == rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() == std::declval<U>()),
cpp17::negation<is_poll<T>>> = true>
constexpr bool operator==(const T& lhs, const poll<U>& rhs) {
return rhs.is_ready() && rhs.output() == lhs;
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() == std::declval<poll<U>>()),
cpp17::negation<is_poll<T>>> = true>
constexpr bool operator!=(const T& lhs, const poll<U>& rhs) {
return !(lhs == rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() < std::declval<U>())> = true>
constexpr bool operator<(const poll<T>& lhs, const poll<U>& rhs) {
return rhs.is_ready() && (lhs.is_pending() || lhs.output() < rhs.output());
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<poll<T>>() <
std::declval<poll<U>>())> = true>
constexpr bool operator>=(const poll<T>& lhs, const poll<U>& rhs) {
return !(lhs < rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<poll<T>>() >=
std::declval<poll<U>>())> = true>
constexpr bool operator>(const poll<T>& lhs, const poll<U>& rhs) {
return (lhs >= rhs) && (lhs != rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<poll<T>>() >
std::declval<poll<U>>())> = true>
constexpr bool operator<=(const poll<T>& lhs, const poll<U>& rhs) {
return !(lhs > rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() < std::declval<U>()),
cpp17::negation<is_poll<U>>> = true>
constexpr bool operator<(const poll<T>& lhs, const U& rhs) {
return !lhs.is_ready() || lhs.output() < rhs;
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<poll<T>>() < std::declval<U>()),
cpp17::negation<is_poll<U>>> = true>
constexpr bool operator>=(const poll<T>& lhs, const U& rhs) {
return !(lhs < rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<poll<T>>() >= std::declval<U>()),
cpp17::negation<is_poll<U>>> = true>
constexpr bool operator>(const poll<T>& lhs, const U& rhs) {
return (lhs >= rhs) && (lhs != rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<poll<T>>() > std::declval<U>()),
cpp17::negation<is_poll<U>>> = true>
constexpr bool operator<=(const poll<T>& lhs, const U& rhs) {
return !(lhs > rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() < std::declval<U>()),
cpp17::negation<is_poll<T>>> = true>
constexpr bool operator<(const T& lhs, const poll<U>& rhs) {
return rhs.is_ready() && lhs < rhs.output();
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() < std::declval<poll<U>>()),
cpp17::negation<is_poll<T>>> = true>
constexpr bool operator>=(const T& lhs, const poll<U>& rhs) {
return !(lhs < rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() >= std::declval<poll<U>>()),
cpp17::negation<is_poll<T>>> = true>
constexpr bool operator>(const T& lhs, const poll<U>& rhs) {
return (lhs >= rhs) && (lhs != rhs);
}
template <typename T, typename U,
::fitx::internal::enable_rel_op<decltype(std::declval<T>() > std::declval<poll<U>>()),
cpp17::negation<is_poll<T>>> = true>
constexpr bool operator<=(const T& lhs, const poll<U>& rhs) {
return !(lhs > rhs);
}
} // namespace fasync
LIB_FASYNC_CPP_VERSION_COMPAT_END
#endif // SRC_LIB_FASYNC_INCLUDE_LIB_FASYNC_POLL_H_