Home Dashboard Directory Help
Search

std::thread::join() hangs if called after main() exits when using VS2012 RC by FraserH999


Status: 

Active


17
0
Sign in
to vote
Type: Bug
ID: 747145
Opened: 6/6/2012 1:49:54 PM
Access Restriction: Public
1
Workaround(s)
view
10
User(s) can reproduce this bug

Description

Running the following program compiled using VS 2012 RC causes the execution to hang at the join() call.

Uncommenting the last line fixes the problem.


#include <iostream>
#include <string>
#include <thread>

class ThreadTest {
public:
ThreadTest() : thread_([] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }) {}
~ThreadTest() { thread_.join(); }
private:
std::thread thread_;
};

int main() {
static ThreadTest thread_test;
// std::this_thread::sleep_for(std::chrono::milliseconds(100));
}





Details
Sign in to post a comment.
Posted by Microsoft on 7/1/2014 at 10:15 AM
Hello there!

I'm Artur Laksberg, I work with Stephan on the STL and other things. I fixed this
bug in our private branch and the fix is on it's way to Dev14.

If you're interested in details -- we're creating a critical section and scheduling
its destruction under the atexit lock. We then scheduled the destruction of that
critical section which also required taking the atexit lock, getting us into
a deadlock.

Thanks for reporting this bug, and keep your feedback coming.
Posted by _Nebur_ on 9/23/2013 at 7:52 PM
Bug still exist in VC2013 RC. Will it be fixed any time soon?
Example code does block for ever in join after passing getchar.

#include "stdafx.h"
#include <thread>
#include <functional>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <MacroAux.h>
#include <DbgAux.h>


class AsyncWorker final
{
public:
    using Job = std::function<void()>;
    NO_COPY_CLASS(AsyncWorker);
    
    AsyncWorker() :m_th{std::mem_fn(&AsyncWorker::Work), this} {}
    ~AsyncWorker()
    {
        m_join = true;
        m_wakeup.notify_one();
        if(m_th.joinable()) m_th.join();
    }
    
    template<typename T>
    void Enqueue(T&& job){*this += forward<T>(job);}
    
    template<typename T>
    AsyncWorker& operator<<(T&& job)
    {
        std::lock_guard<std::mutex> lck(m_mtx);
        m_q.push(std::forward<T>(job));
        m_wakeup.notify_one();
        return *this;
    }

private:
    void Work()
    {
        while(!m_join)
        {
            Job job;
            {    
                std::lock_guard<std::mutex> lck(m_mtx);
                if(!m_q.size()) m_wakeup.wait(m_mtx);
                if(m_q.size()) // double check, spurious wakeups
                {
                    job = std::move(m_q.front());
                    m_q.pop();
                }            
            }
            if(job) job();
        }    
    }

    std::mutex m_mtx;
    std::queue<Job> m_q;
    std::condition_variable_any m_wakeup;
    std::atomic<bool> m_join = false;
    std::thread m_th;
};

template<typename T>
void AsyncDelete(T p)
{
    static AsyncWorker worker;
    worker <<[p] { delete p; };
}

int _tmain(int argc, _TCHAR* argv[])
{
    /*AsyncWorker worker;
    worker << [] { trace("lala1"); }
    << [] { trace("lala2"); }
    << [] { trace("lala3"); };*/

    AsyncDelete(new int);

    ::getchar();
    return 0;
}


Posted by Catalin Alexandru Zamfir on 9/13/2013 at 11:31 AM
Hi,

Bug is affecting us also. We're allocating a singleton object, as static and using std::thread::join () in the destructor to call the join on the threads spawned by our object. What this bug does is actually hang the application at exit. Calling detach () on the threads gets the good functionality, but afaik, it may leak resources. This should be fixed urgently!
Posted by Microsoft on 2/21/2013 at 1:50 PM
Hi,

