Cleanup function
Purpose
One of the corner cases of RESTinio usage is user’s resources cleanup on server’s shutdown. A user can easily create a cyclical dependency between dynamically created objects which will prevent deallocation of these objects at the end of the server’s work.
For example, let’s consider a case when all requests are stored in some thread-safe storage to be processed later. It could look like:
void launch_server(thread_safe_request_queue & pending_requests)
{
restinio::run(
restinio::on_this_thread().port(8080).address("localhost")
.request_handler([&](auto req) {
pending_requests.push_back(std::move(req));
return restinio::request_accepted();
}));
}
This simple code contains a serious defect: when the server finished its work
it will be destroyed just before return from restinio::run
. But some
request_handle_t
will live inside pending_requests
container. It means
that some internal RESTinio objects (like connections objects and associated
socket objects) will be alive because there are request_handle_t
which
holds references to them.
A user can add a cleanup of pending_requests
just after return from
restinio::run
like this:
void launch_server(thread_safe_request_queue & pending_requests)
{
restinio::run(
restinio::on_this_thread().port(8080).address("localhost")
.request_handler([&](auto req) {
pending_requests.push_back(std::move(req));
return restinio::request_accepted();
}));
pending_requests.clean();
}
But there are some other problems. The main of them is: when
request_handle_t
objects from pending_requests
will be destroyed then
calls to already destroyed restinio::http_server_t
and corresponding
asio::io_context
objects can be made. This can lead to various hard to
detect bugs.
It means that user should clean up its resources at the moment of the server’s
shutdown just before return from restinio::run
. To do that user can set up
cleanup_func
which will be called by the server during shutdown procedure.
By using cleanup_func
the code above can be rewritten this way:
void launch_server(thread_safe_request_queue & pending_requests)
{
restinio::run(
restinio::on_this_thread().port(8080).address("localhost")
.request_handler([&](auto req) {
pending_requests.push_back(std::move(req));
return restinio::request_accepted();
})
.cleanup_func([&]{
pending_requests.clean();
}));
}
Note that cleanup_func
is a part of server_settings_t
.
It means that it can be used even when RESTinio server is running via
restino::http_server_t
instance. For example:
restinio::http_server_t<> server{
restinio::own_io_context(),
[&](auto & settings) {
settings.port(8080).address("localhost")
.request_handler(...)
.cleanup_func([&]{
pending_requests.clean();
});
}};
When cleanup_func is called
If a user sets cleanup_func
in server_settings_t
then this cleanup
function will be called if the server is started and:
restinio::http_server_t::close_sync
is called directly or indirectly (as result ofclose_async
call);- destructor for
restinio::http_server_t
is called when the server wasn’t closed manually. In that caseclose_sync
is called insidehttp_server_t
’s destructor.
Technically speaking the cleanup_func
is called inside close_sync
just
after the acceptor will be closed.
Important notes
The topic of cleaning up connections, requests and associated resources is a hard one. During the work on RESTinio v.0.3 we have found one solution which seems to be working. But it could be not the best one. So if you have some troubles with it or have some ideas on this topic please let us know. We are working hard on this topic and will be glad to hear any feedback from you.