Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crash when assigning to std::future under MSVC's Debug mode

The following code crashes in random intervals when built with MSVC under Debug mode, unlike the Release.

#include <future>

using namespace std;

int main() {
    auto l = [](){};
    auto f = async(launch::async, l);

    for (int i = 0; i < 1000000; ++i)
        f = async(launch::async, l);
}

The console output says:

f:\dd\vctools\crt\crtw32\stdcpp\thr\mutex.c(51): mutex destroyed while busy

The full call stack is: https://pastebin.com/0g2ZF5C1

Now obviously it's just a stress test, but am I doing something utterly stupid? It seems to me it's fine to reassign a new task to an existing future, as it's said that operator=:

Releases any shared state and move-assigns the contents of other to *this

(Due to http://en.cppreference.com/w/cpp/thread/future/operator%3D).

Is it a bug in MSVC's runtime?

Remarkably, the program stops crashing if I manually call wait() before the assignment, thus making the loop into:

for (int i = 0; i < 1000000; ++i) {
    f.wait();
    f = async(launch::async, l);
}

Isn't operator= itself supposed to call wait?

Background:

_MSC_VER equals 1911

Code was built with help of:

Microsoft Visual Studio Community 2017 Preview(2)
Version 15.4.0 Preview 2.0

Just opened a brand new C++ project.

like image 278
Pythagoras of Samos Avatar asked Sep 15 '17 18:09

Pythagoras of Samos


1 Answers

Isn't operator= itself supposed to call wait?

I don't know whether it is supposed to, but a cursory glance at the MSVC15.3.4 implementation of <future> seems to strongly suggest it doesn't.

//User Code
future f = /*...*/;
f = /*...*/; //(1)
//MSVC Code
future& operator=(future&& _Right) _NOEXCEPT //(1)
    {   // assign from rvalue future object
    _Mybase::operator=(_STD move(_Right)); //(2)
    return (*this);
    }
_State_manager& operator=(_State_manager&& _Other) //(2)
    {   // assign from rvalue _Other
    _Move_from(_Other); //(3)
    return (*this);
    }
void _Move_from(_State_manager& _Other) //(3)
    {   // move stored associated asynchronous state object from _Other
    if (this != _STD addressof(_Other))
        {   // different, move
        if (_Assoc_state)
            _Assoc_state->_Release(); //(4)
        _Assoc_state = _Other._Assoc_state;
        _Other._Assoc_state = 0;
        _Get_only_once = _Other._Get_only_once;
        }
    }
void _Release() //(4)
    {   // decrement reference count and destroy when zero
    if (_MT_DECR(_Refs) == 0)
        _Delete_this(); //(5)
    }
void _Delete_this() //(5)
    {   // delete this object
    if (_Deleter)
        _Deleter->_Delete(this); //External Code
    else
        delete this;
    }

Seeing as calling wait helps synchronize things and ensure that the future object is in a safe state to be modified, it might be better to include the wait statement.

like image 82
Xirema Avatar answered Nov 03 '22 05:11

Xirema