Home > other >  Interrupt blocking code outside module using boost::thread::interrupt()
Interrupt blocking code outside module using boost::thread::interrupt()

Time:12-07

I have some ActiveMQ initialization code like this:

bool Base::init()
{
    boost::thread th(boost::bind(&Derived::init_derived, this));
    if(!th.timed_join(boost::posix_time::seconds(10)) {
        th.interrupt();
        return false;
    }
    return true;
}

In child class:

void Derived::init_derived()
{
    try
    {
        m_session = connection.createSession(cms::Session::AUTO_ACKNOWLEDGE);  // Line 1
    }
    catch(boost::thread_interrupted const&)
    {
        // log error...
    }
}

In the caller:

template <typename Derived>
boost::shared_ptr<Derived> Base::create(Derived *obj)
{
    boost::shared_ptr<Derived> inst(obj);
    if(inst != nullptr && !inst->init()) {
        inst.reset();  // Line 2
    }
    return inst;
}

The problem is Line 2 causes crash. If omitted, it's a memory leak. If I replace Line 1 with other in-module blocking code like: for(;;){}, it still breaks and doesn't crash. Am I not permitted to use boost thread functions outside module boundary? How do I fix this?

CodePudding user response:

I'm not sure I can reproduce the error. Mainly because I had trouble coming up with legal code that matches your snippets. (There's a typo in it, the this doesn't seem compatible in the bind and it would appear that Base is a enter image description here

Here's what I came up with to make your code self-contained:

Live On Coliru

//#define BOOST_THREAD_PROVIDES_THREAD_DESTRUCTOR_CALLS_TERMINATE_IF_JOINABLE
#include <boost/thread.hpp>
#include <iostream>

template <typename Derived> struct Base {
    virtual ~Base() = default;
    bool init();

    static boost::shared_ptr<Derived> create(Derived*);
};

struct Derived : Base<Derived> {
    void init_derived();
};

template <typename Derived> bool Base<Derived>::init() {
    boost::thread th(boost::bind(&Derived::init_derived, static_cast<Derived*>(this)));

    if (!th.timed_join(boost::posix_time::seconds(1))) {
        std::cerr << "init(): Timeout, interrupting" << std::endl;
        th.interrupt();
        //std::cerr << "init(): Interrupted, joining" << std::endl;
        //th.join();
        std::cerr << "init(): Return false" << std::endl;
        return false;
    }
    std::cerr << "init(): Return true" << std::endl;
    return true;
}

void Derived::init_derived() {
    try {
        std::cerr << "init_derived(): begin work" << std::endl;
        boost::this_thread::sleep_for(boost::chrono::seconds(2));
        // m_session = connection.createSession(cms::Session::AUTO_ACKNOWLEDGE);
        std::cerr << "init_derived(): complete work" << std::endl;
    } catch (boost::thread_interrupted const&) {
        std::cerr << "init_derived(): interrupted" << std::endl;
        // log error...
    }
}

template <typename Derived>
boost::shared_ptr<Derived> Base<Derived>::create(Derived* obj) {
    boost::shared_ptr<Derived> inst(obj);
    return inst && inst->init() ? inst : nullptr;
}

int main() {
    auto d = Base<Derived>::create(new Derived());
}

Althought the output seems alright:

init_derived(): begin work
init(): Timeout, interrupting
init(): Return false

Things are not fine. The thread outlives the instance. You can see it because init_derived() never prints the "init_derived(): interrupted" trace message.

You can force your program to fail early, using

#define BOOST_THREAD_PROVIDES_THREAD_DESTRUCTOR_CALLS_TERMINATE_IF_JOINABLE

(Note: this is the standard behaviour for std::thread)

See it Live prints:

init_derived(): begin work
init(): Timeout, interrupting
init(): Return false
terminate called without an active exception
bash: line 7:  5541 Aborted                 (core dumped) ./a.out

There are several ways to fix it, but the simplest is quite obviously:

Join!

Uncommenting the lines

    std::cerr << "init(): Interrupted, joining" << std::endl;
    th.join();

adds the missing join. It should be "non-blocking" assuming that the thread_interrupted exception is handled swiftly. See it Live printing:

init_derived(): begin work
init(): Timeout, interrupting
init(): Interrupted, joining
init_derived(): interrupted
init(): Return false

Success!

Other Observations/Ideas

It seems like really you could use a [packaged_task][3]<shared_ptr<Base>(Args...)>. You would have the task own the shared pointer until returned, and throw on timeout.

  • Related