Crash on std :: thread destructor with VS CTP 14

I am implementing a concurrency library with choppy threads like boost and Java based on the C ++ 11 standard library using Visual Studio CTP 14.

After some refactoring, I ran into a crash (sometimes trying to dereference a null pointer, sometimes "Debug Error! R6010", and sometimes not crashing at all) inside a std :: thread destructor. After a lot of code extraction to find the problem, I am no closer to understanding if the problem is with my code, or if it might be a compiler error.

Even after removing the code, there is still a lot left to reproduce the problem, so bear with me, please :)

Parts of the thread wrapper implementation:

class InterruptException : public std::exception
{
public:
    const char* what() const override {return "InterruptException";}
};

class Thread
{
public:
    Thread(const Thread&) = delete;
    Thread& operator=(const Thread&) = delete;

    Thread();

    template <typename Callable>
    Thread(Callable&& action);

    Thread(Thread&& other);

    ~Thread();

    Thread& operator=(Thread&& other);

    // Sets interruption flag and call notifyAll for current condition if any.
    void interrupt();

    // Throws interrupted exception if current thread interruption flag is set.
    // This also resets the interruption flag.
    static void interruptionPoint();

    static void interruptCurrent();


    static void setCondition(std::condition_variable* cond);
    static Thread* currentThread(Thread* setter = nullptr);

private:
    std::atomic<bool>                       _interruptionFlag;
    std::atomic<std::condition_variable*>   _currentCondition;
    std::thread _stdThread;
};

template <typename Callable>
Thread::Thread(Callable&& action)
: _stdThread(),
  _interruptionFlag(false),
  _currentCondition(nullptr)
{
    _stdThread = std::thread(
        [this, runner = std::move(action)]
        {
            currentThread(this);

            try
            {
                runner();
            }
            catch (InterruptException&)
            {
                // Normal exit.
            }
            catch (...)
            {
                // Removed logging calls.
            }
        }
    );
}

Thread::~Thread()
{
    // Block at thread destruction, no detached thread support.
    if (_stdThread.joinable())
    {
        interrupt();
        _stdThread.join();
    }
}

// (More code if requested.)

      

(Note that details are missing for clearer code)

I've written some tests for the Thread class and it seems to work as you would expect. The next part is a stripped down version of the task runner.

template <typename Runner>
class StrippedSystem
{
public:
    template <typename... CtorArgs>
    StrippedSystem(CtorArgs&&... args);

    StrippedSystem(const StrippedSystem&) = delete;
    StrippedSystem(StrippedSystem&&) = delete;

    // ...

private:
    template <typename... CtorArgs>
    Thread createRunnerThread(CtorArgs&&... args);

    std::mutex _mutex;
    std::condition_variable _cond;

    Thread _runner;
};

template <typename Runner>
template <typename... CtorArgs>
StrippedSystem<Runner>::StrippedSystem(CtorArgs&&... args)
{
    _runner = createRunnerThread(std::forward<CtorArgs>(args)...);
}

template <typename Runner>
template <typename... CtorArgs>
Thread StrippedSystem<Runner>::createRunnerThread(CtorArgs&&... args)
{
    auto runnerProc =
    [&]
    {
        Thread::setCondition(&_cond);

        try
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _cond.wait(
                lock,
                [&]
                {
                    Thread::interruptionPoint();
                    return false;
                }
            );
        }
        catch (...)
        {
            Thread::setCondition(nullptr);
            std::rethrow_exception(std::current_exception());
        }
    };

    return Thread(runnerProc);
}

      

The code for StrippedSystem Stripped Down is pretty simple. It creates a thread that waits until it is interrupted. When Thread's destructor is called an interrupt flag, the condition is set and notified. The thread is then executed until the breakpoint (inside the _cond.wait lambda), which throws an interrupt exception that gets caught in the thread's wrapper, and the thread exits normally.

Now for the code that dumps everything:

struct ConstructedRunner
{
    ConstructedRunner(int a, std::string b)
    : i(a), j(b)
    {
    }

    int i;
    std::string j;
};

int main(int argc, char* argv[])
{
    {
        StrippedSystem<ConstructedRunner> testSys2(1, "foobar");
    }

    return 0;
}

      

As written above, the crash can be either "Debug Error! Abort was caused", "Access violation" (an attempt to dereference a null pointer), or in some cases not a crash at all.

After some troubleshooting, I found that the following did not fail:

struct DummyRunner
{
};

int main(int argc, char* argv[])
{
    {
        StrippedSystem<DummyRunner> testSys1;
    }

    return 0;
}

      

After some more troubleshooting, I found that in the system designer replacing

_runner = createRunnerThread(std::forward<CtorArgs>(args)...);

      

from

_runner = Thread(
        [&]
        {
            Thread::setCondition(&_cond);

            try
            {
                std::unique_lock<std::mutex> lock(_mutex);
                _cond.wait(
                    lock,
                    [&]
                    {
                        Thread::interruptionPoint();
                        return false;
                    }
                );
            }
            catch (...)
            {
                Thread::setCondition(nullptr);
                std::rethrow_exception(std::current_exception());
            }
        }
    );

      

Also fixes the crash. Even if forwarded arguments are not used.

It doesn't make any sense to me since the code that runs should be the same. Is this a compiler issue or am I doing something really wrong that is causing some weird concurrency issues?

Build in Debug Mode with Visual Studio CTP 14 for Windows 7.

+3


source to share


1 answer


After some help, I got an error.

The problem was with the currentThread(this)

template constructor Thread

. This function sets the thread pointer as a local static thread variable, which is then read to obtain the interrupt flag and the current condition, if any. The problem was that the pointer this

was referring to a temporary object. This can be seen in _runner = Thread(...)

where the temporary object Thread

is on the right side and is set as stream local data. The data was not updated after moving the stream.



In the destructor, we Thread

interrupt

checked the local thread flag and the reason for the failure in the destructor.

0


source







All Articles