Skip to content

Commit

Permalink
Added experimental::promise.
Browse files Browse the repository at this point in the history
The experimental::promise type allows eager execution and
synchronisation of async operations.

    auto promise = async_read(
        stream, asio::buffer(my_buffer),
        asio::experimental::use_promise);

    ... do other stuff while the read is going on ...

    promise.async_wait( // completion the operation
        [](error_code ec, std::size_t bytes_read)
        {
          ...
        });

Promises can be safely disregarded if the result is no longer required.

Different operations can be combined to either wait for all to complete
or for one to complete (and cancel the rest). For example, to wait for
one to complete:

    auto timeout_promise =
      timer.async_wait(
        asio::experimental::use_promise);

    auto read_promise = async_read(
        stream, asio::buffer(my_buffer),
        asio::experimental::use_promise);

    auto promise =
      asio::experimental::promise<>::race(
        timeout_promise, read_promise);

    promise.async_wait(
        [](std::variant<error_code, std::tuple<error_code, std::size_t>> v)
        {
          if (v.index() == 0) {} //timed out
          else if (v.index() == 1) // completed in time
        });

or to wait for all to complete:

    auto write_promise = async_write(
        stream, asio::buffer(my_write_buffer),
        asio::experimental::use_promise);

    auto read_promise = async_read(
        stream, asio::buffer(my_buffer),
        asio::experimental::use_promise);

    auto promise =
      asio::experimental::promise<>::all(
        write_promise, read_promise);

    promise.async_wait(
        [](std::tuple<error_code, std::size_t> write_result,
          std::tuple<error_code, std::size_t> read_result)
        {
        });
  • Loading branch information
klemens-morgenstern authored and chriskohlhoff committed Jul 1, 2021
1 parent 903c9fa commit 7e3d996
Show file tree
Hide file tree
Showing 7 changed files with 1,183 additions and 0 deletions.
2 changes: 2 additions & 0 deletions asio/boostify.pl
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ sub copy_include_files
"include/asio/execution/detail",
"include/asio/execution/impl",
"include/asio/experimental",
"include/asio/experimental/detail",
"include/asio/experimental/impl",
"include/asio/generic",
"include/asio/generic/detail",
Expand Down Expand Up @@ -440,6 +441,7 @@ sub copy_unit_tests
"src/tests/unit",
"src/tests/unit/archetypes",
"src/tests/unit/execution",
"src/tests/unit/experimental",
"src/tests/unit/generic",
"src/tests/unit/ip",
"src/tests/unit/local",
Expand Down
158 changes: 158 additions & 0 deletions asio/include/asio/experimental/detail/completion_handler_erasure.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//
// experimental/detail/completion_handler_erasure.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2021 Klemens D. Morgenstern
// (klemens dot morgenstern at gmx dot net)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef ASIO_EXPERIMENTAL_DETAIL_COMPLETION_HANDLER_ERASURE_HPP
#define ASIO_EXPERIMENTAL_DETAIL_COMPLETION_HANDLER_ERASURE_HPP

#include <asio/associated_allocator.hpp>
#include <asio/dispatch.hpp>

