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 of close_async call);
  • destructor for restinio::http_server_t is called when the server wasn’t closed manually. In that case close_sync is called inside http_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.