Search code examples
c++multithreadingpromise

Lifetime of promise and set_value_at_thread_exit


Suppose we have the following code:

std::promise<int> promise;
auto future = promise.get_future();

const auto task = [](auto promise) {
    try {            
        promise.set_value_at_thread_exit(int_generator_that_can_throw());
    } catch (...) {
        promise.set_exception_at_thread_exit(std::current_exception());
    }
};
    
std::thread thread(task, std::move(promise));
// use future
thread.join();

I wonder if this code is correct and safe, and if no, why.

It appears to work fine when compiled with GCC, but crashes (no message is printed) when compiled with MSVC (2017). My guess is that a crash happens because promise local variable inside task goes out of scope and is destroyed too early. If I remove _at_thread_exit suffixes, this code works as expected (or appears to work). It also works correctly when the promise is captured:

const auto task = [p = std::move(promise)]() mutable {
    /*...*/
};

Complete compilable example


Solution

  • Why does your code generate problems? Let's start with ansewer to 'when _at_thread_exit writes to shared state of std::future and std::promise?'. It happens after destruction of all thread local variables. Your lambda is called within the thread and after its scope is left, the promise is already destroyed. But what happens when thread calling your lambda has some thread-local variables? Well, the writing will occur after destruction of the std::promise object. Actually, the rest is really undefined in standard. It seems that passing data to shared state could be done after destruction of std::promise but information is not really there.

    Simplest solution is of course this:

    std::promise<int> promise;
    auto future = promise.get_future();
    
    const auto task = [](std::promise<int>& promise) {
        try {            
            promise.set_value_at_thread_exit(int_generator_that_can_throw());
        } catch (...) {
            promise.set_exception_at_thread_exit(std::current_exception());
        }
    };
        
    std::thread thread(task, std::ref(promise));
    // use future
    thread.join();