llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.68k stars 11.86k forks source link

std::async with default policy moves their arguments twice if std::thread cannot be created #28659

Open e48d0f12-b0d3-485a-93ce-75b823b0ccac opened 8 years ago

e48d0f12-b0d3-485a-93ce-75b823b0ccac commented 8 years ago
Bugzilla Link 28285
Version unspecified
OS Linux
CC @mclow

Extended Description

In the libc++'s current implementation of std::async with default policy (i.e. async | deferred), first, the argument function is launched in the context of a new thread asynchronously (async). If it was failed, the function is launched in the context of the current thread later (deffered).

But if the first launch was failed (ex. std::thread creation trows an exception), std::async's arguments was already moved. So, the arguments have no valid values when second launch is executing.

The sample code below simulates std::thread creation failure.

============================== sample code ==============================

include

include

include

include

struct S { S() : b(false) {} // to simulate thread creation failure, it throws resource_unavailable_try_again when 'o' is first move target S(S&& o) : b(true) { if (!o.b) { o.b = true; throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again)); } } S(const S&) = delete; S& operator=(const S&) = delete; S&& operator=(S&&) = delete; bool b; };

int main() { auto f = std::async([](std::string&& s, S&&){ std::cout << "s = '" << s << '\'' << std::endl; }, std::string{"str"}, S{}); f.get(); } ============================== sample code ============================== ============================== output ============================== s = '' ============================== output ============================== cf. http://melpon.org/wandbox/permlink/IZDRW8D7i0wifN91

I think that it should output "s = 'str'" or should output nothing, whether exception is thrown or not.

llvmbot commented 8 years ago

OK, so according to the standard this is not a bug in libc++. The semantics of async act according to the spec. However I would agree that it is a bug in the spec that allows the move to happen twice.

The provided reproducer depends on the order of evaluation for function arguments when evaluating the expression INVOKE(fn, decay_copy(string&&), decay_copy(S&&)). Clang evaluates decay_copy(string&&) first and then decay_copy(S&&) meaning the string gets moved from before the exception is thrown. GCC evaluates them in reverse order, meaning the string is never moved from because S&& is always evaluated first.

This is why libstdc++ w/ GCC appears to pass your reproducer while libc++ w/ clang appears to fail. In reality both libc++ and libstdc++ pass when using GCC and fail when using Clang.

On the same note if you switch the order of '[](std::string&& s, S&&)' to be '[](S&&, std::string&&)' the reproducer will fail with GCC and pass with Clang.

I'll look into filing a standard defect against the wording for async, allowing a pure copy to be done, but I wouldn't expect a fix to this any time soon.