How to design proper release of a boost::asio socket or wrapper thereof

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

How to design proper release of a boost::asio socket or wrapper thereof

Christopher Pisz
I am making a few attempts at making my own simple asynch TCP server using boost::asio after not having touched it for several years.

The latest example listing I can find is: http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html

The problem I have with this example listing is that it cheats and it cheats big, by making the tcp_connection a shared_ptr, such that it doesn't worry about the lifetime management of each connection.

I am worried about the lifetime management of each connection. I figure the natural thing to do would be to keep some collection of tcp_connection objects or pointers to them inside tcp_server. Adding to that collection from the OnConnect callback and removing from that collection OnDisconnect.

Note that OnDisconnect would most likely be called from an actual Disconnect method, which in turn would be called from OnReceive callback or OnSend callback, in the case of an error.

Well, therein lies the problem.

Consider we'd have a callstack that looked something like this:
tcp_connection::~tcp_connection
tcp_server::OnDisconnect
tcp_connection::OnDisconnect
tcp_connection::Disconnect
tcp_connection::OnReceive

This would cause errors as the call stack unwinds and we are executing code in a object that has had its destructor called...I think, right?

I then thought to myself, well I could flag the tcp_connection as disconnected when it disconnects and then on some timer on the tcp_server, go through its collection and start deleting disconnected tcp_connection objects, but then the timer could fire after the flag is set, but before it returns from a OnReceive where an error occurred and I Disconnected as a response.

I imagine everyone doing server programming comes across this scenario in some fashion. What is a strategy for handling it?

I hope the explanation is good enough to follow. If not let me know and I will create my own source listing, but it will be very large.
 
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: How to design proper release of a boost::asio socket or wrapper thereof

Boost - Users mailing list
On 04/05/2017 10:32 PM, Christopher Pisz via Boost-users wrote:

> The problem I have with this example listing is that it cheats and it cheats
> big, by making the tcp_connection a shared_ptr, such that it doesn't worry
> about the lifetime management of each connection.

That is not cheating.

> Consider we'd have a callstack that looked something like this:
> tcp_connection::~tcp_connection
> tcp_server::OnDisconnect
> tcp_connection::OnDisconnect
> tcp_connection::Disconnect

If Disconnect is an asynchronous operation, then it should not call
OnDisconnect directly. You can post it on the io_service instead.

> I imagine everyone doing server programming comes across this scenario in
> some fashion. What is a strategy for handling it?

Preferably shared_ptr, or io_service::post() if for some reason you
cannot use shared_ptr, e.g. if the object has to be destroyed by a
a specific thread.

_______________________________________________
Boost-users mailing list
[hidden email]
http://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: How to design proper release of a boost::asio socket or wrapper thereof

Christopher Pisz
Using shared_ptr is absolutely cheating. I do not think it is real world at all to maintain a connection's lifetime through the use of outstanding receive calls.

A simple scenario as to why it is cheating is:
What if the server wants to send "Hello" to all clients every 5 minutes?
You need a collection. If you have a collection, then you maintain a reference count when using shared_ptrs, and just killed the tutorials lifetime management scheme. In the end, you need to know when it is safe to remove the connection from a collection and destroy it.

If I post disconnect, then I am guaranteed no other outstanding IO operation is in progress for that connection. That's a step in the right direction. Only if I have one and only one thread that called io_service.run, but then I still have to know, "when is it safe to destroy my connection object?"

Let's say I posted a callback to tcp_server::Disconnect(tcp_connection * connection) from tcp_connection::OnReceive. Is it safe for me then to delete tcp_connection in the body and therefore the socket as well? Will no "cancel" notifications be called back afterward when tcp_server::Disconnect then calls close() on the socket? Or should I call close() back in OnReceive and post a callback to tcp_server::OnDisconnect, which simply deletes the connection object?

I think the latter might make sense. I will make an attempt at this in code. Meanwhile a little more clarification on the order of events there might help. Thanks!


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: How to design proper release of a boost::asio socket or wrapper thereof

Boost - Users mailing list
On 6/04/2017 10:14, Christopher Pisz via Boost-users wrote:
> What if the server wants to send "Hello" to all clients every 5 minutes?
> You need a collection. If you have a collection, then you maintain a
> reference count when using shared_ptrs, and just killed the tutorials
> lifetime management scheme. In the end, you need to know when it is safe to
> remove the connection from a collection and destroy it.

Make a collection of weak_ptrs.  When you go through and try to send to
them, some of them will have expired, and you can remove them from the
collection then.  The rest are still alive (although some might error
out when you try to send to them, due to the way TCP works; if that
happens you can just close() them and they'll be expired the next time
you check).

If you want to explicitly disconnect a given connection, you simply need
to call shutdown() and close() on it (via a method on your connection
class), which will abort the pending receive and release the internal
references, so once you discard the local shared_ptr you just used to
close it then the connection object will be destroyed automatically.

(Note that it's not safe to assume you can destroy the object
immediately after calling close() on the socket -- if the receive was
still pending, it still needs to call the completion handler with the
error, which might happen later on a different thread.  This is why
using shared_ptr is the preferred solution as it handles different
lifetime ordering correctly without explicit locking.)


_______________________________________________
Boost-users mailing list
[hidden email]
http://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: How to design proper release of a boost::asio socket or wrapper thereof

Boost - Users mailing list
Genious. Thank you sir.

On Wed, Apr 5, 2017 at 10:02 PM, Gavin Lambert via Boost-users <[hidden email]> wrote:
On 6/04/2017 10:14, Christopher Pisz via Boost-users wrote:
What if the server wants to send "Hello" to all clients every 5 minutes?
You need a collection. If you have a collection, then you maintain a
reference count when using shared_ptrs, and just killed the tutorials
lifetime management scheme. In the end, you need to know when it is safe to
remove the connection from a collection and destroy it.

Make a collection of weak_ptrs.  When you go through and try to send to them, some of them will have expired, and you can remove them from the collection then.  The rest are still alive (although some might error out when you try to send to them, due to the way TCP works; if that happens you can just close() them and they'll be expired the next time you check).

If you want to explicitly disconnect a given connection, you simply need to call shutdown() and close() on it (via a method on your connection class), which will abort the pending receive and release the internal references, so once you discard the local shared_ptr you just used to close it then the connection object will be destroyed automatically.

(Note that it's not safe to assume you can destroy the object immediately after calling close() on the socket -- if the receive was still pending, it still needs to call the completion handler with the error, which might happen later on a different thread.  This is why using shared_ptr is the preferred solution as it handles different lifetime ordering correctly without explicit locking.)


_______________________________________________
Boost-users mailing list
[hidden email]
http://lists.boost.org/mailman/listinfo.cgi/boost-users


_______________________________________________
Boost-users mailing list
[hidden email]
http://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: How to design proper release of a boost::asio socket or wrapper thereof

Boost - Users mailing list
In reply to this post by Christopher Pisz
Christopher Pisz via Boost-users wrote:
> The problem I have with this example listing is that it cheats and it cheats
> big, by making the tcp_connection a shared_ptr, such that it doesn't worry
> about the lifetime management of each connection.
>


What's wrong with how connections are managed in the chat_server example?

http://think-async.com/Asio/boost_asio_1_10_6/doc/html/boost_asio/example/cpp11/chat/chat_server.cpp



_______________________________________________
Boost-users mailing list
[hidden email]
http://lists.boost.org/mailman/listinfo.cgi/boost-users
Loading...