How to design a base class that needs to join a thread in its destructor, which operates on the same class instance?

113 views Asked by At

I just got a juicy race condition. Consider the following classes:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

class A
{
    std::thread th;
    std::atomic_bool stop = false;

  public:
    A() = default;
    A(const A &) = delete;
    A &operator=(const A &) = delete;

    ~A()
    {
        stop.store(true);
        th.join();
    }

    virtual void Step() = 0;

    void Start()
    {
        th = std::thread([this]
        {
            while (true)
            {
                if (stop.load())
                    return;

                // Just to help reproduce the race condition.
                std::this_thread::sleep_for(std::chrono::milliseconds(50));

                Step();
            }
        });
    }
};

struct B : A
{
    void Step() override
    {
        std::cout << "Step!" << std::endl;
    }
};

int main()
{
    B b;

    b.Start();

    // Just to help reproduce the race condition.
    std::this_thread::sleep_for(std::chrono::milliseconds(110));
}

This crashes due to a pure virtual function call (if we ignore the data race UB).

Right before we enter the body of ~A(), before we can stop the thread, the vtable pointer is changed to point to A's vtable, so the next call of Step() crashes, now being a pure virtual call.

This can be fixed by stopping the thread in ~B(), but that means every derived class must remember to do it, or bad things will happen.

I wish I could design A to take care of this by itself. Is it possible?

0

There are 0 answers