Reputation: 4948
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
Upvotes: 0
Views: 2846
Reputation: 51961
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.
Upvotes: 1
Reputation: 2787
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.
Upvotes: 1
Reputation: 10730
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.
Upvotes: 2
Reputation: 2787
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.
Upvotes: 2