Search code examples
c++boost-asioshared-ptrstdasync

Member std::future preventing boost::shared_ptr from going out of scope


I have a function that creates a new bridge object and stores it as a boost::shared_ptr:

bool proxy::bridge::acceptor::accept_connections() {
    try {
        session_ = boost::shared_ptr<bridge>(new bridge(io_service_));

        acceptor_.async_accept(session_->downstream_socket(),
            boost::bind(&acceptor::handle_accept,
                this, boost::asio::placeholders::error));
    }
    catch (std::exception& e) {
        std::cerr << e.what() << std::endl;
        return false;
    }

return true;
}

void proxy::bridge::acceptor::handle_accept(const boost::system::error_code& error) {
    if (!error) {
        session_->local_connect();
        if (!accept_connections()) {
        }
    }
    else {
        std::cerr << error.message() << std::endl;
    }
}

In the bridge class is a std::future variable defined in the header and initialized in a bridge class method:

//proxy.h
std::mutex add_data_lock;
std::vector<int> global_resource_protected_by_lock;

class proxy {
    //...
    class bridge {
        //...
        std::future<void> f_read;
    };

    class acceptor {
         //...
    };
};

//proxy.cpp
void proxy::bridge::read(const boost::system::error_code& error, const size_t& bytes_transferred) {
    if (!error) {
        if (f_read.valid()) { f_read.wait(); }
        f_read = std::async(std::launch::async, std::bind(&proxy::bridge::handle_add_data, shared_from_this(), bytes_transferred));
    }
    else {
        close();
    }
}

void proxy::bridge::handle_add_data(const size_t& bytes_transferred) {
    add_data_lock.lock();
    //do work here
    add_data_lock.unlock();
}

void proxy::bridge::close() {
    //this is called when this object is no longer useful (ie. connection closed)

    if (f_read.valid()) { f_read.wait(); }

    //other closing functions...
}

The read() method is called repeatedly - the goal is to call handle_add_data() asynchronously and do work in between cycles of read().

However, the dynamically created bridge object never gets deleted even after the socket is closed (process takes more and more memory).

If I replace the async call and future with a direct function call to handle_add_data(), everything is deallocated properly when close() is called.

If I move the f_read future to outside of the class as a static variable with file scope, then everything is deallocated properly as well, but sometimes I get 'mutex destroyed while busy' errors.

If I don't assign the async call to anything, then it blocks when going out of scope of the function and defeats the purpose of using it.

What should I be doing so the dynamically created bridge objects get deleted properly after close() is called?

Sorry for the confusing code - I condensed it as best as I could to illustrate the problem.


Solution

  • Your bind is holding onto a shared_ptr longer than it needs to. You can change it to explicitly release the captured pointer when it is done.

    f_read = std::async(std::launch::async, [=, self = shared_from_this()]() mutable { self->handle_add_data(bytes_transferred); self = nullptr; });
    

    Prior to C++14, you need to do the initialisation as a local that gets captured

    auto self = shared_from_this();
    f_read = std::async(std::launch::async, [=]() mutable { self->handle_add_data(bytes_transferred); self = nullptr; });