217 lines
5.5 KiB
C++
217 lines
5.5 KiB
C++
//
|
|
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.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 BOOST_COBALT_DETAIL_THREAD_HPP
|
|
#define BOOST_COBALT_DETAIL_THREAD_HPP
|
|
|
|
#include <boost/cobalt/config.hpp>
|
|
#include <boost/cobalt/detail/forward_cancellation.hpp>
|
|
#include <boost/cobalt/detail/handler.hpp>
|
|
#include <boost/cobalt/concepts.hpp>
|
|
#include <boost/cobalt/op.hpp>
|
|
#include <boost/cobalt/this_coro.hpp>
|
|
|
|
#include <boost/asio/cancellation_signal.hpp>
|
|
|
|
#include <thread>
|
|
|
|
namespace boost::cobalt
|
|
{
|
|
|
|
struct as_tuple_tag;
|
|
struct as_result_tag;
|
|
|
|
namespace detail
|
|
{
|
|
struct thread_promise;
|
|
}
|
|
|
|
struct thread;
|
|
|
|
namespace detail
|
|
{
|
|
|
|
|
|
struct signal_helper_2
|
|
{
|
|
asio::cancellation_signal signal;
|
|
};
|
|
|
|
|
|
struct thread_state
|
|
{
|
|
asio::io_context ctx{1u};
|
|
asio::cancellation_signal signal;
|
|
std::mutex mtx;
|
|
std::optional<completion_handler<std::exception_ptr>> waitor;
|
|
std::atomic<bool> done = false;
|
|
};
|
|
|
|
struct thread_promise : signal_helper_2,
|
|
promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
|
|
promise_throw_if_cancelled_base,
|
|
enable_awaitables<thread_promise>,
|
|
enable_await_allocator<thread_promise>,
|
|
enable_await_executor<thread_promise>,
|
|
enable_await_deferred
|
|
{
|
|
BOOST_COBALT_DECL thread_promise();
|
|
|
|
struct initial_awaitable
|
|
{
|
|
bool await_ready() const {return false;}
|
|
void await_suspend(std::coroutine_handle<thread_promise> h)
|
|
{
|
|
h.promise().mtx.unlock();
|
|
}
|
|
|
|
void await_resume() {}
|
|
};
|
|
|
|
auto initial_suspend() noexcept
|
|
{
|
|
return initial_awaitable{};
|
|
}
|
|
std::suspend_never final_suspend() noexcept
|
|
{
|
|
wexec_.reset();
|
|
return {};
|
|
}
|
|
|
|
#if !defined(BOOST_NO_EXCEPTIONS)
|
|
void unhandled_exception() { throw; }
|
|
#endif
|
|
void return_void() { }
|
|
|
|
using executor_type = typename cobalt::executor;
|
|
const executor_type & get_executor() const {return *exec_;}
|
|
|
|
#if !defined(BOOST_COBALT_NO_PMR)
|
|
using allocator_type = pmr::polymorphic_allocator<void>;
|
|
using resource_type = pmr::unsynchronized_pool_resource;
|
|
|
|
resource_type * resource;
|
|
allocator_type get_allocator() const { return allocator_type(resource); }
|
|
#endif
|
|
|
|
using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
|
|
using promise_throw_if_cancelled_base::await_transform;
|
|
using enable_awaitables<thread_promise>::await_transform;
|
|
using enable_await_allocator<thread_promise>::await_transform;
|
|
using enable_await_executor<thread_promise>::await_transform;
|
|
using enable_await_deferred::await_transform;
|
|
|
|
BOOST_COBALT_DECL
|
|
boost::cobalt::thread get_return_object();
|
|
|
|
void set_executor(asio::io_context::executor_type exec)
|
|
{
|
|
wexec_.emplace(exec);
|
|
exec_.emplace(exec);
|
|
}
|
|
|
|
std::mutex mtx;
|
|
private:
|
|
|
|
std::optional<asio::executor_work_guard<asio::io_context::executor_type>> wexec_;
|
|
std::optional<cobalt::executor> exec_;
|
|
};
|
|
|
|
struct thread_awaitable
|
|
{
|
|
asio::cancellation_slot cl;
|
|
std::optional<std::tuple<std::exception_ptr>> res;
|
|
bool await_ready(const boost::source_location & loc = BOOST_CURRENT_LOCATION) const
|
|
{
|
|
if (state_ == nullptr)
|
|
boost::throw_exception(std::invalid_argument("Thread expired"), loc);
|
|
std::lock_guard<std::mutex> lock{state_->mtx};
|
|
return state_->done;
|
|
}
|
|
|
|
template<typename Promise>
|
|
bool await_suspend(std::coroutine_handle<Promise> h)
|
|
{
|
|
BOOST_ASSERT(state_);
|
|
|
|
std::lock_guard<std::mutex> lock{state_->mtx};
|
|
if (state_->done)
|
|
return false;
|
|
|
|
if constexpr (requires (Promise p) {p.get_cancellation_slot();})
|
|
if ((cl = h.promise().get_cancellation_slot()).is_connected())
|
|
{
|
|
cl.assign(
|
|
[st = state_](asio::cancellation_type type)
|
|
{
|
|
std::lock_guard<std::mutex> lock{st->mtx};
|
|
asio::post(st->ctx,
|
|
[st, type]
|
|
{
|
|
BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__));
|
|
st->signal.emit(type);
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
state_->waitor.emplace(h, res);
|
|
return true;
|
|
}
|
|
|
|
void await_resume()
|
|
{
|
|
if (cl.is_connected())
|
|
cl.clear();
|
|
if (thread_)
|
|
thread_->join();
|
|
if (!res) // await_ready
|
|
return;
|
|
if (auto ee = std::get<0>(*res))
|
|
std::rethrow_exception(ee);
|
|
}
|
|
|
|
system::result<void, std::exception_ptr> await_resume(const as_result_tag &)
|
|
{
|
|
if (cl.is_connected())
|
|
cl.clear();
|
|
if (thread_)
|
|
thread_->join();
|
|
if (!res) // await_ready
|
|
return {system::in_place_value};
|
|
if (auto ee = std::get<0>(*res))
|
|
return {system::in_place_error, std::move(ee)};
|
|
|
|
return {system::in_place_value};
|
|
}
|
|
|
|
std::tuple<std::exception_ptr> await_resume(const as_tuple_tag &)
|
|
{
|
|
if (cl.is_connected())
|
|
cl.clear();
|
|
if (thread_)
|
|
thread_->join();
|
|
|
|
return std::get<0>(*res);
|
|
}
|
|
|
|
explicit thread_awaitable(std::shared_ptr<detail::thread_state> state)
|
|
: state_(std::move(state)) {}
|
|
|
|
explicit thread_awaitable(std::thread thread,
|
|
std::shared_ptr<detail::thread_state> state)
|
|
: thread_(std::move(thread)), state_(std::move(state)) {}
|
|
private:
|
|
std::optional<std::thread> thread_;
|
|
std::shared_ptr<detail::thread_state> state_;
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
#endif //BOOST_COBALT_DETAIL_THREAD_HPP
|