eidheim / Simple-Web-Server

A very simple, fast, multithreaded, platform independent HTTP and HTTPS server and client library implemented using C++11 and Boost.Asio. Created to be an easy way to make REST resources available from C++ applications.
MIT License
2.61k stars 751 forks source link

Deterministic way to know if server has started #176

Open sharpie7 opened 6 years ago

sharpie7 commented 6 years ago

Hi,

Is there a deterministic way to know from the parent thread if the server has started successfully? In the example application it just calls the start method and waits for a second and then assumes the server is running. But, I guess it is possible that the server start-up may take more than a second (e.g. on a machine that has very high CPU occupancy).

I am particularly thinking of how best to detect from the parent thread exceptions in the server thread (such as port conflicts) that prevent the server starting successfully.

With thanks for a great tool.

Iain

vitor-alves commented 6 years ago

I have a piece of code that may help you. Not exactly what you are asking for, but you certanly can extend it to your personal needs. One option for you would be to catch an exception on server.start() inside the thread and pass this information to the parent thread. My code only catches the exception and logs it, but modifying it to pass this information to the parent is not hard.

Note: HttpServer server is defined outside the lambda and passed by reference

void RestAPI::start_server() {
    server_thread = std::make_unique<std::thread>( [&]() { 
                try {
                    server.start();
                    LOG_INFO << "Web UI and REST API server have been started and are listening on "
                        << server.config.address << ":" << server.config.port;
                }
                catch(std::exception const &e) {
                    LOG_ERROR << "Web server error: " << e.what() << ". Is port " <<
                        server.config.port << " already in use?" ;
                    std::cerr << "Web server error: " << e.what() << ". Is port " <<
                        server.config.port << " already in use?"
                    << " Check log files for more information." << std::endl;
                }} );

    server.on_error = [](std::shared_ptr<HttpServer::Request> request, const SimpleWeb::error_code & ec) {
        LOG_ERROR << "Web server error: " << ec.message();
    };
}

Edit: What you really want to do is to catch a thread exception in the parent, so take a look at this: https://stackoverflow.com/questions/233127/how-can-i-propagate-exceptions-between-threads

sharpie7 commented 6 years ago

@vitor-alves

Thanks for the suggestion. I understand the technique you describe, but it addresses a different aspect of the problem. Because the start() method doesn't exit until the server has been stopped, the catch statement in your example will catch exceptions during server start-up, and also exceptions while the server is running (e.g. due to a bug or resoure limitations).

That's fine, but if I want to block the calling thread until the server has successfully started I have no way of knowing how long I should wait for except for guessing a maximum amount of time that the server might need to start.

There are two reasons for an application to know the difference between a server that is running and one that is still starting up: 1) So that application knows whether it is capable of accepting requests or not 2) So that application knows whether the server has successfully gone past the start-up phase when it is most at risk of encountering unrecoverable errors.

vitor-alves commented 6 years ago

Wow, I just realized we have the same problem. I have assumed server.start() exits right after the server is started, but I was wrong. In fact LOG_INFO in my code only gets executed when the server is stopping. This is no big deal, since my main concern is catching the exceptions, but I am looking forward to a solution for this.

eidheim commented 6 years ago

I did a quick implementation here:

#include "server_http.hpp"

using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;

int main() {
  HttpServer server;
  server.config.port = 8080;

  std::thread server_thread([&server] {
    server.start();
  });

  {
    // Wait till server is started
    while(!server.io_service)
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::condition_variable cv;
    std::mutex cv_mutex;
    server.io_service->post([&cv, &cv_mutex] {
      std::unique_lock<std::mutex> lock(cv_mutex);
      cv.notify_one();
    });
    std::unique_lock<std::mutex> lock(cv_mutex);
    cv.wait(lock);
    std::cout << "Server is now accepting requests" << std::endl;
  }

  server_thread.join();
}

I'm not very fond of the while-loop, but that can be fixed by using an external io_service:

