Repository: CrowCpp/Crow (C++)

Issue:
There are no exceptions with run_async()
Somehow config errors are suppressed if the the server runs in async mode.

For example if i'm using a invalid `.bindaddr("127.0.0")`. If used with `.run()` i get a exception 

```
(2024-09-03 14:35:15) [INFO    ] Before Init
terminate called after throwing an instance of 'std::system_error'
  what():  Invalid argument
```
(maybe not the greatest exception but good enough)

If i do the same with `.run_async()` it just hangs on `wait_for_server_start()` with out any exceptions. 

Is that on purpose?
Is there a way to wait or crash or maybe find out if the server crashed?
Am i holding it wrong? :sweat_smile:  


Full example:

```
#include <crow.h>

#include <chrono>
#include <thread>

int main(int /*ac*/, char ** /*av*/) {
  crow::SimpleApp app;

  CROW_ROUTE(app, "/")
  ([]() {
    return "foo";
  });

  CROW_LOG_INFO << "Before Init";
  // async example
  auto f = app.bindaddr("127.0.0").port(8001).run_async();
  app.wait_for_server_start();

  // sync example
  // app.bindaddr("127.0.0").port(8001).run();

  CROW_LOG_INFO << "After Init";
  using namespace std::chrono_literals;
  std::this_thread::sleep_for(5s);
  app.stop();
}
```


Discussion:
It fails in the constructor of the server due to asio::ip::address::from_string and throws an exception, therefore never a notification will be sent in run() and wait_for_server_start() waits forever.
In general I think a overload for `wait_for_server_start(1s)` with a overload for a timeout would be useful, i think there are a lot of cases where one would like to fail if it does not become ready in a certain time

Is there a way to achieve something with the current crow implementation?
I agree, a timeout would be generally useful. I would assume that it is easily possible.



Patch:
diff --git a/include/crow/app.h b/include/crow/app.h
index fbf53a075..4119859b7 100644
--- a/include/crow/app.h
+++ b/include/crow/app.h
@@ -365,7 +365,7 @@ namespace crow
         }
 
         /// \brief Get the number of threads that server is using
-        std::uint16_t concurrency()
+        std::uint16_t concurrency() const
         {
             return concurrency_;
         }
@@ -506,11 +506,18 @@ namespace crow
 #endif
             validate();
 
+            error_code ec;
+            asio::ip::address addr = asio::ip::make_address(bindaddr_,ec);
+            if (ec){
+                CROW_LOG_ERROR << ec.message() << " - Can not create valid ip address from string: \"" << bindaddr_ << "\"";
+                return;
+            }
+            tcp::endpoint endpoint(addr, port_);
 #ifdef CROW_ENABLE_SSL
             if (ssl_used_)
             {
                 router_.using_ssl = true;
-                ssl_server_ = std::move(std::unique_ptr<ssl_server_t>(new ssl_server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, timeout_, &ssl_context_)));
+                ssl_server_ = std::move(std::unique_ptr<ssl_server_t>(new ssl_server_t(this, endpoint, server_name_, &middlewares_, concurrency_, timeout_, &ssl_context_)));
                 ssl_server_->set_tick_function(tick_interval_, tick_function_);
                 ssl_server_->signal_clear();
                 for (auto snum : signals_)
@@ -523,7 +530,7 @@ namespace crow
             else
 #endif
             {
-                server_ = std::move(std::unique_ptr<server_t>(new server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, timeout_, nullptr)));
+                server_ = std::move(std::unique_ptr<server_t>(new server_t(this, endpoint, server_name_, &middlewares_, concurrency_, timeout_, nullptr)));
                 server_->set_tick_function(tick_interval_, tick_function_);
                 for (auto snum : signals_)
                 {
@@ -695,19 +702,32 @@ namespace crow
         }
 
         /// \brief Wait until the server has properly started
-        void wait_for_server_start()
+        std::cv_status wait_for_server_start(std::chrono::milliseconds wait_timeout = std::chrono::milliseconds(3000))
         {
+            std::cv_status status = std::cv_status::no_timeout;
+            auto wait_until = std::chrono::steady_clock::now() + wait_timeout;
             {
                 std::unique_lock<std::mutex> lock(start_mutex_);
-                while (!server_started_)
-                    cv_started_.wait(lock);
+                while (!server_started_ && (status == std::cv_status::no_timeout))
+                {
+                    status = cv_started_.wait_until(lock, wait_until);
+                }
             }
-            if (server_)
-                server_->wait_for_start();
+            
+            if (status == std::cv_status::no_timeout)
+            {
+                if (server_)
+                {
+                    status = server_->wait_for_start(wait_until);
+                }
 #ifdef CROW_ENABLE_SSL
-            else if (ssl_server_)
-                ssl_server_->wait_for_start();
+                else if (ssl_server_)
+                {
+                    status = ssl_server_->wait_for_start(wait_until);
+                }
 #endif
+            }
+            return status;
         }
 
     private:
