cpprestsdk icon indicating copy to clipboard operation
cpprestsdk copied to clipboard

pplx::when_all crashes with default constructed pplx::tasks

Open garethsb opened this issue 3 years ago • 1 comments

A default constructed task behaves quite similarly to a task containing an exception, e.g. by pplx::task_from_exception or that has been canceled, in that the default constructed task throws pplx::invalid_operation from wait and get, where a canceled task throws pplx::task_canceled. There are differences, e.g. is_done throws rather than returning true as a canceled task would, but much task-handling code will only use either wait or `get.

However, pplx::when_all does not handle default tasks.

when_all_test.cpp

#include <iostream>
#include "pplx/pplxtasks.h"

int main()
{
    pplx::task<void> default_task;
    std::cout << "wait:" << std::endl;
    try
    {
        default_task.wait();
    }
    catch (const pplx::invalid_operation& e)
    {
        std::cout << e.what() << '\n';
    }
    std::cout << "when_all:" << std::endl;
    auto tasks = { default_task };
    pplx::when_all(tasks.begin(), tasks.end()).then([](pplx::task<void> finally)
    {
        try
        {
            finally.wait();
        }
        catch (const pplx::invalid_operation& e)
        {
            std::cout << e.what() << '\n';
        }
    }).wait();
    std::cout << "done" << std::endl;
}

Output in gdb:

wait:
wait() cannot be called on a default constructed task.
when_all:

Program received signal SIGSEGV, Segmentation fault.
pplx::details::_WhenAllImpl<void, pplx::task<void> const*>::_Perform (
    _TaskOptions=..., _Begin=0x7fffffffce50, _End=0x7fffffffce60)
    at .../include/pplx/pplxtasks.h:6746
6746                        _JoinAllTokens_add(_MergedSource, _PTasks->_GetImpl()->_M_pTokenState);

I also tried when_any:

<    pplx::when_all(tasks.begin(), tasks.end()).then([](pplx::task<void> finally)
>    pplx::when_any(tasks.begin(), tasks.end()).then([](pplx::task<size_t> finally)

In this case, the call to when_any produces the exception:

  is_apartment_aware() cannot be called on a default constructed task.

At least this doesn't result in a core dump, but wouldn't it be more consistent if that exception were contained in the resulting task rather than thrown from when_any?

garethsb avatar May 05 '22 09:05 garethsb

As an additional note, the documentation for pplx::task says that the exception thrown by a default constructed instance is std::invalid_argument. However, pplx::invalid_operation does not have that as a base.

garethsb avatar May 05 '22 13:05 garethsb