#include "server_http.hpp"

using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;

int main() {
  HttpServer server;
  server.config.port = 8080;
  server.io_service = std::make_shared<SimpleWeb::asio::io_service>();

  std::thread server_thread([&server] {
    server.start();
    server.io_service->run();
  });

  {
    // Wait till server is started
    std::condition_variable cv;
    std::mutex cv_mutex;
    server.io_service->post([&cv, &cv_mutex] {
      std::unique_lock<std::mutex> lock(cv_mutex);
      cv.notify_one();
    });
    std::unique_lock<std::mutex> lock(cv_mutex);
    cv.wait(lock);
    std::cout << "Server is now accepting requests" << std::endl;
  }

  server_thread.join();
}

In both examples above, I post a task to the io_service, and when this task has been performed I know that the server's io_service is up and running and is accepting tasks through async_accept call.

sharpie7 commented 6 years ago

Thanks - that addresses the problem!

Would it be sensible to add some code similar to that to the library as a testIfServerIsRunning() type method?

ghost commented 6 years ago

How do i unsubscribe for this, so annoying.

Rgds Sashidhar Rathod shashirathod254@gmail.com +917353043601

On Wed, Nov 29, 2017 at 8:56 PM, Vitor Alves notifications@github.com wrote:

I have a piece of code that may help you. Not exactly what you are asking for, but you certanly can extend it to your personal needs. One option for you would be to catch an exception on server.start() inside the thread and pass this information to the parent thread. My code only catches the exception and logs it, but modifying it to pass this information to the parent is not hard.

Note: HttpServer server is defined outside the lambda and passed by reference

void RestAPI::start_server() { server_thread = std::make_unique( [&]() { try { server.start(); LOG_INFO << "Web UI and REST API server have been started and are listening on " << server.config.address << ":" << server.config.port; } catch(std::exception const &e) { LOG_ERROR << "Web server error: " << e.what() << ". Is port " << server.config.port << " already in use?" ; std::cerr << "Web server error: " << e.what() << ". Is port " << server.config.port << " already in use?" << " Check log files for more information." << std::endl; }} );

server.on_error = [](std::shared_ptr request, const SimpleWeb::error_code & ec) { LOG_ERROR << "Web server error: " << ec.message(); }; }

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/eidheim/Simple-Web-Server/issues/176#issuecomment-347894767, or mute the thread https://github.com/notifications/unsubscribe-auth/AY1M8vuepoJgrhq_ZimEukEKz1xgrPzXks5s7XezgaJpZM4QvGj3 .

vitor-alves commented 6 years ago

click the unsubscribe button in the right

314159 commented 6 years ago

On Dec 1, 2017, at 5:57 AM, Shashirathod7 notifications@github.com wrote:

How do i unsubscribe for this, so annoying.

This is from GitHub, so you can use all of the standard GitHub ways of deciding what you want to listed to.

At the bottom of the email, GitHub supplies two links. The first one can be used to unsubscribe from all of Simple-Web-Server: go to the top of the page and click “Unwatch”. The second link can be used to unsubscribe from this particular conversation (i.e. Deterministic way to know if server has started).

jschrotter commented 6 years ago

I think there is a synchronization issue in suggested code: std::unique_lock<std::mutex> lock(cv_mutex); should be created prior to server.io_service->post... otherwise the thread might be executed before the lock is created and then cv.wait(lock); waits forever.

eidheim commented 6 years ago

@jschrotter you are right. This is hopefully a better solution:

#include "server_http.hpp"

using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;

int main() {
  HttpServer server;
  server.config.port = 8080;
  server.io_service = std::make_shared<SimpleWeb::asio::io_service>();

  std::thread server_thread([&server] {
    server.start();
    server.io_service->run();
  });

  std::promise<void> started;
  server.io_service->post([&started] {
    started.set_value();
  });
  started.get_future().get();
  std::cout << "Server is now accepting requests" << std::endl;

  server_thread.join();
}