diff --git a/include/crow/http_server.h b/include/crow/http_server.h
index ed9ae0fc1..12ff3726d 100644
--- a/include/crow/http_server.h
+++ b/include/crow/http_server.h
@@ -42,16 +42,20 @@ namespace crow // NOTE: Already documented in "crow/app.h"
     class Server
     {
     public:
-        Server(Handler* handler, std::string bindaddr, uint16_t port, std::string server_name = std::string("Crow/") + VERSION, std::tuple<Middlewares...>* middlewares = nullptr, uint16_t concurrency = 1, uint8_t timeout = 5, typename Adaptor::context* adaptor_ctx = nullptr):
-          acceptor_(io_context_, tcp::endpoint(asio::ip::make_address(bindaddr), port)),
+      Server(Handler* handler,
+             const tcp::endpoint& endpoint,
+             std::string server_name = std::string("Crow/") + VERSION,
+             std::tuple<Middlewares...>* middlewares = nullptr,
+             uint16_t concurrency = 1,
+             uint8_t timeout = 5,
+             typename Adaptor::context* adaptor_ctx = nullptr):
+          acceptor_(io_context_,endpoint),
           signals_(io_context_),
           tick_timer_(io_context_),
           handler_(handler),
           concurrency_(concurrency),
           timeout_(timeout),
           server_name_(server_name),
-          port_(port),
-          bindaddr_(bindaddr),
           task_queue_length_pool_(concurrency_ - 1),
           middlewares_(middlewares),
           adaptor_ctx_(adaptor_ctx)
@@ -150,11 +154,12 @@ namespace crow // NOTE: Already documented in "crow/app.h"
                   });
             }
 
-            port_ = acceptor_.local_endpoint().port();
-            handler_->port(port_);
+            handler_->port(acceptor_.local_endpoint().port());
 
 
-            CROW_LOG_INFO << server_name_ << " server is running at " << (handler_->ssl_used() ? "https://" : "http://") << bindaddr_ << ":" << acceptor_.local_endpoint().port() << " using " << concurrency_ << " threads";
+            CROW_LOG_INFO << server_name_
+                          << " server is running at " << (handler_->ssl_used() ? "https://" : "http://")
+                          << acceptor_.local_endpoint().address() << ":" << acceptor_.local_endpoint().port() << " using " << concurrency_ << " threads";
             CROW_LOG_INFO << "Call `app.loglevel(crow::LogLevel::Warning)` to hide Info level logs.";
 
             signals_.async_wait(
@@ -192,16 +197,19 @@ namespace crow // NOTE: Already documented in "crow/app.h"
             io_context_.stop(); // Close main io_service
         }
 
-        uint16_t port(){
+        uint16_t port() const {
             return acceptor_.local_endpoint().port();
         }
 
-        /// Wait until the server has properly started
-        void wait_for_start()
+        /// Wait until the server has properly started or until timeout
+        std::cv_status wait_for_start(std::chrono::steady_clock::time_point wait_until)
         {
             std::unique_lock<std::mutex> lock(start_mutex_);
-            while (!server_started_)
-                cv_started_.wait(lock);
+            
+            std::cv_status status = std::cv_status::no_timeout;
+            while (!server_started_ && ( status==std::cv_status::no_timeout ))
+                status = cv_started_.wait_until(lock,wait_until);
+            return status;
         }
 
         void signal_clear()
@@ -290,8 +298,6 @@ namespace crow // NOTE: Already documented in "crow/app.h"
         uint16_t concurrency_{2};
         std::uint8_t timeout_;
         std::string server_name_;
-        uint16_t port_;
-        std::string bindaddr_;
         std::vector<std::atomic<unsigned int>> task_queue_length_pool_;
 
         std::chrono::milliseconds tick_interval_;


Test patch:
diff --git a/tests/unittest.cpp b/tests/unittest.cpp
index d2ca615a4..ff2114620 100644
--- a/tests/unittest.cpp
+++ b/tests/unittest.cpp
@@ -643,7 +643,22 @@ TEST_CASE("server_handling_error_request")
     app.stop();
 } // server_handling_error_request
 
-TEST_CASE("server_dynamic_port_allication")
+TEST_CASE("server_invalid_ip_address")
+{
+    SimpleApp app;
+    CROW_ROUTE(app, "/")
+    ([] {
+        return "A";
+    });
+    auto _ = app.bindaddr("192.").port(45451).run_async();
+    auto state = app.wait_for_server_start();
+
+    // we should run into a timeout as the server will not started
+    CHECK(state==cv_status::timeout);
+} // server_invalid_ip_address
+
+
+TEST_CASE("server_dynamic_port_allocation")
 {
     SimpleApp app;
     CROW_ROUTE(app, "/")
@@ -659,7 +674,7 @@ TEST_CASE("server_dynamic_port_allication")
           asio::ip::make_address(LOCALHOST_ADDRESS), app.port()));
     }
     app.stop();
-} // server_dynamic_port_allication
+} // server_dynamic_port_allocation
 
 TEST_CASE("server_handling_error_request_http_version")
 {
@@ -1520,7 +1535,9 @@ struct NullSimpleMiddleware
 TEST_CASE("middleware_simple")
 {
     App<NullMiddleware, NullSimpleMiddleware> app;
-    decltype(app)::server_t server(&app, LOCALHOST_ADDRESS, 45451);
+    asio::ip::address adr = asio::ip::make_address(LOCALHOST_ADDRESS);
+    tcp::endpoint ep(adr,45451);
+    decltype(app)::server_t server(&app, ep);
     CROW_ROUTE(app, "/")
     ([&](const crow::request& req) {
         app.get_context<NullMiddleware>(req);