[Asio] How do i code a resource clean server shutdown?

I'm studying the async daytime server example, Daytime.3 (0.3.7 flavor): http://asio.sourceforge.net/boost_asio_0_3_7/libs/asio/doc/tutorial/tutdayti... (I'm doing an Asio proof/concept for an app that has to dynamically change it's service ports.) I'm trying to think through how to perform a clean shutdown in this example. There could be a number of pending async_write()'s, each with their own socket and uncalled handler. There is probably also a socket waiting on the async_accept() with an an uncalled handler. Questions: - With the work pending, io_service will need to call it's interrupt() function (from another thread) to stop processing, correct? - If io_service is destroyed, what happens to the otherwise pending work, i.e., the async_write's and async_accepts that it controls? What about the socket resources? - I can close the tcp::acceptor in tcp_server, but what about the socket that's listening on it? - If Asio doesn't release system handles, e.g., sockets, how do I get at the resources to clean up? - Is there an example of a resource clean shutdown? I skimmed the example apps and didn't see one first pass. Thanks, Rod - If the io_service is destroyed, what Is there a way to do this with the io_service? In a watchdog thread... -call io_service.interrupt() to stop processing -tell the tcp_server to The io_service.run() won't exit until all services and handlers are finished. Let me take a stab at a shutdown design and perhaps Asio vets would be willing to tear it apart/fix it? This assumes the Daytime.3 server code at http://asio.sourceforge.net/boost_asio_0_3_7/libs/asio/doc/tutorial/tutdayti... (I noticed the 0.3.6 example is somewhat different.) in tcp_server ------------- - add a cleanup() method that calls acceptor_.close() in main() --------- - add a watchdog thread that will shutdown the server's io_service - if shutdown time then How do I notify handlers io_service.dispatch() or io_service.post() to wake up handlers

Hi Rod, Rod Morison <rod@morison.biz> wrote:
I'm trying to think through how to perform a clean shutdown in this example. There could be a number of pending async_write()'s, each with their own socket and uncalled handler. There is probably also a socket waiting on the async_accept() with an an uncalled handler.
- With the work pending, io_service will need to call it's interrupt() function (from another thread) to stop processing, correct?
Actually, there are two basic approaches to clean shutdown. Interrupting the io_service::run() is one. The other is to cancel all outstanding work and let io_service::run() exit because it runs out of work. The first involves forcing io_service::run() to exit by calling io_service::interrupt(). As you say, this means that there will still be work pending. However, the io_service destructor causes all copies of user-defined handlers to be destroyed. If you follow the daytime server example of using shared_ptrs to manage your object lifetimes, these objects will all be cleaned up automatically. The second approach is to cancel the asynchronous operations by explicitly closing all the sockets and acceptor. The operations will all complete with the operation_aborted error, and provided you don't start any new async operations the io_service::run() function will exit due to lack of work. See the HTTP server program for an example of this approach.
- I can close the tcp::acceptor in tcp_server, but what about the socket that's listening on it?
If you're referring to the peer socket that is being "accepted", you don't need to do anything with it. The asynchronous accept operation is "owned" by the acceptor, so closing the acceptor is sufficient to abort the operation. No resource is assigned to the peer socket unless the accept is successful. Cheers, Chris

Christopher Kohlhoff wrote:
The first involves forcing io_service::run() to exit by calling io_service::interrupt(). As you say, this means that there will still be work pending. However, the io_service destructor causes all copies of user-defined handlers to be destroyed. If you follow the daytime server example of using shared_ptrs to manage your object lifetimes, these objects will all be cleaned up automatically.
Right, that's a nice design, with shared_ptrs. So the socket destructor will call shutdown/close? What if I have a linger timeout set on the socket, the destructor can't hang on that, can it? Or does it work differently than I'm thinking? I just need to make sure I know how to open and close lots of sockets, like thousands per second, and that a resource leak (socket handles, usually) isn't going to max out a kernel table.
The second approach is to cancel the asynchronous operations by explicitly closing all the sockets and acceptor. The operations will all complete with the operation_aborted error, and provided you don't start any new async operations the io_service::run() function will exit due to lack of work. See the HTTP server program for an example of this approach.
So this approach lets me explicity handle shutdowns...at the cost of having to keep track of those sockets instead of letting shared ptrs within the io_service destructor do the bookkeeping for me, right?
- I can close the tcp::acceptor in tcp_server, but what about the socket that's listening on it?
If you're referring to the peer socket that is being "accepted", you don't need to do anything with it. The asynchronous accept operation is "owned" by the acceptor, so closing the acceptor is sufficient to abort the operation. No resource is assigned to the peer socket unless the accept is successful.
What I meant (in Daytime.3) was the acceptor handler (handle_accept) owns a tcp_connection, which owns a socket. But, I see that shared ptrs take care of that business. Thanks for letting me pelt you with these questions. It makes the difference between getting this app right the second time vs the 3rd or 4th ;-)

Hi Rod, Rod Morison <rod@morison.biz> wrote:
Right, that's a nice design, with shared_ptrs. So the socket destructor will call shutdown/close?
Yep, it calls close.
What if I have a linger timeout set on the socket, the destructor can't hang on that, can it? Or does it work differently than I'm thinking?
Good question, I hadn't considered the effects of a linger timeout on the destructor. As it stands, it will block. I think I need to change it so that the destructor sets the linger option back to the default behaviour before calling close. If you really do want a blocking close, then the close() function can be used explicitly.
The second approach is to cancel the asynchronous operations by explicitly closing all the sockets and acceptor. The operations will all complete with the operation_aborted error, and provided you don't start any new async operations the io_service::run() function will exit due to lack of work. See the HTTP server program for an example of this approach.
So this approach lets me explicity handle shutdowns...at the cost of having to keep track of those sockets instead of letting shared ptrs within the io_service destructor do the bookkeeping for me, right?
Yep, exactly. I see it as most useful for applications that need to perform additional socket operations as part of shutdown. For example, an app might need to send messages over a socket to a logging server, etc. Cheers, Chris
participants (2)
-
Christopher Kohlhoff
-
Rod Morison