简体   繁体   中英

can I use a boost::shared_ptr when creating&accepting a socket in boost::asio async mode?

Sorry if I wasn't able to put a better title to my question. I was debugging my program when I noticed something very interesting. The code is very straightforward. please follow my comments inline:

//my session class
class Session
{
public:
  /// Constructor.
  Session(boost::asio::io_service &io_service)
    : socket_(io_service)
  {
  }

  boost::asio::ip::tcp::socket& socket()
  {
    return socket_;
  }

void async_read(/*...*/);
void async_write(/*...*/);
//blah blah
private:
std::vector<char> inbound_data_;//<---note this variable, but don't mind it until i tell you
std::string outbound_data_;
boost::asio::ip::tcp::socket socket_;
}

typedef boost::shared_ptr<Session> session_ptr; //just for easy reading


//and this is my connection server class
class ConnectionServer {
public:
void ConnectionServer::CreatSocketAndAccept() {
    session_ptr new_sess(new Session(io_service_));//<--I created a scope limited shared_ptr
    Print()<< "new_sess.use_count()= " << new_sess.use_count() << std::endl;//prints 1
    acceptor_.async_accept(new_sess->socket(),//<-used it for async connection acceptance
            boost::bind(&ConnectionServer::handle_accept, this,
                    boost::asio::placeholders::error, new_sess));
   Print()<< "new_sess.use_count()= " << new_sess.use_count() << std::endl;//prints 2
}//<-- Scope is ending. what happens to my new_sess? who keeps a copy of my session?

//and now the strangest thing:
void ConnectionServer::handle_accept(const boost::system::error_code& e, session_ptr sess) {
    if (!e) {

        Print()<< "sess.use_count()= " << sess.use_count() << std::endl;//prints 4 !!!! while I have never copied the session anywhere else in between
        Print() << "Connection Accepted" << std::endl;
        handleNewClient(sess);
    }
    else
    {
        std::cout << "Connection Refused" << std::endl;
    }
    CreatSocketAndAccept();
} 

I don't know who(in boost::asio ) copies my shared_ptr internally and when it is going to release them all.

In fact, I noticed this situation when: My application runs to completion and at the time when containers full of nested shared_ptr ed objects are being cleaned up(automatically and not by me), I get a seg fault after ~Session() is called where program is trying to deal with a std::vector<char> (this is where I told you to remember in the beginning). I could see this through eclipse debugger.

I am not good in reading seg faults but I guess the program is trying to clear a vector that doesn't exist.

Sorry for the long question I value your time and appreciate your kind comments.

EDIT-1: I just modified my application to use raw pointers for creating new Session (s) rather than shared_ptr . The seg fault is gone if I dont delete the Session. So at least I am sure the cause of the seg fault is in Session .

EDIT-2: As I mentioned in my previous update, the problem occurs when I try to delete the session but every time the trace leading to the seg fault is different. sometimes this:

Basic Debug [C/C++ Application] 
    SimMobility_Short [10350] [cores: 0]    
        Thread [1] 10350 [core: 0] (Suspended : Signal : SIGSEGV:Segmentation fault)    
            malloc_consolidate() at malloc.c:4,246 0x7ffff5870e20   
            malloc_consolidate() at malloc.c:4,215 0x7ffff5871b19   
            _int_free() at malloc.c:4,146 0x7ffff5871b19    
            __gnu_cxx::new_allocator<char>::deallocate() at new_allocator.h:100 0xa4ab4a    
            std::_Vector_base<char, std::allocator<char> >::_M_deallocate() at stl_vector.h:175 0xab9508    
            std::_Vector_base<char, std::allocator<char> >::~_Vector_base() at stl_vector.h:161 0xabf8c7    
            std::vector<char, std::allocator<char> >::~vector() at stl_vector.h:404 0xabeca4    
            sim_mob::Session::~Session() at Session.hpp:35 0xabea8d 
            safe_delete_item<sim_mob::Session>() at LangHelpers.hpp:136 0xabef31    
            sim_mob::ConnectionHandler::~ConnectionHandler() at ConnectionHandler.cpp:40 0xabd7e6   
            <...more frames...> 
    gdb

and some times this:

Basic Debug [C/C++ Application] 
    SimMobility_Short [10498] [cores: 1]    
        Thread [1] 10498 [core: 1] (Suspended : Signal : SIGSEGV:Segmentation fault)    
            _int_free() at malloc.c:4,076 0x7ffff5871674    
            std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() at 0x7ffff639d540   
            sim_mob::ConnectionHandler::~ConnectionHandler() at ConnectionHandler.cpp:30 0xabd806   
            boost::checked_delete<sim_mob::ConnectionHandler>() at checked_delete.hpp:34 0xadd482   
            boost::detail::sp_counted_impl_p<sim_mob::ConnectionHandler>::dispose() at sp_counted_impl.hpp:78 0xadd6a2  
            boost::detail::sp_counted_base::release() at sp_counted_base_gcc_x86.hpp:145 0x849d5e   
            boost::detail::shared_count::~shared_count() at shared_count.hpp:305 0x849dd7   
            boost::shared_ptr<sim_mob::ConnectionHandler>::~shared_ptr() at shared_ptr.hpp:164 0x84a668 
            sim_mob::ClientHandler::~ClientHandler() at ClientHandler.cpp:42 0xac726d   
            sim_mob::ClientHandler::~ClientHandler() at ClientHandler.cpp:45 0xac72da   
            <...more frames...> 
    gdb 

does it mean my memory is already corrupted? How can I do more checks? Thank you

This line is where the magic lives:

acceptor_.async_accept(new_sess->socket(),//<-used it for async connection acceptance
        boost::bind(&ConnectionServer::handle_accept, this,
                boost::asio::placeholders::error, new_sess));

The async_accept has an (optional) second parameter - a completion function which you are using here. You are using boost::bind to create a functor that matches the completion function declaration. You are passing a new_sess smart pointer to that handler (this is why the smart_pointer is not deleted when you leave the scope).

In other words: The async_accept function takes either a functor with no parameters or a functor that accepts an error. You may now create a class that overloads the operator() with that signature. Instead you use boost::bind. Boost::bind allows you to either provide the parameters when the (inner) function is called or when constructing the functor by calling boost::bind. You provided some parameters when calling boost::bind - the smart pointer to the session.

This is a common pattern with boost::asio. You pass your context to the asynchronous function. When this function detects an error all you need to do is to leave the function. The context then leaves the scope and will be deleted. When no error is detected you pass the context (via boost::bind) to the next async function and the context will be kept alive.

You should be able to use shared_ptr in that way, I use it in the same manner without issue.

Internally, asio keeps a copy of your shared_ptr (via boost::bind) until it calls handle_accept . This is what allows you to pass the shared_ptr to begin with. If you did not add it as one of the arguments, then it would clean up the object as soon as it scoped in the function you created it.

I suspect that you have other undefined behavior that using a raw pointer with does not uncover.

To (try to) answer your second question: It seems like a you are issuing a double delete on the session. This is only possible if you create a second scoped_ptr from a raw pointer. This is something you shouldn't do. Are you passing a raw pointer to session to any function that in turn creates a scoped ptr of it?

You could try to let Session inherit enable_shared_from_this. This will fix the problem as any raw pointer uses the same scoped_ptr counter. But you should not see this as a real fix. The real fix would be to eliminate the multiple scope_ptr instanciations.

Edit: Added another debug possibility

Something else you could try would be to set a breakpoint in the destructor of the session and see the backtrace of the first/second delete.

As covered in this answer , it is fine to use shared pointers with Boost.Asio's async_* functions.


Based on the call stacks and behavior, it looks as though at least one resource is being deleted twice. Is it possible that Session is being managed through both a raw pointer and a shared_ptr ?

Managing with boost::shared_ptr :

void ConnectionServer::CreatSocketAndAccept() {
  session_ptr new_sess(new Session(io_service_)); // shared pointer
  ...
}

Managing with raw-pointer:

sim_mob::Session::~Session()
safe_delete_item<sim_mob::Session>() // raw pointer
sim_mob::ConnectionHandler::~ConnectionHandler()

If ConnectionHandler was managing Session with boost::shared_ptr , then the call stack should show boost::shared_ptr<sim_mob::Session>::~shared_ptr() . Also, be careful not to create a shared_ptr from a raw pointer that is already being managed by a shared_ptr , as it will result in the shared_ptr s managing the resource as two distinct resources, resulting in a double deletion:

// p1 and p2 use the same reference count to manage the int.
boost::shared_ptr<int> p1(new int(42));
boost::shared_ptr<int> p2(p1); // good

// p3 uses a different reference count, causing int to be managed
// as if it was a different resource.
boost::shared_ptr<int> p3(p1.get()); // bad

As a side note, one common idiom is to have Session inherit from enable_shared_from_this . It allows for Session to remain alive throughout the duration of its asynchronous call chains by passing the shared pointer as a handle to the instance in place of this . For example, it would allow for Session to remain alive while an asynchronous read operation is outstanding, as long as the result of shared_from_this() is bound as the instance handle to the Session::async_read callback.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM