C++ Logo

std-proposals

Advanced search

Re: Coroutine local destructor

From: Lewis Baker <lbaker_at_[hidden]>
Date: Sun, 24 May 2020 04:11:12 +0000
The reason that the promise_type::operator delete() is not invoked with
the arguments is because those arguments would have already been destructed
by the time the 'operator delete' function is called.

When the 'operator new' is called to allocate the coroutine frame, the arguments
that are passed to it are references to the actual arguments to the coroutine
function. These arguments are guaranteed to still be alive at this point as we
are still inside the execution of the coroutine function.
This allows us to "preview" those parameters when allocating the coroutine-frame.

After allocating the coroutine frame, those parameters are then copied/moved
into the coroutine-frame before then constructing the promise object.
The promise object can also preview the arguments, but in this case it is
passed references to the parameter copies instead of the original arguments.
 
Assuming coroutine suspends at some point and returns from the original call
to the coroutine then the original arguments will have been destroyed,
leaving the coroutine with only access to the copies it made in the
coroutine frame.


When the coroutine-frame is destroyed (either by implicitly running off the
end of the coroutine - not suspending at final_suspend(), or by calling
.destroy() on the coroutine_handle) the in-scope locals are destroyed, then
the promise is destroyed and then the parameter copies are destroyed.
This leaves the coroutine-frame now with uninitialized memory that is safe
for the deallocation function to free.


If you want to have access to any of the arguments in the `operator delete`
implementation then you will need to allocate some additional storage within
the memory allocated by 'operator new' and store a copy of the argument(s)
in that additional storage so that they are still available to
'operator delete' after the promise and parameter copies have already been
destroyed.

This is the strategy taken by the implementation of P1056R0 that written
for libc++. See https://reviews.llvm.org/D46140. This allows customization
of the allocator by passing std::allocator_arg and an allocator as the first
two arguments of a task coroutine.


Cheers,
Lewis

On 5/22/20, 9:56 AM, "Std-Proposals on behalf of Marcin Jaczewski via Std-Proposals" <std-proposals-bounces_at_lists.isocpp.org on behalf of std-proposals_at_lists.isocpp.org> wrote:

    After some time playing around C++20 I find out that corutine can't
    have local aware destructor.

    code like this:
    ```
    task<> foo(Storage& t, Args args);
    ```

    Can use operator `new` like:
    ```
    void* operator new(std::size_t s, Storage& t, Args args);
    ```

    But destructor can only have two forms:
    ```
    void operator delete(void* p, std::size_t s);
    void operator delete(void* p);
    ```

    This mean if I would want store this corutine in `Storage` then I have
    now easy way to inform `Storage` to release memory for that corutine.

    Could be possible for C++ support destructor like:
    ```
    void operator delete(void* p, std::size_t s, Storage& t, Args args);
    ```

    or at least new in C++20 destructor:
    ```
    void operator delete(task_promise* p, std::destroying_delete_t);
    ```

    second options will have benefit of not needed defining how exactly
    args could survive to point of calling destructor as they should be
    placed on allocated space by corutine.
    --
    Std-Proposals mailing list
    Std-Proposals_at_[hidden]
    https://urldefense.proofpoint.com/v2/url?u=https-3A__lists.isocpp.org_mailman_listinfo.cgi_std-2Dproposals&d=DwICAg&c=5VD0RTtNlTh3ycd41b3MUw&r=92rQsBGSkJNIu4JBui5p1Q&m=Dq_rroTe3O1tUw9OwwQTruO0c6PdFZFcYdEKVdLT_dI&s=mblaPnhzX0Af-BFj3NoGrbAfMjlw1FBH6rISrsllzwE&e=

Received on 2020-05-23 23:14:33