namespace asio {

class any_io_executor;

namespace experimental {
namespace detail {

template<typename Signature, typename Executor>
struct completion_handler_erasure_base;

template<typename Func, typename Signature, typename Executor>
struct completion_handler_erasure_impl;

template<typename Return, typename ... Args, typename Executor>
struct completion_handler_erasure_base<Return(Args...), Executor>
{
Executor executor;

completion_handler_erasure_base(Executor&& executor)
: executor(std::move(executor))
{
}

virtual Return call(Args ...args) = 0;
virtual ~completion_handler_erasure_base() = default;
};

template<typename Func, typename Return, typename ... Args, typename Executor>
struct completion_handler_erasure_impl<Func, Return(Args...), Executor> final
: completion_handler_erasure_base<Return(Args...), Executor>
{
completion_handler_erasure_impl(Executor&& exec, Func&& func)
: completion_handler_erasure_base<Return(Args...), Executor>(
std::move(exec)), func(std::move(func))
{
}

virtual Return call(Args ...args) override
{
std::move(func)(std::move(args)...);
}

Func func;
};

template<typename Signature, typename Executor = any_io_executor>
struct completion_handler_erasure;

template<typename Return, typename ... Args, typename Executor>
struct completion_handler_erasure<Return(Args...), Executor>
{
struct deleter_t
{
using allocator_base = typename associated_allocator<Executor>::type;
using allocator_type =
typename std::allocator_traits<allocator_base>::template rebind_alloc<
completion_handler_erasure_base<Return(Args...), Executor>>;

allocator_type allocator;
std::size_t size;

template<typename Func>
static std::unique_ptr<
completion_handler_erasure_base<Return(Args...), Executor>, deleter_t>
make(Executor exec, Func&& func)
{
using type = completion_handler_erasure_impl<
std::remove_reference_t<Func>, Return(Args...), Executor>;
using alloc_type = typename std::allocator_traits<
allocator_base>::template rebind_alloc<type>;
auto alloc = alloc_type(get_associated_allocator(exec));
auto size = sizeof(type);
auto p = std::allocator_traits<alloc_type>::allocate(alloc, size);
auto res = std::unique_ptr<type, deleter_t>(
p, deleter_t{allocator_type(alloc), size});
std::allocator_traits<alloc_type>::construct(alloc,
p, std::move(exec), std::forward<Func>(func));
return res;
}

void operator()(
completion_handler_erasure_base<Return(Args...), Executor> * p)
{
std::allocator_traits<allocator_type>::destroy(allocator, p);
std::allocator_traits<allocator_type>::deallocate(allocator, p, size);
}
};

completion_handler_erasure(const completion_handler_erasure&) = delete;
completion_handler_erasure(completion_handler_erasure&&) = default;
completion_handler_erasure& operator=(
const completion_handler_erasure&) = delete;
completion_handler_erasure& operator=(
completion_handler_erasure&&) = default;

constexpr completion_handler_erasure() = default;

constexpr completion_handler_erasure(nullptr_t)
: completion_handler_erasure()
{
}

template<typename Func>
completion_handler_erasure(Executor exec, Func&& func)
: impl_(deleter_t::make(std::move(exec), std::forward<Func>(func)))
{
}

~completion_handler_erasure()
{
if (auto f = std::exchange(impl_, nullptr); f != nullptr)
{
asio::dispatch(f->executor,
[f = std::move(f)]() mutable
{
std::move(f)->call(Args{}...);
});
}
}

Return operator()(Args ... args)
{
if (auto f = std::exchange(impl_, nullptr); f != nullptr)
f->call(std::move(args)...);
}

constexpr bool operator==(nullptr_t) const noexcept {return impl_ == nullptr;}
constexpr bool operator!=(nullptr_t) const noexcept {return impl_ != nullptr;}
constexpr bool operator!() const noexcept {return impl_ == nullptr;}

private:
std::unique_ptr<
completion_handler_erasure_base<Return(Args...), Executor>, deleter_t>
impl_;
};

} // namespace detail
} // namespace experimental
} // namespace asio

#endif // ASIO_EXPERIMENTAL_DETAIL_COMPLETION_HANDLER_ERASURE_HPP
94 changes: 94 additions & 0 deletions asio/include/asio/experimental/impl/promise.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// experimental/impl/promise.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2021 Klemens D. Morgenstern
// (klemens dot morgenstern at gmx dot net)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASIO_EXPERIMENTAL_IMPL_PROMISE_HPP
#define ASIO_EXPERIMENTAL_IMPL_PROMISE_HPP

#include "asio/cancellation_signal.hpp"
#include "asio/experimental/detail/completion_handler_erasure.hpp"
#include <tuple>
#include <optional>

namespace asio {
namespace experimental {

template<typename Signature = void(), typename Executor = any_io_executor>
struct promise;

namespace detail {

template<typename Signature, typename Executor>
struct promise_impl;

template<typename ... Ts, typename Executor>
struct promise_impl<void(Ts...), Executor>
{
using result_type = std::tuple<Ts...>;

promise_impl(Executor executor = {})
: executor(std::move(executor))
{
}

std::optional<result_type> result;
bool done{false};
detail::completion_handler_erasure<void(Ts...), Executor> completion;
cancellation_signal cancel;
Executor executor;
};

template<typename Signature = void(), typename Executor = any_io_executor>
struct promise_handler;

template<typename Signature, typename Executor>
struct promise_handler;

template<typename ... Ts, typename Executor>
struct promise_handler<void(Ts...), Executor>
{
using promise_type = promise<void(Ts...), Executor>;

promise_handler(Executor executor) // get_associated_allocator(exec)
: impl_{
std::allocate_shared<promise_impl<void(Ts...), Executor>>(
get_associated_allocator(executor))}
{
impl_->executor = std::move(executor);
}

std::shared_ptr<promise_impl<void(Ts...), Executor>> impl_;

using cancellation_slot_type = cancellation_slot;

cancellation_slot_type get_cancellation_slot() const noexcept
{
return impl_->cancel.slot();
}

auto make_promise() -> promise<void(Ts...), Executor>
{
return {impl_};
}

void operator()(std::remove_reference_t<Ts>... ts)
{
assert(impl_);
impl_->result.emplace(std::move(ts)...);
impl_->done = true;
if (auto f = std::exchange(impl_->completion, nullptr); f != nullptr)
std::apply(std::move(f), std::move(*impl_->result));
}
};

} // namespace detail
} // namespace experimental
} // namespace asio

#endif // ASIO_EXPERIMENTAL_IMPL_PROMISE_HPP
Loading

0 comments on commit 7e3d996

Please sign in to comment.