Thanks for reporting this bug. I wanted to let you know what's happening with it. I'm still keeping track of it, but it's been resolved as "Deferred" because we may not have time to fix it in VC12. (Note: VC8 = VS 2005, VC9 = VS 2008, VC10 = VS 2010, VC11 = VS 2012.)

Note: Connect doesn't notify me about comments. If you have any further questions, please E-mail me.

Stephan T. Lavavej
Senior Developer - Visual C++ Libraries
stl@microsoft.com
Posted by CMWoods on 11/21/2012 at 11:16 PM
I'm not sure that this bug has to do with the Concurrency Runtime. This looks like a straight forward deadlock case when tracing it with VS2012:

Main thread has acquired _EXIT_LOCK1, called ThreadTest destructor resulting in the join() call waiting [indefinitely] for second thread to exit.

Second thread completed but is waiting [indefinitely] to acquire _EXIT_LOCK1 in order to finish exiting.

Anything that delays the exit of the main() function long enough for the second thread to acquire _EXIT_LOCK1 first avoids the deadlock situation. Examples:

The uncommenting the sleep call which allows the second thread to complete.
Or alternatively removing static declaration on ThreadTest which then moves the destruction up to the exit of the main() function and blocks on the join() until the second thread has fully exited.
Posted by Microsoft on 6/18/2012 at 3:25 PM
Hi,

Thanks for reporting this bug. I'm Microsoft's maintainer of the STL, and I wanted to let you know that while this bug remains active in our database, it won't be fixed in VC11 RTM (VS 2012 RTM). All bugs are important to us, but some are more severe than others and rise to the top of our priority queue.

I'm copying-and-pasting this response across all of the STL's active Connect bugs, but the following terse comments apply specifically to your bug:

* I observe that while we're hanging, the Concurrency Runtime is calling Concurrency::details::_Timer::_Stop(). (std::mutex and other parts of our C++11 multithreading implementation are powered by ConcRT, and although std::thread itself directly wraps a HANDLE, launching one involves some ConcRT machinery.) We'll need to investigate further in order to figure out where the true problem is.

I can't promise when we'll be able to resolve this bug, but we hope to do so as soon as possible (and I'll send another response when that happens) - our first opportunity will be the "out of band" release between VC11 and VC12 that Herb Sutter announced at the GoingNative 2012 conference.

Note: Connect doesn't notify me about comments. If you have any further questions, please E-mail me.

Stephan T. Lavavej
Senior Developer - Visual C++ Libraries
stl@microsoft.com
Posted by Microsoft on 6/6/2012 at 11:35 PM
Thanks for your feedback.

We are rerouting this issue to the appropriate group within the Visual Studio Product Team for triage and resolution. These specialized experts will follow-up with your issue.
Posted by Microsoft on 6/6/2012 at 2:52 PM
Thank you for your feedback, we are currently reviewing the issue you have submitted. If this issue is urgent, please contact support directly(http://support.microsoft.com)
Sign in to post a workaround.
Posted by Timo Bingmann on 11/3/2013 at 2:28 PM
I have been battling this bug for a day, and found the following work-around, which turned out the be the least dirty trick:

Instead of returning, one can use the standard Windows API function call ExitThread() to terminate the thread. This method of course may mess up the internal state of the std::thread object and associated library, but since the program is going to terminate anyway, well, so be it.

#include <iostream>
#include <string>
#include <thread>

class ThreadTest {
public:
    ThreadTest() : thread_([] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); ExitThread(NULL); }) {}
    ~ThreadTest() { thread_.join(); }
private:
    std::thread thread_;
};

int main() {
    static ThreadTest thread_test;
    // std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

The join() call apparently works correctly. However, I chose to use a more safe method in our solution. One can get the thread HANDLE via std::thread::native_handle(). With this handle we can call the Windows API directly to join the thread:

WaitForSingleObject(thread_.native_handle(), INFINITE);
CloseHandle(thread_.native_handle());

Thereafter, the std::thread object must not be destroyed, as the destructor would try to join the thread a second time. So we just leave the std::thread object dangling at program exit.