C++ Logo

std-proposals

Advanced search

Re: [std-proposals] std::async_noexcept

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Tue, 7 Nov 2023 22:09:03 +0000
On Tue, Nov 7, 2023 at 6:45 PM Matthew Taylor wrote:
>
> In what way? If after std::async is finished "setting up the asynchronous task", the thread it
> spawns may freeze or fail, what causes that in your use-case? And in what way does your
> proposed implementation solve that? As always forgive me if I'm mistaken but as far as I can
> see the only exceptions and failures your suggestion seems to deal with are exclusively
> related to the call to async, and anything which happens when executing the task is outside
> the scope of this change.


Instead of writing directly to a COM port, I might be writing into a
TCP socket that forwards to a PC somewhere on the internet, which then
forwards it to a COM port . . . a hundred things can go wrong along
the way. Or I might just be writing to a local COM port, but the
microcontroller on the other end stops responding, or it changes baud
and loads the bootloader.


> This sounds a lot like deducing the success or failure of an asynchronous task based some
> a timer recording it in another thread. Is that not inherently susceptible to race conditions
> affecting your logic should the scheduler decide to sleep either thread? Indeed is this not a
> possible cause of your "freezing up or failing in some way"? In any case, I'm not sure that
> the program design of stacking up enough failed threads/asyncs that the program requires
> a manual restart to clear itself out is a sufficiently common pattern to merit adding a standard
> way to work with it.


I've had to write code to accommodate systems that have a high rate of
failure, I wrote about it on comp.lang.c++ 2.5 years ago:
        https://groups.google.com/g/comp.lang.c/c/kXf638iHzqA/m/xf9BauxmBQAJ

I won't be able to get 'async_noexcept' to work with 'std::future',
because the constructor of 'std::promise' might throw an exception,
and so instead I've written my own class 'future_noexcept'. Here's how
my code looks now, but I'm going to take this over to comp.lang.c++
instead of continuing with it here:

#include <chrono> // duration, time_point
#include <functional> // function
#include <future> // async, future, future_status,
launch, promise
#include <utility> // move, forward
#include <type_traits> // decay, invoke_result,
is_nothrow_copy_constructible, is_nothrow_move_constructible
#include <variant> // variant

template<typename T> requires
std::is_nothrow_default_constructible_v<T> &&
std::is_nothrow_copy_constructible_v<T> &&
std::is_nothrow_move_constructible_v<T>
struct future_dummy {
    T value;
    future_dummy(void) noexcept : value() {}
    future_dummy(T arg) noexcept : value(arg) {}
    future_dummy(future_dummy &&other) noexcept :
value(std::move(other.value)) {}
    future_dummy(future_dummy const &other) = delete;
    future_dummy &operator=(future_dummy &&other) noexcept { value =
std::move(other.value); return *this; }
    future_dummy &operator=(future_dummy const &other) = delete;
    T get(void) /* const */ { return value; }
    bool valid(void) const noexcept { return true; }
    void wait(void) const {}

    template<typename Rep,typename Period>
    std::future_status wait_for(std::chrono::duration<Rep,Period>
const&) const { return std::future_status::ready; }

    template<typename Clock,typename Duration>
    std::future_status
wait_until(std::chrono::time_point<Clock,Duration> const&) const {
return std::future_status::ready; }
};

template<typename T> requires
std::is_nothrow_default_constructible_v<T> &&
std::is_nothrow_copy_constructible_v<T> &&
std::is_nothrow_move_constructible_v<T>
struct future_noexcept {
    std::variant< future_dummy<T>, std::future<T> > v;

    future_noexcept(T arg) : v( std::in_place_type< future_dummy<T> >, arg ) {}
    future_noexcept(std::future<T> &&arg) : v( std::move(arg) ) {}
    future_noexcept(void) noexcept : v() {}
    future_noexcept(future_noexcept &&other) noexcept : v(
std::move(other.v) ) {}
    future_noexcept(future_noexcept const &other) = delete;
    future_noexcept &operator=(future_noexcept &&other) noexcept
    {
        v = std::move(other.v);
        return *this;
    }
    future_noexcept &operator=(future_noexcept const &other) = delete;
    T get(void) /* const */
    {
        return std::visit([this](auto &arg){ return arg.get(); }, v);
    }
    bool valid(void) const noexcept
    {
        return std::visit([this](auto &arg){ return arg.valid(); }, v);
    }
    void wait(void) const
    {
        std::visit([this](auto &arg){ arg.wait(); }, v);
    }
    template<typename Rep,typename Period>
    std::future_status wait_for(std::chrono::duration<Rep,Period>
const &timeout_duration) const
    {
        return std::visit([this,&timeout_duration](auto &arg){ return
arg.template wait_for<Rep,Period>(timeout_duration); }, v);
    }

    template<typename Clock,typename Duration>
    std::future_status
wait_until(std::chrono::time_point<Clock,Duration> const
&timeout_time) const
    {
        return std::visit([this,&timeout_time](auto &arg){ return
arg.template wait_until<Clock,Duration>(timeout_time); }, v);
    }
};

template<typename FunctorType, typename... Params>
[[nodiscard]]
future_noexcept< std::invoke_result_t< std::decay_t<FunctorType>,
std::decay_t<Params>... > >
async_noexcept(
    std::invoke_result_t< std::decay_t<FunctorType>,
std::decay_t<Params>... > default_retval,
    std::launch policy,
    FunctorType &&f,
    Params&&... args) noexcept
requires std::is_nothrow_copy_constructible_v< std::invoke_result_t<
std::decay_t<FunctorType>, std::decay_t<Params>... > >
      && std::is_nothrow_move_constructible_v< std::invoke_result_t<
std::decay_t<FunctorType>, std::decay_t<Params>... > >
{
    typedef std::invoke_result_t< std::decay_t<FunctorType>,
std::decay_t<Params>... > R;

    // The lambda on the next line is created on the stack, however
    // there are no captures, so it's OK to invoke the lambda after
    // it has gone out of scope (i.e. from another thread).
    auto const mylambda = [](R lambda_default_retval, FunctorType
&&lambda_f, std::decay_t<Params>... lambda_args) -> R
      {
        try
        {
          return std::forward<FunctorType>(lambda_f)( lambda_args... );
        }
        catch(...)
        {
          return lambda_default_retval;
        }
      };

    try
    {
        return std::async(
          static_cast<std::launch>(policy),
          mylambda,
          default_retval,
          std::forward<FunctorType>(f),
          std::forward<Params>(args)... );
    }
    catch(...)
    {
        return future_noexcept<R>(default_retval); // returns a
variant containing a future_dummy
    }
}

Received on 2023-11-07 22:09:16