Skip to content

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

@garethsb

Description

@garethsb

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions